Skip to content

test: add Playwright E2E test suite with mock mode and CI#16

Open
thepastaclaw wants to merge 11 commits intoPastaPastaPasta:mainfrom
thepastaclaw:feat/playwright-e2e-testnet
Open

test: add Playwright E2E test suite with mock mode and CI#16
thepastaclaw wants to merge 11 commits intoPastaPastaPasta:mainfrom
thepastaclaw:feat/playwright-e2e-testnet

Conversation

@thepastaclaw
Copy link
Contributor

@thepastaclaw thepastaclaw commented Feb 20, 2026

Summary

Adds Playwright E2E test infrastructure with two test modes:

Deterministic mock mode (CI-safe)

  • Runs against ?e2e=mock URL parameter
  • Mock mode intercepts all platform API calls and returns deterministic fake data
  • Covers all UI flows: create identity, top-up, manage keys, DPNS registration, standalone DPNS
  • Zero network access required — runs entirely in-browser with mocked responses
  • Executes in CI via GitHub Actions with headless Chromium

Optional live testnet mode

  • Gated by PW_E2E_LIVE=1 environment variable
  • Tests real API interactions (identity lookup, key validation) against testnet
  • Individual tests skip gracefully when required env vars are missing
  • Not run in CI — intended for local validation before releases

Changes

  • src/main.ts: Mock mode implementation (~180 lines, gated behind URL param)
  • e2e/deterministic.spec.ts: 4 deterministic UI flow tests
  • e2e/live.testnet.spec.ts: 4 optional live testnet tests
  • playwright.config.ts: Chromium config with Vite dev server integration
  • vitest.config.ts: Excludes e2e/ from unit test runs
  • .github/workflows/ci.yml: New e2e job with Playwright install + run
  • package.json: npm scripts for E2E (headless, headed, live, install)
  • README.md: E2E usage documentation with env var reference

Note

Playwright is used via npx playwright@1.52.0 (not a dev dependency) to keep the install footprint minimal. The mock mode tests have not been verified locally (headless macOS machine without display) — CI will be the first real validation. Follow-up commits may be needed to fix test selector mismatches.

Summary by CodeRabbit

  • New Features

    • Added E2E testing modes: deterministic mock UI for repeatable flows and optional live testnet runs.
  • Tests

    • Playwright end-to-end suites cover identity creation, top-ups, username registration, and key management; includes deterministic mock and env-gated live tests.
  • Documentation

    • README updated with E2E modes, install/run commands, and environment variable guidance.
  • Chores

    • CI now runs the E2E suite; added E2E scripts and ignored test artifact paths.

Validation

  1. What was tested

    • GitHub Actions CI on this PR:
      • build (20)
      • build (22)
      • e2e
  2. Results

    • build (20): pass
    • build (22): pass
    • e2e: pass
    • CodeRabbit review completed
  3. Environment

    • GitHub Actions (Ubuntu runner, headless CI execution)
    • E2E suite executed in CI workflow context for this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Adds Playwright end-to-end testing (deterministic mock mode and optional live testnet), Playwright and Vitest configs, CI e2e job and npm scripts, .gitignore updates, new e2e specs, and application mock-mode logic gated by e2e=mock.

Changes

