Files
reverse-engineering-anything/tests/boundary/cli/domValueLengthUnits.test.ts
T
gensokyo-dev 50b2ab137f fix(browser): preserve DOM length units in captures and comparisons (#7971)
* fix(browser): declare DOM node value length units

* fix(browser): preserve unknown DOM length comparisons
2026-10-29 11:27:01 +08:00

227 lines
8.2 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { expect } from "vitest";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { webPageInspectionSchema } from "../../../src/domain/browserObservationSchemas.js";
import { electronPageInspectionSchema } from "../../../src/domain/javascript/electronObservation.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
import { passiveNetworkCapture } from "../../fixtures/passiveNetworkCapture.js";
import { webCaptureDiffSchema } from "../../../src/domain/webCaptureDiffSchemas.js";
for (const electron of [false, true])
cliTest(
`declares ${electron ? "Electron UTF-8" : "web UTF-16"} DOM value lengths through CLI and MCP without reinterpreting historical captures`,
async ({ cli, onTestFinished }) => {
const root = await createTestTempDirectory("rea-dom-value-units-");
const path = join(root, "index.html");
await writeFile(path, "<html/>");
const fileUrl = pathToFileURL(path).href;
const values = ["ASCII", "ééé😀", "漢字", "e\u0301"];
let missingStrings = false;
const browser = await startFakeCdpBrowser({
...(electron ? { electronFileUrl: fileUrl } : {}),
commandResult: ({ method }, origin) => {
if (method !== "DOMSnapshot.captureSnapshot") return undefined;
if (missingStrings) return { documents: [] };
return {
strings: [
electron ? fileUrl : `${origin}/app`,
"#document",
"#text",
...values,
],
documents: [
{
documentURL: 0,
nodes: {
nodeType: [9, 3, 3, 3, 3],
nodeName: [1, 2, 2, 2, 2],
nodeValue: [-1, 3, 4, 5, 6],
parentIndex: [-1, 0, 0, 0, 0],
attributes: [[], [], [], [], []],
},
},
],
};
},
});
onTestFinished(() => browser.close());
const command = electron ? "inspect-electron-page" : "inspect-web-page";
const target = electron ? "electron-page" : "allowed-page";
const input = {
cdp_endpoint: browser.endpoint,
target_id: target,
observation_ms: 0,
...(electron ? {} : { allowed_origins: [browser.allowedOrigin] }),
};
const result = await cli.run({
arguments: [
command,
browser.endpoint,
target,
"--observation-ms",
"0",
...(electron ? [] : ["--allowed-origins", browser.allowedOrigin]),
"--json",
],
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.stderr).toBe("");
const schema = electron
? electronPageInspectionSchema
: webPageInspectionSchema;
const check = (value: unknown) => {
const evidence = parseEvidence(value);
const report = schema.parse(evidence.normalized_result);
expect(report.dom).toMatchObject({
node_value_length_unit: electron ? "utf8-bytes" : "utf16-code-units",
total_nodes: 5,
});
expect(report.dom.nodes.map((node) => node.node_value_length)).toEqual([
0,
...(electron ? [5, 10, 6, 3] : [5, 5, 2, 2]),
]);
expect(report.dom.nodes.map((node) => node.index)).toEqual([
0, 1, 2, 3, 4,
]);
expect(report.dom.nodes[1]).not.toHaveProperty(
"node_value_length_unit",
);
expect(JSON.stringify(report.dom)).not.toContain("ééé😀");
const legacyDom = {
total_nodes: report.dom.total_nodes,
nodes: report.dom.nodes,
};
expect(schema.parse({ ...report, dom: legacyDom }).dom).toEqual(
legacyDom,
);
expect(
schema.safeParse({
...report,
dom: { ...legacyDom, node_value_length_unit: "characters" },
}).success,
).toBe(false);
};
check(result.json);
const call = await cli.sharedMcpCall!();
const mcp = await call(command.replaceAll("-", "_"), input);
expect(mcp.isError).not.toBe(true);
check(mcp.structuredContent);
missingStrings = true;
const partial = await call(command.replaceAll("-", "_"), input);
expect(partial.isError).not.toBe(true);
const report = schema.parse(
parseEvidence(partial.structuredContent).normalized_result,
);
expect(report.dom).toEqual({
node_value_length_unit: electron ? "utf8-bytes" : "utf16-code-units",
total_nodes: 0,
nodes: [],
});
expect(report.completeness.excluded).toEqual(
expect.arrayContaining([
expect.objectContaining({
section: "dom",
reason: "invalid_protocol_value",
}),
]),
);
},
);
cliTest(
"compares only commensurable DOM lengths while retaining structural changes through CLI and MCP",
async ({ cli }) => {
const call = await cli.sharedMcpCall!();
for (const [beforeUnit, afterUnit, length, variation, status] of [
["utf16-code-units", "utf16-code-units", 5, "none", "unchanged"],
["utf8-bytes", "utf8-bytes", 10, "none", "changed"],
[undefined, undefined, 5, "none", "unknown"],
[undefined, "utf16-code-units", 10, "none", "unknown"],
["utf16-code-units", "utf8-bytes", 5, "none", "unknown"],
["utf16-code-units", "utf8-bytes", 10, "none", "unknown"],
[undefined, undefined, 5, "node", "changed"],
["utf16-code-units", "utf8-bytes", 10, "frame", "changed"],
[undefined, undefined, 5, "empty", "unchanged"],
] as const) {
const before = passiveNetworkCapture();
before.dom = {
total_nodes: 2,
...(beforeUnit === undefined
? {}
: { node_value_length_unit: beforeUnit }),
nodes: [
{
index: 0,
parent_index: -1,
node_type: 9,
node_name: "#document",
node_value_length: 0,
attribute_names: [],
},
{
index: 1,
parent_index: 0,
node_type: 3,
node_name: "#text",
node_value_length: 5,
attribute_names: [],
},
],
};
const after = structuredClone(before);
if (afterUnit === undefined) delete after.dom.node_value_length_unit;
else after.dom.node_value_length_unit = afterUnit;
after.dom.nodes[1]!.node_value_length = length;
if (variation === "node") {
after.dom.nodes[1]!.node_type = 8;
after.dom.nodes[1]!.node_name = "#comment";
}
if (variation === "frame")
after.frames.push({
frame_id: "frame",
parent_frame_id: null,
url: "https://app.example.test/other",
origin: "https://app.example.test",
});
if (variation === "empty") {
before.dom = { total_nodes: 0, nodes: [] };
after.dom = { total_nodes: 0, nodes: [] };
}
const result = await cli.sharedCliRun!({
arguments: [
"compare-web-captures",
JSON.stringify({ inspection: before }),
JSON.stringify({ inspection: after }),
"--json",
],
});
expect(result.exitCode, result.stderr).toBe(0);
const mcp = await call("compare_web_captures", {
before: { inspection: after },
after: { inspection: before },
});
expect(mcp.isError).not.toBe(true);
const label = JSON.stringify({
beforeUnit,
afterUnit,
length,
variation,
});
for (const output of [result.json, mcp.structuredContent]) {
const diff = webCaptureDiffSchema.parse(
parseEvidence(output).normalized_result,
);
expect(diff.dimensions.dom_structure.status, label).toBe(status);
if (status === "unknown")
expect(diff.dimensions.dom_structure.reason).toContain("unit");
}
}
},
);