--- phase: 01-stabilize-video-pipeline plan: 11 subsystem: test-infrastructure tags: - puppeteer - uat - harness - e2e - mv3-extension - spike-then-pivot - architectural-falsification - test-bundle-separation requires: - 01-08 (Plan 01-08 SUMMARY + Tier-1 SW-bundle-import gate GREEN) - 01-09 (Bug A + Bug B fixes landed; the regression-catch targets) provides: - test-build infrastructure (vite.test.config.ts + dist-test/ + npm scripts build:test/test:uat) - Tier-1 hook-leak grep gate (no-test-hooks-in-prod-bundle.test.ts enforces __mokoshTest absent from production dist/) - empirical falsification of original RESEARCH §6 (MV3 SW blocks dynamic import — verified) - working prototype validating Approach B architecture (commit c647f61; A6 5/5 GREEN; Bug-B regression rewind verified) - offscreen-side test hooks scaffolding (src/test-hooks/offscreen-hooks.ts with installFakeDisplayMedia) - input for Plan 01-13 (carries the proven architecture forward) affects: - package.json (puppeteer + tsx + @types/node devDeps) - vite.test.config.ts (NEW — extends production config with mode='test', outDir='dist-test') - tsconfig.json (test path inclusion) - src/test-hooks/sw-hooks.ts (NEW — BROKEN architecturally; deleted in 01-13) - src/test-hooks/offscreen-hooks.ts (NEW — works; carried to 01-13) - src/test-hooks/types.ts (NEW — type contracts; carried to 01-13) - src/background/index.ts (Wave 1 added dynamic import — silently kills SW per MV3 limit; reverted in 01-13) - src/offscreen/recorder.ts (Wave 1 added dynamic import — works because offscreen IS a DOM document) - tests/uat/lib/*.ts (Wave 2 popup-bridge scaffolding — wrong architecture; replaced in 01-13) - tests/uat/harness.test.ts (Wave 2 skeleton with A0 GREEN — rewired in 01-13) - tests/uat/prototype/*.ts (NEW — proves Approach B; promoted to production paths in 01-13) - tests/background/no-test-hooks-in-prod-bundle.test.ts (NEW Tier-1 gate; permanent) tech-stack: added: - puppeteer 25.x (extension automation driver) - tsx (TS runner for harness scripts) - "@types/node" patterns: - Two-bundle separation (production dist/ vs test dist-test/ via Vite mode flag) - __MOKOSH_UAT__ Vite define-token gating (replaces import.meta.env.MODE='test' which collides with vitest) - Tier-1 grep gate as production-bundle hygiene contract - Synthetic MediaStream via Canvas.captureStream() (bypasses getDisplayMedia picker in test) - track.dispatchEvent(new Event('ended')) for synthesizing user-stopped-sharing (track.stop() does NOT fire ended per W3C spec) falsified: - "MV3 SW supports dynamic import gated by import.meta.env.MODE" — empirically wrong; await import() in SW never resolves; SW silently dies - "Puppeteer sw.evaluate exposes extension chrome.*" — empirically wrong; only chrome.{loadTimes,csi} exposed (CDP treats MV3 SW as generic worker) - "Popup-bridge architecture sufficient for SW state queries" — empirically wrong; setPopup state changes don't propagate to popup chrome.action.getPopup queries reliably - "Headless --auto-select-desktop-capture-source triggers getDisplayMedia resolution" — empirically wrong in --headless=new key-files: created: - vite.test.config.ts - src/test-hooks/sw-hooks.ts (deleted in 01-13) - src/test-hooks/offscreen-hooks.ts (carried to 01-13) - src/test-hooks/types.ts (carried to 01-13) - tests/uat/lib/launch.ts + extension.ts + sw.ts + offscreen.ts + assertions.ts + zip.ts (Wave 2; popup-bridge — replaced in 01-13) - tests/uat/harness.test.ts (Wave 2 skeleton; rewired in 01-13) - tests/uat/prototype/extension-page-harness.html + .ts (PROVEN ARCHITECTURE; promoted in 01-13) - tests/uat/prototype/a6.test.ts (Puppeteer driver template; promoted in 01-13) - tests/background/no-test-hooks-in-prod-bundle.test.ts (Tier-1 grep gate; permanent) modified: - package.json (puppeteer + tsx + @types/node devDeps; build:test + test:uat scripts) - tsconfig.json (test paths) - src/background/index.ts (Wave 1 dynamic import — reverted in 01-13) - src/offscreen/recorder.ts (Wave 1 dynamic import — preserved in 01-13) decisions: - Approach A (Puppeteer sw.evaluate per RESEARCH §1+6) attempted across Waves 1-3; empirically falsified by both Wave 3 execution (commit f44ca3a) + dedicated feasibility research subagent (Verdict-A research, prototype c647f61). - Approach B (extension-internal-page harness + offscreen-side synthetic MediaStream + chrome.runtime.sendMessage bridge) validated via prototype; industry-standard pattern per MetaMask, eyeo, Chrome MV3 official testing docs convergence. - Wave 0 infrastructure retained as foundation for 01-13. Waves 1-2 partially retained — offscreen-hooks + types + harness.test.ts skeleton + Tier-1 gate kept; sw-hooks + popup-bridge lib deleted/replaced. - Spike-then-pivot framing: 01-11 reads retroactively as a SPIKE that delivered useful infrastructure + falsified the original architectural hypothesis + produced a working prototype. Full 14-assertion harness implementation defers to Plan 01-13. - __MOKOSH_UAT__ Vite define-token chosen over import.meta.env.MODE='test' (per original RESEARCH §6 sketch) because vitest defaults MODE='test' and would have activated hooks during unit tests, clobbering 8 existing chrome.* vi.fn() mocks. - Tier-1 grep gate (no-test-hooks-in-prod-bundle.test.ts) is permanent baseline — verifies __mokoshTest absent from production dist/ on every test run. metrics: duration: "~8h cumulative (planning + research + 2 executor attempts + feasibility research + prototype + pivot)" completed: "2026-05-18 (closed as spike-then-pivot; full harness deferred to 01-13)" task_count: "Wave 0 (T1) complete; Waves 1-4 partial or not delivered as originally planned" files_modified: "18 created + 4 modified (some files will be deleted in 01-13 baseline)" net_loc: "~1500+ (test infrastructure + prototype + Tier-1 gate)" pivot: "yes — full harness implementation deferred to Plan 01-13" --- # Phase 1 Plan 11: UAT Harness — Approach A Spike (Pivoted to 01-13) Attempted to retire operator UAT for functional gates via Puppeteer-driven MV3 extension automation. Approach A (Puppeteer `sw.evaluate` per original RESEARCH §1+6) empirically falsified across Wave 3 + dedicated feasibility research. Approach B (extension-internal-page harness + offscreen-side synthetic stream) validated via working prototype. **Full harness implementation deferred to Plan 01-13.** ## One-Liner Approach A spike: shipped two-bundle infrastructure + Tier-1 grep gate + offscreen-hooks scaffolding + empirical falsification of "MV3 SW supports dynamic import" hypothesis + working prototype of Approach B (extension-internal-page harness). Full 14-assertion implementation pivots to Plan 01-13. ## What Landed ### Infrastructure (Wave 0, commit 96fa8e8) — Retained for 01-13 - `vite.test.config.ts` — extends production config with `mode='test'` + `outDir='dist-test'` - `package.json` scripts `build:test` + `test:uat` (orchestrate build + harness) - `tests/background/no-test-hooks-in-prod-bundle.test.ts` — Tier-1 grep gate (A0 assertion); permanent in test suite - Bonus: `0cd50fd` — imported Bug B recovery-flow debug record from prior session (cleanup that was overdue) ### Hooks scaffolding (Wave 1, commit cb1a729) — Partially Retained | File | Status | Reason | |---|---|---| | `src/test-hooks/offscreen-hooks.ts` | RETAINED + EXTENDED in 01-13 | Offscreen IS a DOM document; dynamic import works there | | `src/test-hooks/types.ts` | RETAINED in 01-13 | Type contracts valid regardless of arch | | `src/test-hooks/sw-hooks.ts` | DELETED in 01-13 | MV3 SW blocks dynamic import — silently kills SW | | `src/background/index.ts` Wave 1 import block | REVERTED in 01-13 | Same MV3 SW limit | ### Harness scaffolding (Wave 2, commit dbd977c) — Mostly Replaced | File | Status | Reason | |---|---|---| | `tests/uat/lib/*.ts` (popup-bridge) | DELETED/REPLACED in 01-13 | Wrong architecture; setPopup state changes don't propagate reliably | | `tests/uat/harness.test.ts` skeleton | REWIRED in 01-13 | Top-level structure stays; assertions point at new extension-page architecture | | A0 Tier-1 gate (GREEN here) | RETAINED — permanent | Grep gate is architecture-agnostic | ### Working prototype (commit c647f61) — PROVES Approach B; Promoted in 01-13 - `tests/uat/prototype/extension-page-harness.html + .ts` — extension-internal page (privileged chrome.* access) drives assertions via direct chrome.* calls + `chrome.runtime.sendMessage` bridge - `tests/uat/prototype/a6.test.ts` — Puppeteer driver template - **A6 assertion (Bug B regression catch): 5/5 GREEN** - **Bug-B regression rewind verified**: `if (false)` patch on `src/background/index.ts:759` turns 4/5 RED; restoring turns 5/5 GREEN - Total prototype runtime: ~7s end-to-end ### Empirical Falsifications (Research Output) These were the original RESEARCH §1 + §6 hypotheses. All falsified by execution + feasibility research: 1. **MV3 SW blocks dynamic import.** `await import(...)` in `src/background/index.ts` silently never resolves; SW dies. Verified across two independent investigations. Cited: [Chromium `es_modules.md`](https://chromium.googlesource.com/chromium/src/+/HEAD/content/browser/service_worker/es_modules.md) + [w3c/webextensions#212](https://github.com/w3c/webextensions/issues/212). 2. **Puppeteer `WebWorker.evaluate` against MV3 SW** only exposes `chrome.{loadTimes, csi}` — NOT extension chrome.* API. CDP-level abstraction treats MV3 SW as a generic worker. 3. **Popup-bridge `chrome.action.setPopup` state changes** don't propagate to popup-side `chrome.action.getPopup` queries within waitable timeframes. 4. **Headless `--auto-select-desktop-capture-source`** doesn't trigger getDisplayMedia resolution in `--headless=new` for screen-capture sources. ## Test Counts - Before plan: 83/83 vitest GREEN - After Wave 0 (96fa8e8): 84/84 GREEN (+1 Tier-1 grep gate) - After Wave 1 (cb1a729): 89/89 GREEN (+5 hook contract tests) - After Wave 2 (dbd977c): 89/89 GREEN (popup-bridge tests stubbed RED-by-design per plan; A0 GREEN) - After prototype (c647f61): 89/89 vitest unchanged; prototype runs out-of-band via `npx tsx tests/uat/prototype/a6.test.ts` (5/5 GREEN, ~7s) - End of plan: **89/89 vitest GREEN; production bundle hook-free (Tier-1 gate enforces)** ## Deviations from Plan The plan as written assumed Approach A architecture would work. Across Wave 3 attempted execution + dedicated feasibility research, Approach A architecturally infeasible. Pivoted to Approach B per prototype. Plan 01-09 closure via harness PASS (the original Task 5 redirect target) NOT achieved — Plan 01-09 still requires operator UAT pending Plan 01-13 landing. ## Architectural Notes Worth Carrying Forward - **Drive Chrome FROM INSIDE the extension, not FROM OUTSIDE.** MetaMask/eyeo/Chrome-docs convergence: extension-internal pages have FULL chrome.* API access as privileged contexts. Puppeteer drives the page; page drives chrome.*. SW state queried indirectly via `chrome.runtime.sendMessage`. - **`track.dispatchEvent(new Event('ended'))` for synthesizing user-stopped-sharing**, NOT `track.stop()` (which does NOT fire `ended` per W3C spec + empirical Chrome 148 verification). - **`__MOKOSH_UAT__` Vite define-token** over `import.meta.env.MODE='test'` — vitest defaults `MODE='test'`, which would activate hooks during unit tests and clobber chrome.* mocks. - **MV3 SW blocks dynamic import** — never use `await import(...)` in `src/background/index.ts`. Test-mode SW augmentation must be done via build-time inclusion (Vite plugin) or eager static import, not runtime import gate. - **Pages CAN call `chrome.offscreen.createDocument`** — confirmed empirically. Chrome docs don't say this explicitly. Architectural unlock for the prototype. - **Spike-then-pivot framing is a legitimate plan outcome** — when a plan attempts an approach that proves infeasible, the right move is honest SUMMARY + new plan, not in-place rewrite + AMENDMENT artifact. (Lesson for the orchestrator.) ## Self-Check: PARTIAL — Closed as Spike-Pivot - FOUND: `vite.test.config.ts` (Wave 0) - FOUND: `tests/background/no-test-hooks-in-prod-bundle.test.ts` (Tier-1 gate, A0 GREEN) - FOUND: `src/test-hooks/offscreen-hooks.ts` (carries to 01-13) - FOUND: `src/test-hooks/types.ts` (carries to 01-13) - FOUND: `tests/uat/prototype/extension-page-harness.{html,ts}` - FOUND: `tests/uat/prototype/a6.test.ts` (5/5 GREEN) - FOUND: `tests/uat/lib/*.ts` (wrong arch — to delete/replace in 01-13) - FOUND: `src/test-hooks/sw-hooks.ts` (broken — to delete in 01-13) - FOUND: 96fa8e8 (Wave 0 infrastructure) - FOUND: 0cd50fd (bonus debug record import) - FOUND: cb1a729 (Wave 1 — partial retained; sw-hooks broken) - FOUND: dbd977c (Wave 2 — popup-bridge wrong arch) - FOUND: f44ca3a (Wave 3 partial — abandoned) - FOUND: c647f61 (prototype — proves Approach B; PROVENANCE for 01-13) - NOT-DELIVERED: A1-A13 functional assertions (deferred to 01-13) - NOT-DELIVERED: Plan 01-09 closure via harness PASS (still requires operator UAT pending 01-13) ## Known Stubs (To Be Resolved in 01-13) - `tests/uat/lib/*.ts` — popup-bridge architecture stubs from Wave 2 dbd977c. Wrong architecture; 01-13 deletes or rewrites. - `src/test-hooks/sw-hooks.ts` + dynamic-import block in `src/background/index.ts` — BROKEN (MV3 SW limit). 01-13 deletes both. - `tests/uat/prototype/*` — proof-of-concept code, not production-quality. 01-13 promotes to production paths under `tests/uat/` proper. ## Bridge to Plan 01-13 Plan 01-13 implements full UAT harness via Approach B (extension-internal-page architecture). Inputs: - This SUMMARY (pivot rationale + retained infrastructure + falsified hypotheses) - Plan 01-11-RESEARCH.md (original research; partially historical now) - Feasibility research findings (Verdict-A architecture details, embedded in c647f61 commit body + this SUMMARY) - Prototype files (`tests/uat/prototype/*`, `src/test-hooks/offscreen-hooks.ts`, `vite.test.config.ts`) - The 14-assertion scope from original Plan 01-11 PLAN.md 01-13 will land all 14 assertions reliably, close Plan 01-09 functional contract via harness PASS, and become the standard closure gate for future phases.