// tests/uat/lib/harness-page-driver.ts — Plan 01-13 Wave 2. // // Driver wrappers — one per assertion (A1..A13). Each wraps a single // `page.evaluate(() => window.__mokoshHarness.assertXX())` call, // returning the structured AssertionRecord (or the extended shape with // `bytesBase64` for A5/A12/A13 which return host-side-required payloads // like the downloaded zip bytes or the recorded webm bytes). // // Centralizing the page.evaluate call here means adding or renaming an // assertion requires a two-file edit: // 1. extension-page-harness.ts — page-side impl + window.__mokoshHarness wire // 2. this file — host-side driver wrapper // instead of touching every test-file that calls the assertion. // // Wave 2 ONLY wires `driveA6` (the proven assertion from the c647f61 // prototype). The 12 Wave-3 assertions are stubbed as `throw new // Error('NOT YET IMPLEMENTED — Wave 3 wires this')` so the // orchestrator's `for (const drive of drivers)` loop fails cleanly on // the first unimplemented one (bail-on-first-failure semantics in // `harness.test.ts` lands in Wave 3A). // // References: // - puppeteer Page.evaluate: // https://pptr.dev/api/puppeteer.page.evaluate import type { Page } from 'puppeteer'; import type { AssertionRecord, CheckRecord } from './assertions'; /** * Extended assertion-record shape for A5/A12/A13 which return * host-side-required binary payloads: * - A5 (SAVE_ARCHIVE): `bytesBase64` is the downloaded zip bytes * (read by host-side from `handles.downloadsDir`); page side only * returns the trigger ack. * - A12 (ffprobe): `bytesBase64` is the recorded webm bytes — * extracted from the zip by the host so ffprobe (host-side binary) * can analyze it. * - A13 (zip shape): `bytesBase64` is the zip bytes; `expectedVersion` * is the manifest version the harness was built against. * * All Wave-3 assertions; not used in Wave 2. */ export interface AssertionWithBytes { readonly passed: boolean; readonly name: string; readonly checks: ReadonlyArray; readonly diagnostics: ReadonlyArray; readonly error?: string; readonly bytesBase64?: string; readonly expectedVersion?: string; } /** Marker error message for unimplemented Wave-3 drivers — orchestrator * matches on this prefix to format the diagnostic distinctly from a * genuine assertion failure. */ const WAVE3_STUB_PREFIX = 'NOT YET IMPLEMENTED'; /** * Drive the A6 (Bug B canonical) assertion. The proven, prototype- * inherited driver. Page side does all orchestration (ensureOffscreen + * start + wait + dispatch + assert); host side just triggers + reads * the result. * * @param page - The harness page (from `launchHarnessBrowser`). * @returns Structured AssertionRecord with 5 checks (SETUP + A6.1..A6.4). */ export async function driveA6(page: Page): Promise { return await page.evaluate(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose. const harness = (window as any).__mokoshHarness; const r: AssertionRecord = await harness.assertA6(); return r; }) as AssertionRecord; } /* ─── Wave 3A — WIRED ─────────────────────────────────────────────── */ /** * Drive A1 (SW bootstrap state). Asserts the post-load idle-mode state: * badge='', popup='', isRecording=false. MUST run BEFORE A2 in any * orchestrated sequence — A2 manually sets badge='REC' which invalidates * the A1 contract until the SW is reset. * * @param page - The harness page from `launchHarnessBrowser`. * @returns Structured AssertionRecord with 3 checks (badge + popup + isRecording). */ export async function driveA1(page: Page): Promise { return await page.evaluate(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose. const harness = (window as any).__mokoshHarness; const r: AssertionRecord = await harness.assertA1(); return r; }) as AssertionRecord; } /** * Drive A2 (toolbar onClicked → REC). Uses the direct-offscreen workaround * for the missing `tabs` manifest permission (per 01-11-SUMMARY). Leaves * the offscreen recording active — A3 + A4 chain off A2's REC state. * * @param page - The harness page from `launchHarnessBrowser`. * @returns Structured AssertionRecord with 2 checks (badge + popup). */ export async function driveA2(page: Page): Promise { return await page.evaluate(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose. const harness = (window as any).__mokoshHarness; const r: AssertionRecord = await harness.assertA2(); return r; }) as AssertionRecord; } /** * Drive A3 (displaySurface === 'monitor'). Assumes A2 left recording * active. Queries the offscreen `get-display-surface` bridge op. * * @param page - The harness page from `launchHarnessBrowser`. * @returns Structured AssertionRecord with 1 check (displaySurface). */ export async function driveA3(page: Page): Promise { return await page.evaluate(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose. const harness = (window as any).__mokoshHarness; const r: AssertionRecord = await harness.assertA3(); return r; }) as AssertionRecord; } /** * Drive A4 (popup pinned + single offscreen during recording). Assumes * A2 left recording active. Verifies getPopup unchanged + hasDocument * true (no duplicate offscreen spawned). * * @param page - The harness page from `launchHarnessBrowser`. * @returns Structured AssertionRecord with 2 checks (popup + hasDocument). */ export async function driveA4(page: Page): Promise { return await page.evaluate(async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any -- evaluate runs in browser context where Window types are loose. const harness = (window as any).__mokoshHarness; const r: AssertionRecord = await harness.assertA4(); return r; }) as AssertionRecord; } /* ─── Wave 3B — NOT YET IMPLEMENTED ──────────────────────────────── */ /** * Drive A5 (SAVE_ARCHIVE download). Wave 3B wires this; signature will * take a second `downloadsDir` parameter so the host side can poll * for the dropped zip file. * * @throws Always — replace stub when Wave 3B lands. */ export async function driveA5(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3B wires driveA5`); } /** * Drive A7 (genuine error → ERR + recovery notification). Wave 3B wires. * @throws Always — replace stub when Wave 3B lands. */ export async function driveA7(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3B wires driveA7`); } /* ─── Wave 3C — NOT YET IMPLEMENTED ──────────────────────────────── */ /** * Drive A8 (Bug A onStartup → notification creates). Wave 3C wires. * @throws Always — replace stub when Wave 3C lands. */ export async function driveA8(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3C wires driveA8`); } /** * Drive A9 (icon file sizes). Wave 3C wires. * @throws Always — replace stub when Wave 3C lands. */ export async function driveA9(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3C wires driveA9`); } /** * Drive A10 (manifest shape). Wave 3C wires. * @throws Always — replace stub when Wave 3C lands. */ export async function driveA10(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3C wires driveA10`); } /* ─── Wave 3D — NOT YET IMPLEMENTED ──────────────────────────────── */ /** * Drive A11 (35s → ≥3 segments). Wave 3D wires. * @throws Always — replace stub when Wave 3D lands. */ export async function driveA11(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3D wires driveA11`); } /** * Drive A12 (ffprobe — host-side returns webm bytes). Wave 3D wires. * @throws Always — replace stub when Wave 3D lands. */ export async function driveA12(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3D wires driveA12`); } /** * Drive A13 (zip structure + meta.json). Wave 3D wires. * @throws Always — replace stub when Wave 3D lands. */ export async function driveA13(_page: Page): Promise { throw new Error(`${WAVE3_STUB_PREFIX} — Wave 3D wires driveA13`); }