diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 345fba54..bb2a0522 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -38,11 +38,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} e2e: - name: E2E Tests + name: E2E Tests (${{ matrix.shard }}/${{ strategy.job-total }}) runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: read + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3] steps: - name: Clone repository @@ -54,6 +58,17 @@ jobs: - name: Setup pnpm run: corepack enable pnpm + - name: Get pnpm store directory + id: pnpm-cache + run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Cache pnpm dependencies + uses: actions/cache@v4 + with: + path: ${{ steps.pnpm-cache.outputs.dir }} + key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: pnpm-${{ runner.os }}- + - name: Install dependencies run: pnpm install @@ -77,12 +92,12 @@ jobs: run: pnpm exec playwright install-deps chromium - name: Run Playwright tests - run: pnpm run test:e2e + run: pnpm run test:e2e --shard=${{ matrix.shard }}/3 - name: Upload test results uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: - name: playwright-report + name: playwright-report-${{ matrix.shard }} path: playwright-report/ retention-days: 15 diff --git a/biome.json b/biome.json index 933e82b6..b1b81b1d 100644 --- a/biome.json +++ b/biome.json @@ -40,6 +40,14 @@ }, "files": { "ignoreUnknown": false, - "includes": ["**", "!dist", "!node_modules", "!specs/lib", "!src/snippets/unformatted"] + "includes": [ + "**", + "!dist", + "!node_modules", + "!playwright-report", + "!specs/lib", + "!src/snippets/unformatted", + "!test-results" + ] } } diff --git a/e2e/create-a-stablecoin.test.ts b/e2e/create-a-stablecoin.test.ts new file mode 100644 index 00000000..de36c52f --- /dev/null +++ b/e2e/create-a-stablecoin.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from '@playwright/test' + +test('create a stablecoin', async ({ page }) => { + test.setTimeout(120000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/issuance/create-a-stablecoin') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + // Wait for sign out button (indicates successful sign up) + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + // Wait for "Add more funds" button (indicates funds were added) + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Fill in token details and deploy + // Use label-based selectors to ensure we're filling the right inputs in the demo form + const nameInput = page.getByLabel('Token name').first() + await expect(nameInput).toBeVisible() + await nameInput.fill('TestUSD') + + const symbolInput = page.getByLabel('Token symbol').first() + await expect(symbolInput).toBeVisible() + await symbolInput.fill('TEST') + + const deployButton = page.getByRole('button', { name: 'Deploy' }).first() + await expect(deployButton).toBeVisible() + await deployButton.click() + + // Wait for success - View receipt link + await expect(page.getByRole('link', { name: 'View receipt' })).toBeVisible({ timeout: 90000 }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/distribute-rewards.test.ts b/e2e/distribute-rewards.test.ts new file mode 100644 index 00000000..39ae4275 --- /dev/null +++ b/e2e/distribute-rewards.test.ts @@ -0,0 +1,110 @@ +import { expect, test } from '@playwright/test' + +test('distribute rewards', async ({ page }) => { + test.setTimeout(240000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/issuance/distribute-rewards') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Create a token + // Use label-based selectors to ensure we're filling the right inputs in the demo form + const nameInput = page.getByLabel('Token name').first() + await expect(nameInput).toBeVisible() + await nameInput.fill('RewardTestUSD') + + const symbolInput = page.getByLabel('Token symbol').first() + await expect(symbolInput).toBeVisible() + await symbolInput.fill('REWARD') + + const deployButton = page.getByRole('button', { name: 'Deploy' }).first() + await expect(deployButton).toBeVisible() + await deployButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 4: Grant issuer role + const grantEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(grantEnterDetails).toBeVisible() + await grantEnterDetails.click() + + const grantButton = page.getByRole('button', { name: 'Grant' }).first() + await grantButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(1)).toBeVisible({ + timeout: 90000, + }) + + // Step 5: Mint tokens (after grant completes, Enter details button is the first visible one) + const mintEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(mintEnterDetails).toBeVisible() + await mintEnterDetails.click() + + const mintButton = page.getByRole('button', { name: 'Mint' }).first() + await mintButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(2)).toBeVisible({ + timeout: 90000, + }) + + // Step 6: Opt in to rewards + const optInButton = page.getByRole('button', { name: 'Opt In' }).first() + await expect(optInButton).toBeVisible() + await optInButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(3)).toBeVisible({ + timeout: 90000, + }) + + // Step 7: Start reward + const startButton = page.getByRole('button', { name: 'Start Reward' }).first() + await expect(startButton).toBeVisible() + await startButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(4)).toBeVisible({ + timeout: 90000, + }) + + // Step 8: Claim reward + const claimButton = page.getByRole('button', { name: 'Claim' }).first() + await expect(claimButton).toBeVisible() + await claimButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(5)).toBeVisible({ + timeout: 90000, + }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/executing-swaps.test.ts b/e2e/executing-swaps.test.ts new file mode 100644 index 00000000..264d02d2 --- /dev/null +++ b/e2e/executing-swaps.test.ts @@ -0,0 +1,49 @@ +import { expect, test } from '@playwright/test' + +test('executing swaps', async ({ page }) => { + test.setTimeout(180000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/stablecoin-dex/executing-swaps') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Execute a swap (Buy AlphaUSD with BetaUSD) + const buyButton = page.getByRole('button', { name: 'Buy' }).first() + await expect(buyButton).toBeVisible() + await buyButton.click() + + // Wait for swap receipt + await expect(page.getByRole('link', { name: 'View receipt' })).toBeVisible({ timeout: 90000 }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/faucet.spec.ts b/e2e/faucet.test.ts similarity index 73% rename from e2e/faucet.spec.ts rename to e2e/faucet.test.ts index dceb26c4..b862fc8c 100644 --- a/e2e/faucet.spec.ts +++ b/e2e/faucet.test.ts @@ -1,10 +1,14 @@ import { expect, test } from '@playwright/test' test('fund an address via faucet', async ({ page }) => { + test.setTimeout(120000) + await page.goto('/quickstart/faucet') // Switch to "Fund an address" tab - await page.getByRole('tab', { name: 'Fund an address' }).click() + const tab = page.getByRole('tab', { name: 'Fund an address' }) + await expect(tab).toBeVisible({ timeout: 90000 }) + await tab.click() // Enter an address const addressInput = page.getByPlaceholder('0x...') @@ -14,5 +18,5 @@ test('fund an address via faucet', async ({ page }) => { await page.getByRole('button', { name: 'Add funds' }).click() // Confirm "View receipt" link is visible - await expect(page.getByRole('link', { name: 'View receipt' })).toBeVisible({ timeout: 30000 }) + await expect(page.getByRole('link', { name: 'View receipt' })).toBeVisible({ timeout: 90000 }) }) diff --git a/e2e/manage-stablecoin.test.ts b/e2e/manage-stablecoin.test.ts new file mode 100644 index 00000000..8aa13c4d --- /dev/null +++ b/e2e/manage-stablecoin.test.ts @@ -0,0 +1,86 @@ +import { expect, test } from '@playwright/test' + +test('manage stablecoin - grant and revoke roles', async ({ page }) => { + test.setTimeout(180000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/issuance/manage-stablecoin') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Create a token + // Use label-based selectors to ensure we're filling the right inputs in the demo form + const nameInput = page.getByLabel('Token name').first() + await expect(nameInput).toBeVisible() + await nameInput.fill('ManageTestUSD') + + const symbolInput = page.getByLabel('Token symbol').first() + await expect(symbolInput).toBeVisible() + await symbolInput.fill('MANAGE') + + const deployButton = page.getByRole('button', { name: 'Deploy' }).first() + await expect(deployButton).toBeVisible() + await deployButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 4: Grant issuer role + const grantEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(grantEnterDetails).toBeVisible() + await grantEnterDetails.click() + + const grantButton = page.getByRole('button', { name: 'Grant' }).first() + await expect(grantButton).toBeVisible() + await grantButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(1)).toBeVisible({ + timeout: 90000, + }) + + // Step 5: Revoke issuer role (now the first visible Enter details) + const revokeEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(revokeEnterDetails).toBeVisible() + await revokeEnterDetails.click() + + const revokeButton = page.getByRole('button', { name: 'Revoke' }).first() + await expect(revokeButton).toBeVisible() + await revokeButton.click() + + // Wait for revoke receipt + await expect(page.getByRole('link', { name: 'View receipt' }).nth(2)).toBeVisible({ + timeout: 90000, + }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/mint-stablecoins.test.ts b/e2e/mint-stablecoins.test.ts new file mode 100644 index 00000000..05c2ac98 --- /dev/null +++ b/e2e/mint-stablecoins.test.ts @@ -0,0 +1,88 @@ +import { expect, test } from '@playwright/test' + +test('mint stablecoins', async ({ page }) => { + test.setTimeout(180000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/issuance/mint-stablecoins') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Create a token (fill form and deploy) + // Use label-based selectors to ensure we're filling the right inputs in the demo form + const nameInput = page.getByLabel('Token name').first() + await expect(nameInput).toBeVisible() + await nameInput.fill('MintTestUSD') + + const symbolInput = page.getByLabel('Token symbol').first() + await expect(symbolInput).toBeVisible() + await symbolInput.fill('MINT') + + const deployButton = page.getByRole('button', { name: 'Deploy' }).first() + await expect(deployButton).toBeVisible() + await deployButton.click() + + // Wait for token to be created (View receipt appears) + await expect(page.getByRole('link', { name: 'View receipt' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 4: Grant issuer role - click "Enter details" then "Grant" + const grantEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(grantEnterDetails).toBeVisible() + await grantEnterDetails.click() + + const grantButton = page.getByRole('button', { name: 'Grant' }).first() + await expect(grantButton).toBeVisible() + await grantButton.click() + + // Wait for grant receipt + await expect(page.getByRole('link', { name: 'View receipt' }).nth(1)).toBeVisible({ + timeout: 90000, + }) + + // Step 5: Mint tokens - click "Enter details" then "Mint" (now the first visible Enter details) + const mintEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(mintEnterDetails).toBeVisible() + await mintEnterDetails.click() + + const mintButton = page.getByRole('button', { name: 'Mint' }).first() + await expect(mintButton).toBeVisible() + await mintButton.click() + + // Wait for mint receipt + await expect(page.getByRole('link', { name: 'View receipt' }).nth(2)).toBeVisible({ + timeout: 90000, + }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/passkey-accounts.test.ts b/e2e/passkey-accounts.test.ts new file mode 100644 index 00000000..0503f280 --- /dev/null +++ b/e2e/passkey-accounts.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from '@playwright/test' + +test('sign up, sign out, then sign in with passkey', async ({ page }) => { + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/use-accounts/embed-passkeys') + + // Wait for the demo to load + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + + // Sign up with passkey + await signUpButton.click() + + // Wait for sign out button (indicates successful sign up) + const signOutButton = page.getByRole('button', { name: 'Sign out' }).first() + await expect(signOutButton).toBeVisible({ timeout: 30000 }) + + // Sign out + await signOutButton.click() + + // Wait for sign in button to reappear + const signInButton = page.getByRole('button', { name: 'Sign in' }).first() + await expect(signInButton).toBeVisible({ timeout: 10000 }) + + // Sign in with the same passkey + await signInButton.click() + + // Confirm signed in again (sign out button visible) + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/providing-liquidity.test.ts b/e2e/providing-liquidity.test.ts new file mode 100644 index 00000000..3967c2eb --- /dev/null +++ b/e2e/providing-liquidity.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from '@playwright/test' + +test('providing liquidity - place and query order', async ({ page }) => { + test.setTimeout(180000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/stablecoin-dex/providing-liquidity') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Place order + const placeOrderButton = page.getByRole('button', { name: 'Place order' }).first() + await expect(placeOrderButton).toBeVisible() + await placeOrderButton.click() + + // Wait for order to be placed - should see View receipt + await expect(page.getByRole('link', { name: 'View receipt' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 4: Query order - button should become enabled after placing + const queryButton = page.getByRole('button', { name: 'Query' }).first() + await expect(queryButton).toBeEnabled({ timeout: 30000 }) + await queryButton.click() + + // Wait for order details to show (order type indicator) + await expect(page.getByText('Buy').first()).toBeVisible({ timeout: 30000 }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/send-a-payment.test.ts b/e2e/send-a-payment.test.ts new file mode 100644 index 00000000..8049bfdd --- /dev/null +++ b/e2e/send-a-payment.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from '@playwright/test' + +test('send a payment', async ({ page }) => { + test.setTimeout(120000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/payments/send-a-payment') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + // Wait for sign out button (indicates successful sign up) + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + // Wait for "Add more funds" button (indicates funds were added) + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Send payment + const enterDetailsButton = page.getByRole('button', { name: 'Enter details' }).first() + await expect(enterDetailsButton).toBeVisible() + await enterDetailsButton.click() + + // Fill in optional memo + const memoInput = page.getByLabel('Memo (optional)').first() + await expect(memoInput).toBeVisible() + await memoInput.fill('test-memo') + + // Click send + const sendButton = page.getByRole('button', { name: 'Send' }).first() + await sendButton.click() + + // Wait for transaction receipt link + await expect(page.getByRole('link', { name: 'View receipt' })).toBeVisible({ timeout: 90000 }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/e2e/use-for-fees.test.ts b/e2e/use-for-fees.test.ts new file mode 100644 index 00000000..5a6df28d --- /dev/null +++ b/e2e/use-for-fees.test.ts @@ -0,0 +1,105 @@ +import { expect, test } from '@playwright/test' + +test('use stablecoin for fees', async ({ page }) => { + test.setTimeout(240000) + + // Set up virtual authenticator via CDP + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }) + + await page.goto('/guide/issuance/use-for-fees') + + // Step 1: Sign up with passkey + const signUpButton = page.getByRole('button', { name: 'Sign up' }).first() + await expect(signUpButton).toBeVisible({ timeout: 90000 }) + await signUpButton.click() + + await expect(page.getByRole('button', { name: 'Sign out' }).first()).toBeVisible({ + timeout: 30000, + }) + + // Step 2: Add funds + const addFundsButton = page.getByRole('button', { name: 'Add funds' }).first() + await expect(addFundsButton).toBeVisible() + await addFundsButton.click() + + await expect(page.getByRole('button', { name: 'Add more funds' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 3: Create a token + // Use label-based selectors to ensure we're filling the right inputs in the demo form + const nameInput = page.getByLabel('Token name').first() + await expect(nameInput).toBeVisible() + await nameInput.fill('FeeTestUSD') + + const symbolInput = page.getByLabel('Token symbol').first() + await expect(symbolInput).toBeVisible() + await symbolInput.fill('FEE') + + const deployButton = page.getByRole('button', { name: 'Deploy' }).first() + await expect(deployButton).toBeVisible() + await deployButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).first()).toBeVisible({ + timeout: 90000, + }) + + // Step 4: Grant issuer role + const grantEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(grantEnterDetails).toBeVisible() + await grantEnterDetails.click() + + const grantButton = page.getByRole('button', { name: 'Grant' }).first() + await grantButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(1)).toBeVisible({ + timeout: 90000, + }) + + // Step 5: Mint tokens (after grant completes, Enter details button is the first visible one) + const mintEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(mintEnterDetails).toBeVisible() + await mintEnterDetails.click() + + const mintButton = page.getByRole('button', { name: 'Mint' }).first() + await mintButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(2)).toBeVisible({ + timeout: 90000, + }) + + // Step 6: Add fee AMM liquidity + const addLiquidityButton = page.getByRole('button', { name: 'Add Liquidity' }).first() + await expect(addLiquidityButton).toBeVisible() + await addLiquidityButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(3)).toBeVisible({ + timeout: 90000, + }) + + // Step 7: Send payment using token as fee (now the only Enter details button visible) + const payEnterDetails = page.getByRole('button', { name: 'Enter details' }).first() + await expect(payEnterDetails).toBeVisible() + await payEnterDetails.click() + + const sendButton = page.getByRole('button', { name: 'Send' }).first() + await expect(sendButton).toBeVisible() + await sendButton.click() + + await expect(page.getByRole('link', { name: 'View receipt' }).nth(4)).toBeVisible({ + timeout: 90000, + }) + + // Clean up + await client.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }) +}) diff --git a/playwright.config.ts b/playwright.config.ts index 3b060a86..8c52ede9 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -4,8 +4,9 @@ export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + retries: process.env.CI ? 1 : 1, // Retry once due to testnet flakiness + workers: process.env.CI ? 4 : undefined, + timeout: 180000, // 3 min default timeout for testnet transactions reporter: 'html', use: { baseURL: 'http://localhost:5173', @@ -18,8 +19,10 @@ export default defineConfig({ }, ], webServer: { - command: 'pnpm run dev', + command: 'pnpm run dev 2>/dev/null', url: 'http://localhost:5173', reuseExistingServer: !process.env.CI, + stdout: 'ignore', + stderr: 'ignore', }, }) diff --git a/src/components/ConnectWallet.tsx b/src/components/ConnectWallet.tsx index 93ace37d..3b121866 100644 --- a/src/components/ConnectWallet.tsx +++ b/src/components/ConnectWallet.tsx @@ -17,7 +17,7 @@ export function ConnectWallet({ showAddChain = true }: { showAddChain?: boolean const isSupported = chains.some((c) => c.id === chain?.id) if (!injectedConnectors.length) return ( -
No browser wallets found.
+
No browser wallets found.
) if (!address || connector?.id === 'webAuthn') return ( @@ -64,7 +64,7 @@ export function ConnectWallet({ showAddChain = true }: { showAddChain?: boolean )} {switchChain.isSuccess && ( -
+
Added Tempo to {connector?.name ?? 'Wallet'}!
)} diff --git a/src/components/IndexSupplyQuery.tsx b/src/components/IndexSupplyQuery.tsx index ac18c85c..ceac85cb 100644 --- a/src/components/IndexSupplyQuery.tsx +++ b/src/components/IndexSupplyQuery.tsx @@ -246,7 +246,7 @@ export function IndexSupplyQuery(props: IndexSupplyQueryProps = {}) { return ( +

{props.title || 'IndexSupply SQL Query'}

} @@ -325,7 +325,7 @@ export function IndexSupplyQuery(props: IndexSupplyQueryProps = {}) {
{error && ( -
+
{error}
)} diff --git a/src/components/TokenSelector.tsx b/src/components/TokenSelector.tsx index df4fff2b..eca9ac78 100644 --- a/src/components/TokenSelector.tsx +++ b/src/components/TokenSelector.tsx @@ -29,7 +29,7 @@ export function TokenSelector(props: TokenSelectorProps) { name={name} value={value} onChange={(e) => onChange(e.target.value as Address)} - className="-tracking-[2%] h-[34px] rounded-lg border border-gray4 px-3.25 font-normal text-[14px] text-black dark:text-white" + className="h-[34px] rounded-lg border border-gray4 px-3.25 font-normal text-[14px] text-black -tracking-[2%] dark:text-white" > {tokens.map((token) => ( diff --git a/src/components/guides/Demo.tsx b/src/components/guides/Demo.tsx index fc26a485..a0f7c33b 100644 --- a/src/components/guides/Demo.tsx +++ b/src/components/guides/Demo.tsx @@ -51,7 +51,7 @@ export function ExplorerLink({ hash }: { hash: string }) { href={url} target="_blank" rel="noreferrer" - className="-tracking-[1%] flex items-center gap-1 text-[13px] text-accent hover:underline" + className="flex items-center gap-1 text-[13px] text-accent -tracking-[1%] hover:underline" onClick={() => trackExternalLinkClick(url, 'View receipt')} > View receipt @@ -71,7 +71,7 @@ export function ExplorerAccountLink({ address }: { address: string }) { href={url} target="_blank" rel="noreferrer" - className="-tracking-[1%] flex items-center gap-1 text-[13px] text-accent hover:underline" + className="flex items-center gap-1 text-[13px] text-accent -tracking-[1%] hover:underline" onClick={() => trackExternalLinkClick(url, 'View account')} > View account @@ -142,7 +142,7 @@ export function Container( -

+

{name}

{showBadge && ( @@ -322,7 +322,7 @@ export function Step( > {completed ? : number}
-
+
{title}
@@ -334,7 +334,7 @@ export function Step( {error && ( <>
-
+
{'shortMessage' in error ? error.shortMessage : error.message}
@@ -376,7 +376,7 @@ export function Login() {
diff --git a/src/components/guides/steps/amm/CheckFeeAmmPool.tsx b/src/components/guides/steps/amm/CheckFeeAmmPool.tsx index f644cd69..050307ff 100644 --- a/src/components/guides/steps/amm/CheckFeeAmmPool.tsx +++ b/src/components/guides/steps/amm/CheckFeeAmmPool.tsx @@ -49,7 +49,7 @@ export function CheckFeeAmmPool(props: DemoStepProps) { {active && pool && lpBalance && (
-
+
Your LP Balance diff --git a/src/components/guides/steps/amm/MintFeeAmmLiquidity.tsx b/src/components/guides/steps/amm/MintFeeAmmLiquidity.tsx index 724e3c21..e8cf3b95 100644 --- a/src/components/guides/steps/amm/MintFeeAmmLiquidity.tsx +++ b/src/components/guides/steps/amm/MintFeeAmmLiquidity.tsx @@ -115,7 +115,7 @@ export function MintFeeAmmLiquidity(props: DemoStepProps & { waitForBalance?: bo disabled={!active || mintFeeLiquidity.isPending} onClick={handleMintAll} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {mintFeeLiquidity.isPending ? 'Adding...' diff --git a/src/components/guides/steps/exchange/ApproveSpend.tsx b/src/components/guides/steps/exchange/ApproveSpend.tsx index a8e60173..3205d9e7 100644 --- a/src/components/guides/steps/exchange/ApproveSpend.tsx +++ b/src/components/guides/steps/exchange/ApproveSpend.tsx @@ -43,7 +43,7 @@ export function ApproveSpend(props: DemoStepProps) { }) }} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {approve.isPending ? 'Approving...' : 'Approve Spend'} diff --git a/src/components/guides/steps/exchange/BuySwap.tsx b/src/components/guides/steps/exchange/BuySwap.tsx index f02019c3..0648c0b5 100644 --- a/src/components/guides/steps/exchange/BuySwap.tsx +++ b/src/components/guides/steps/exchange/BuySwap.tsx @@ -76,7 +76,7 @@ export function BuySwap({ onSuccess }: { onSuccess?: () => void }) { }) }} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {sendCalls.isPending ? 'Buying...' : 'Buy'} diff --git a/src/components/guides/steps/exchange/CancelOrder.tsx b/src/components/guides/steps/exchange/CancelOrder.tsx index 37159f2c..19923ad8 100644 --- a/src/components/guides/steps/exchange/CancelOrder.tsx +++ b/src/components/guides/steps/exchange/CancelOrder.tsx @@ -45,7 +45,7 @@ export function CancelOrder(props: DemoStepProps) { } }} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {cancelOrder.isPending ? 'Canceling...' : 'Cancel Order'} diff --git a/src/components/guides/steps/exchange/PlaceOrder.tsx b/src/components/guides/steps/exchange/PlaceOrder.tsx index f8ff7378..c372fdda 100644 --- a/src/components/guides/steps/exchange/PlaceOrder.tsx +++ b/src/components/guides/steps/exchange/PlaceOrder.tsx @@ -78,7 +78,7 @@ export function PlaceOrder(props: DemoStepProps) { }) }} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {sendCalls.isPending ? 'Placing Order...' : 'Place Order'} diff --git a/src/components/guides/steps/exchange/QueryOrder.tsx b/src/components/guides/steps/exchange/QueryOrder.tsx index 6e0255a0..75ae4db9 100644 --- a/src/components/guides/steps/exchange/QueryOrder.tsx +++ b/src/components/guides/steps/exchange/QueryOrder.tsx @@ -52,7 +52,7 @@ export function QueryOrder(props: DemoStepProps) { disabled={!active || isQuerying} onClick={handleQuery} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {isQuerying ? 'Querying...' : hasQueried ? 'Query Again' : 'Query Order'} diff --git a/src/components/guides/steps/exchange/SellSwap.tsx b/src/components/guides/steps/exchange/SellSwap.tsx index e705013f..8b52cd6a 100644 --- a/src/components/guides/steps/exchange/SellSwap.tsx +++ b/src/components/guides/steps/exchange/SellSwap.tsx @@ -76,7 +76,7 @@ export function SellSwap({ onSuccess }: { onSuccess?: () => void }) { }) }} type="button" - className="-tracking-[2%] font-normal text-[14px]" + className="font-normal text-[14px] -tracking-[2%]" > {sendCalls.isPending ? 'Selling...' : 'Sell'} diff --git a/src/components/guides/steps/issuance/BurnToken.tsx b/src/components/guides/steps/issuance/BurnToken.tsx index 8f3b0ccc..0f3de823 100644 --- a/src/components/guides/steps/issuance/BurnToken.tsx +++ b/src/components/guides/steps/issuance/BurnToken.tsx @@ -73,7 +73,7 @@ export function BurnToken(props: DemoStepProps) { @@ -98,13 +98,14 @@ export function BurnToken(props: DemoStepProps) {
-