Skip to content

feat(e2e): failure-path matrix for Send with reusable fixture surface - #204

Open
Labi-Joy wants to merge 4 commits into
wraith-protocol:developfrom
Labi-Joy:feat/e2e-failure-matrix-send
Open

Labi-Joy wants to merge 4 commits into
wraith-protocol:developfrom
Labi-Joy:feat/e2e-failure-matrix-send

Conversation

@Labi-Joy

Copy link
Copy Markdown
Contributor

Closes #187

Delivers PR 1 of 3 for the failure-path matrix: fixture surface for all six axes, the Send-flow matrix on top of it, and a Playwright CI job so the matrix runs on every PR. Receive, Batch, Schedule, and Vault will follow in later PRs once the pattern is approved, as flagged in the assignment thread.

Failure axes covered (Send)

# Axis Fixture surface Status
1 Signature rejected freighter.mock({ shouldFailSignTx: true }) passing
2 RPC exhausted horizon.mock({ accountFetchStatus: 503 }) passing
3 Wrong network freighter.mock({ network, networkPassphrase }) passing
4 Insufficient balance horizon.mock({ accountBalance: '2' }) passing
5 Missing trustline horizon.mock({ senderTrustlines: [...] }) skipped
6 Stale session session.expire() (new fixture) passing

App fixes required to unblock coverage

Working through the matrix surfaced four pre-existing bugs that kept the whole e2e suite red. Fixed in this PR so the failure-path assertions have something real to assert against:

  1. index.html FOUC guard never released. <body style=\"visibility:hidden\"> was added alongside the theme boot script, but the boot only unhid document.documentElement, so <body> stayed hidden forever and Playwright saw a blank body on every navigation. Theme class is applied synchronously in <head> before first paint, so the inline body visibility was overkill; removed it.
  2. handleSend useCallback deps stale. [address, recipient, amount, assetKey, signTransaction, t] did not include canSubmit, validationError, isNetworkMismatch, or memo, all of which change after the debounced balance / trustline effects settle. Every click fired with the closure from the last keystroke and short-circuited on the generic Enter valid send details message.
  3. stealthResult pinned the pending panel after a signing failure. setStealthResult(result) runs before signTransaction. On rejection or submit-time network error, the panel stayed set and the error paragraph inside {!stealthResult && ...} never rendered; the UI showed PENDING forever with no retry affordance. Reset in the catch so the failure surface is reachable.
  4. AssetPicker emitted <code>:<issuer> for non-XLM entries but getAssetByKey only knows short codes. Selecting USDC threw Unknown Stellar asset: USDC:GBBD47IF... and crashed the tree. Store entry.key = code in the balance-entries list so the picker emits USDC and downstream getAssetByKey resolves it.

Deferred: axis 5 (missing trustline)

Fifth pre-existing bug surfaced by the same recon: STELLAR_USDC.issuer in src/lib/stellar/assets.ts fails StrKey.isValidEd25519PublicKey, so getAssetByKey('USDC').toAsset() throws Issuer is invalid and the entire USDC path (including the trustline check) is unreachable end to end. Replacing the placeholder with a real testnet USDC issuer is a scope-of-USDC-support change, not a failure-path change; the trustline spec is committed as test.skip with an inline pointer and will land in the follow-up alongside the issuer fix.

Fixture additions

  • FreighterMockConfig: network, networkPassphrase. Mock now also exposes getNetworkDetails, isAllowed, and WatchWalletChanges so the wallet context can walk its mount path without silently failing.
  • HorizonMockConfig: accountFetchStatus (retryable-status branch for the sender lookup), senderTrustlines (extra credit_alphanum4 balances on the sender account only; the stealth address stays native-only so the trustline check still fires the missing branch).
  • session.expire(): fires window.__wraithForceIdleLock, a dev/test hook wired in src/App.tsx and dropped from production by Vite dead code elimination (!import.meta.env.PROD).

CI

.github/workflows/e2e.yml runs pnpm exec playwright test --project=chromium inside the Playwright container image. Chromium-only keeps the run under two minutes; the full multi-browser matrix stays available locally via pnpm test:e2e.

Acceptance criteria

  • Mock wallet and RPC failures deterministically (fixture-level).
  • Cover send with the failure matrix; receive / batch / schedule / vault to follow in subsequent PRs, as proposed in the assignment.
  • Assert user-facing recovery actions and no stuck spinners.
  • Run the matrix in CI.

Local verification

  • pnpm exec playwright test send-failure-matrix.spec.ts --project=chromium: 5 passed, 1 skipped, 0 failed (~1.4 min).
  • pnpm build: green.
  • pnpm format:check: green.

