Skip to content
Draft
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
3 changes: 3 additions & 0 deletions e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TEST_BASE_URL=http://localhost:4003
WALLET_BACKEND_CONTAINER=wallet-backend-local
ENABLE_SCREENSHOTS=false
5 changes: 5 additions & 0 deletions e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
node_modules
playwright-report
test-results
.features-gen
19 changes: 19 additions & 0 deletions e2e/features/auth-signup-dashboard.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Feature: Wallet authentication onboarding
As a new wallet user
I want to sign up, verify my email, complete KYC, and reach my default account
So that I can access my wallet dashboard

Scenario: New user completes signup, verification, login, KYC, and account access
Given I am a new unique wallet user
When I open the signup page
And I complete the signup form
And I submit signup
Then I should see signup confirmation
When I open the verification link from backend logs
Then I should see verification success
When I continue to login
And I login with my new credentials
And I complete KYC if I am redirected to KYC
Then I should see the accounts dashboard
When I open the EUR default account
Then I should see the account balance page
12 changes: 12 additions & 0 deletions e2e/features/cross-currency-transfer.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: Cross-currency payment transfers
As a wallet user
I want to send payments between accounts in different currencies
So that I can transfer value across currency boundaries

Scenario: User can navigate to send page and select accounts
Given I am a verified and logged-in wallet user
When I navigate to the send page
And I select a source account
Then I should see the wallet address selector
When I select a wallet address
Then I should see the recipient address input field
146 changes: 146 additions & 0 deletions e2e/features/steps/auth-signup-dashboard.steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { expect } from '@playwright/test'
import {
completeLocalMockKyc,
waitForVerificationLinkFromLogs
} from '../../helpers/local-wallet'
import { Given, Then, When } from './fixtures'

Given('I am a new unique wallet user', async ({ flow }) => {
expect(flow.credentials.email).toContain('e2e-')
expect(flow.credentials.password).toContain('Testnet!')
})

When('I open the signup page', async ({ page, flow }) => {
await page.goto('/auth/signup')
await expect(
page.getByRole('heading', { name: 'Create Account' })
).toBeVisible()
await flow.takeScreenshot('signup-page')
})

When('I complete the signup form', async ({ page, flow }) => {
const signUpForm = page.locator('form')

await signUpForm
.getByLabel('E-mail *', { exact: true })
.fill(flow.credentials.email)
await flow.takeScreenshot('signup-email-filled')
await signUpForm
.getByLabel('Password *', { exact: true })
.fill(flow.credentials.password)
await flow.takeScreenshot('signup-password-filled')
await signUpForm
.getByLabel('Confirm password *', { exact: true })
.fill(flow.credentials.password)
await flow.takeScreenshot('signup-confirm-password-filled')
})

When('I submit signup', async ({ page, flow }) => {
const signUpForm = page.locator('form')

await Promise.all([
page.waitForResponse(
(response) =>
response.url().endsWith('/signup') &&
response.request().method() === 'POST' &&
response.status() === 201
),
signUpForm.locator('button[type="submit"]').click()
])

await flow.takeScreenshot('signup-submitted')
})

Then('I should see signup confirmation', async ({ page, flow }) => {
await expect(
page.getByText('A verification link has been sent to your email account.')
).toBeVisible()
await flow.takeScreenshot('signup-success')
})

When(
'I open the verification link from backend logs',
async ({ page, flow }) => {
const verificationLink = await waitForVerificationLinkFromLogs({
since: flow.logMarker,
containerName: flow.containerName
})

flow.verificationLink = verificationLink

await page.goto(verificationLink)
await flow.takeScreenshot('verification-page-opened')
}
)

Then('I should see verification success', async ({ page, flow }) => {
await expect(
page.getByText(
'Your email has been verified. Continue to login to use Interledger Test Wallet.'
)
).toBeVisible()
await flow.takeScreenshot('verify-success')
})

When('I continue to login', async ({ page, flow }) => {
await page.locator('a[href="/auth/login"]').first().click()
await expect(page).toHaveURL(/\/auth\/login$/)
await flow.takeScreenshot('login-page-opened')
})

When('I login with my new credentials', async ({ page, flow }) => {
const loginForm = page.locator('form')

await loginForm
.getByLabel('E-mail *', { exact: true })
.fill(flow.credentials.email)
await flow.takeScreenshot('login-email-filled')
await loginForm
.getByLabel('Password *', { exact: true })
.fill(flow.credentials.password)
await flow.takeScreenshot('login-password-filled')

await Promise.all([
page.waitForResponse(
(response) =>
response.url().endsWith('/login') &&
response.request().method() === 'POST' &&
response.status() === 200
),
loginForm.locator('button[type="submit"]').click()
])

await flow.takeScreenshot('login-submitted')
await page.waitForURL(/\/(kyc)?$/, { timeout: 60_000 })
await flow.takeScreenshot('post-login')
})

When('I complete KYC if I am redirected to KYC', async ({ page, flow }) => {
if (page.url().endsWith('/kyc')) {
await completeLocalMockKyc(page, flow.takeScreenshot)
}
})

