Files
reverse-engineering-anything/tests/webCaptureDiff.test.ts
T

231 lines
7.5 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import { CdpBrowserProvider } from "../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../src/domain/browserObservation.js";
import {
compareWebCaptures,
compareWebCapturesInputSchema,
} from "../src/domain/webCaptureDiff.js";
import {
startFakeCdpBrowser,
type FakeCdpBrowser,
} from "./fixtures/fakeCdpBrowser.js";
describe("web capture diff", () => {
const browsers: FakeCdpBrowser[] = [];
afterEach(async () => {
await Promise.all(
browsers.splice(0).map(async (browser) => browser.close()),
);
});
it("reports stable observed changes while preserving unknown dimensions", async () => {
const browser = await startFakeCdpBrowser();
browsers.push(browser);
const captured = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
approved: true,
observation_ms: 0,
}),
);
if (!captured.ok) throw captured.error;
const after = structuredClone(captured.value);
after.scripts.items = [];
const request = after.network.requests[0];
if (request !== undefined) request.status = 204;
const result = compareWebCaptures(
compareWebCapturesInputSchema.parse({
before: { inspection: captured.value },
after: { inspection: after },
}),
);
expect(result.overall_status).toBe("changed");
expect(result.dimensions.scripts).toMatchObject({
status: "changed",
total_changes: 1,
changes: [expect.objectContaining({ change: "removed" })],
});
expect(result.dimensions.network).toMatchObject({
status: "changed",
changes: [expect.objectContaining({ change: "modified" })],
});
expect(result.dimensions.webmcp).toMatchObject({
status: "unknown",
total_changes: 0,
});
});
it("does not claim unchanged when a relevant section is incomplete", async () => {
const browser = await startFakeCdpBrowser();
browsers.push(browser);
const captured = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
approved: true,
observation_ms: 0,
}),
);
if (!captured.ok) throw captured.error;
const incomplete = structuredClone(captured.value);
incomplete.completeness.truncated_sections.push("dom");
const result = compareWebCaptures(
compareWebCapturesInputSchema.parse({
before: { inspection: incomplete },
after: { inspection: captured.value },
}),
);
expect(result.dimensions.dom_structure.status).toBe("unknown");
expect(result.dimensions.dom_structure.reason).toContain("incomplete");
});
it("compares accessibility, storage, console errors, routes, and lifecycle", async () => {
const browser = await startFakeCdpBrowser();
browsers.push(browser);
const captured = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
approved: true,
observation_ms: 0,
}),
);
if (!captured.ok) throw captured.error;
const after = structuredClone(captured.value);
const accessibilityNode = after.accessibility.nodes[0];
if (accessibilityNode !== undefined) accessibilityNode.role = "dialog";
after.storage.local_storage_keys.push("new-key");
const consoleEvent = after.console.events[0];
if (consoleEvent !== undefined) consoleEvent.type = "error";
after.target.url = `${browser.allowedOrigin}/changed`;
after.workers.push({
target_id: "worker-added",
type: "worker",
url: `${browser.allowedOrigin}/worker.js`,
origin: browser.allowedOrigin,
attached: true,
opener_target_id: null,
parent_frame_id: null,
});
markSectionsComplete(captured.value, [
"accessibility",
"storage",
"storage_keys",
"console_events",
"frames",
"workers",
]);
markSectionsComplete(after, [
"accessibility",
"storage",
"storage_keys",
"console_events",
"frames",
"workers",
]);
const result = compareWebCaptures(
compareWebCapturesInputSchema.parse({
before: { inspection: captured.value },
after: { inspection: after },
}),
);
expect(result.dimensions.accessibility.status).toBe("changed");
expect(result.dimensions.storage.status).toBe("changed");
expect(result.dimensions.console_errors.status).toBe("changed");
expect(result.dimensions.current_route.status).toBe("changed");
expect(result.dimensions.lifecycle.status).toBe("changed");
});
it("ignores transient request IDs and capture-approval state", async () => {
const browser = await startFakeCdpBrowser();
browsers.push(browser);
const captured = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
approved: true,
observation_ms: 0,
}),
);
if (!captured.ok) throw captured.error;
const after = structuredClone(captured.value);
const request = after.network.requests[0];
if (request !== undefined) request.request_id = "different-cdp-request-id";
const response = after.metadata.responses[0];
if (response !== undefined)
response.request_id = "different-cdp-request-id";
const script = after.scripts.items[0];
if (script !== undefined && !script.source.included)
script.source.reason = "different approval explanation";
const accessibilityNode = after.accessibility.nodes[0];
if (accessibilityNode !== undefined)
accessibilityNode.node_id = "different-cdp-ax-node-id";
const worker = after.workers[0];
if (worker !== undefined) worker.target_id = "different-worker-target-id";
markSectionsComplete(captured.value, [
"scripts",
"metadata",
"accessibility",
"frames",
"workers",
]);
markSectionsComplete(after, [
"scripts",
"metadata",
"accessibility",
"frames",
"workers",
]);
const result = compareWebCaptures(
compareWebCapturesInputSchema.parse({
before: { inspection: captured.value },
after: { inspection: after },
}),
);
expect(result.dimensions.scripts.status).toBe("unchanged");
expect(result.dimensions.metadata.status).toBe("unchanged");
expect(result.dimensions.accessibility.status).toBe("unchanged");
expect(result.dimensions.lifecycle.status).toBe("unchanged");
expect(result.dimensions.network.status).toBe("unknown");
expect(result.dimensions.network.total_changes).toBe(0);
});
});
const markSectionsComplete = (
inspection: {
completeness: {
policy_filtered_sections: string[];
attach_limited_sections: string[];
truncated_sections: string[];
unavailable_sections: string[];
};
},
sections: readonly string[],
): void => {
const completed = new Set(sections);
for (const key of [
"policy_filtered_sections",
"attach_limited_sections",
"truncated_sections",
"unavailable_sections",
] as const)
inspection.completeness[key] = inspection.completeness[key].filter(
(section) => !completed.has(section),
);
};