@vercel

vercel Bot commented Sep 26, 2026

Copy link
Copy Markdown

@Labi-Joy is attempting to deploy a commit to the truthixify's projects Team on Vercel.

A member of the Team first needs to authorize it.

@truthixify

Copy link
Copy Markdown
Contributor

This only covers Send, leaves trustline skipped, and does not cover receive, batch, schedule, or vault as required by #187. Please complete the full matrix before marking the issue closed.

Labi-Joy added a commit to Labi-Joy/wraith-demo that referenced this pull request Sep 29, 2026
Responds to the maintainer review on wraith-protocol#204: the earlier commit only
covered Send and skipped the missing-trustline case; wraith-protocol#187's DoD calls
for the whole flow set. This commit extends the matrix and also fixes
the two remaining app bugs that were blocking full coverage.

New spec: `e2e/flows-failure-matrix.spec.ts`
  - Receive: signature-rejected on Derive Keys, stale-session on the
    Receive page. Insufficient-balance / missing-trustline / RPC-exhaust
    are n/a on Receive (no sender funds, no AssetPicker).
  - Batch: signature-rejected on Send Batch, stale-session. Row-level
    insufficient-balance / missing-trustline live inside sendBatch and
    are left to a follow-up spec since they need row-scoped fixture
    plumbing.
  - Schedule: stale-session is the only axis that applies. Schedule is
    a local Zustand store with a mock tick executor and does not touch
    the wallet or Horizon or Soroban.
  - Vault: wrong-network on Claim, signature-rejected on Claim,
    stale-session on the Vault page. Insufficient-balance / trustline /
    RPC-exhaust do not apply to today's Vault UI (simulated executor
    pending the on-chain contract).

Send trustline test un-skipped. The USDC issuer in
`src/lib/stellar/assets.ts` was a placeholder that failed
`StrKey.isValidEd25519PublicKey`, so `new Asset('USDC', issuer)` threw
and the entire USDC path was unreachable. Swapped in Circle's canonical
USDC issuer (`GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`)
so the AssetPicker resolves USDC, the trustline check runs, and the
missing-trustline test lands.

Second app fix required to unblock Receive coverage:
`StellarReceive.tsx` had `toggleSelectAll` (with dep `filteredMatched`)
defined above `filteredMatched`. React evaluates the useCallback dep
array on every render, so the initial render hit a temporal-dead-zone
`ReferenceError: Cannot access 'filteredMatched' before initialization`
and Receive crashed before any assertion could reach the DOM. Reordered
so the callback is declared below its dep.

Send RPC-exhaustion test now uses `expect().toPass` to poll for
`Connection failed after 3 attempts` after Send is clicked, instead of
a fixed settle wait; the fixed wait was flaky under CI load because
`setBalanceLookupError` and the `useCallback` re-memo do not always
commit within a single tick.

CI: workflow now also runs `e2e/flows-failure-matrix.spec.ts`.

Local verification (workers=1):
  send-failure-matrix.spec.ts: 6 passed
  flows-failure-matrix.spec.ts: 8 passed
  Total: 14 passed / 0 skipped / 0 failed, ~1.7 min.
`pnpm build` and `pnpm format:check` both green.
@Labi-Joy

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Just pushed 483c0bb which extends the matrix to the remaining flows and un-skips the missing-trustline case.

Coverage now:

  • Send (6 tests): signature-rejected, RPC-exhausted, wrong-network, insufficient-balance, missing-trustline, stale-session.
  • Receive (2 tests): signature-rejected on Derive Keys, stale-session. Insufficient-balance / missing-trustline / RPC-exhaust are n/a on Receive (no sender funds, no AssetPicker).
  • Batch (2 tests): signature-rejected on Send Batch, stale-session. Row-scoped balance/trustline live inside sendBatch and are noted for a follow-up spec.
  • Schedule (1 test): stale-session. Schedule is a local Zustand store with a mock tick executor and does not touch the wallet / Horizon / Soroban, so the other axes have no surface to fail on.
  • Vault (3 tests): wrong-network on Claim, signature-rejected on Claim, stale-session. Other axes n/a on today's Vault UI (simulated executor pending the on-chain contract).

Total: 14 passed / 0 skipped / 0 failed locally against the pinned Chromium image.

