Summary
The full matrix leg of the Visual Regression workflow (e2e/visual/authenticated.test.ts) has never passed in CI. It was introduced in #186 and merged without the leg ever going green — possible because the check is non-required and only runs on PR open/label (not on pushes). Two distinct bugs block it: the first is fixed and validated locally; the second needs a small test redesign.
Symptom
- Job:
iOS Visual Regression (full). Example failing run: https://github.com/DFXswiss/dfx-wallet/actions/runs/27835644312
Onboard (PIN + legal) → reaches the dashboard times out; every later authenticated block (Settings, Seed export, Hardware connect, Multi-sig ×2) then cascade-fails on verify-pin-screen, because the wallet the blocks reuse was never created.
- The 2 passkey screens (no wallet init) pass.
Root cause #1 — onboard hang (FIXED, validated locally)
On "Continue", app/(onboarding)/create-wallet.tsx calls WDK restoreWallet() and only navigates to setup-pin after it resolves. WDK initializes all 8 configured chains (ethereum/arbitrum/polygon/base/spark/bitcoin/plasma/sepolia) on first wallet setup, each with its own RPC/bundler round-trips. On a fresh CI simulator this exceeds the test timeout (observed: 225 s, still unfinished). The suite's own German handbook entry already documents "WDK-Init nach Continue … bis zu 120 s".
Fix: gate the chain set behind an allowlist env var so the E2E build inits a minimal set. Production is unaffected — the allowlist only applies when the env var is set.
src/config/chains.ts — split the network object into buildAllNetworks() and filter:
export const getWdkConfigs = (): WdkConfigs => {
const networks = buildAllNetworks();
const allow = process.env.EXPO_PUBLIC_WDK_CHAINS?.split(',')
.map((id: string) => id.trim())
.filter(Boolean);
if (!allow?.length) return { networks }; // unset → all chains (prod)
const filtered = Object.fromEntries(
Object.entries(networks).filter(([id]) => allow.includes(id)),
) as WdkConfigs['networks'];
return { networks: filtered };
};
.env.testnet:
EXPO_PUBLIC_WDK_CHAINS=sepolia,bitcoin
Local validation (iOS release build, iPhone 17 Pro sim): with the trim, Onboard (PIN + legal) → reaches the dashboard completes in ~43 s (was hanging > 180 s). Passkey + onboard tests pass.
Root cause #2 — relaunch + PIN-unlock hang (NOT yet fixed)
Once a wallet exists, the WDK worklet keeps the app's JS thread continuously busy — the app never goes "idle" (the suite header comment acknowledges this: "the WDK keeps the main queue busy"). The per-screen pattern relaunchToDashboard() cold-restarts the app and re-enters the PIN. Detox waits for the app to be idle during:
device.launchApp() — synchronization is re-enabled on every new instance (per Detox docs), and
enterPin() — which calls device.enableSynchronization().
So both block indefinitely (observed > 13 min stuck on Run loop "JS Run Loop" is awake). The Android launch arg that starts an instance with synchronization disabled (detoxEnableSynchronization) does not exist on iOS, so this is not a one-line fix.
Proposed fix (option A): restructure the suite to onboard once and navigate screen-to-screen without relaunching, keeping synchronization disabled throughout (exactly as the onboard flow already does successfully). Alternatives: (B) make the app idle in the E2E build by gating WDK/balance background activity — uncertain, since the busyness is the worklet itself, not just the 30 s balance refetch; (C) keep relaunch but drive launch + PIN-unlock fully sync-disabled.
Why CI didn't catch this (the gate hole)
The Visual Regression check is:
- not a required check, so a red run never blocks merge; and
- configured to skip pushes (
types: [opened, reopened, ready_for_review, labeled] — no synchronize), so it doesn't run on iterative commits.
That combination let #186 merge a suite that had never passed, with the PR showing green. Recommendation: once the suite is fixed and proven stable, make it required and run it on push. Tradeoff: it's a ~30-min macOS build, so any flakiness would block merges — a team decision, not a pure win.
Next steps
References
Summary
The
fullmatrix leg of the Visual Regression workflow (e2e/visual/authenticated.test.ts) has never passed in CI. It was introduced in #186 and merged without the leg ever going green — possible because the check is non-required and only runs on PR open/label (not on pushes). Two distinct bugs block it: the first is fixed and validated locally; the second needs a small test redesign.Symptom
iOS Visual Regression (full). Example failing run: https://github.com/DFXswiss/dfx-wallet/actions/runs/27835644312Onboard (PIN + legal) → reaches the dashboardtimes out; every later authenticated block (Settings, Seed export, Hardware connect, Multi-sig ×2) then cascade-fails onverify-pin-screen, because the wallet the blocks reuse was never created.Root cause #1 — onboard hang (FIXED, validated locally)
On "Continue",
app/(onboarding)/create-wallet.tsxcalls WDKrestoreWallet()and only navigates tosetup-pinafter it resolves. WDK initializes all 8 configured chains (ethereum/arbitrum/polygon/base/spark/bitcoin/plasma/sepolia) on first wallet setup, each with its own RPC/bundler round-trips. On a fresh CI simulator this exceeds the test timeout (observed: 225 s, still unfinished). The suite's own German handbook entry already documents "WDK-Init nach Continue … bis zu 120 s".Fix: gate the chain set behind an allowlist env var so the E2E build inits a minimal set. Production is unaffected — the allowlist only applies when the env var is set.
src/config/chains.ts— split the network object intobuildAllNetworks()and filter:.env.testnet:Local validation (iOS release build, iPhone 17 Pro sim): with the trim,
Onboard (PIN + legal) → reaches the dashboardcompletes in ~43 s (was hanging > 180 s). Passkey + onboard tests pass.Root cause #2 — relaunch + PIN-unlock hang (NOT yet fixed)
Once a wallet exists, the WDK worklet keeps the app's JS thread continuously busy — the app never goes "idle" (the suite header comment acknowledges this: "the WDK keeps the main queue busy"). The per-screen pattern
relaunchToDashboard()cold-restarts the app and re-enters the PIN. Detox waits for the app to be idle during:device.launchApp()— synchronization is re-enabled on every new instance (per Detox docs), andenterPin()— which callsdevice.enableSynchronization().So both block indefinitely (observed > 13 min stuck on
Run loop "JS Run Loop" is awake). The Android launch arg that starts an instance with synchronization disabled (detoxEnableSynchronization) does not exist on iOS, so this is not a one-line fix.Proposed fix (option A): restructure the suite to onboard once and navigate screen-to-screen without relaunching, keeping synchronization disabled throughout (exactly as the onboard flow already does successfully). Alternatives: (B) make the app idle in the E2E build by gating WDK/balance background activity — uncertain, since the busyness is the worklet itself, not just the 30 s balance refetch; (C) keep relaunch but drive launch + PIN-unlock fully sync-disabled.
Why CI didn't catch this (the gate hole)
The Visual Regression check is:
types: [opened, reopened, ready_for_review, labeled]— nosynchronize), so it doesn't run on iterative commits.That combination let #186 merge a suite that had never passed, with the PR showing green. Recommendation: once the suite is fixed and proven stable, make it required and run it on push. Tradeoff: it's a ~30-min macOS build, so any flakiness would block merges — a team decision, not a pure win.
Next steps
References