Cohort / File(s) Summary
CI & Workflow
/.github/workflows/ci.yml
Adds an e2e CI job that sets up Node 22, installs Playwright browsers, and runs the e2e test sequence.
NPM Scripts & Dependencies
package.json
Adds scripts: test:e2e, test:e2e:headed, test:e2e:live, test:e2e:install; adds @playwright/test devDependency.
Playwright & Vitest Config
playwright.config.ts, vitest.config.ts
Adds Playwright config (env-driven host/port, baseURL, webServer, retries, reporters, chromium project, conditional CI behavior) and Vitest config excluding e2e/**.
E2E Tests
e2e/deterministic.spec.ts, e2e/live.testnet.spec.ts
Adds deterministic mock-mode Playwright spec and optional live-testnet spec (live tests gated/skipped based on env vars).
Application Mock Mode
src/main.ts, src/e2e-mock-constants.ts, src/types.ts, src/vite-env.d.ts
Implements e2e=mock branching: mock UTXO/identity/DPNS data, timed deterministic flows, __e2eMockAdvance hook and helpers, new exported mock constants, and a Vite types reference.
Docs & VCS
README.md, .gitignore
Documents Playwright E2E usage and env vars in README; adds playwright-report/, test-results/, and .env.playwright-live to .gitignore.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(63,81,181,0.5)
    participant CI as CI Runner
  end
  rect rgba(76,175,80,0.5)
    participant Server as Dev Server
  end
  rect rgba(255,193,7,0.5)
    participant Browser as Browser (Playwright)
  end
  rect rgba(156,39,176,0.5)
    participant AppMock as App Mock Engine
  end

  CI->>Server: start webServer (host/port from env)
  CI->>Browser: launch Playwright, open baseURL?e2e=mock
  Browser->>AppMock: check isE2EMockMode -> true
  Browser->>AppMock: trigger flows (create / top-up / manage)
  AppMock-->>Browser: staged progress (delays, __e2eMockAdvance)
  Browser-->>CI: run assertions and report results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble keys and hum a testing tune,
mock coins tumble beneath a paper moon.
Hop — advance the hook, let steps align,
deterministic hops make the UI shine.
Bravo — the suite skips flakiness every time.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: add Playwright E2E test suite with mock mode and CI' directly and clearly describes the main changes in the PR: adding Playwright end-to-end tests with mock mode support and CI integration.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/main.ts (1)

599-606: Mock key validation accepts only the exact hardcoded WIF strings.

The mock WIFs (cMockDpnsPrivateKeyWif, cMockManagePrivateKeyWif) are hardcoded on lines 110-111 here and duplicated on lines 5-6 of e2e/deterministic.spec.ts. If either side changes independently, the tests will silently fail with an unhelpful "Mock mode: use the configured test private key" error.

Consider exporting these constants from a shared location or at minimum adding a code comment cross-referencing the test file.

Also applies to: 846-853

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 599 - 606, The mock WIF validation currently
compares privateKeyWif against hardcoded constants (cMockDpnsPrivateKeyWif,
cMockManagePrivateKeyWif) duplicated in tests, which can cause silent test
breakage; refactor by moving those constants into a shared exported constant
(e.g., export MOCK_DPNS_WIF and MOCK_MANAGE_WIF from a new/shared module) and
import them into src/main.ts (where isE2EMockMode() check lives) and into
e2e/deterministic.spec.ts, updating the comparisons in the dpns mock validation
block (and the analogous block around setManageKeyValidationError) to use the
shared constants; if you cannot refactor now, add a clear code comment in both
places referencing e2e/deterministic.spec.ts and the source constant names to
ensure they stay in sync.
e2e/deterministic.spec.ts (1)

3-6: Mock constants duplicated from src/main.ts.

These values must stay in sync with E2E_MOCK_IDENTITY_ID, E2E_MOCK_DPNS_WIF, and E2E_MOCK_MANAGE_WIF in src/main.ts (lines 109-111). A mismatch would cause silent test failures. Consider extracting them to a shared constants file importable by both.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/deterministic.spec.ts` around lines 3 - 6, The test defines MOCK_QUERY,
MOCK_IDENTITY_ID, MOCK_DPNS_WIF, and MOCK_MANAGE_WIF which duplicate
E2E_MOCK_IDENTITY_ID, E2E_MOCK_DPNS_WIF, and E2E_MOCK_MANAGE_WIF in the main
code and risk drifting out of sync; refactor by extracting these values into a
single shared constants module (e.g., export the constants from a new
e2e/constants or test/constants file) and update deterministic.spec.ts to import
MOCK_QUERY, MOCK_IDENTITY_ID, MOCK_DPNS_WIF, and MOCK_MANAGE_WIF from that
shared module (or import the existing E2E_MOCK_* exports if appropriate) so both
the test and the application use the same source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 29-41: The e2e job currently never uploads Playwright artifacts;
update the e2e job (job id "e2e") to upload Playwright artifacts on failure by
adding an artifacts upload step that runs conditionally (if: failure() or
always()) and points to the Playwright output directories such as
playwright-report/ and test-results/ (include traces, screenshots and videos if
present), so add a step after the test step that uses actions/upload-artifact@v4
(or equivalent) to upload those directories; ensure the step references the same
job name "e2e" and the directories "playwright-report/" and "test-results/" so
CI failures provide traces for debugging.

In `@e2e/deterministic.spec.ts`:
- Around line 22-25: The transient assertion for "Creating your identity" is
flaky because __e2eMockAdvance() advances the mock bridge through multiple
states quickly; remove the short-lived assertion await
expect(page.getByText('Creating your identity')).toBeVisible() and only assert
the stable final state ("Save your keys") and identity ID, or alternatively
increase the delay in the mock flow where setIdentityRegistered is called (the
mock bridge progression invoked by __e2eMockAdvance()) so the
registering_identity state is visible long enough for Playwright's retrier to
observe it.
- Around line 56-58: The test asserts a transient "Processing top-up" text which
can disappear before Playwright sees it; remove the flaky assertion against
page.getByText('Processing top-up') and instead assert the stable final state by
awaiting visibility of the final message (e.g., page.getByText('Top-up
complete!')) after the existing await
expect(page.locator('.deposit-headline')).toBeVisible(); ensure you update the
assertion sequence in deterministic.spec.ts to check for the final "Top-up
complete!" text rather than the transient "Processing top-up".

In `@README.md`:
- Around line 70-72: The `(CRITICAL/HIGH)` annotation on
PW_LIVE_DPNS_PRIVATE_KEY_WIF is ambiguous; update the README line for
PW_LIVE_DPNS_PRIVATE_KEY_WIF to replace that parenthetical with a clearer phrase
like "DPNS authentication key (Dash Platform key purpose/priority level)" or a
short explanatory note and/or add a link to the Dash Platform docs that describe
key priorities so readers understand the risk/priority semantics; ensure the
wording is mirrored for the other identity-related entries if applicable
(PW_LIVE_MANAGE_IDENTITY_ID, PW_LIVE_MANAGE_PRIVATE_KEY_WIF).
- Around line 56-63: The README shows setting WIF private keys inline (e.g.,
PW_LIVE_DPNS_PRIVATE_KEY_WIF, PW_LIVE_MANAGE_PRIVATE_KEY_WIF) which will record
secrets in shell history; change the snippet to warn users and provide safer
alternatives: add a brief callout warning about history exposure and recommend
exporting variables from a protected env file or using a secure prompt/read -s,
sourcing from a file with strict permissions, or using a credential manager, and
show the env var names (PW_E2E_LIVE, PW_LIVE_TOPUP_IDENTITY_ID,
PW_LIVE_DPNS_IDENTITY_ID, PW_LIVE_DPNS_PRIVATE_KEY_WIF,
PW_LIVE_MANAGE_IDENTITY_ID, PW_LIVE_MANAGE_PRIVATE_KEY_WIF) so readers know
which variables to set securely.

In `@src/main.ts`:
- Around line 109-193: The file includes E2E mock constants, types and helper
functions (E2EMockWindow, isE2EMockMode, createE2EMockUtxo,
createE2EMockIdentityKeys, createE2EMockDpnsAvailability,
createE2EMockDpnsResults, clearE2EMockAdvanceHook, waitForE2EMockAdvance and the
E2E_* constants) that are shipped into production and can be enabled via a URL
param; move all test-only code into a separate dev-only module (e.g.,
src/e2e-mocks.ts) and the E2EMockWindow type into src/types.ts, then guard any
imports/usage with a build-time flag (import.meta.env.DEV or a custom VITE_E2E)
so Vite can tree-shake the mock module from production (or use dynamic import
inside an if (import.meta.env.DEV || import.meta.env.VITE_E2E) block), and
ensure isE2EMockMode becomes local to that dev-only module so production builds
no longer include the mock helpers or constants.

---

Nitpick comments:
In `@e2e/deterministic.spec.ts`:
- Around line 3-6: The test defines MOCK_QUERY, MOCK_IDENTITY_ID, MOCK_DPNS_WIF,
and MOCK_MANAGE_WIF which duplicate E2E_MOCK_IDENTITY_ID, E2E_MOCK_DPNS_WIF, and
E2E_MOCK_MANAGE_WIF in the main code and risk drifting out of sync; refactor by
extracting these values into a single shared constants module (e.g., export the
constants from a new e2e/constants or test/constants file) and update
deterministic.spec.ts to import MOCK_QUERY, MOCK_IDENTITY_ID, MOCK_DPNS_WIF, and
MOCK_MANAGE_WIF from that shared module (or import the existing E2E_MOCK_*
exports if appropriate) so both the test and the application use the same source
of truth.

In `@src/main.ts`:
- Around line 599-606: The mock WIF validation currently compares privateKeyWif
against hardcoded constants (cMockDpnsPrivateKeyWif, cMockManagePrivateKeyWif)
duplicated in tests, which can cause silent test breakage; refactor by moving
those constants into a shared exported constant (e.g., export MOCK_DPNS_WIF and
MOCK_MANAGE_WIF from a new/shared module) and import them into src/main.ts
(where isE2EMockMode() check lives) and into e2e/deterministic.spec.ts, updating
the comparisons in the dpns mock validation block (and the analogous block
around setManageKeyValidationError) to use the shared constants; if you cannot
refactor now, add a clear code comment in both places referencing
e2e/deterministic.spec.ts and the source constant names to ensure they stay in
sync.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/main.ts (3)

123-131: Inline import('./types.js').UTXO is inconsistent with the top-level import type block.

Replace with the top-level UTXO import already suggested above.

-function createE2EMockUtxo(): import('./types.js').UTXO {
+function createE2EMockUtxo(): UTXO {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 123 - 131, The inline type annotation
"import('./types.js').UTXO" in the createE2EMockUtxo function should be replaced
with the top-level imported type name UTXO (use the existing `import type { UTXO
} from './types.js'`), so update the function signature for createE2EMockUtxo to
return UTXO and remove the inline import reference to keep type imports
consistent with the file's import type block.

1062-1083: Add clearE2EMockAdvanceHook() before waitForE2EMockAdvance() in the top-up mock for consistency with the bridge mock (line 1198).

Without it, a stale hook from a cancelled bridge flow would be silently overwritten rather than explicitly cleared, making the intent less obvious.

 if (isE2EMockMode()) {
   const network = getNetwork(state.network);
   const assetLockKeyPair = generateKeyPair();
   const depositAddress = publicKeyToAddress(assetLockKeyPair.publicKey, network);

+  clearE2EMockAdvanceHook();
   updateState(setStep(state, 'generating_keys'));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1062 - 1083, In the isE2EMockMode() top-up mock
branch, call clearE2EMockAdvanceHook() immediately before invoking
waitForE2EMockAdvance() so any stale advance hook from a cancelled bridge flow
is explicitly cleared; locate the block using isE2EMockMode(), updateState(...)
calls and replace the current sequence where waitForE2EMockAdvance() is awaited
to first call clearE2EMockAdvanceHook(), then await waitForE2EMockAdvance(),
leaving the rest of the mock sequence (setUtxoDetected, setTransactionSigned,
setTransactionBroadcast, setInstantLockReceived, setTopUpComplete) unchanged.

93-102: Merge the two import type statements from './types.js' and add UTXO to the top-level import (see line 123).

♻️ Proposed fix
-import type {
-  KeyType,
-  KeyPurpose,
-  SecurityLevel,
-  ManageNewKeyConfig,
-  DpnsUsernameEntry,
-  DpnsRegistrationResult,
-  IdentityPublicKeyInfo,
-} from './types.js';
-import type { BridgeState } from './types.js';
+import type {
+  KeyType,
+  KeyPurpose,
+  SecurityLevel,
+  ManageNewKeyConfig,
+  DpnsUsernameEntry,
+  DpnsRegistrationResult,
+  IdentityPublicKeyInfo,
+  BridgeState,
+  UTXO,
+} from './types.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 93 - 102, Two separate `import type` blocks from
'./types.js' should be combined into a single top-level import and must include
the missing UTXO type; update the import to import KeyType, KeyPurpose,
SecurityLevel, ManageNewKeyConfig, DpnsUsernameEntry, DpnsRegistrationResult,
IdentityPublicKeyInfo, BridgeState and UTXO from './types.js' in one statement
so references like KeyType, BridgeState and UTXO resolve correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 185-193: The waitForE2EMockAdvance function currently waits
indefinitely; add an internal timeout (e.g., 5s or configurable) that rejects
with a descriptive Error if __e2eMockAdvance is not called in time, and ensure
you clear the timer and remove/undefine mockWindow.__e2eMockAdvance on both
resolve and reject to avoid leaks; locate function waitForE2EMockAdvance and
implement a setTimeout that calls reject(new Error("Timed out waiting for E2E
mock advance")) and clearTimeout in the success callback.

---

Duplicate comments:
In `@src/main.ts`:
- Around line 109-193: The PR accidentally includes E2E mock utilities
(E2E_MOCK_IDENTITY_ID, E2E_MOCK_DPNS_WIF, isE2EMockMode, createE2EMockUtxo,
createE2EMockIdentityKeys, createE2EMockDpnsAvailability,
createE2EMockDpnsResults, waitForE2EMockAdvance, clearE2EMockAdvanceHook) in
production bundle; move all of these into a separate test-only module (e.g.,
src/e2e/mocks.ts) and remove their exports/usage from src/main.ts, or wrap usage
behind a compile-time flag so the bundler can tree-shake them out (e.g., use
process.env.NODE_ENV or a dedicated BUILD_E2E flag and conditional import),
ensuring main.ts only imports mocks during test/e2e builds so they are not
shipped in production.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 123-131: The inline type annotation "import('./types.js').UTXO" in
the createE2EMockUtxo function should be replaced with the top-level imported
type name UTXO (use the existing `import type { UTXO } from './types.js'`), so
update the function signature for createE2EMockUtxo to return UTXO and remove
the inline import reference to keep type imports consistent with the file's
import type block.
- Around line 1062-1083: In the isE2EMockMode() top-up mock branch, call
clearE2EMockAdvanceHook() immediately before invoking waitForE2EMockAdvance() so
any stale advance hook from a cancelled bridge flow is explicitly cleared;
locate the block using isE2EMockMode(), updateState(...) calls and replace the
current sequence where waitForE2EMockAdvance() is awaited to first call
clearE2EMockAdvanceHook(), then await waitForE2EMockAdvance(), leaving the rest
of the mock sequence (setUtxoDetected, setTransactionSigned,
setTransactionBroadcast, setInstantLockReceived, setTopUpComplete) unchanged.
- Around line 93-102: Two separate `import type` blocks from './types.js' should
be combined into a single top-level import and must include the missing UTXO
type; update the import to import KeyType, KeyPurpose, SecurityLevel,
ManageNewKeyConfig, DpnsUsernameEntry, DpnsRegistrationResult,
IdentityPublicKeyInfo, BridgeState and UTXO from './types.js' in one statement
so references like KeyType, BridgeState and UTXO resolve correctly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main.ts (1)

1064-1085: clearE2EMockAdvanceHook() missing from startTopUp catch block, but present unconditionally in startBridge catch.

Two inconsistencies:

  1. startBridge calls clearE2EMockAdvanceHook() at the start of its mock path (line 1200) and in its catch block (line 1328), but startTopUp does neither — if waitForE2EMockAdvance (line 1073) is pending when an error occurs, the dangling hook is never cleaned up.

  2. Line 1328 calls clearE2EMockAdvanceHook() unconditionally (even for non-mock errors). This is harmless but untidy — it should be inside an isE2EMockMode() guard or, better yet, handled by the build-time gating suggested in the prior review.

♻️ Add cleanup to startTopUp catch block for symmetry
   } catch (error) {
+    clearE2EMockAdvanceHook();
     console.error('Top-up error:', error);
     updateState(setError(state, error instanceof Error ? error : new Error(String(error))));
   }

Also applies to: 1195-1216, 1328-1328

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1064 - 1085, The startTopUp flow can leave a
dangling E2E mock hook if waitForE2EMockAdvance throws, so update startTopUp's
catch block to call clearE2EMockAdvanceHook() (only when isE2EMockMode() is
true) before rethrowing/handling the error; likewise, in startBridge's catch
replace the unconditional clearE2EMockAdvanceHook() with a guarded call wrapped
by isE2EMockMode() or consolidate cleanup into a small helper invoked only in
mock mode, referencing startTopUp, startBridge, waitForE2EMockAdvance,
clearE2EMockAdvanceHook, and isE2EMockMode to locate the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 155-170: The mock uses entry.label.toLowerCase() for contest
checks which diverges from production behavior; update
createE2EMockDpnsAvailability to use the homograph-normalized label
(entry.normalizedLabel) when computing contest/availability. Specifically, in
createE2EMockDpnsAvailability replace the use of lower =
entry.label.toLowerCase() with the normalized label (or
normalizedLabel.toLowerCase() if you want a case-fold) and pass that value into
isContestedUsername (and use it when deriving isAvailable/status) so the mock
mirrors production's normalized checks.

---

Duplicate comments:
In `@src/main.ts`:
- Around line 110-195: The E2E mock helpers (isE2EMockMode, createE2EMockUtxo,
createE2EMockIdentityKeys, createE2EMockDpnsAvailability,
createE2EMockDpnsResults, clearE2EMockAdvanceHook, waitForE2EMockAdvance) are
being shipped into production and the waitForE2EMockAdvance has no timeout;
guard their inclusion with a build-time check (use import.meta.env.PROD or
equivalent) so these functions are only imported/defined in non-production
builds, and modify waitForE2EMockAdvance to race the resolve callback against a
setTimeout that rejects after a configurable timeout (ensure you clear the
timeout and remove the __e2eMockAdvance hook on both resolve and reject by
calling/using clearE2EMockAdvanceHook) so the promise never hangs.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 1064-1085: The startTopUp flow can leave a dangling E2E mock hook
if waitForE2EMockAdvance throws, so update startTopUp's catch block to call
clearE2EMockAdvanceHook() (only when isE2EMockMode() is true) before
rethrowing/handling the error; likewise, in startBridge's catch replace the
unconditional clearE2EMockAdvanceHook() with a guarded call wrapped by
isE2EMockMode() or consolidate cleanup into a small helper invoked only in mock
mode, referencing startTopUp, startBridge, waitForE2EMockAdvance,
clearE2EMockAdvanceHook, and isE2EMockMode to locate the changes.

@thepastaclaw
Copy link
Contributor Author

Addressed the latest CodeRabbit feedback in 27e0b6f:

  • Added Playwright artifact upload (playwright-report/, test-results/) to CI e2e job.
  • Removed transient/flaky deterministic assertions and kept stable end-state checks.
  • Added README key-safety warning + protected env-file workflow; clarified DPNS key wording.
  • Hardened mock mode/runtime in src/main.ts:
    • build-time PROD guard for mock mode
    • timeout + cleanup in waitForE2EMockAdvance
    • stale-hook cleanup in top-up/bridge error paths (mock-only)
    • contested check uses normalized label
    • shared mock constants module + typed UTXO return

Validation run locally:

  • npm run build
  • npm test -- --run --passWithNoTests
  • npm run test:e2e did not produce output in this OpenClaw runner and was terminated by session timeout; CI remains the authoritative e2e verification environment.

@thepastaclaw
Copy link
Contributor Author

Triage follow-up on the mention: all currently actionable CodeRabbit comments appear addressed in commit 27e0b6f and acknowledged in-thread. I don’t see any remaining unresolved changes requested from the mention context, so no additional code changes are needed from this ping.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/main.ts (1)

1034-1052: manageMoreBtn handler has no mock path — calls real network in mock mode.

After startManageUpdate completes in mock mode the UI shows an "Update Complete!" page with a "Manage More" button. Clicking it invokes getIdentityPublicKeys(E2E_MOCK_IDENTITY_ID, network) against real testnet. The mock identity '1111…1' doesn't exist, so the call fails and surfaces an error. The deterministic tests don't exercise this button, so CI won't catch it, but it breaks manual mock-mode exploration.

♻️ Suggested fix
      if (targetId) {
        try {
+          if (isE2EMockMode()) {
+            updateState(setManageIdentityFetched(refreshedState, createE2EMockIdentityKeys()));
+            return;
+          }
          const keys = await getIdentityPublicKeys(targetId, refreshedState.network);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1034 - 1052, The manageMoreBtn click handler calls
getIdentityPublicKeys for the recovered refreshedState and so triggers real
network calls in mock mode; modify the handler to detect mock-mode (e.g., check
refreshedState.network or an existing isMock flag used elsewhere) and in that
case avoid calling getIdentityPublicKeys — instead populate the state with the
deterministic/mock identity keys (or a noop success) by calling
setManageIdentityFetched with the mock keys (e.g., for E2E_MOCK_IDENTITY_ID) or
skip to reset state; preserve the current error handling path for non-mock
networks so only real networks invoke getIdentityPublicKeys, and still call
updateState(setManageIdentityFetchError(...)) on real failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/deterministic.spec.ts`:
- Line 88: Replace the flaky Playwright .check() call on the manage-disable-key
checkbox with a forced click to avoid actionability timeouts: locate the element
used in deterministic.spec.ts (the expression
page.locator('.manage-disable-key-checkbox').first()) and call .click({ force:
true }) on that locator instead of .check() so the test is stable in headless CI
and matches the existing pattern used for `#dpns-contested-checkbox`.

In `@README.md`:
- Around line 56-72: The .env.playwright-live file containing private keys must
be excluded from version control; update the repository .gitignore by adding
either the exact entry ".env.playwright-live" or a broader pattern like ".env.*"
so any env files (including .env.playwright-live) are ignored; ensure the change
references the env filename used in the README (".env.playwright-live") so
accidental git adds won't commit secrets.

---

Duplicate comments:
In `@src/main.ts`:
- Around line 116-118: Move the E2EMockWindow type out of its current location
and into the central types file where other interfaces live: create (or add) an
exported type E2EMockWindow = Window & { __e2eMockAdvance?: () => void } in
src/types.ts, remove the local declaration from main.ts, and update any files
that reference E2EMockWindow (including the code that uses E2E_MOCK_* constants)
to import the type from the shared types module; ensure the type is exported so
consumers compile without changes.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 1034-1052: The manageMoreBtn click handler calls
getIdentityPublicKeys for the recovered refreshedState and so triggers real
network calls in mock mode; modify the handler to detect mock-mode (e.g., check
refreshedState.network or an existing isMock flag used elsewhere) and in that
case avoid calling getIdentityPublicKeys — instead populate the state with the
deterministic/mock identity keys (or a noop success) by calling
setManageIdentityFetched with the mock keys (e.g., for E2E_MOCK_IDENTITY_ID) or
skip to reset state; preserve the current error handling path for non-mock
networks so only real networks invoke getIdentityPublicKeys, and still call
updateState(setManageIdentityFetchError(...)) on real failures.

@thepastaclaw
Copy link
Contributor Author

Follow-up on the mention triage: I addressed the remaining actionable CodeRabbit items in cc5d9fe.

Changes included:

  • prevent real network fetch in mock mode for Manage More (use deterministic mock keys)
  • move E2EMockWindow type into src/types.ts
  • switch flaky .check() to .click({ force: true }) in deterministic E2E
  • ignore .env.playwright-live in .gitignore

Validation:

  • npm run build
  • npm test -- --run --passWithNoTests
  • npm run test:e2e produced no output in this runner and was stopped after a reasonable wait; CI remains source of truth for e2e.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main.ts (1)

1374-1396: ⚠️ Potential issue | 🟡 Minor

recheckDeposit has no mock-mode guard — real API calls would fire if the deposit-timed-out state is ever reached during E2E tests.

The mock paths in startTopUp and startBridge never call setDepositTimedOut, so recheckDeposit is unreachable under normal mock flows. However, the deterministic guarantee relies entirely on that precondition holding — there is no explicit guard. If a test ever exercises this button, insightClient.waitForUtxo and dapiClient.waitForInstantSendLock would fire against real infrastructure.

🛡️ Suggested fix
 async function recheckDeposit() {
   if (!state.depositAddress) {
     console.error('No deposit address available');
     return;
   }

+  if (isE2EMockMode()) {
+    // Mock flows never time out, so this path should not be reached.
+    // Guard here to prevent accidental real API calls in deterministic tests.
+    return;
+  }
+
   // Reset timeout state and start polling again
   updateState(setDepositTimedOut(state, false, 0));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1374 - 1396, recheckDeposit currently calls real
network methods (insightClient.waitForUtxo / dapiClient.waitForInstantSendLock)
without checking mock mode, so E2E tests can trigger real API calls; add an
explicit mock-mode guard at the start of recheckDeposit (e.g., check isMockMode
or a similar flag from state/config) and early-return or use the existing mock
clients when mock mode is active; also ensure any related handlers (startTopUp,
startBridge) that toggle setDepositTimedOut still set or pass the same mock flag
so recheckDeposit's guard is effective; reference the functions recheckDeposit,
startTopUp, startBridge, setDepositTimedOut, insightClient.waitForUtxo and
dapiClient.waitForInstantSendLock when making the change.
🧹 Nitpick comments (1)
src/main.ts (1)

626-633: Hardcoded mock key IDs are undocumented magic numbers coupled to createE2EMockIdentityKeys().

setDpnsKeyValidated(state, 1, ...) (Line 628) and setManageKeyValidated(state, 0, 0, ...) (Line 875) hardcode the key IDs and security level that must match the mock key array from createE2EMockIdentityKeys(). If the mock key fixture is ever changed (e.g., key IDs reordered or security levels adjusted), these validation blocks would silently produce wrong results without any compile-time signal.

Consider exporting these IDs from src/e2e-mock-constants.ts alongside the existing WIF constants:

♻️ Suggested extraction to `src/e2e-mock-constants.ts`
 export const E2E_MOCK_DPNS_WIF = 'cMockDpnsPrivateKeyWif';
 export const E2E_MOCK_MANAGE_WIF = 'cMockManagePrivateKeyWif';
 export const E2E_MOCK_IDENTITY_ID = '11111111111111111111111111111111111111111111';
+/** Key ID for DPNS validation — must match the CRITICAL/AUTH key in createE2EMockIdentityKeys() */
+export const E2E_MOCK_DPNS_KEY_ID = 1;
+/** Key ID for manage validation — must match the MASTER key in createE2EMockIdentityKeys() */
+export const E2E_MOCK_MANAGE_KEY_ID = 0;
+export const E2E_MOCK_MANAGE_SECURITY_LEVEL = 0; // MASTER

