Milestone v1 (v2.0.0): Mokosh — Session Capture #1
@@ -3418,6 +3418,137 @@ async function assertA29(): Promise<AssertionResult> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Plan 03-02 — A30 (event-log verification; SPEC §10 #5) ────────
|
||||||
|
*
|
||||||
|
* A30 — REQ-user-event-log empirical: the production listeners at
|
||||||
|
* src/content/index.ts (setupClickLogging at line 61,
|
||||||
|
* setupInputLogging at line 77, setupNavigationLogging at line
|
||||||
|
* 99, setupErrorLogging at line 133, setupNetworkLogging at
|
||||||
|
* line 164) all fire on synthetic browser events dispatched
|
||||||
|
* on the harness page, producing UserEvent entries with each
|
||||||
|
* of the 5 type-values (click / input / navigation /
|
||||||
|
* js_error / network_error) in logs/events.json.
|
||||||
|
*
|
||||||
|
* Trigger strategy (all on the harness page; no new tabs opened):
|
||||||
|
* - click: programmatic .click() on #probe-submit
|
||||||
|
* - input: set #probe-email.value + dispatch Event('input', bubbles:true)
|
||||||
|
* - navigation: history.pushState (intercepted at src/content/index.ts:121)
|
||||||
|
* - js_error: window.dispatchEvent(new ErrorEvent('error', ...))
|
||||||
|
* - network_error: fetch(404-probe-url).catch(noop) — production
|
||||||
|
* fetch interception at src/content/index.ts:167 logs response.ok===false
|
||||||
|
*
|
||||||
|
* Page-side dispatches all 5 triggers + settles + SAVE. Host-side
|
||||||
|
* driveA30 JSZip-parses logs/events.json and asserts each of the 5
|
||||||
|
* UserEvent.type literal values is present.
|
||||||
|
*
|
||||||
|
* FORBIDDEN_HOOK_STRINGS impact: NONE. A30 rides production listeners
|
||||||
|
* + existing helpers. Tier-1 inventory stays at 12.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** SAVE_ARCHIVE dispatch timeout for A30 — matches A24/A25/A27/A29. */
|
||||||
|
const A30_SAVE_ARCHIVE_TIMEOUT_MS = 15_000;
|
||||||
|
/** Pre-SAVE segment-settle window (10s rotation + 1s slack). */
|
||||||
|
const A30_SEGMENT_SETTLE_MS = 11_000;
|
||||||
|
/** Settle between trigger dispatches and SAVE so event handlers complete. */
|
||||||
|
const A30_TRIGGER_SETTLE_MS = 500;
|
||||||
|
/** 404 probe URL — chrome.tabs perm grant is irrelevant; fetch happens
|
||||||
|
* from the harness page realm. example.com is RFC 2606 reserved +
|
||||||
|
* serves a 404 reliably for unknown paths under headless Chrome. */
|
||||||
|
const A30_404_PROBE_URL = 'https://example.com/this-path-does-not-exist-404-probe-a30';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A30 — Event-log empirical (SPEC §10 #5 / REQ-user-event-log).
|
||||||
|
*
|
||||||
|
* Dispatches 5 synthetic browser events that exercise each of the
|
||||||
|
* production listeners; runs setupFreshRecording so event-log
|
||||||
|
* cleanup hasn't dropped anything; settles a segment; SAVEs. Host-side
|
||||||
|
* driveA30 inspects logs/events.json from the produced zip and asserts
|
||||||
|
* each of the 5 UserEvent.type literal values appears at least once.
|
||||||
|
*
|
||||||
|
* @returns AssertionResult with 1 page-side check (SAVE ack); host-side
|
||||||
|
* driveA30 appends 5 UserEvent.type presence checks.
|
||||||
|
*/
|
||||||
|
async function assertA30(): Promise<AssertionResult> {
|
||||||
|
const result: AssertionResult = {
|
||||||
|
passed: false,
|
||||||
|
name: 'A30 — event log captures 5 UserEvent types (SPEC §10 #5 / REQ-user-event-log)',
|
||||||
|
checks: [],
|
||||||
|
diagnostics: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
diag(result, 'Step 1: setupFreshRecording (A30 owns its recording — clean event-log window)');
|
||||||
|
const setupResp = await setupFreshRecording();
|
||||||
|
if (!setupResp.ok) {
|
||||||
|
throw new Error(`setupFreshRecording failed: ${setupResp.error ?? '(no error)'}`);
|
||||||
|
}
|
||||||
|
diag(result, 'Step 1 OK — REC state established');
|
||||||
|
|
||||||
|
diag(result, `Step 2: settle ${A30_SEGMENT_SETTLE_MS}ms for first segment rotation`);
|
||||||
|
await new Promise((r) => setTimeout(r, A30_SEGMENT_SETTLE_MS));
|
||||||
|
|
||||||
|
diag(result, 'Step 3: click trigger — programmatic .click() on #probe-submit');
|
||||||
|
const submitBtn = document.querySelector<HTMLButtonElement>('#probe-submit');
|
||||||
|
if (submitBtn !== null) {
|
||||||
|
submitBtn.click();
|
||||||
|
} else {
|
||||||
|
diag(result, 'Step 3 WARN — #probe-submit missing; click trigger skipped');
|
||||||
|
}
|
||||||
|
|
||||||
|
diag(result, 'Step 4: input trigger — set #probe-email.value + dispatch input event');
|
||||||
|
const emailInput = document.querySelector<HTMLInputElement>('#probe-email');
|
||||||
|
if (emailInput !== null) {
|
||||||
|
emailInput.value = 'a30@probe.local';
|
||||||
|
emailInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
} else {
|
||||||
|
diag(result, 'Step 4 WARN — #probe-email missing; input trigger skipped');
|
||||||
|
}
|
||||||
|
|
||||||
|
diag(result, 'Step 5: navigation trigger — history.pushState (production wrapper at src/content/index.ts:121 intercepts)');
|
||||||
|
history.pushState({}, '', window.location.pathname + '#a30-probe');
|
||||||
|
|
||||||
|
diag(result, 'Step 6: js_error trigger — window.dispatchEvent(ErrorEvent("error"))');
|
||||||
|
window.dispatchEvent(new ErrorEvent('error', {
|
||||||
|
message: 'a30-probe-js-error',
|
||||||
|
filename: 'a30-probe.js',
|
||||||
|
lineno: 1,
|
||||||
|
colno: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
diag(result, `Step 7: network_error trigger — fetch(${A30_404_PROBE_URL}) (.catch noop)`);
|
||||||
|
try {
|
||||||
|
await fetch(A30_404_PROBE_URL);
|
||||||
|
} catch (fetchErr) {
|
||||||
|
diag(result, `Step 7 fetch threw (acceptable for network_error path): ${String(fetchErr)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
diag(result, `Step 8: settle ${A30_TRIGGER_SETTLE_MS}ms so async handlers (fetch.then) complete`);
|
||||||
|
await new Promise((r) => setTimeout(r, A30_TRIGGER_SETTLE_MS));
|
||||||
|
|
||||||
|
diag(result, 'Step 9: dispatch SAVE_ARCHIVE');
|
||||||
|
const ack = await sendMessageWithTimeout<{ success: boolean; error?: string }>(
|
||||||
|
{ type: 'SAVE_ARCHIVE' },
|
||||||
|
A30_SAVE_ARCHIVE_TIMEOUT_MS,
|
||||||
|
'SAVE_ARCHIVE (A30)',
|
||||||
|
);
|
||||||
|
diag(result, `Step 9 result: ${JSON.stringify(ack)}`);
|
||||||
|
|
||||||
|
result.checks.push({
|
||||||
|
name: 'A30.1: SAVE_ARCHIVE ack received with success=true',
|
||||||
|
expected: true,
|
||||||
|
actual: ack.success,
|
||||||
|
passed: ack.success === true,
|
||||||
|
});
|
||||||
|
|
||||||
|
result.passed = result.checks.every((c) => c.passed);
|
||||||
|
} catch (err) {
|
||||||
|
result.error = err instanceof Error ? err.message : String(err);
|
||||||
|
diag(result, `THREW: ${result.error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read `chrome.runtime.getManifest().version`. Used by the host-side
|
* Read `chrome.runtime.getManifest().version`. Used by the host-side
|
||||||
* orchestrator at startup to capture the expected version for A13's
|
* orchestrator at startup to capture the expected version for A13's
|
||||||
@@ -3471,6 +3602,8 @@ declare global {
|
|||||||
assertA28: () => Promise<AssertionResult>;
|
assertA28: () => Promise<AssertionResult>;
|
||||||
// Plan 03-01 — rrweb DOM verification (SPEC §10 #4)
|
// Plan 03-01 — rrweb DOM verification (SPEC §10 #4)
|
||||||
assertA29: () => Promise<AssertionResult>;
|
assertA29: () => Promise<AssertionResult>;
|
||||||
|
// Plan 03-02 — event-log verification (SPEC §10 #5)
|
||||||
|
assertA30: () => Promise<AssertionResult>;
|
||||||
getManifestVersion: () => Promise<string>;
|
getManifestVersion: () => Promise<string>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -3506,14 +3639,15 @@ window.__mokoshHarness = {
|
|||||||
assertA27,
|
assertA27,
|
||||||
assertA28,
|
assertA28,
|
||||||
assertA29,
|
assertA29,
|
||||||
|
assertA30,
|
||||||
getManifestVersion,
|
getManifestVersion,
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusEl = document.getElementById('status');
|
const statusEl = document.getElementById('status');
|
||||||
if (statusEl !== null) {
|
if (statusEl !== null) {
|
||||||
statusEl.textContent = 'Harness ready. window.__mokoshHarness.{assertA1..A29, getManifestVersion} available.';
|
statusEl.textContent = 'Harness ready. window.__mokoshHarness.{assertA1..A30, getManifestVersion} available.';
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[harness-page] ready — window.__mokoshHarness installed (Plan 01-13 Task 9: A1..A14 + Plan 01-10 Wave 3: A15..A17 + Plan 01-12 Wave 6: A18..A22 + Plan 01-14: A23 + Plan 02-04 Tasks 1-2: A24+A25 + Plan 02-04 Task 3: A26+A27+A28 + Plan 03-01: A29 + getManifestVersion)');
|
console.log('[harness-page] ready — window.__mokoshHarness installed (Plan 01-13 Task 9: A1..A14 + Plan 01-10 Wave 3: A15..A17 + Plan 01-12 Wave 6: A18..A22 + Plan 01-14: A23 + Plan 02-04 Tasks 1-2: A24+A25 + Plan 02-04 Task 3: A26+A27+A28 + Plan 03-01: A29 + Plan 03-02: A30 + getManifestVersion)');
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Reference in New Issue
Block a user