mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 11:56:12 +00:00
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.
193 lines
6.1 KiB
TypeScript
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"),
|
|
);
|
|
});
|