test(browser): consolidate CDP workflows and colocate parser coverage

This commit is contained in:
morluto committed 2026-10-08 04:03:24 +08:00
1 parent 1fd1989d34
commit 26829045d0
18 files changed
+104 -397

No files matched your search

@@ -115,29 +115,6 @@ describe("DOM metadata document base URLs", () => {
});
describe("empty form destinations with a document base URL", () => {
it.each([
{ nodeName: "FORM", attribute: "action" },
{ nodeName: "BUTTON", attribute: "formaction" },
{ nodeName: "INPUT", attribute: "formaction" },
])(
"resolves an empty $nodeName $attribute to the document",
({ nodeName, attribute }) => {
const pageUrl = `${documentUrl}?selected=fixture#section`;
const baseUrl = `${origin}/assets/`;
const value = snapshot(baseUrl, "/assets/", pageUrl, "");
value.strings[6] = nodeName;
value.strings[14] = attribute;
value.strings[15] = "";
const result = capture(value);
expect(result.urls[3]).toMatchObject({
attribute,
url: pageUrl,
destination_scope: "approved",
});
expect(result.urls[1]?.url).toBe(baseUrl);
},
);
it("keeps an empty form action approved when the document base is foreign", () => {
const foreign = "https://cdn.example.test/assets/";
const value = snapshot(foreign, foreign);
@@ -1,7 +1,7 @@
import { expect, it } from "vitest";
import { CdpCaptureEvents } from "../../../src/browser/CdpCaptureEvents.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { CdpCaptureEvents } from "./CdpCaptureEvents.js";
import { inspectWebPageInputSchema } from "../domain/browserObservation.js";
it("preserves an already captured response shape without marking it unavailable", () => {
const origin = "http://127.0.0.1:43127";
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { safeResponseMetadata } from "../../../src/browser/CdpSafeMetadata.js";
import { safeResponseMetadata } from "./CdpSafeMetadata.js";
const origin = "https://app.example.test";
@@ -1,39 +0,0 @@
import { expect, it } from "vitest";
import type { Page } from "playwright-core";
import { BrowserScenarioSecrets } from "./BrowserScenarioSecrets.js";
import { performPlaywrightScenarioAction } from "./PlaywrightScenarioActions.js";
import { browserScenarioSchema } from "../domain/browserScenario.js";
it("uses exact accessible-name matching without a caller confirmation field", async () => {
const scenario = browserScenarioSchema.parse({
browser: { mode: "launch", executable_path: "/opt/chromium/chrome" },
start_url: { url: "https://app.example.test/" },
actions: [
{
step_id: "submit",
action: "click",
locator: { kind: "role", role: "button", name: "Submit" },
},
],
});
const calls: unknown[][] = [];
const page = {
getByRole: (...args: unknown[]) => {
calls.push(args);
return { click: async () => undefined };
},
} as unknown as Page;
const secrets = BrowserScenarioSecrets.resolve(scenario, {});
if (secrets === undefined) throw new Error("No secret declarations expected");
const action = scenario.actions.at(0);
if (action === undefined) throw new Error("Expected one scenario action");
await performPlaywrightScenarioAction({
page,
action,
secrets,
});
expect(calls).toEqual([["button", { name: "Submit", exact: true }]]);
});
@@ -1,59 +0,0 @@
import { describe, expect, it } from "vitest";
import { listBrowserTargetsInputSchema } from "../../../src/domain/browserObservation.js";
import { browserCaptureComparisonInputSchema } from "../../../src/domain/browserCaptureComparison.js";
import { compareWebScreenshotsInputSchema } from "../../../src/domain/webScreenshot.js";
import { BROWSER_TOOL_CONTRACTS } from "../../../src/contracts/browserToolContracts.js";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import {
compareWebCaptureEvidence,
compareWebScreenshotEvidence,
listBrowserTargets,
} from "../../../src/application/BrowserObservationService.js";
describe("browser observation service prerequisites", () => {
it("reports a missing provider without requiring a permission grant", async () => {
const endpoint = "http://127.0.0.1:9222";
const origin = "http://[::1]:3000";
const result = await listBrowserTargets(
undefined,
listBrowserTargetsInputSchema.parse({
cdp_endpoint: endpoint,
allowed_origins: [origin],
}),
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCapabilityUnavailableError",
},
});
});
it("compares supplied scenarios and PNG artifacts without a CDP endpoint", async () => {
const browser = new CdpBrowserProvider();
const captures = await compareWebCaptureEvidence(
browser,
browserCaptureComparisonInputSchema.parse(
example("compare_web_captures"),
),
);
const screenshots = await compareWebScreenshotEvidence(
browser,
compareWebScreenshotsInputSchema.parse(
example("compare_web_screenshots"),
),
);
for (const result of [captures, screenshots]) {
expect(result.ok).toBe(true);
if (!result.ok) throw result.error;
expect(result.value.parameters).not.toHaveProperty("cdp_endpoint");
}
});
});
const example = (name: string) => {
const input = BROWSER_TOOL_CONTRACTS.find((tool) => tool.name === name)
?.examples[0]?.input;
if (input === undefined) throw new Error(`Missing example for ${name}`);
return input;
};
@@ -1,4 +1,4 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import {
@@ -6,9 +6,9 @@ import {
listBrowserTargetsInputSchema,
} from "../../../src/domain/browserObservation.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider: discovery authorization 1", () => {
describe("CdpBrowserProvider: discovery authorization 1", () => {
it("discovers pages and captures a selected target without an origin list", async () => {
const browser = await startFakeCdpBrowser();
trackBrowser(browser);
@@ -158,7 +158,7 @@ describeBrowser("CdpBrowserProvider: discovery authorization 1", () => {
});
});
describeBrowser("CdpBrowserProvider: discovery authorization 2", () => {
describe("CdpBrowserProvider: discovery authorization 2", () => {
it("rejects empty browser attachment session identifiers", async () => {
const browser = await startFakeCdpBrowser({ invalidAttachedSession: true });
trackBrowser(browser);
@@ -1,4 +1,4 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
@@ -6,20 +6,9 @@ import { analyzeWebBundleInputSchema } from "../../../src/domain/webBundleAnalys
import { discoverWebMcpToolsInputSchema } from "../../../src/domain/webMcpDiscovery.js";
import { captureWebScreenshotInputSchema } from "../../../src/domain/webScreenshot.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider: document script 1", () => {
it("rejects unknown WebMCP discovery fields", () => {
expect(
discoverWebMcpToolsInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://example.test"],
target_id: "target",
unknown_field: true,
}).success,
).toBe(false);
});
import { trackBrowser } from "./cdpBrowserProvider.support.js";
describe("CdpBrowserProvider: approved accessibility and source capture", () => {
it("captures approved accessibility text inline", async () => {
const browser = await startFakeCdpBrowser();
trackBrowser(browser);
@@ -120,7 +109,7 @@ describeBrowser("CdpBrowserProvider: document script 1", () => {
});
});
describeBrowser("CdpBrowserProvider: transient WebMCP frames", () => {
describe("CdpBrowserProvider: transient WebMCP frames", () => {
it("drops stale child tools and reports partial coverage when a blank commit remains", async () => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
@@ -205,7 +194,7 @@ describeBrowser("CdpBrowserProvider: transient WebMCP frames", () => {
});
});
describeBrowser("CdpBrowserProvider: document script 2", () => {
describe("CdpBrowserProvider: WebMCP discovery and completeness", () => {
it("discovers untrusted WebMCP declarations without registering or invoking them", async () => {
const browser = await startFakeCdpBrowser({ webMcpTools: true });
trackBrowser(browser);
@@ -360,7 +349,7 @@ describeBrowser("CdpBrowserProvider: document script 2", () => {
});
});
describeBrowser("CdpBrowserProvider WebMCP inventory completeness", () => {
describe("CdpBrowserProvider WebMCP inventory completeness", () => {
it("retains every field in a large declared WebMCP input schema", async () => {
const propertyCount = 5_001;
const browser = await startFakeCdpBrowser({
@@ -392,7 +381,7 @@ describeBrowser("CdpBrowserProvider WebMCP inventory completeness", () => {
});
});
describeBrowser("CdpBrowserProvider: document script 3", () => {
describe("CdpBrowserProvider: frame scope and screenshots", () => {
it("removes WebMCP declarations after their child frame leaves scope", async () => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
@@ -474,7 +463,7 @@ describeBrowser("CdpBrowserProvider: document script 3", () => {
});
});
describeBrowser("CdpBrowserProvider: document script 4", () => {
describe("CdpBrowserProvider: complete passive inventories", () => {
it("returns every frame, resource, worker, accessibility, and storage inventory item", async () => {
const browser = await startFakeCdpBrowser({ extraCollections: true });
trackBrowser(browser);
@@ -501,7 +490,7 @@ describeBrowser("CdpBrowserProvider: document script 4", () => {
});
});
describeBrowser("CdpBrowserProvider: WebMCP registration owners", () => {
describe("CdpBrowserProvider: WebMCP registration owners", () => {
it.each(["retain", "remove-second"] as const)(
"keeps same-URL frame owners separate: %s",
async (mode) => {
@@ -1,4 +1,4 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import {
@@ -11,9 +11,9 @@ import {
startFakeCdpBrowser,
type FakeCdpBrowser,
} from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider: lifecycle failures 1", () => {
describe("CdpBrowserProvider: cancellation and session events", () => {
it("projects direct-session cancellation onto the requested operation", async () => {
const browser = await startFakeCdpBrowser({
pageScopedVersionWebSocket: true,
@@ -132,7 +132,7 @@ describeBrowser("CdpBrowserProvider: lifecycle failures 1", () => {
});
});
describeBrowser("CdpBrowserProvider: lifecycle failures 2", () => {
describe("CdpBrowserProvider: session termination and scope changes", () => {
it("ends a browser session when its flat target session detaches", async () => {
const browser = await startFakeCdpBrowser({
sessionTimeline: "target_detached",
@@ -243,7 +243,7 @@ describeBrowser("CdpBrowserProvider: lifecycle failures 2", () => {
});
});
describeBrowser("CdpBrowserProvider: lifecycle failures 3", () => {
describe("CdpBrowserProvider: protocol failures and cancellation cleanup", () => {
it("degrades optional domains but propagates protocol and payload failures", async () => {
const optional = await startFakeCdpBrowser({
unsupportedMethods: ["Accessibility.getFullAXTree"],
@@ -373,7 +373,7 @@ describeBrowser("CdpBrowserProvider: lifecycle failures 3", () => {
});
});
describeBrowser("CdpBrowserProvider: lifecycle failures 4", () => {
describe("CdpBrowserProvider: navigation commit and target identity", () => {
it("waits for navigation commit and rechecks the attached main-frame origin", async () => {
const transitioning = await startFakeCdpBrowser({
transitionalFrameReads: 2,
@@ -1,11 +1,11 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider: network 1", () => {
describe("CdpBrowserProvider: network 1", () => {
it("retains same-origin redirect hops on the one final request", async () => {
const browser = await startFakeCdpBrowser({ redirectWithinOrigin: true });
trackBrowser(browser);
@@ -1,10 +1,10 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import type { FakeOptions } from "../../fixtures/fakeCdpBrowserTypes.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
const inspectNetwork = async (options: FakeOptions) => {
const browser = await startFakeCdpBrowser(options);
@@ -22,7 +22,7 @@ const inspectNetwork = async (options: FakeOptions) => {
return { browser, inspection: result.value };
};
describeBrowser("CdpBrowserProvider: redirect errors", () => {
describe("CdpBrowserProvider: redirect errors", () => {
it.each([
{ label: "missing", url: undefined, reason: "invalid_protocol_value" },
{ label: "malformed", url: "http://%", reason: "invalid_protocol_value" },
@@ -1,4 +1,4 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import {
@@ -9,9 +9,9 @@ import {
startFakeCdpBrowser,
type FakeCdpBrowser,
} from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider: sensitive data 1", () => {
describe("CdpBrowserProvider: passive evidence redaction", () => {
it("captures bounded passive evidence without retaining sensitive values", async () => {
const browser = await startFakeCdpBrowser({
binaryWebSocketEvent: true,
@@ -158,7 +158,7 @@ describeBrowser("CdpBrowserProvider: sensitive data 1", () => {
});
});
describeBrowser("CdpBrowserProvider: page cookie scope", () => {
describe("CdpBrowserProvider: page cookie scope", () => {
it("uses the attached page URL for cookies and its origin for DOM storage", async () => {
const browser = await startFakeCdpBrowser();
trackBrowser(browser);
@@ -190,7 +190,7 @@ describeBrowser("CdpBrowserProvider: page cookie scope", () => {
});
});
describeBrowser("CdpBrowserProvider: complete storage fingerprints", () => {
describe("CdpBrowserProvider: complete storage fingerprints", () => {
it("fingerprints cache bodies above the former byte ceiling", async () => {
const body = "x".repeat(64 * 1_024 + 1);
const browser = await startFakeCdpBrowser({ cachedResponseBody: body });
@@ -217,7 +217,7 @@ describeBrowser("CdpBrowserProvider: complete storage fingerprints", () => {
});
});
describeBrowser("CdpBrowserProvider: sensitive data 2", () => {
describe("CdpBrowserProvider: explicitly requested text and payload shapes", () => {
it("captures requested console text verbatim and value-free payload shapes", async () => {
const browser = await startFakeCdpBrowser({
sensitiveShapes: true,
@@ -309,45 +309,7 @@ describeBrowser("CdpBrowserProvider: sensitive data 2", () => {
});
});
describeBrowser("CdpBrowserProvider: complete sensitive data", () => {
it("returns complete requested text and payload shapes verbatim", async () => {
const browser = await startFakeCdpBrowser({ sensitiveShapes: true });
trackBrowser(browser);
const request = inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
include_console_text: true,
include_json_body_shapes: true,
include_websocket_shapes: true,
});
const result = await new CdpBrowserProvider().inspectPage(request);
if (!result.ok) throw result.error;
expect(result.value.console.events[0]?.text_capture).toEqual({
status: "included",
values: [
{
argument_index: 0,
type: "string",
text: "authorization=Bearer console-secret",
},
{ argument_index: 1, type: "number", text: "42" },
],
retained_bytes:
Buffer.byteLength("authorization=Bearer console-secret") +
Buffer.byteLength("42"),
});
expect(result.value.network.requests[0]?.body_shapes.status).toBe(
"included",
);
expect(result.value.network.websocket_events[0]?.payload_shape).toEqual({
format: "json",
json_shape: expect.objectContaining({ root_type: "object" }),
});
});
describe("CdpBrowserProvider: malformed approved payloads", () => {
it("fails closed on malformed approved response and binary payload encodings", async () => {
const browser = await startFakeCdpBrowser({
invalidResponseBodyBase64: true,
@@ -1,4 +1,4 @@
import { afterEach, describe } from "vitest";
import { afterEach } from "vitest";
import type { FakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
@@ -11,5 +11,3 @@ afterEach(async () => {
export const trackBrowser = (browser: FakeCdpBrowser): void => {
browsers.push(browser);
};
export const describeBrowser = describe;
@@ -1,29 +1,11 @@
import { expect, it } from "vitest";
import { describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { observeWebSessionInputSchema } from "../../../src/domain/browserSession.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { describeBrowser, trackBrowser } from "./cdpBrowserProvider.support.js";
describeBrowser("CdpBrowserProvider navigation timeline", () => {
it("accepts caller-selected windows and rejects the removed event-count option", () => {
const accepted = observeWebSessionInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
observation_ms: 120_000,
});
expect(accepted.success).toBe(true);
expect(
observeWebSessionInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://app.example.test"],
target_id: "page-1",
max_timeline_events: 1,
}).success,
).toBe(false);
});
import { trackBrowser } from "./cdpBrowserProvider.support.js";
describe("CdpBrowserProvider navigation timeline", () => {
it("returns every event beyond the former default collection cap", async () => {
const additionalEvents = 2_001;
const browser = await startFakeCdpBrowser({
@@ -1,52 +1,61 @@
import { expect, it } from "vitest";
import { CdpCaptureCompleteness } from "../../../src/browser/CdpCaptureCompleteness.js";
import {
captureFrames,
captureResources,
} from "../../../src/browser/CdpCaptureDocuments.js";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
it("reports malformed child frame entries instead of silently completing inventory", () => {
const completeness = new CdpCaptureCompleteness();
const captured = captureFrames(
{
frameTree: {
frame: {
id: "main",
url: "https://example.test/",
},
childFrames: [
null,
{ frame: { id: "child", url: "https://example.test/child" } },
],
},
},
new Set(["https://example.test"]),
undefined,
completeness,
it("reports malformed child frame entries through the CDP boundary", async () => {
const browser = await startFakeCdpBrowser({
commandResult: (command, origin) =>
command.method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "main", url: `${origin}/` },
childFrames: [
null,
{ frame: { id: "child", url: `${origin}/child` } },
],
},
}
: undefined,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
expect(captured.items.map(({ frame_id }) => frame_id)).toEqual([
if (!result.ok) throw result.error;
expect(result.value.frames.map(({ frame_id }) => frame_id)).toEqual([
"main",
"child",
]);
expect(completeness.snapshot().unavailable_sections).toContain("frames");
expect(result.value.completeness.unavailable_sections).toContain("frames");
});
it("reports missing top-level frame trees for frame and resource inventories", () => {
const completeness = new CdpCaptureCompleteness();
expect(
captureFrames(
{},
new Set(["https://example.test"]),
undefined,
completeness,
).items,
).toEqual([]);
expect(
captureResources({}, new Set(["https://example.test"]), completeness).items,
).toEqual([]);
expect(completeness.snapshot().unavailable_sections).toEqual(
expect.arrayContaining(["frames", "resources"]),
it("marks a missing resource frame tree incomplete through CDP", async () => {
const browser = await startFakeCdpBrowser({
commandResult: (command) =>
command.method === "Page.getResourceTree" ? {} : undefined,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.resources).toEqual([]);
expect(result.value.completeness.unavailable_sections).toContain("resources");
});
@@ -1,65 +1,8 @@
import { expect, it, onTestFinished } from "vitest";
import { CdpCaptureEvents } from "../../../src/browser/CdpCaptureEvents.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import type { CdpEvent } from "../../../src/browser/CdpConnection.js";
const events = (): CdpCaptureEvents =>
new CdpCaptureEvents(
inspectWebPageInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: ["https://example.test"],
target_id: "page-1",
}),
new Set(["https://example.test"]),
);
const frameNavigated = (url: string, loaderId: string): CdpEvent => ({
method: "Page.frameNavigated",
params: {
frame: { id: "main", url, loaderId },
},
});
it("ignores a repeated frameNavigated for the document already committed", () => {
const state = events();
state.beginAuthorizedFrame("main");
state.ingest(frameNavigated("https://example.test/page", "loader-1"));
expect(state.navigationDuringCapture).toBe(true);
state.navigationDuringCapture = false;
// A re-fire with the same URL and loader is not a navigation.
state.ingest(frameNavigated("https://example.test/page", "loader-1"));
expect(state.navigationDuringCapture).toBe(false);
});
it("still reports a genuine change of document", () => {
const state = events();
state.beginAuthorizedFrame("main");
state.ingest(frameNavigated("https://example.test/page", "loader-1"));
state.navigationDuringCapture = false;
state.ingest(frameNavigated("https://example.test/other", "loader-2"));
expect(state.navigationDuringCapture).toBe(true);
});
it("does not forget a navigation that happened during the observation window", () => {
const state = events();
state.beginAuthorizedFrame("main");
state.ingest(frameNavigated("https://example.test/page", "loader-1"));
// The final capture re-attaches to the resolved frame; clearing the flag here
// used to report a clean capture while carrying pre-navigation state.
state.beginFinalCapture("main");
expect(state.navigationDuringCapture).toBe(true);
});
it("keeps a settled observation window clean", () => {
const state = events();
state.beginAuthorizedFrame("main");
state.beginFinalCapture("main");
expect(state.navigationDuringCapture).toBe(false);
});
it("captures a page without aborting on a duplicate frame event", async () => {
const browser = await startFakeCdpBrowser({ sessionTimeline: "same_origin" });
@@ -199,25 +199,6 @@ describe("CdpElectronProvider target selection", () => {
});
});
describe("Electron request schemas", () => {
it("rejects unknown request fields", () => {
expect(
listElectronTargetsInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9223",
unknown_field: true,
}).success,
).toBe(false);
expect(
inspectElectronPageInputSchema.safeParse({
cdp_endpoint: "http://127.0.0.1:9223",
target_id: "page-1",
observation_ms: 0,
unknown_field: true,
}).success,
).toBe(false);
});
});
describe("CdpElectronProvider capture", () => {
it("captures Electron script source only after separate approval", async () => {
const root = await electronFixture();
@@ -122,8 +122,11 @@ it("retains active hook lifecycle and IPC evidence without a count ceiling", asy
const script = join(root, "exercise.cjs");
await writeFile(script, activeCaptureSource);
const hook = join(process.cwd(), "scripts/electron-active-hook.cjs");
const output = await runNode(hook, script, root, root);
const result = JSON.parse(output.trim().split("\n").at(-1) ?? "null") as {
const execution = await runNode(hook, script, root, root);
expect(execution.code).toBe(0);
const result = JSON.parse(
execution.stdout.trim().split("\n").at(-1) ?? "null",
) as {
readonly default_popup_decision: { readonly action: string };
readonly popup_decision: { readonly action: string };
readonly snapshot: {
@@ -181,7 +184,7 @@ it("preserves fatal process termination after recording uncaught exceptions", as
const script = join(root, "fatal.cjs");
await writeFile(script, 'throw new Error("fatal-hook-secret");\n');
const hook = join(process.cwd(), "scripts/electron-active-hook.cjs");
const result = await runNodeStatus(hook, script, root, root);
const result = await runNode(hook, script, root, root);
expect(result.code).not.toBe(0);
expect(result.stderr).toContain("fatal-hook-secret");
@@ -192,7 +195,11 @@ const runNode = (
script: string,
moduleRoot: string,
cwd: string,
): Promise<string> =>
): Promise<{
readonly code: number | null;
readonly stdout: string;
readonly stderr: string;
}> =>
new Promise((resolve, reject) => {
const child = spawn(process.execPath, ["-r", hook, script], {
cwd,
@@ -207,44 +214,11 @@ const runNode = (
child.stdout.on("data", (chunk: Buffer) => stdout.push(chunk));
child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk));
child.once("error", reject);
child.once("close", (code) => {
if (code === 0) {
const text = Buffer.concat(stdout).toString("utf8");
if (text.length > 0) resolve(text);
else
reject(
new Error(
`hook fixture produced no output: ${Buffer.concat(stderr).toString("utf8")}`,
),
);
} else
reject(
new Error(
`hook fixture exited with ${String(code)}: ${Buffer.concat(stderr).toString("utf8")}`,
),
);
});
});
const runNodeStatus = (
hook: string,
script: string,
moduleRoot: string,
cwd: string,
): Promise<{ readonly code: number | null; readonly stderr: string }> =>
new Promise((resolve, reject) => {
const child = spawn(process.execPath, ["-r", hook, script], {
cwd,
env: {
...process.env,
NODE_PATH: moduleRoot,
},
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: Buffer[] = [];
child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk));
child.once("error", reject);
child.once("close", (code) =>
resolve({ code, stderr: Buffer.concat(stderr).toString("utf8") }),
resolve({
code,
stdout: Buffer.concat(stdout).toString("utf8"),
stderr: Buffer.concat(stderr).toString("utf8"),
}),
);
});
@@ -22,16 +22,6 @@ afterEach(async () => {
});
describe("web capture diff", () => {
it("rejects caller limits that would omit difference records", () => {
expect(
compareWebCapturesInputSchema.safeParse({
before: {},
after: {},
max_changes: 1,
}).success,
).toBe(false);
});
it("reports stable observed changes while preserving unknown dimensions", async () => {
const browser = await startFakeCdpBrowser();
browsers.push(browser);