-
Notifications
You must be signed in to change notification settings - Fork 3
test: add Playwright E2E test suite with mock mode and CI #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
thepastaclaw
wants to merge
12
commits into
PastaPastaPasta:main
Choose a base branch
from
thepastaclaw:feat/playwright-e2e-testnet
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
be62323
feat: add E2E mock mode for deterministic Playwright testing
thepastaclaw f8a07db
test: add Playwright E2E test suite with CI workflow
thepastaclaw 6a2405c
docs: add Playwright E2E usage and env var documentation
thepastaclaw 0f97e3f
fix: add @playwright/test as dev dependency
thepastaclaw 6576a88
Stabilize e2e mock bridge flow and manage key assertions
thepastaclaw 1885776
fix: add mock advance hook to top-up flow and stabilize DPNS checkbox…
thepastaclaw c724030
fix: set isContested in mock DPNS availability results
thepastaclaw 069e697
fix: use force click on contested checkbox for Playwright actionability
thepastaclaw a9b343d
fix(e2e): address CodeRabbit stability and safety feedback
thepastaclaw 250efc8
fix(e2e): apply remaining CodeRabbit follow-ups
thepastaclaw 60a746d
fix(e2e): use AssetLockProofData objects in mock flows instead of pla…
thepastaclaw 5a851e9
merge: resolve conflicts with main
thepastaclaw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,6 @@ dist/ | |
| .claude/ | ||
| *.log | ||
| .DS_Store | ||
| playwright-report/ | ||
| test-results/ | ||
| .env.playwright-live | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
| import { | ||
| E2E_MOCK_DPNS_WIF, | ||
| E2E_MOCK_IDENTITY_ID, | ||
| E2E_MOCK_MANAGE_WIF, | ||
| } from '../src/e2e-mock-constants'; | ||
|
|
||
| const MOCK_QUERY = '/?network=testnet&e2e=mock'; | ||
|
|
||
| test.describe('Deterministic UI E2E (mock mode)', () => { | ||
| test('create identity flow transitions to completion and DPNS registration', async ({ page }) => { | ||
| await page.goto(MOCK_QUERY); | ||
|
|
||
| await page.click('#mode-create-btn'); | ||
| await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible(); | ||
|
|
||
| await page.click('#continue-btn'); | ||
|
|
||
| await expect(page.locator('.deposit-headline')).toBeVisible(); | ||
| await expect.poll(async () => { | ||
| return page.evaluate(() => typeof (window as { __e2eMockAdvance?: () => void }).__e2eMockAdvance); | ||
| }).toBe('function'); | ||
| await page.evaluate(() => (window as { __e2eMockAdvance?: () => void }).__e2eMockAdvance?.()); | ||
|
|
||
| await expect(page.getByText('Save your keys')).toBeVisible(); | ||
| await expect(page.locator('.contract-id-section', { hasText: 'Your Identity ID' }).locator('.identity-id')).toHaveText(E2E_MOCK_IDENTITY_ID); | ||
|
|
||
| await page.click('#dpns-from-identity-btn'); | ||
| await expect(page.getByText('Choose Your Usernames')).toBeVisible(); | ||
|
|
||
| const usernameInput = page.locator('.dpns-username-input').first(); | ||
| await usernameInput.fill('alpha'); | ||
| await page.click('#check-availability-btn'); | ||
|
|
||
| await expect(page.getByText('Review Usernames')).toBeVisible(); | ||
| await expect(page.getByText('Available (Contested)')).toBeVisible(); | ||
| await expect(page.locator('#dpns-contested-checkbox')).toBeVisible(); | ||
| await expect(page.locator('#register-dpns-btn')).toBeDisabled(); | ||
|
|
||
| await page.locator('#dpns-contested-checkbox').click({ force: true }); | ||
| await expect(page.locator('#register-dpns-btn')).toBeEnabled(); | ||
|
|
||
| await page.click('#register-dpns-btn'); | ||
| await expect(page.getByText('Registration Complete!')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('top up flow validates identity input and reaches completion', async ({ page }) => { | ||
| await page.goto(MOCK_QUERY); | ||
|
|
||
| await page.click('#mode-topup-btn'); | ||
| await expect(page.locator('#identity-id-input')).toBeVisible(); | ||
|
|
||
| await page.click('#continue-topup-btn'); | ||
| await expect(page.locator('#validation-msg')).toContainText('Please enter a valid identity ID'); | ||
|
|
||
| await page.fill('#identity-id-input', E2E_MOCK_IDENTITY_ID); | ||
| await page.click('#continue-topup-btn'); | ||
|
|
||
| await expect(page.locator('.deposit-headline')).toBeVisible(); | ||
| await expect.poll(async () => { | ||
| return page.evaluate(() => typeof (window as { __e2eMockAdvance?: () => void }).__e2eMockAdvance); | ||
| }).toBe('function'); | ||
| await page.evaluate(() => (window as { __e2eMockAdvance?: () => void }).__e2eMockAdvance?.()); | ||
|
|
||
| await expect(page.getByText('Top-up complete!')).toBeVisible(); | ||
| await expect(page.locator('.contract-id-section', { hasText: 'Identity ID' }).locator('.identity-id')).toHaveText(E2E_MOCK_IDENTITY_ID); | ||
| }); | ||
|
|
||
| test('manage identity flow validates key and applies changes', async ({ page }) => { | ||
| await page.goto(MOCK_QUERY); | ||
|
|
||
| await page.click('#mode-manage-btn'); | ||
| await expect(page.locator('#manage-identity-id-input')).toBeVisible(); | ||
|
|
||
| await page.fill('#manage-identity-id-input', E2E_MOCK_IDENTITY_ID); | ||
| await page.locator('#manage-identity-id-input').press('Tab'); | ||
| await expect(page.getByText('Identity found with 2 keys')).toBeVisible(); | ||
|
|
||
| await page.fill('#manage-private-key-input', 'bad-key'); | ||
| await page.locator('#manage-private-key-input').press('Tab'); | ||
| await expect(page.getByText('Mock mode: use the configured test private key')).toBeVisible(); | ||
|
|
||
| await page.fill('#manage-private-key-input', E2E_MOCK_MANAGE_WIF); | ||
| await page.locator('#manage-private-key-input').blur(); | ||
| await expect(page.getByText('Manage Keys')).toBeVisible(); | ||
|
|
||
| await page.click('#add-manage-key-btn'); | ||
| await page.locator('.manage-disable-key-checkbox').first().click({ force: true }); | ||
| await expect(page.getByText('Will add 1 key, disable 1 key')).toBeVisible(); | ||
|
|
||
| await page.click('#apply-manage-btn'); | ||
| await expect(page.getByText('Update Complete!')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('standalone DPNS flow validates identity + key and completes registration', async ({ page }) => { | ||
| await page.goto(MOCK_QUERY); | ||
|
|
||
| await page.click('#mode-dpns-btn'); | ||
| await expect(page.getByText('Register a Username')).toBeVisible(); | ||
|
|
||
| await page.click('#dpns-choose-existing-btn'); | ||
| await expect(page.locator('#dpns-identity-id-input')).toBeVisible(); | ||
|
|
||
| await page.fill('#dpns-identity-id-input', E2E_MOCK_IDENTITY_ID); | ||
| await page.locator('#dpns-identity-id-input').press('Tab'); | ||
| await expect(page.getByText('Identity found with 2 keys')).toBeVisible(); | ||
|
|
||
| await page.fill('#dpns-private-key-input', E2E_MOCK_DPNS_WIF); | ||
| await page.locator('#dpns-private-key-input').press('Tab'); | ||
| await expect(page.getByText('Key matches key #1 (CRITICAL level)')).toBeVisible(); | ||
|
|
||
| await page.click('#dpns-identity-continue-btn'); | ||
| await expect(page.getByText('Choose Your Usernames')).toBeVisible(); | ||
|
|
||
| await page.locator('.dpns-username-input').first().fill('noncontested123456789012345'); | ||
| await page.click('#check-availability-btn'); | ||
|
|
||
| await expect(page.getByText('Review Usernames')).toBeVisible(); | ||
| await expect(page.locator('#register-dpns-btn')).toBeEnabled(); | ||
|
|
||
| await page.click('#register-dpns-btn'); | ||
| await expect(page.getByText('Registration Complete!')).toBeVisible(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
|
|
||
| const LIVE_ENABLED = process.env.PW_E2E_LIVE === '1'; | ||
| const LIVE_TOPUP_IDENTITY_ID = process.env.PW_LIVE_TOPUP_IDENTITY_ID; | ||
| const LIVE_DPNS_IDENTITY_ID = process.env.PW_LIVE_DPNS_IDENTITY_ID; | ||
| const LIVE_DPNS_PRIVATE_KEY_WIF = process.env.PW_LIVE_DPNS_PRIVATE_KEY_WIF; | ||
| const LIVE_MANAGE_IDENTITY_ID = process.env.PW_LIVE_MANAGE_IDENTITY_ID; | ||
| const LIVE_MANAGE_PRIVATE_KEY_WIF = process.env.PW_LIVE_MANAGE_PRIVATE_KEY_WIF; | ||
|
|
||
| test.describe('Live testnet E2E (optional)', () => { | ||
| test.skip(!LIVE_ENABLED, 'Set PW_E2E_LIVE=1 to enable live testnet checks'); | ||
|
|
||
| test('create mode renders real deposit details on testnet', async ({ page }) => { | ||
| await page.goto('/?network=testnet&e2e=live'); | ||
|
|
||
| await page.click('#mode-create-btn'); | ||
| await page.click('#continue-btn'); | ||
|
|
||
| await expect(page.locator('.deposit-headline')).toBeVisible({ timeout: 15000 }); | ||
| await expect(page.getByRole('button', { name: 'Request Testnet Funds' })).toBeVisible({ timeout: 15000 }); | ||
| await expect(page.locator('.address')).toBeVisible({ timeout: 15000 }); | ||
| }); | ||
|
|
||
| test('top up mode validates and renders testnet deposit step', async ({ page }) => { | ||
| test.skip(!LIVE_TOPUP_IDENTITY_ID, 'Set PW_LIVE_TOPUP_IDENTITY_ID to run top-up live check'); | ||
|
|
||
| await page.goto('/?network=testnet&e2e=live'); | ||
|
|
||
| await page.click('#mode-topup-btn'); | ||
| await page.fill('#identity-id-input', LIVE_TOPUP_IDENTITY_ID!); | ||
| await page.click('#continue-topup-btn'); | ||
|
|
||
| await expect(page.locator('.deposit-headline')).toContainText('Top up', { timeout: 15000 }); | ||
| await expect(page.locator('.address')).toBeVisible({ timeout: 15000 }); | ||
| }); | ||
|
|
||
| test('dpns existing identity key validation works against testnet', async ({ page }) => { | ||
| test.skip( | ||
| !LIVE_DPNS_IDENTITY_ID || !LIVE_DPNS_PRIVATE_KEY_WIF, | ||
| 'Set PW_LIVE_DPNS_IDENTITY_ID and PW_LIVE_DPNS_PRIVATE_KEY_WIF to run DPNS live check' | ||
| ); | ||
|
|
||
| await page.goto('/?network=testnet&e2e=live'); | ||
|
|
||
| await page.click('#mode-dpns-btn'); | ||
| await page.click('#dpns-choose-existing-btn'); | ||
|
|
||
| await page.fill('#dpns-identity-id-input', LIVE_DPNS_IDENTITY_ID!); | ||
| await page.locator('#dpns-identity-id-input').press('Tab'); | ||
|
|
||
| await expect(page.locator('.identity-status.success')).toContainText('Identity found with', { timeout: 90000 }); | ||
|
|
||
| await page.fill('#dpns-private-key-input', LIVE_DPNS_PRIVATE_KEY_WIF!); | ||
| await page.locator('#dpns-private-key-input').press('Tab'); | ||
|
|
||
| await expect(page.locator('.key-status.success')).toContainText('Key matches key', { timeout: 30000 }); | ||
| await expect(page.locator('#dpns-identity-continue-btn')).toBeEnabled(); | ||
| }); | ||
|
|
||
| test('manage identity key validation works against testnet', async ({ page }) => { | ||
| test.skip( | ||
| !LIVE_MANAGE_IDENTITY_ID || !LIVE_MANAGE_PRIVATE_KEY_WIF, | ||
| 'Set PW_LIVE_MANAGE_IDENTITY_ID and PW_LIVE_MANAGE_PRIVATE_KEY_WIF to run manage live check' | ||
| ); | ||
|
|
||
| await page.goto('/?network=testnet&e2e=live'); | ||
|
|
||
| await page.click('#mode-manage-btn'); | ||
| await page.fill('#manage-identity-id-input', LIVE_MANAGE_IDENTITY_ID!); | ||
| await page.locator('#manage-identity-id-input').press('Tab'); | ||
|
|
||
| await expect(page.locator('.identity-status.success')).toContainText('Identity found with', { timeout: 90000 }); | ||
|
|
||
| await page.fill('#manage-private-key-input', LIVE_MANAGE_PRIVATE_KEY_WIF!); | ||
| await page.locator('#manage-private-key-input').press('Tab'); | ||
|
|
||
| await expect(page.locator('.key-status.success')).toContainText('MASTER level', { timeout: 30000 }); | ||
| await expect(page.locator('#manage-identity-continue-btn')).toBeEnabled(); | ||
| }); | ||
| }); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.