Then('I should see the accounts dashboard', async ({ page, flow }) => {
await expect(page.getByRole('heading', { name: 'Accounts' })).toBeVisible()
await expect(page.getByText('Here is your account overview!')).toBeVisible()
await flow.takeScreenshot('dashboard-confirmed')
})

When('I open the EUR default account', async ({ page, flow }) => {
const defaultAccount = page
.locator('a[href*="/account/"]')
.filter({ hasText: 'EUR Account' })
.first()

await expect(defaultAccount).toBeVisible()
await flow.takeScreenshot('dashboard')
await defaultAccount.click()
await flow.takeScreenshot('default-account-opened')
})

Then('I should see the account balance page', async ({ page, flow }) => {
await expect(page).toHaveURL(/\/account\/.+/)
await expect(page.getByRole('heading', { name: 'Balance' })).toBeVisible()
await flow.takeScreenshot('account-page')
})
77 changes: 77 additions & 0 deletions e2e/features/steps/cross-currency-transfer.steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect } from '@playwright/test'
import { setupVerifiedUser } from '../../helpers/local-wallet'
import { Given, Then, When } from './fixtures'

Given('I am a verified and logged-in wallet user', async ({ page, flow }) => {
const containerName = flow.containerName

// Use the helper to quickly set up a verified user
const credentials = await setupVerifiedUser({
page,
takeScreenshot: flow.takeScreenshot,
containerName,
skipScreenshots: false
})

// Store credentials in flow for later use if needed
flow.credentials = credentials
await flow.takeScreenshot('verified-user-ready')
})

When('I navigate to the send page', async ({ page, flow }) => {
await page.goto('/send')
await expect(page).toHaveURL(/\/send$/)
await expect(page.getByRole('heading', { name: 'Send' })).toBeVisible()
await flow.takeScreenshot('send-page-loaded')
})

When('I select a source account', async ({ page, flow }) => {
// Click on the account selector
const accountSelect = page.locator('#selectAccount')
await expect(accountSelect).toBeVisible()
await flow.takeScreenshot('before-select-account')

await accountSelect.click()
await flow.takeScreenshot('account-dropdown-opened')

// Select the first account (EUR Account or whatever is available)
const firstAccountOption = page.locator('[role="option"]').first()
await expect(firstAccountOption).toBeVisible()
await firstAccountOption.click()
await flow.takeScreenshot('account-selected')
})

Then('I should see the wallet address selector', async ({ page, flow }) => {
const walletAddressSelect = page.locator('#selectWalletAddress')
await expect(walletAddressSelect).toBeVisible()
await flow.takeScreenshot('wallet-address-selector-visible')
})

When('I select a wallet address', async ({ page, flow }) => {
const walletAddressSelect = page.locator('#selectWalletAddress')
await expect(walletAddressSelect).toBeVisible()
await flow.takeScreenshot('before-select-wallet-address')

await walletAddressSelect.click()
await flow.takeScreenshot('wallet-address-dropdown-opened')

// Select the first wallet address option
const firstWalletOption = page.locator('[role="option"]').first()
await expect(firstWalletOption).toBeVisible()
await firstWalletOption.click()
await flow.takeScreenshot('wallet-address-selected')
})

Then(
'I should see the recipient address input field',
async ({ page, flow }) => {
const recipientInput = page.locator('#addRecipientWalletAddress')
await expect(recipientInput).toBeVisible()
await flow.takeScreenshot('recipient-address-input-visible')

// Verify amount input is also visible
const amountInput = page.locator('#addAmount')
await expect(amountInput).toBeVisible()
await flow.takeScreenshot('amount-input-visible')
}
)
53 changes: 53 additions & 0 deletions e2e/features/steps/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createBdd, test as base } from 'playwright-bdd'
import {
type Credentials,
createUniqueCredentials
} from '../../helpers/local-wallet'
import { mkdir } from 'node:fs/promises'

type FlowState = {
credentials: Credentials
logMarker: Date
containerName: string
screenshotCounter: number
verificationLink?: string
featureName: string
takeScreenshot: (name: string) => Promise<void>
}

export const test = base.extend<{ flow: FlowState }>({
flow: async ({ page }, use, testInfo) => {
// Extract feature name from the generated test file path
// e.g., ".features-gen/auth-signup-dashboard.feature.spec.js" → "auth-signup-dashboard"
const testFile = testInfo.file
const fileName = testFile.split('/').pop() || 'unknown'
const featureName = fileName
.replace('.feature.spec.js', '')
.replace('.feature.spec.ts', '')
.replace('.spec.js', '')
.replace('.spec.ts', '')

const state: FlowState = {
credentials: createUniqueCredentials(),
logMarker: new Date(),
containerName:
process.env.WALLET_BACKEND_CONTAINER || 'wallet-backend-local',
screenshotCounter: 0,
featureName,
takeScreenshot: async (name: string) => {
state.screenshotCounter += 1
const screenshotDir = `test-results/${featureName}`
await mkdir(screenshotDir, { recursive: true })
await page.screenshot({
path: `${screenshotDir}/${String(state.screenshotCounter).padStart(3, '0')}-${name}.png`,
fullPage: true
})
}
}

// eslint-disable-next-line react-hooks/rules-of-hooks
await use(state)
}
})

export const { Given, When, Then } = createBdd(test)
Loading
Loading