# E2E full-suite hardening — session handoff notes Branch: `ci/full-e2e-run-2026-05-23` on `senamakel/openhuman` (fork). Date: 2026-05-23 → 2026-05-24. This is the snapshot of what's been done, what's known, and what to pick up next. Pair this with `gitbooks/developing/e2e-testing.md` (the existing E2E doc) — this file documents the multi-session push to get the **full** suite (all ~87 specs) reliably green on Linux + reproducible locally in Docker. --- ## TL;DR — Current state | Surface | Status | |---|---| | Full-suite CI on Linux | **72 / 87 passing** (15 failing), 6 parallel shards, ~25 min wall | | Two shards 100% green | **commerce (11/0)** + **webhooks (9/0)** | | Local Docker runs same 6-shard layout | `bash app/scripts/e2e-run-shards.sh` | | Local + CI agree on shard pass/fail | Yes (per-spec counts differ inside failing shards; see *CEF instability* below) | | macOS / Windows full-suite | Not yet validated this session — sharded jobs exist in workflow but only Linux was iterated on | Branch SHA at handoff: `2bad1f046` (`revert: drop Escape press in openConnectorModal`). --- ## What changed (commits, top → bottom is most recent) 1. `revert: drop Escape press in openConnectorModal` — regressed 7 connectors, reverted. 2. `fix(e2e): only press Escape in openConnectorModal when a modal backdrop is actually present` — superseded by revert. 3. `perf(e2e): split integrations into providers + webhooks shards` — 6-shard matrix. 4. `test(e2e): finish composio_sync URL-drop + close stale modal in openConnectorModal` 5. `test(e2e): local shard runner + fix telegram-flow reference + connector log refs` — adds `app/scripts/e2e-run-shards.sh`. 6. `test(e2e): drop URL-based assertions for composio_sync/_execute` 7. `perf(e2e): isolate connector smoke specs into their own shard` 8. `test(e2e): orchestrator coverage + state-bleed fixes` — adds 23 missing specs to `e2e-run-all-flows.sh`. 9. `test(e2e): align Linux specs with PR #2550 settings restructure` 10. `test(e2e): point auth-access-control logout test to /settings/account` 11. `test(e2e): drop assertions for surfaces removed in PR #2550` 12. `test(e2e): switch auth bypass from deep-link to loopback OAuth path` — important fidelity change, see below. 13. `perf(e2e): hoist Linux full-suite build into a single job, fan-out tests` — build-once → matrix shards. 14. `fix(e2e): gate build-skip on BOTH binary + CEF cache hits` 15. `fix(e2e): align CEF cache paths with actual download location` 16. `fix(e2e): set CEF_PATH when binary cache skips build` 17. `perf(e2e): cache built binary across shard runs` 18. `fix(e2e): install x86_64-apple-darwin target for Mac shard build` 19. `perf(e2e): shard full suite across 4 parallel jobs per OS` 20. `perf(e2e): run full suite in one shared session (no per-spec relaunch)` — first big perf jump. 21. `fix(e2e): repair stale assertions in linux-cef-deb-runtime spec` 22. `fix(e2e): put cargo-tauri install root on PATH for macOS/Windows CI` — Mac/Win build fix. --- ## Architecture as it stands today ### CI workflow `.github/workflows/e2e-reusable.yml` defines three Linux job tiers: ```text e2e-linux (smoke + mega-flow only, runs when inputs.full == false) rust-e2e-linux (Rust-side `tests/*_e2e.rs` against mock backend) build-linux-full (one job: cargo tauri build + tar artifact, uploads) e2e-linux-full (matrix of 6 shards, each `needs: build-linux-full`) ``` The build job tars `app/src-tauri/target/debug/OpenHuman`, `app/dist`, and `$HOME/Library/Caches/tauri-cef/` into a single `tar -czf` artifact (~600 MB). Each shard downloads + extracts to the canonical paths and skips the build step entirely. CEF/binary caches still live on the build job to keep cold builds fast. ### Shard layout ```text foundation = auth,navigation,system (~21 specs) chat = chat,skills,journeys (~19 specs) providers = providers,notifications (~14 specs) webhooks = webhooks (~9 specs) connectors = connectors (~16 specs) commerce = payments,settings (~11 specs) ``` The 6th shard (`webhooks` carved out of the original `integrations`) was added because anything over ~18-20 specs in one shared CEF session goes unstable on Linux. The `connectors` suite is its own category in `e2e-run-all-flows.sh` for the same reason. ### Local equivalent `app/scripts/e2e-run-shards.sh` is the local mirror of the CI matrix. Runs each shard as a fresh `e2e-run-all-flows.sh --suite=…` invocation, so each shard gets a fresh CEF process. ```bash docker compose -f e2e/docker-compose.yml run --rm e2e \ bash -lc "bash app/scripts/e2e-run-shards.sh" # or one shard: docker compose -f e2e/docker-compose.yml run --rm e2e \ bash -lc "bash app/scripts/e2e-run-shards.sh foundation" ``` ### Orchestrator (`app/scripts/e2e-run-all-flows.sh`) Collects all spec paths into one list (`_spec_paths[@]`) and calls `e2e-run-session.sh` ONCE with the full list, instead of per-spec. That restored the design intent in `wdio.conf.ts` ("WDIO creates ONE session per worker ... all specs run sequentially in the same session"). Per-spec relaunch was costing ~15-30s of CEF cold-start × 65 specs = 15+ min of pure overhead before this change. `--suite=` accepts a comma-separated list now (`--suite=auth,navigation,system`). slack-flow is explicitly **commented out** in the orchestrator — it crashed the CEF session mid-spec consistently. Investigate before re-enabling. --- ## Loopback auth bypass (production fidelity) Per PR #2550 the real OAuth login flow uses an RFC 8252 loopback listener (`http://127.0.0.1:53824/auth?state=…`) instead of the `openhuman://` deep-link. E2E auth bypass was still firing `openhuman://auth?token=…` directly through `window.__simulateDeepLink`, which is now the legacy fallback path. Switched in `app/test/e2e/helpers/loopback-auth-helpers.ts` + `reset-app.ts`: 1. WebView calls production `startLoopbackOauthListener()` (exposed on `window.__startLoopbackOauthListener` when the E2E build flag `VITE_OPENHUMAN_E2E_RESTART_APP_AS_RELOAD === 'true'` is set in `app/src/utils/loopbackOauthListener.ts`). 2. WebView wires `awaitCallback()` → `__simulateDeepLink` so the callback URL is rewritten `http://127.0.0.1:…/auth?…` → `openhuman://auth?…` and dispatched through the existing deep-link handler — mirroring exactly what `OAuthProviderButton.tsx` does in production. 3. Node-side `fetch()` hits the loopback URL with the bypass JWT + state nonce appended; the Rust listener accepts, validates state, emits `loopback-oauth-callback`. This means every spec's `resetApp()` now exercises the same Rust HTTP server + state nonce check + Tauri event emit that ships to users. `triggerAuthDeepLink` / `triggerAuthDeepLinkBypass` are still kept for oauth-success deep links (e.g. mega-flow's connector callbacks) that the loopback path doesn't cover. --- ## Known failures and root causes ### Foundation (2 failing on CI, more on local) | Spec | Cause | Difficulty | |---|---|---| | `onboarding-modes` (Phase B) | After Phase A reaches `/home`, `resetOnboardingFlagAndReload` resets `onboarding_completed=false` + reloads, but the Custom-card click in Phase B doesn't register (data-testid found but click is intercepted or stale). Needs DOM inspection of the wizard re-mount. | medium | | `runtime-picker-login` | `resetApp(skipAuth: true)` should land on Welcome screen, but the renderer re-hydrates from a persisted snapshot and lands on /home instead. `resetApp` already polls for the Welcome heading + re-replaces `#/` for up to 10s; insufficient. Likely needs to wait for `snapshot.sessionToken` to be cleared (via `fetchCoreAppSnapshot`) before considering the reset done. | medium | ### Chat (3 failing) | Spec | Cause | |---|---| | `chat-harness-subagent` | Agent orchestrator doesn't produce expected canary string. Real product/agent behavior — not a test bug. | | `chat-harness-wallet-flow` | Crypto agent doesn't produce wallet quote. Real product behavior. | | `chat-multi-tool-round` | T2.1 (`agent calls tool 1 (file_read); timeline shows it`) — `expect.toBe(true)` fails. Could be timing or real product change. | `chat-conversation-history` H1.4 was fixed earlier — root cause was `getSelectedThreadId()` returning the prior-spec's stale thread id before the New-thread click had time to update Redux. Fix: capture prior id, wait for `selectedThreadId !== priorThreadId`. ### Providers (3 failing) `conversations-web-channel-flow`, `telegram-channel-flow`, `whatsapp-flow` — likely the same shared-CEF-session instability hitting late-shard specs. Worth re-checking after any further shard reduction. ### Connectors (7 failing — all hit "expired auth" subtest) The other 9 connector tests in each spec pass. The one consistent failure is "expired auth shows Reconnect button and does not log user out" — `openConnectorModal()`'s card click is intercepted because the previous test left a modal backdrop up. Attempted fix (`Escape` before click) regressed other tests; reverted. Real fix probably: guarantee modal close in `afterEach` rather than working around it in the open helper. --- ## CEF shared-session instability — the recurring theme Empirically, the shared-CEF debug build becomes unreliable past ~18-20 specs in a single session. Symptoms vary: - `__simulateDeepLink ready? false (poll N)` after the listener was previously fine - `A sessionId is required for this command` - ECONNREFUSED to Appium :4723 mid-suite - Mysterious `esbuild` platform-mismatch errors during WDIO's TS transform of a spec file (red herring — sub-symptom of WDIO failing to bring the spec into scope after a session loss) Mitigations applied: - Shard so no shard runs more than ~16 specs (and the busiest two — foundation 21, chat 19 — are at the edge of what works). - Run each shard as a fresh `e2e-run-session.sh` invocation locally (mirrors CI matrix isolation). What might fix it for real (not attempted this session): - Bump WDIO `specFileRetries` so a session loss restarts the failing spec. - Periodic `openhuman.test_reset` + reload at a fixed cadence (every 10 specs?) to clear in-process leaks. - Build the test binary in `--release` to reduce per-process memory pressure (debug CEF + tauri builds are heavy). --- ## Stale assertions / PR #2550 drift — handled PR #2550 ("fix(oauth): make loopback redirect actually work, plus settings cleanup") moved a bunch of settings surfaces. Fixed tests: - Logout/Clear App Data lives at `/settings/account` (was `/settings`). Updated `logoutViaSettings` helper + `settings-data-management` + `auth-access-control`. - `/settings/connections` route deleted (ConnectionsPanel removed). `settings-account-preferences` dropped the post-recovery-phrase wallet status assertion; `navigation-settings-panels` N2.2 `.skip`-ed with PR pointer. - "Notification Routing" no longer a top-level Developer Options entry — moved into a tab on `/settings/notifications#routing`. `settings-advanced-config` navigates to `/settings/notifications` and clicks the Routing tab. - `screen-intelligence` dropped the "Permissions" assertion on Linux (the section is gated behind `status.platform_supported`, true only on macOS). --- ## Composio connector specs — the `composio_sync` URL gotcha The 15 connector smoke specs each had: ```ts clearRequestLog(); await callOpenhumanRpc('openhuman.composio_sync', { toolkit: TOOLKIT_SLUG }); const syncReq = getRequestLog().find( r => r.method === 'POST' && r.url.includes('/composio/sync') ); expect(syncReq).toBeDefined(); // always failed ``` `/composio/sync` does not exist in the mock router and the `composio_sync` RPC short-circuits with "no native provider registered" for any connector without a Rust-side provider, so no HTTP request is ever logged. The probe-style assertion never had a chance. The real intent (per the spec's `PASS:` log message: "sync does not nuke session") is covered by `assertSessionNotNuked()` on the next line. Dropped the URL check across all 15 specs. Same fix for `composio_execute` / `/composio/execute`. --- ## Local docker quirks - The docker-compose has named volumes per-platform for `node_modules` and `.pnpm-store` (the bind-mounted host `node_modules` would clobber Linux binaries with macOS ones). - `e2e-bootstrap` (in `e2e/docker-entrypoint.sh`) installs Appium 3 + chromium driver on first entry and caches into the npm volume. - Docker Desktop dies if the host has < ~1 GB free. Watch for `ENOSPC` while running long suites — output files grow fast. - `tee /tmp/local-shards.log` is the recommended way to capture the sharded run output; the bg-task output file gets cleaned up aggressively by the harness. --- ## Suggested next-session priorities 1. **Foundation Phase B onboarding + runtime-picker Welcome.** `resetApp(skipAuth)` is close — it polls for Welcome heading, force-replaces hash to `#/`, gives 10s. Needs to additionally poll `fetchCoreAppSnapshot()` until `sessionToken` is gone before returning. Probably 1-2 hours of careful work. 2. **Connector expired-auth `openConnectorModal`.** Add an `afterEach` that explicitly closes any open modal (`Escape` + wait for backdrop to disappear) rather than the failed "Escape-before-open" approach. ~30 min. 3. **CEF session retry.** Add WDIO `specFileRetries: 1` so a session-loss in shard N+1 retries spec N+1 in a fresh slot instead of cascading the rest of the shard. This should recover maybe 5-8 of the late-shard failures. 4. **Validate macOS + Windows full-suite.** Workflow already has the shard structure for both, but they haven't been exercised this session (Linux focus). Re-dispatch with `-f run_macos=true -f run_windows=true -f full=true` and triage. 5. **Re-enable slack-flow once the CEF stability fix lands.** It's the only spec the orchestrator deliberately skips today. --- ## Key file paths - Workflow: `.github/workflows/e2e-reusable.yml` - Orchestrator: `app/scripts/e2e-run-all-flows.sh` - Local sharder: `app/scripts/e2e-run-shards.sh` - Session runner: `app/scripts/e2e-run-session.sh` - Build script: `app/scripts/e2e-build.sh` - WDIO config: `app/test/wdio.conf.ts` - Loopback auth helper: `app/test/e2e/helpers/loopback-auth-helpers.ts` - Production loopback (exposes `__startLoopbackOauthListener` for E2E): `app/src/utils/loopbackOauthListener.ts` - Reset-app helper: `app/test/e2e/helpers/reset-app.ts` - Composio test helper: `app/test/e2e/helpers/composio-helpers.ts` - Docker setup: `e2e/docker-compose.yml`, `e2e/docker-entrypoint.sh` --- ## Useful commands cheatsheet ```bash # CI: dispatch a Linux-only full run on the fork (only run_macos / run_windows are inputs) gh workflow run E2E --repo senamakel/openhuman \ --ref ci/full-e2e-run-2026-05-23 \ -f run_macos=false -f run_windows=false -f full=true # CI: shard summary gh run view --repo senamakel/openhuman | grep -E '^(✓|X|\*|-) ' # CI: per-shard pass/fail + failing spec list gh api repos/senamakel/openhuman/actions/jobs//logs > /tmp/job.log grep -c 'PASSED in linux' /tmp/job.log grep -c 'FAILED in linux' /tmp/job.log grep 'FAILED in linux' /tmp/job.log \ | sed -E 's|.*specs/||;s|\.spec\.ts.*||' | sort -u # Local: full sharded run docker compose -f e2e/docker-compose.yml run --rm e2e \ bash -lc "bash app/scripts/e2e-run-shards.sh" 2>&1 | tee /tmp/local-shards.log # Local: single shard docker compose -f e2e/docker-compose.yml run --rm e2e \ bash -lc "bash app/scripts/e2e-run-shards.sh foundation" # Local: single spec docker compose -f e2e/docker-compose.yml run --rm e2e \ bash -lc "bash app/scripts/e2e-run-session.sh test/e2e/specs/.spec.ts" ```