Files
rea/tests/boundary/browser/cdpAuthorizedMainFrameRetry.test.ts
T
Can 0154141304 fix(browser): preserve frame identity across captures (#4040)
* fix(browser): preserve capture evidence fidelity

* fix(browser): align frame identity comparisons

* fix(browser): retain historical frame evidence

* fix(electron): retain same-document script owners

* fix(electron): bind frame proof to tree reads

* fix(browser): retain malformed frame scope

* fix(browser): retain WebMCP event scope

* fix(browser): retain malformed timeline identity

* fix(browser): unify frame identity and scope

* fix(electron): prove enclosing frame ancestry

* fix(electron): separate ancestry from file policy

* fix(browser): preserve transient fragment semantics

* fix(browser): retry uncertain initial frame scope

* fix(browser): preserve runtime frame identity

* refactor(browser): separate independent capture fixes

* fix(electron): detect tree-first document replacement

* fix(electron): retain same-document tree authority

---------

Co-authored-by: eco-null <eco-null@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-23 17:19:35 +08:00

205 lines
6.1 KiB
TypeScript

import { expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { CdpWebRuntimeProvider } from "../../../src/browser/execution/CdpWebRuntimeProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { observeWebSessionInputSchema } from "../../../src/domain/browserSession.js";
import { observeWebExecutionInputSchema } from "../../../src/domain/webExecution.js";
import { startRuntimeBrowser } from "../../fixtures/webRuntime.js";
import type { FakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
const operations = ["inspect", "session", "runtime"] as const;
type Operation = (typeof operations)[number];
const run = async (
browser: FakeCdpBrowser,
operation: Operation,
signal?: AbortSignal,
) => {
const input = {
cdp_endpoint: browser.endpoint,
target_id: "allowed-page",
allowed_origins: [browser.allowedOrigin],
observation_ms: 1,
};
const options = signal === undefined ? {} : { signal };
switch (operation) {
case "inspect":
return new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse(input),
options,
);
case "session":
return new CdpBrowserProvider().observeSession(
observeWebSessionInputSchema.parse(input),
options,
);
case "runtime":
return new CdpWebRuntimeProvider().observeExecution(
observeWebExecutionInputSchema.parse(input),
options,
);
}
};
it.each(
operations.flatMap((operation) =>
["about:blank", "about:srcdoc", "file:///local.html", "not a URL"].flatMap(
(url) =>
[7, "bad-fragment"].map((urlFragment) => ({
operation,
url,
urlFragment,
})),
),
),
)(
"retries uncertain initial $url/$urlFragment before $operation authorizes",
async ({ operation, url, urlFragment }) => {
let reads = 0;
const browser = await startRuntimeBrowser({
commandResult: ({ method }) => {
if (method !== "Page.getFrameTree" || ++reads > 2) return undefined;
return {
frameTree: { frame: { id: "runtime-main", url, urlFragment } },
};
},
});
trackBrowser(browser);
const result = await run(browser, operation);
expect(result.ok).toBe(true);
expect(reads).toBeGreaterThan(2);
},
);
it.each(
operations.flatMap((operation) =>
[false, true].flatMap((foreign) =>
[7, "bad-fragment"].map((urlFragment) => ({
operation,
foreign,
urlFragment,
})),
),
),
)(
"rejects known $foreign scope with malformed $urlFragment immediately for $operation",
async ({ operation, foreign, urlFragment }) => {
let reads = 0;
const browser = await startRuntimeBrowser({
commandResult: ({ method }, origin) => {
if (method !== "Page.getFrameTree") return undefined;
reads += 1;
return {
frameTree: {
frame: {
id: "runtime-main",
url: foreign ? "https://foreign.example/page" : `${origin}/app`,
urlFragment,
},
},
};
},
});
trackBrowser(browser);
expect(await run(browser, operation)).toMatchObject({
ok: false,
error: { reason: foreign ? "target_not_allowed" : "protocol_error" },
});
expect(reads).toBe(1);
},
);
it("bounds unsupported malformed frame retries without authorizing execution", async () => {
let reads = 0;
const browser = await startRuntimeBrowser({
commandResult: ({ method }) => {
if (method !== "Page.getFrameTree") return undefined;
reads += 1;
return {
frameTree: {
frame: { id: "runtime-main", url: "about:blank", urlFragment: 7 },
},
};
},
});
trackBrowser(browser);
expect(await run(browser, "runtime")).toMatchObject({
ok: false,
error: { reason: "target_not_allowed" },
});
expect(reads).toBe(80);
expect(browser.commands.map(({ method }) => method)).not.toContain(
"Debugger.enable",
);
});
it("preserves cancellation while an unsupported malformed frame awaits authorization", async () => {
const controller = new AbortController();
const browser = await startRuntimeBrowser({
commandResult: ({ method }) => {
if (method !== "Page.getFrameTree") return undefined;
setTimeout(() => controller.abort("caller stopped authorization"), 5);
return {
frameTree: {
frame: { id: "runtime-main", url: "about:blank", urlFragment: 7 },
},
};
},
});
trackBrowser(browser);
expect(await run(browser, "inspect", controller.signal)).toMatchObject({
ok: false,
error: {
_tag: "AnalysisCancelledError",
cause: "caller stopped authorization",
},
});
expect(
browser.commands.filter(({ method }) => method === "Page.getFrameTree"),
).toHaveLength(1);
});
// Ordinary startup uncertainty and known policy exits keep their existing semantics.
it.each(
operations.flatMap((operation) =>
["unsupported", "missing", "foreign"].map((kind) => ({ operation, kind })),
),
)(
"preserves $kind authorization behavior for $operation",
async ({ operation, kind }) => {
let reads = 0;
const browser = await startRuntimeBrowser({
commandResult: ({ method }) => {
if (method !== "Page.getFrameTree" || ++reads > 2) return undefined;
return {
frameTree: {
frame: {
id: "runtime-main",
...(kind === "missing"
? {}
: {
url:
kind === "foreign"
? "https://foreign.example/page"
: "about:blank",
}),
},
},
};
},
});
trackBrowser(browser);
const result = await run(browser, operation);
if (kind === "foreign") {
expect(result).toMatchObject({
ok: false,
error: { reason: "target_not_allowed" },
});
expect(reads).toBe(1);
} else {
expect(result.ok).toBe(true);
expect(reads).toBeGreaterThan(2);
}
},
);