Build out the Approach-B harness driver utilities atop the Wave 1
production paths. Three new files form the shared scaffold that
Wave 3's 13 assertion drivers (A1-A5, A7-A13) and the eventual
orchestrator (`tests/uat/harness.test.ts`) will all consume. The
standalone A6 driver (`tests/uat/a6.test.ts`) is rewritten to use
the new lib — behavior-preserving: A6 still PASSES 5/5 in ~7s.
New files:
- tests/uat/lib/launch.ts (~320 LoC)
`launchHarnessBrowser({ headless?, downloadsDir? }) → HarnessHandles`
Extracts the Chrome-launch + victim-page + harness-page + console-
attach pattern from a6.test.ts into a single reusable helper.
NEW vs prototype: CDP `Browser.setDownloadBehavior` wires
Chrome's download path to a per-run `mkdtempSync` tmp dir so A5
(SAVE_ARCHIVE) can poll a known location without colliding with
the operator's real downloads. Architectural commitments
enforced (per 01-11-SUMMARY): no `--auto-select-desktop-capture-
source` flag; victim about:blank brought to front for the
production `chrome.tabs.query({active:true})` workaround; SW
console attach best-effort with bounded poll; offscreen console
attach opportunistic via `targetcreated` listener (offscreen
target appears later, when the harness page calls
chrome.offscreen.createDocument).
- tests/uat/lib/assertions.ts (~210 LoC)
Host-side assertion primitives:
* `AssertionRecord`, `CheckRecord`, `ConsoleBuffers` types —
mirror the page-side shape returned by `assertA*` methods.
* `runAssertion(name, fn, buffers)` — try/catch wrapper that
dumps the SW + offscreen console tails (last 100 lines each)
to stderr on failure, then returns `{passed: false, error}`
if `fn` throws.
* `printAssertionResult(result)` — single source of truth for
the formatted result print. Extracted from the inline
`printResult` previously in the prototype's a6.test.ts so
Wave 3's orchestrator can reuse it across all 14 assertions.
* `assertEqual / assertGte / assertMatch / assertTrue` —
structured failure messages atop node:assert/strict.
* `waitFor(probe, predicate, timeoutMs, description)` — host-
side polling primitive; mirrors the page-side waitFor
semantics verbatim (they can't share a module: page-side is
bundled into the harness HTML, host-side runs in Node).
NO chrome.* helpers here — all chrome.* work happens inside the
extension-internal harness page. This module is host-side ONLY
by construction (no chrome global in Node anyway).
- tests/uat/lib/harness-page-driver.ts (~170 LoC)
One driver wrapper per assertion (A1..A13). Each wraps a single
`page.evaluate(() => window.__mokoshHarness.assertXX())`.
Centralizing this means adding/renaming an assertion = two-file
edit (extension-page-harness.ts impl + this file) instead of
touching every test-file caller.
Wave 2 wires `driveA6` (proven from c647f61). The 12 Wave-3
drivers (driveA1..A5, A7..A13) are stubbed as
`throw new Error('NOT YET IMPLEMENTED — Wave 3<X> wires driveXX')`
so the future orchestrator's `for (const drive of drivers)` loop
fails cleanly on the first unimplemented one (bail-on-first-
failure semantics). The `AssertionWithBytes` type is declared
for A5/A12/A13 which return `bytesBase64` payloads (zip / webm
bytes that the host side processes after the page-side
assertion completes).
Rewrite — `tests/uat/a6.test.ts`:
- Drops ~80 LoC of Chrome-launch + console-attach + result-print
plumbing now living in lib/launch.ts + lib/assertions.ts.
- Now ~70 LoC total — pure orchestration of
launchHarnessBrowser → runAssertion(driveA6) → printAssertionResult
→ browser.close() → exit code.
- Behavior-preserving: A6 still 5/5 GREEN with the same diagnostic
output (SETUP, A6.1-A6.4) and the same ~7s end-to-end runtime.
Verification (all GREEN):
- `npx tsc --noEmit` — exit 0 (root + tests/uat/tsconfig.json).
- `npx tsx tests/uat/a6.test.ts` — exits 0 with "PASS"; 5 checks
GREEN (SETUP, A6.1, A6.2, A6.3, A6.4). End-to-end runtime ~7s
headless on this workstation.
- `npm run build` — exit 0; Tier-1 grep gate GREEN (production
bundle contains zero hook strings AND zero lib symbol names —
the new lib files are test-only and not bundled into dist/).
- `npm run build:test` — exit 0; dist-test/ still emits the
extension-page-harness.html harness (lib files are host-side,
not rollup inputs).
- `npx vitest run` — 92/92 GREEN.
Wave 3 ready: harness-page-driver.ts has driveA1..A5/A7..A13 stubs
in place; extending requires only:
1. Add `assertAXX` method to window.__mokoshHarness in
tests/uat/extension-page-harness.ts.
2. Replace the corresponding stub body in this file with the
page.evaluate wrapper.
3. (Wave 3A) Create tests/uat/harness.test.ts orchestrator that
iterates over [A0 grep gate, driveA1..A13] with bail-on-fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.6 KiB
TypeScript
69 lines
2.6 KiB
TypeScript
// tests/uat/a6.test.ts — Plan 01-13 standalone A6 entry point.
|
|
//
|
|
// Refactored in Wave 2 to use the shared `tests/uat/lib/` scaffolding
|
|
// (`launchHarnessBrowser`, `driveA6`, `runAssertion`, `printAssertionResult`).
|
|
// Behavior-preserving: A6 still PASSES 5/5 in ~7s end-to-end. The ~80
|
|
// LoC of Chrome-launch + console-attach + result-print plumbing
|
|
// previously inlined here now lives in `tests/uat/lib/{launch,assertions,
|
|
// harness-page-driver}.ts` — single source of truth for Wave 3's 13
|
|
// additional assertions.
|
|
//
|
|
// This standalone entry is RETAINED throughout the rest of Plan 01-13
|
|
// for fast TDD iteration on the A6 contract:
|
|
// `npx tsx tests/uat/a6.test.ts` # headless, ~7s
|
|
// `HEADLESS=0 npx tsx tests/uat/a6.test.ts` # interactive debug view
|
|
//
|
|
// The orchestrator-level entry `npm run test:uat` (lands in Wave 3A)
|
|
// runs all 14 assertions (~60-90s); this single-A6 entry is for the
|
|
// inner loop when iterating on Bug B fix verification or harness-page
|
|
// surface changes.
|
|
//
|
|
// Pre-flight: requires `dist-test/` from `npm run build:test`. The
|
|
// `assertBundlePresent` call inside `launchHarnessBrowser` fails
|
|
// loudly if the bundle is missing.
|
|
|
|
import { launchHarnessBrowser } from './lib/launch';
|
|
import { driveA6 } from './lib/harness-page-driver';
|
|
import { runAssertion, printAssertionResult } from './lib/assertions';
|
|
|
|
/**
|
|
* Standalone A6 driver entry point.
|
|
*
|
|
* @returns Process exit code: 0 on PASS, 1 on FAIL.
|
|
*/
|
|
async function main(): Promise<number> {
|
|
process.stdout.write('\nMokosh Plan 01-13 — A6 (Bug B canonical) standalone driver\n');
|
|
process.stdout.write('Architecture: extension-internal page + bridge + synthetic stream\n');
|
|
process.stdout.write('='.repeat(72) + '\n');
|
|
|
|
const handles = await launchHarnessBrowser();
|
|
process.stdout.write(`Extension id: ${handles.extensionId}\n`);
|
|
process.stdout.write(`Downloads dir: ${handles.downloadsDir}\n`);
|
|
process.stdout.write('Harness page ready; invoking assertA6()...\n\n');
|
|
|
|
let exitCode = 1;
|
|
try {
|
|
const result = await runAssertion(
|
|
'A6 — BUG B canonical: user-stopped-sharing routes via setIdleMode',
|
|
() => driveA6(handles.harnessPage),
|
|
{ swConsole: handles.swConsole, offConsole: handles.offConsole },
|
|
);
|
|
printAssertionResult(result);
|
|
exitCode = result.passed ? 0 : 1;
|
|
} catch (err) {
|
|
process.stderr.write(`\n*** Top-level harness error: ${String(err)}\n`);
|
|
exitCode = 1;
|
|
} finally {
|
|
try {
|
|
await handles.browser.close();
|
|
} catch (closeErr) {
|
|
process.stderr.write(`(non-fatal: browser close threw: ${String(closeErr)})\n`);
|
|
}
|
|
}
|
|
|
|
return exitCode;
|
|
}
|
|
|
|
const code = await main();
|
|
process.exit(code);
|