Two more app fixes were needed to enable coverage:

  1. STELLAR_USDC.issuer in src/lib/stellar/assets.ts was a placeholder that failed StrKey.isValidEd25519PublicKey, so new Asset('USDC', issuer) threw and the whole USDC path (including the trustline check) was unreachable. Swapped in Circle's canonical USDC issuer.
  2. StellarReceive.tsx had toggleSelectAll (with dep filteredMatched) declared above filteredMatched. React evaluates useCallback deps on every render, so the initial render hit a TDZ ReferenceError and Receive crashed before any assertion could reach the DOM. Reordered so the callback lives below its dep.

Ready for another look.

Delivers PR 1 of 3 for the failure-path matrix: fixture surface for all
six axes, the Send-flow matrix on top of it, and a Playwright CI job so
the matrix runs on every PR. Receive, Batch, Schedule, and Vault will
follow in later PRs once the pattern is approved, as flagged in the
assignment thread.

Failure axes covered on Send:
- Signature rejected (freighter.shouldFailSignTx)
- RPC exhausted (horizon.accountFetchStatus: 503, exercises full
  withRetry cycle before throwing RetryExhaustedError)
- Wrong network (freighter.network / networkPassphrase, drives the
  NetworkMismatchModal)
- Insufficient balance (horizon.accountBalance)
- Missing trustline (test.skip pending a follow-up fix, see below)
- Stale session (session.expire, wired via a dev-only window hook that
  Vite strips from production)

Working through the matrix surfaced four pre-existing bugs that kept
the whole e2e suite red. Fixed here so the failure-path assertions have
something real to assert against:

1. index.html FOUC guard never released. `<body style="visibility:hidden">`
   was added alongside the theme boot script but the boot only unhid
   `document.documentElement`, so `<body>` stayed hidden forever and
   Playwright saw a blank body on every navigation. Theme class is
   applied synchronously in `<head>` before first paint, so the inline
   body visibility was overkill; removed it.
2. `handleSend` `useCallback` deps stale. Missing `canSubmit`,
   `validationError`, `isNetworkMismatch`, and `memo`, all of which
   change after the debounced balance / trustline effects settle. Every
   click fired with the closure from the last keystroke and
   short-circuited on the generic `Enter valid send details` message.
3. `stealthResult` pinned the pending panel after a signing failure.
   `setStealthResult(result)` runs before `signTransaction`. On
   rejection or submit-time network error, the panel stayed set and the
   error paragraph inside `{!stealthResult && ...}` never rendered; UI
   showed `PENDING` forever with no retry affordance. Reset in the
   catch so the failure surface is reachable.
4. AssetPicker emitted `<code>:<issuer>` for non-XLM entries but
   `getAssetByKey` only knows short codes. Selecting USDC threw
   `Unknown Stellar asset: USDC:GBBD47IF...` and crashed the tree.
   Store `entry.key = code` in the balance-entries list so the picker
   emits `USDC` and downstream lookup resolves.

Deferred: axis 5 (missing trustline). A fifth bug surfaced by the same
recon: `STELLAR_USDC.issuer` in `src/lib/stellar/assets.ts` fails
`StrKey.isValidEd25519PublicKey`, so `getAssetByKey('USDC').toAsset()`
throws `Issuer is invalid` and the entire USDC path is unreachable end
to end. Replacing the placeholder with a real testnet issuer is a
USDC-support scope change, not a failure-path change. The trustline
spec is committed as `test.skip` with an inline pointer.

Fixture additions (e2e/fixtures.ts):
- FreighterMockConfig: `network`, `networkPassphrase`. Mock now also
  exposes `getNetworkDetails`, `isAllowed`, and `WatchWalletChanges` so
  the wallet context can walk its mount path without silently failing.
- HorizonMockConfig: `accountFetchStatus` (retryable-status branch for
  the sender lookup), `senderTrustlines` (extra `credit_alphanum4`
  balances on the sender only; stealth address stays native-only so the
  trustline check still fires the missing branch).
- New `session.expire()` fixture: fires `window.__wraithForceIdleLock`,
  a dev/test hook in `src/App.tsx` dropped from production by Vite dead
  code elimination.

CI: `.github/workflows/e2e.yml` runs
`pnpm exec playwright test --project=chromium` inside the Playwright
container image. Chromium-only keeps the run under two minutes; the
full multi-browser matrix stays available locally via `pnpm test:e2e`.

Local verification: 5 passed, 1 skipped, 0 failed. `pnpm build` and
`pnpm format:check` both green.

Closes wraith-protocol#187
The initial workflow ran the entire e2e suite, which pulled in
`e2e/stellar.spec.ts` (LocaleSwitcher-vs-Chain select ambiguity) and
the `tests/vault-*.spec.ts` files, both of which fail on develop
independent of this PR. With the CI retry x2 config, the run stretched
to about 1.6h before finally exiting non-zero on the pre-existing
breakages even though the new failure-matrix tests all passed.