Then in src/main.ts:

-import { E2E_MOCK_DPNS_WIF, E2E_MOCK_IDENTITY_ID, E2E_MOCK_MANAGE_WIF } from './e2e-mock-constants.js';
+import { E2E_MOCK_DPNS_WIF, E2E_MOCK_IDENTITY_ID, E2E_MOCK_MANAGE_WIF, E2E_MOCK_DPNS_KEY_ID, E2E_MOCK_MANAGE_KEY_ID, E2E_MOCK_MANAGE_SECURITY_LEVEL } from './e2e-mock-constants.js';
-          updateState(setDpnsKeyValidated(state, 1, privateKeyWif));
+          updateState(setDpnsKeyValidated(state, E2E_MOCK_DPNS_KEY_ID, privateKeyWif));
-          updateState(setManageKeyValidated(state, 0, 0, privateKeyWif));
+          updateState(setManageKeyValidated(state, E2E_MOCK_MANAGE_KEY_ID, E2E_MOCK_MANAGE_SECURITY_LEVEL, privateKeyWif));

Also applies to: 873-880

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 626 - 633, The code hardcodes mock key IDs/security
levels (e.g., setDpnsKeyValidated(state, 1, ...) and
setManageKeyValidated(state, 0, 0, ...)) that must match the array returned by
createE2EMockIdentityKeys(); export the corresponding key id and security-level
constants (e.g., E2E_MOCK_DPNS_KEY_ID, E2E_MOCK_MANAGE_KEY_ID,
E2E_MOCK_MANAGE_KEY_SECURITY_LEVEL) from src/e2e-mock-constants.ts (alongside
E2E_MOCK_DPNS_WIF), then replace the numeric literals in main.ts with those
exported constants so the validation calls (setDpnsKeyValidated and
setManageKeyValidated) always reference the canonical fixture identifiers rather
than magic numbers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/main.ts`:
- Around line 1374-1396: recheckDeposit currently calls real network methods
(insightClient.waitForUtxo / dapiClient.waitForInstantSendLock) without checking
mock mode, so E2E tests can trigger real API calls; add an explicit mock-mode
guard at the start of recheckDeposit (e.g., check isMockMode or a similar flag
from state/config) and early-return or use the existing mock clients when mock
mode is active; also ensure any related handlers (startTopUp, startBridge) that
toggle setDepositTimedOut still set or pass the same mock flag so
recheckDeposit's guard is effective; reference the functions recheckDeposit,
startTopUp, startBridge, setDepositTimedOut, insightClient.waitForUtxo and
dapiClient.waitForInstantSendLock when making the change.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 626-633: The code hardcodes mock key IDs/security levels (e.g.,
setDpnsKeyValidated(state, 1, ...) and setManageKeyValidated(state, 0, 0, ...))
that must match the array returned by createE2EMockIdentityKeys(); export the
corresponding key id and security-level constants (e.g., E2E_MOCK_DPNS_KEY_ID,
E2E_MOCK_MANAGE_KEY_ID, E2E_MOCK_MANAGE_KEY_SECURITY_LEVEL) from
src/e2e-mock-constants.ts (alongside E2E_MOCK_DPNS_WIF), then replace the
numeric literals in main.ts with those exported constants so the validation
calls (setDpnsKeyValidated and setManageKeyValidated) always reference the
canonical fixture identifiers rather than magic numbers.

Add mock mode gated behind ?e2e=mock URL parameter that intercepts
all platform API calls and returns fake data. This enables full UI
flow testing without network access or real funds.

Mock mode covers: create identity, top-up, manage keys, DPNS
registration, and standalone DPNS flows. Mock helpers produce
deterministic identity IDs, key data, and DPNS availability.
Add deterministic mock-mode tests (create, top-up, manage, DPNS) and
optional live testnet tests (gated by env vars). Includes Playwright
config, CI workflow with Chromium install, vitest exclude for e2e dir,
and npm scripts for running headed/headless/live modes.
The npx playwright@1.52.0 approach only installs the CLI runner,
not the @playwright/test package that playwright.config.ts imports.
CI failed with ERR_MODULE_NOT_FOUND. Adding as devDependency and
using local bin instead of npx.
… timing

Top-up mock now pauses at detecting_deposit (same pattern as create
flow) so test can assert deposit headline before advancing. Also added
explicit visibility wait for contested checkbox before checking it.
The mock createE2EMockDpnsAvailability was missing the isContested
field. Without it, shouldShowContestedWarning returns false, the
checkbox never renders, and page.check times out. Now uses the real
isContestedUsername logic to match production behavior.
page.check() waits for actionability checks that fail (likely element
obscured by label or CSS). Using click({force: true}) bypasses those
checks since we already assert visibility beforehand.
@thepastaclaw thepastaclaw force-pushed the feat/playwright-e2e-testnet branch from cc5d9fe to 250efc8 Compare February 21, 2026 05:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant