Files
reverse-engineering-anything/tests/boundary/browser/cdpWebMcpCoverage.test.ts
T
morluto 09c2a2d241 fix(browser): preserve main-frame identity and scope refusal context
Read screenshot identity from the selected frame-tree root instead of a
filtered inventory that could substitute a child. Reuse the frame record
parser for initial and event identities, rejecting malformed roots before
capture while preserving known origin refusals.

Share explicit origin-scope diagnostics across CDP workflows without
changing policy or conflating startup uncertainty, document replacement,
worker protection, and Inspector restrictions. Document the recovery and
make screenshot navigation fixture ordering explicit.

Validation: 577 focused checks passed across 13 files; one platform-specific
invalid-byte-filename case skipped. Build, typecheck, lint, formatting,
generated documentation checks, README conformance, and discovery passed.
No full suite or live browser/provider execution.
2026-10-24 08:48:19 +08:00

193 lines
6.1 KiB
TypeScript

import { expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { discoverWebMcpToolsInputSchema } from "../../../src/domain/webMcpDiscovery.js";
import { digestCanonicalValue } from "../../../src/domain/canonicalDigest.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
it.each(["initial", "final"])(
"explains WebMCP %s current-document origin refusal",
async (stage) => {
const browser = await startFakeCdpBrowser({
webMcpTools: true,
commandResult: ({ method }, origin, frameReads) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: {
id: "frame-main",
url:
stage === "initial" || frameReads > 1
? "https://outside.example/page"
: origin + "/app",
},
},
}
: undefined,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (result.ok) throw new Error("Expected WebMCP origin refusal");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "target_unavailable",
message: expect.stringContaining("permitted HTTP(S) origin scope"),
remediation: { action: expect.stringContaining("allowed_origins") },
details: {
operation: "discover_webmcp_tools",
reason: "target_not_allowed",
},
});
expect(
browser.commands.some(({ method }) => method === "WebMCP.enable"),
).toBe(stage === "final");
},
);
it.each([false, true])(
"accepts optional WebMCP input schema (declared=%s)",
async (declared) => {
const browser = await startFakeCdpBrowser({
commandEvents: ({ method, sessionId }) =>
method === "WebMCP.enable"
? [
{
method: "WebMCP.toolsAdded",
sessionId,
params: {
tools: [
{
name: "parameterless",
frameId: "frame-main",
...(declared ? { inputSchema: {} } : {}),
},
],
},
},
]
: undefined,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.tools.items[0]).toMatchObject({
name: "parameterless",
input_schema: declared ? {} : null,
input_schema_sha256: declared ? digestCanonicalValue({}) : null,
});
expect(result.value.completeness.unavailable_sections).not.toContain(
"webmcp_tools",
);
expect(result.value.completeness.excluded).not.toContainEqual(
expect.objectContaining({
section: "webmcp_tools",
reason: "invalid_protocol_value",
}),
);
},
);
it("retains a valid parameterless registration while rejecting malformed annotations", async () => {
const browser = await startFakeCdpBrowser({
commandEvents: ({ method, sessionId }) =>
method === "WebMCP.enable"
? [
{
method: "WebMCP.toolsAdded",
sessionId,
params: {
tools: [
{
name: "parameterless",
frameId: "frame-main",
annotations: { readOnly: "yes" },
},
],
},
},
]
: undefined,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.tools.items).toHaveLength(1);
expect(result.value.completeness.unavailable_sections).toContain(
"webmcp_tools",
);
});
it("retains replay and live subframe tools without claiming replay covered older subframe registrations", async () => {
const browser = await startFakeCdpBrowser({
commandResult: ({ method }, origin) =>
method === "Page.getFrameTree"
? {
frameTree: {
frame: { id: "frame-main", url: origin + "/app" },
childFrames: [
{
frame: {
id: "child",
parentId: "frame-main",
url: origin + "/child",
},
},
],
},
}
: undefined,
commandEvents: ({ method, sessionId }) =>
method === "WebMCP.enable"
? [
{
method: "WebMCP.toolsAdded",
sessionId,
params: { tools: [{ name: "replayed", frameId: "frame-main" }] },
},
{
method: "WebMCP.toolsAdded",
sessionId,
params: { tools: [{ name: "live-child", frameId: "child" }] },
},
]
: undefined,
});
trackBrowser(browser);
const result = await new CdpBrowserProvider().discoverWebMcpTools(
discoverWebMcpToolsInputSchema.parse({
cdp_endpoint: browser.endpoint,
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
expect(result.value.tools.items.map(({ name }) => name).sort()).toEqual([
"live-child",
"replayed",
]);
expect(result.value.completeness.attach_limited_sections).toContain(
"webmcp_tools",
);
expect(result.value.limitations).toContainEqual(
expect.stringContaining("pre-existing subframe registrations"),
);
});