Pin the workflow to `e2e/send-failure-matrix.spec.ts` so it only owns
the tests this PR is actually delivering. The wider suite can be
brought back into CI in a follow-up once the pre-existing specs are
repaired.
Responds to the maintainer review on wraith-protocol#204: the earlier commit only
covered Send and skipped the missing-trustline case; wraith-protocol#187's DoD calls
for the whole flow set. This commit extends the matrix and also fixes
the two remaining app bugs that were blocking full coverage.

New spec: `e2e/flows-failure-matrix.spec.ts`
  - Receive: signature-rejected on Derive Keys, stale-session on the
    Receive page. Insufficient-balance / missing-trustline / RPC-exhaust
    are n/a on Receive (no sender funds, no AssetPicker).
  - Batch: signature-rejected on Send Batch, stale-session. Row-level
    insufficient-balance / missing-trustline live inside sendBatch and
    are left to a follow-up spec since they need row-scoped fixture
    plumbing.
  - Schedule: stale-session is the only axis that applies. Schedule is
    a local Zustand store with a mock tick executor and does not touch
    the wallet or Horizon or Soroban.
  - Vault: wrong-network on Claim, signature-rejected on Claim,
    stale-session on the Vault page. Insufficient-balance / trustline /
    RPC-exhaust do not apply to today's Vault UI (simulated executor
    pending the on-chain contract).

Send trustline test un-skipped. The USDC issuer in
`src/lib/stellar/assets.ts` was a placeholder that failed
`StrKey.isValidEd25519PublicKey`, so `new Asset('USDC', issuer)` threw
and the entire USDC path was unreachable. Swapped in Circle's canonical
USDC issuer (`GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`)
so the AssetPicker resolves USDC, the trustline check runs, and the
missing-trustline test lands.

Second app fix required to unblock Receive coverage:
`StellarReceive.tsx` had `toggleSelectAll` (with dep `filteredMatched`)
defined above `filteredMatched`. React evaluates the useCallback dep
array on every render, so the initial render hit a temporal-dead-zone
`ReferenceError: Cannot access 'filteredMatched' before initialization`
and Receive crashed before any assertion could reach the DOM. Reordered
so the callback is declared below its dep.

Send RPC-exhaustion test now uses `expect().toPass` to poll for
`Connection failed after 3 attempts` after Send is clicked, instead of
a fixed settle wait; the fixed wait was flaky under CI load because
`setBalanceLookupError` and the `useCallback` re-memo do not always
commit within a single tick.

CI: workflow now also runs `e2e/flows-failure-matrix.spec.ts`.

Local verification (workers=1):
  send-failure-matrix.spec.ts: 6 passed
  flows-failure-matrix.spec.ts: 8 passed
  Total: 14 passed / 0 skipped / 0 failed, ~1.7 min.
`pnpm build` and `pnpm format:check` both green.
…ocol#198 rewrite

Rebasing this branch onto develop pulled in wraith-protocol#198 which replaced the
mock Claim flow with a real Soroban contract call. The old Claim tests
tried to click a deposit row that no longer exists until the derive +
scan chain runs against the vault contract; that whole chain is a
follow-up spec.

Instead, the Vault matrix now runs against the Deposit tab, which is
the default and is the surface that reaches the wallet directly.
Wrong-network asserts on the "Switch Freighter to Stellar Testnet
before creating a deposit" text; signature-rejected asserts on the raw
rejection or the "Deposit failed" fallback.

Also puts back the `document.documentElement.style.visibility = 'visible'`
line the rebase dropped: PR wraith-protocol#200 (which merged in develop) fixed FOUC
by hiding `<html>` instead of `<body>` and relying on that visibility
toggle in the theme boot script to reveal it. Without the toggle the
whole app stays hidden and every e2e navigates into a blank page.

Local verification: 14 passed / 0 skipped / 0 failed in ~1.7 min.
@Labi-Joy
Labi-Joy force-pushed the feat/e2e-failure-matrix-send branch from 483c0bb to ff941d8 Compare September 29, 2026 08:06
@truthixify

Copy link
Copy Markdown
Contributor

CI now runs both specs and all five flows are represented. Please add recovery and no-stuck-spinner assertions for the Batch and Vault failures, which currently stop after finding an error message.

This branch has not been deployed

No deployments
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.

[Wave 9] Expand Playwright coverage for wallet and RPC failure paths

2 participants