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
38 changes: 24 additions & 14 deletions miniapps/forge/e2e/helpers/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,39 @@
import type { Page, Locator } from '@playwright/test'

export const UI_TEXT = {
app: {
title: { source: '锻造', pattern: /锻造|Forge/i },
subtitle: { source: '多链熔炉', pattern: /多链熔炉|Multi-chain Forge/i },
},
connect: {
button: /连接钱包|Connect Wallet/i,
loading: /连接中|Connecting/i,
button: { source: '连接钱包', pattern: /连接钱包|Connect Wallet/i },
loading: { source: '连接中', pattern: /连接中|Connecting/i },
},
swap: {
pay: /支付|Pay/i,
receive: /获得|Receive/i,
button: /兑换|Swap/i,
confirm: /确认兑换|Confirm Swap/i,
preview: /预览交易|Preview|预览/i,
max: /全部|Max/i,
pay: { source: '支付', pattern: /支付|Pay/i },
receive: { source: '获得', pattern: /获得|Receive/i },
button: { source: '兑换', pattern: /兑换|Swap/i },
preview: { source: '预览交易', pattern: /预览交易|Preview/i },
},
confirm: {
title: /确认兑换|Confirm Swap/i,
button: /确认|Confirm/i,
cancel: /取消|Cancel/i,
title: { source: '确认锻造', pattern: /确认锻造|Confirm Forge/i },
button: { source: '确认锻造', pattern: /确认锻造|Confirm/i },
},
success: {
title: /兑换成功|Swap Successful/i,
done: /完成|Done/i,
title: { source: '锻造完成', pattern: /锻造完成|Forge Complete/i },
continue: { source: '继续锻造', pattern: /继续锻造|Continue/i },
},
token: {
select: /选择代币|Select Token/i,
select: { source: '选择锻造币种', pattern: /选择锻造币种|Select Token/i },
selected: { source: '已选', pattern: /已选|Selected/i },
},
processing: {
signingExternal: { source: '签名外链交易', pattern: /签名外链交易|Signing External/i },
signingInternal: { source: '签名内链消息', pattern: /签名内链消息|Signing Internal/i },
submitting: { source: '提交锻造请求', pattern: /提交锻造请求|Submitting/i },
},
error: {
sdkNotInit: { source: 'Bio SDK 未初始化', pattern: /Bio SDK 未初始化|SDK not initialized/i },
},
} as const

Expand Down
179 changes: 163 additions & 16 deletions miniapps/forge/e2e/ui.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,59 @@
import { test, expect } from '@playwright/test'
import { UI_TEXT, TEST_IDS, byTestId } from './helpers/i18n'
import { UI_TEXT } from './helpers/i18n'

const mockApiResponses = `
// Mock fetch for API calls
const originalFetch = window.fetch
window.fetch = async (url, options) => {
if (url.includes('getSupport')) {
return {
ok: true,
json: () => Promise.resolve({
recharge: {
bfmeta: {
BFM: {
enable: true,
logo: '',
supportChain: {
ETH: { enable: true, assetType: 'ETH', depositAddress: '0x1234567890', logo: '' },
BSC: { enable: true, assetType: 'BNB', depositAddress: '0xabcdef1234', logo: '' },
},
},
},
},
}),
}
}
if (url.includes('rechargeV2')) {
return {
ok: true,
json: () => Promise.resolve({ orderId: 'order-123456' }),
}
}
return originalFetch(url, options)
}
`

const mockBioSDK = `
window.bio = {
request: async ({ method }) => {
request: async ({ method, params }) => {
if (method === 'bio_closeSplashScreen') return {}
if (method === 'bio_selectAccount') {
return { address: '0x1234...5678', name: 'Test Wallet' }
const chain = params?.[0]?.chain || 'eth'
return {
address: chain === 'bfmeta' ? 'bfmeta1234567890' : '0x1234567890abcdef1234567890abcdef12345678',
chain,
name: 'Test Wallet'
}
}
if (method === 'bio_createTransaction') {
return { txHash: 'unsigned-tx-123' }
}
if (method === 'bio_signTransaction') {
return { data: '0xsigned-tx-data-456' }
}
if (method === 'bio_signMessage') {
return 'signature-789'
}
return {}
}
Expand All @@ -15,51 +63,69 @@ const mockBioSDK = `
test.describe('Forge UI', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 })
await page.addInitScript(mockApiResponses)
})

