fix(debug): race-tolerant offscreen target attach in UAT launch
Plan-04-04 debug session-2 root cause: the offscreen-console capture
in tests/uat/lib/launch.ts:registerOffscreenConsoleAttach matched zero
offscreen targets across 4 spike runs, creating a critical observability
gap that prevented disambiguation of Plan 04-04 Wave 0 spike failure
mode.
Empirical investigation (tests/uat/spike-diagnose-offscreen-target.ts,
NEW): when chrome.offscreen.createDocument fires, Puppeteer's
`targetcreated` event fires with `type='other'` and `url=''` BEFORE the
CDP target metadata stabilizes. The previous filter (whether
`background_page` or `page`) never matched at event time. By the time
the metadata stabilizes (visible via `browser.targets()`), the
target's type is `'background_page'` (not `'page'` — MV2's
background_page type IS still used by Chrome's CDP for invisible
extension documents, despite MV3 abolishing classic background pages).
Fix:
- Match the offscreen target by URL pattern (load-bearing criterion;
type field is intentionally unchecked because it's unreliable at
targetcreated time).
- Bind to BOTH `targetcreated` AND `targetchanged` events (the latter
fires when the URL stabilizes after navigation).
- Add a `browser.targets()` enumeration race-free safety net for
cases where the offscreen target exists at registration time.
Verification: tests/uat/spike-diagnose-offscreen-target.ts now emits
`(launch: offscreen console attached — url=chrome-extension://.../src/offscreen/index.html)`
followed by `[off:log] [OS:Recorder] Recording started ...` (zero such
lines in any prior spike run).
Test-infra correctness fix; ZERO production source changes. FORBIDDEN_HOOK_STRINGS
inventory unchanged at 12 entries. No new test-only `__MOKOSH_UAT__` symbols.
References:
- .planning/debug/sw-offscreen-persistence-investigation-session-2.md
(session-2 debug note documenting empirical root cause)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
67
tests/uat/spike-diagnose-offscreen-target.ts
Normal file
67
tests/uat/spike-diagnose-offscreen-target.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// tests/uat/spike-diagnose-offscreen-target.ts — Plan-04-04 debug
|
||||
// session-2 disambiguation helper.
|
||||
//
|
||||
// Single-purpose diagnostic script: launch the harness, prime the
|
||||
// recording via assertA2 (so the offscreen exists with active
|
||||
// MediaRecorder), then enumerate `browser.targets()` and print each
|
||||
// target's `type()` + `url()`. Used to discover why the production
|
||||
// offscreen target predicate in `launch.ts:registerOffscreenConsoleAttach`
|
||||
// fails to match: empirical inspection of what Puppeteer actually
|
||||
// reports for an MV3 offscreen document.
|
||||
//
|
||||
// Operation: `tsx tests/uat/spike-diagnose-offscreen-target.ts`.
|
||||
// Wall-clock: ~5s (no idle, no SW kill). Exits 0 if the offscreen
|
||||
// target is discoverable; 1 if no target with the offscreen URL
|
||||
// is found.
|
||||
|
||||
import { launchHarnessBrowser } from './lib/launch';
|
||||
|
||||
async function main(): Promise<number> {
|
||||
process.stdout.write('\nMokosh debug session-2 — offscreen target diagnostic\n');
|
||||
process.stdout.write('='.repeat(72) + '\n');
|
||||
|
||||
const handles = await launchHarnessBrowser();
|
||||
process.stdout.write(`Diagnostic: extensionId=${handles.extensionId}\n\n`);
|
||||
|
||||
// Prime recording (offscreen comes alive here).
|
||||
process.stdout.write('Diagnostic: priming via assertA2 to spawn offscreen\n');
|
||||
const a2Result = await handles.harnessPage.evaluate(async () => {
|
||||
const harness = (
|
||||
window as unknown as {
|
||||
__mokoshHarness: { assertA2: () => Promise<{ passed: boolean; error?: string }> };
|
||||
}
|
||||
).__mokoshHarness;
|
||||
return harness.assertA2();
|
||||
});
|
||||
process.stdout.write(`Diagnostic: assertA2.passed=${a2Result.passed}\n\n`);
|
||||
|
||||
// Wait a moment for the offscreen target to materialize fully.
|
||||
await new Promise((res) => setTimeout(res, 1_000));
|
||||
|
||||
// Enumerate every target.
|
||||
const allTargets = handles.browser.targets();
|
||||
process.stdout.write(`Diagnostic: browser.targets() count = ${allTargets.length}\n`);
|
||||
process.stdout.write('-'.repeat(72) + '\n');
|
||||
for (const target of allTargets) {
|
||||
const targetType = target.type();
|
||||
const targetUrl = target.url();
|
||||
process.stdout.write(` type=${targetType.padEnd(20)} url=${targetUrl}\n`);
|
||||
}
|
||||
process.stdout.write('-'.repeat(72) + '\n');
|
||||
|
||||
// Identify any target that matches the offscreen URL pattern.
|
||||
const offscreenCandidates = allTargets.filter((t) => t.url().includes('/src/offscreen/'));
|
||||
process.stdout.write(`\nDiagnostic: targets matching '/src/offscreen/' = ${offscreenCandidates.length}\n`);
|
||||
for (const candidate of offscreenCandidates) {
|
||||
process.stdout.write(
|
||||
` CANDIDATE: type=${candidate.type()} url=${candidate.url()}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
await handles.browser.close();
|
||||
|
||||
return offscreenCandidates.length > 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
const code = await main();
|
||||
process.exit(code);
|
||||
Reference in New Issue
Block a user