Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions miniapps/teleport/e2e/helpers/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,43 @@ import type { Page, Locator } from '@playwright/test'

export const UI_TEXT = {
connect: {
button: /选择源钱包|启动传送门|Select Source Wallet/i,
loading: /连接中|Connecting/i,
button: /启动传送门|Start Teleport/i,
loading: /连接中|加载配置中|Connecting|Loading/i,
},
asset: {
select: /选择要传送的资产|Select asset/i,
select: /选择资产|Select Asset/i,
noAssets: /当前链暂无可传送资产|No assets available/i,
},
amount: {
next: /下一步|Next/i,
max: /全部|Max/i,
max: /MAX/i,
expected: /预计获得|Expected to receive/i,
},
target: {
title: /选择目标钱包|Select Target Wallet/i,
title: /目标钱包|Target Wallet/i,
button: /选择目标钱包|Select Target Wallet/i,
willTransfer: /即将传送|Will transfer/i,
},
confirm: {
title: /确认传送|Confirm Transfer/i,
send: /发送|Send/i,
receive: /接收|Receive/i,
button: /确认传送|Confirm Transfer/i,
free: /免费|Free/i,
},
processing: {
title: /传送中|Transferring/i,
waitingFrom: /等待发送方|Waiting for sender/i,
waitingTo: /等待接收方|Waiting for receiver/i,
},
success: {
title: /传送成功|Transfer Successful/i,
done: /完成|Done/i,
newTransfer: /发起新传送|New Transfer/i,
},
error: {
title: /传送失败|Transfer Failed/i,
restart: /重新开始|Start Over/i,
retry: /重试|Retry/i,
sdkNotInit: /Bio SDK 未初始化|Bio SDK not initialized/i,
},
} as const

Expand Down
227 changes: 200 additions & 27 deletions miniapps/teleport/e2e/ui.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,114 @@
import { test, expect } from '@playwright/test'
import { UI_TEXT } from './helpers/i18n'

// Mock API response for asset type list
const mockAssetTypeListResponse = JSON.stringify({
transmitSupport: {
ETH: {
ETH: {
enable: true,
isAirdrop: false,
assetType: 'ETH',
recipientAddress: '0x1234567890abcdef1234567890abcdef12345678',
targetChain: 'BFMCHAIN',
targetAsset: 'BFM',
ratio: { numerator: 1, denominator: 1 },
transmitDate: { startDate: '2020-01-01', endDate: '2030-12-31' },
},
USDT: {
enable: true,
isAirdrop: false,
assetType: 'USDT',
recipientAddress: '0x1234567890abcdef1234567890abcdef12345678',
targetChain: 'BFMCHAIN',
targetAsset: 'USDM',
ratio: { numerator: 1, denominator: 1 },
transmitDate: { startDate: '2020-01-01', endDate: '2030-12-31' },
},
},
},
})

// Mock Bio SDK with chain support
const mockBioSDK = `
window.bio = {
request: async ({ method }) => {
request: async ({ method, params }) => {
if (method === 'bio_closeSplashScreen') return
if (method === 'bio_selectAccount') {
return { address: '0x1234567890abcdef1234567890abcdef12345678', name: 'Test Wallet' }
return { address: '0x1234567890abcdef1234567890abcdef12345678', chain: 'ETH', name: 'Test Wallet' }
}
if (method === 'bio_pickWallet') {
return { address: '0xabcdef1234567890abcdef1234567890abcdef12', name: 'Target Wallet' }
return { address: 'bfm_abcdef1234567890abcdef1234567890abcdef12', chain: 'BFMCHAIN', name: 'Target Wallet' }
}
if (method === 'bio_getBalance') {
return '1000.00'
}
if (method === 'bio_createTransaction') {
return { chainId: 'ETH', data: { raw: '0x...' } }
}
if (method === 'bio_signTransaction') {
return { chainId: 'ETH', data: {}, signature: '0x123abc...' }
}
return {}
}
},
on: () => {},
off: () => {},
isConnected: () => true,
}
`

test.describe('Teleport UI', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 })

// Mock API calls
await page.route('**/payment/transmit/assetTypeList', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: mockAssetTypeListResponse,
})
})

await page.route('**/payment/transmit/records**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ page: 1, pageSize: 10, dataList: [] }),
})
})

await page.route('**/payment/transmit', async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ orderId: 'mock-order-123' }),
})
}
})

await page.route('**/payment/transmit/recordDetail**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
state: 3,
orderState: 4, // SUCCESS
swapRatio: 1,
updatedTime: new Date().toISOString(),
}),
})
})
})

test('01 - connect page', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')
await expect(page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first()).toBeVisible()

// Wait for config to load and button to show
await expect(page.getByRole('button', { name: UI_TEXT.connect.button })).toBeVisible({ timeout: 10000 })
await expect(page).toHaveScreenshot('01-connect.png')
})