test('01 - connect page', async ({ page }) => {
test('01 - connect page shows welcome screen', async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')

// Should show title and subtitle
await expect(page.locator(`text=${UI_TEXT.app.subtitle.source}`)).toBeVisible()
await expect(page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first()).toBeVisible()

await expect(page).toHaveScreenshot('01-connect.png')
})

test('02 - swap page after connect', async ({ page }) => {
test('02 - swap page after wallet connect', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')

// Click connect button
await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await expect(page.locator(`button:has-text("${UI_TEXT.swap.button.source}")`).first()).toBeVisible()

// Should show swap UI with pay/receive
await expect(page.locator(`text=${UI_TEXT.swap.pay.source}`).first()).toBeVisible({ timeout: 10000 })
await expect(page.locator(`text=${UI_TEXT.swap.receive.source}`).first()).toBeVisible()

await expect(page).toHaveScreenshot('02-swap.png')
})

test('03 - swap page with amount', async ({ page }) => {
test('03 - swap page with amount entered', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('input[type="number"]')
await page.waitForSelector('input[type="number"]', { timeout: 10000 })

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

// Preview button should be enabled
const previewButton = page.locator(`button:has-text("${UI_TEXT.swap.preview.source}")`)
await expect(previewButton).toBeEnabled()

await expect(page).toHaveScreenshot('03-swap-amount.png')
})

test('04 - token picker', async ({ page }) => {
test('04 - token picker modal', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('button:has-text("ETH")')
await page.waitForSelector('button:has-text("ETH")', { timeout: 10000 })

// Click token selector to open picker
await page.click('button:has-text("ETH")')
await expect(page.locator(`text=${UI_TEXT.token.select.source}`).first()).toBeVisible()

// Should show available tokens
await expect(page.locator('text=Ethereum')).toBeVisible()

await expect(page).toHaveScreenshot('04-token-picker.png')
})

Expand All @@ -69,16 +135,97 @@ test.describe('Forge UI', () => {
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('input[type="number"]')
await page.waitForSelector('input[type="number"]', { timeout: 10000 })

// Enter amount
await page.fill('input[type="number"]', '1.5')

const previewButton = page.locator(`button:has-text("${UI_TEXT.swap.preview.source}")`).first()
if (await previewButton.isVisible()) {
await previewButton.click()
await expect(page.locator(`text=${UI_TEXT.confirm.title.source}`).first()).toBeVisible()
}
// Click preview
await page.locator(`button:has-text("${UI_TEXT.swap.preview.source}")`).click()

// Should show confirm page
await expect(page.locator(`button:has-text("${UI_TEXT.confirm.button.source}")`).first()).toBeVisible({ timeout: 5000 })

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

test('06 - error state without bio SDK', async ({ page }) => {
// No bio SDK mock
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()

// Should show error
await expect(page.locator(`text=${UI_TEXT.error.sdkNotInit.source}`)).toBeVisible({ timeout: 5000 })

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

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

// Step 1: Connect
await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('input[type="number"]', { timeout: 10000 })

// Step 2: Enter amount
await page.fill('input[type="number"]', '0.5')

// Step 3: Preview
await page.locator(`button:has-text("${UI_TEXT.swap.preview.source}")`).click()
await expect(page.locator(`button:has-text("${UI_TEXT.confirm.button.source}")`).first()).toBeVisible({ timeout: 5000 })

// Step 4: Confirm
await page.locator(`button:has-text("${UI_TEXT.confirm.button.source}")`).first().click()

// Should show success or processing
await expect(
page.locator(`text=${UI_TEXT.success.title.source}`).or(page.locator(`text=${UI_TEXT.processing.signingExternal.source}`))
).toBeVisible({ timeout: 15000 })

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

test('08 - back navigation from confirm', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')

// Navigate to confirm page
await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('input[type="number"]', { timeout: 10000 })
await page.fill('input[type="number"]', '1.0')
await page.locator(`button:has-text("${UI_TEXT.swap.preview.source}")`).click()
await expect(page.locator(`button:has-text("${UI_TEXT.confirm.button.source}")`).first()).toBeVisible({ timeout: 5000 })

// Click back button
await page.locator('button[aria-label="back"], button:has(svg.lucide-chevron-left)').first().click()

// Should go back to swap page
await expect(page.locator('input[type="number"]')).toBeVisible()
})

test('09 - token selection change', async ({ page }) => {
await page.addInitScript(mockBioSDK)
await page.goto('/')
await page.waitForLoadState('networkidle')

await page.locator(`button:has-text("${UI_TEXT.connect.button.source}")`).first().click()
await page.waitForSelector('button:has-text("ETH")', { timeout: 10000 })

// Open picker
await page.click('button:has-text("ETH")')
await expect(page.locator(`text=${UI_TEXT.token.select.source}`)).toBeVisible()

// Select different token (BNB on BSC)
const bnbOption = page.locator('text=BNB').first()
if (await bnbOption.isVisible()) {
await bnbOption.click()
// Picker should close and new token should be selected
await expect(page.locator('button:has-text("BNB")')).toBeVisible({ timeout: 5000 })
}
})
})
Loading