Expand All @@ -32,7 +117,13 @@ test.describe('Teleport UI', () => {
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
// Wait for and click connect button
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

// Wait for asset selection page
await expect(page.getByText(UI_TEXT.asset.select)).toBeVisible({ timeout: 5000 })
await expect(page.locator('[data-slot="card"]').first()).toBeVisible()

await expect(page).toHaveScreenshot('02-select-asset.png')
Expand All @@ -43,10 +134,16 @@ test.describe('Teleport UI', () => {
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('[data-slot="card"]')
// Connect
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

await page.click('[data-slot="card"]:has-text("BFM")')
// Select first asset (ETH)
await expect(page.locator('[data-slot="card"]').first()).toBeVisible({ timeout: 5000 })
await page.locator('[data-slot="card"]').first().click()

// Verify amount input is visible
await expect(page.locator('input[type="number"]')).toBeVisible()

await expect(page).toHaveScreenshot('03-input-amount.png')
Expand All @@ -57,14 +154,22 @@ test.describe('Teleport UI', () => {
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('[data-slot="card"]')
// Connect
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

await page.click('[data-slot="card"]:has-text("BFM")')
await page.waitForSelector('input[type="number"]')
// Select asset
await expect(page.locator('[data-slot="card"]').first()).toBeVisible({ timeout: 5000 })
await page.locator('[data-slot="card"]').first().click()

// Fill amount
await page.waitForSelector('input[type="number"]')
await page.fill('input[type="number"]', '500')
await expect(page.locator('input[type="number"]')).toHaveValue('500')

// Verify expected receive is shown
await expect(page.getByText(UI_TEXT.amount.expected)).toBeVisible()

await expect(page).toHaveScreenshot('04-amount-filled.png')
})
Expand All @@ -74,15 +179,23 @@ test.describe('Teleport UI', () => {
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('[data-slot="card"]')
// Connect
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

await page.click('[data-slot="card"]:has-text("BFM")')
await page.waitForSelector('input[type="number"]')
// Select asset
await expect(page.locator('[data-slot="card"]').first()).toBeVisible({ timeout: 5000 })
await page.locator('[data-slot="card"]').first().click()

// Fill amount and click next
await page.waitForSelector('input[type="number"]')
await page.fill('input[type="number"]', '500')
await page.locator(`button:has-text("${UI_TEXT.amount.next.source}")`).first().click()
await expect(page.locator(`text=${UI_TEXT.target.title.source}`).first()).toBeVisible()
await page.getByRole('button', { name: UI_TEXT.amount.next }).click()

// Verify target wallet selection page
await expect(page.getByText(UI_TEXT.target.willTransfer)).toBeVisible({ timeout: 5000 })
await expect(page.getByRole('button', { name: UI_TEXT.target.button })).toBeVisible()

await expect(page).toHaveScreenshot('05-select-target.png')
})
Expand All @@ -92,19 +205,79 @@ test.describe('Teleport UI', () => {
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('[data-slot="card"]')
// Connect
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

await page.click('[data-slot="card"]:has-text("BFM")')
await page.waitForSelector('input[type="number"]')
// Select asset
await expect(page.locator('[data-slot="card"]').first()).toBeVisible({ timeout: 5000 })
await page.locator('[data-slot="card"]').first().click()

// Fill amount and proceed
await page.waitForSelector('input[type="number"]')
await page.fill('input[type="number"]', '500')
await page.locator(`button:has-text("${UI_TEXT.amount.next.source}")`).first().click()
await page.waitForSelector(`text=${UI_TEXT.target.title.source}`)
await page.getByRole('button', { name: UI_TEXT.amount.next }).click()

await page.locator(`button:has-text("${UI_TEXT.target.button.source}")`).first().click()
await expect(page.locator(`text=${UI_TEXT.confirm.title.source}`).first()).toBeVisible()
// Select target wallet
await expect(page.getByRole('button', { name: UI_TEXT.target.button })).toBeVisible({ timeout: 5000 })
await page.getByRole('button', { name: UI_TEXT.target.button }).click()

// Verify confirm page
await expect(page.getByText(UI_TEXT.confirm.send)).toBeVisible({ timeout: 5000 })
await expect(page.getByText(UI_TEXT.confirm.receive)).toBeVisible()
await expect(page.getByRole('button', { name: UI_TEXT.confirm.button })).toBeVisible()

await expect(page).toHaveScreenshot('06-confirm.png')
})

test('07 - success page', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')

// Connect
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

// Select asset
await expect(page.locator('[data-slot="card"]').first()).toBeVisible({ timeout: 5000 })
await page.locator('[data-slot="card"]').first().click()

// Fill amount and proceed
await page.waitForSelector('input[type="number"]')
await page.fill('input[type="number"]', '500')
await page.getByRole('button', { name: UI_TEXT.amount.next }).click()

// Select target wallet
await expect(page.getByRole('button', { name: UI_TEXT.target.button })).toBeVisible({ timeout: 5000 })
await page.getByRole('button', { name: UI_TEXT.target.button }).click()

// Confirm transfer
await expect(page.getByRole('button', { name: UI_TEXT.confirm.button })).toBeVisible({ timeout: 5000 })
await page.getByRole('button', { name: UI_TEXT.confirm.button }).click()

// Verify success page
await expect(page.getByText(UI_TEXT.success.title)).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('button', { name: UI_TEXT.success.newTransfer })).toBeVisible()

await expect(page).toHaveScreenshot('07-success.png')
})

test('08 - error when SDK not initialized', async ({ page }) => {
// Don't add bio SDK mock
await page.goto('/')
await page.waitForLoadState('networkidle')

// Wait for connect button
const connectBtn = page.getByRole('button', { name: UI_TEXT.connect.button })
await expect(connectBtn).toBeVisible({ timeout: 10000 })
await connectBtn.click()

// Verify error message
await expect(page.getByText(UI_TEXT.error.sdkNotInit)).toBeVisible({ timeout: 5000 })

await expect(page).toHaveScreenshot('08-error-no-sdk.png')
})
})
3 changes: 3 additions & 0 deletions miniapps/teleport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
"@base-ui/react": "^1.0.0",
"@biochain/bio-sdk": "workspace:*",
"@biochain/keyapp-sdk": "workspace:*",
"@bnqkl/metabox-core": "0.5.2",
"@bnqkl/wallet-typings": "0.23.8",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@tanstack/react-query": "^5.90.12",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.26",
Expand Down
Loading