mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
0154141304
* 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>
169 lines
6.4 KiB
TypeScript
169 lines
6.4 KiB
TypeScript
import { BrowserObservationError } from "../../../src/domain/browserObservationError.js";
|
|
import { expect, it } from "vitest";
|
|
import { CdpWebRuntimeProvider } from "../../../src/browser/execution/CdpWebRuntimeProvider.js";
|
|
import { observeWebExecutionInputSchema } from "../../../src/domain/webExecution.js";
|
|
import { inspectWebEventListenersInputSchema } from "../../../src/domain/webEventListeners.js";
|
|
import { startRuntimeBrowser } from "../../fixtures/webRuntime.js";
|
|
import type { FakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
|
|
import { trackBrowser } from "./cdpBrowserProvider.support.js";
|
|
|
|
const actions = ["execution", "listeners"] as const;
|
|
const run = async (
|
|
browser: FakeCdpBrowser,
|
|
action: (typeof actions)[number],
|
|
) => {
|
|
const provider = new CdpWebRuntimeProvider();
|
|
const input = {
|
|
cdp_endpoint: browser.endpoint,
|
|
target_id: "allowed-page",
|
|
allowed_origins: [browser.allowedOrigin],
|
|
};
|
|
return action === "execution"
|
|
? provider.observeExecution(
|
|
observeWebExecutionInputSchema.parse({ ...input, observation_ms: 1 }),
|
|
)
|
|
: provider.inspectEventListeners(
|
|
inspectWebEventListenersInputSchema.parse({
|
|
...input,
|
|
selector: "#selected",
|
|
}),
|
|
);
|
|
};
|
|
|
|
it.each(
|
|
actions.flatMap((action) =>
|
|
[false, true].flatMap((loader) =>
|
|
["split", "clear", "inline", "credentials"].map((kind) => ({
|
|
action,
|
|
loader,
|
|
kind,
|
|
})),
|
|
),
|
|
),
|
|
)(
|
|
"preserves full $kind frame identity in $action evidence with loader=$loader",
|
|
async ({ action, loader, kind }) => {
|
|
const browser = await startRuntimeBrowser({
|
|
commandResult: ({ method }, origin) =>
|
|
method === "Page.getFrameTree"
|
|
? {
|
|
frameTree: {
|
|
frame: {
|
|
id: "runtime-main",
|
|
...(loader ? { loaderId: "document-1" } : {}),
|
|
url: `${kind === "credentials" ? origin.replace("://", "://user:password@") : origin}/app?caller=retained#old`,
|
|
...(kind === "inline"
|
|
? {}
|
|
: { urlFragment: kind === "clear" ? "" : "#details" }),
|
|
},
|
|
},
|
|
}
|
|
: undefined,
|
|
});
|
|
trackBrowser(browser);
|
|
const result = await run(browser, action);
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.target).toMatchObject({
|
|
initial_url: `${browser.allowedOrigin}/app?caller=retained${kind === "inline" ? "#old" : kind === "clear" ? "" : "#details"}`,
|
|
origin: browser.allowedOrigin,
|
|
loader_id: loader ? "document-1" : null,
|
|
});
|
|
},
|
|
);
|
|
|
|
const transitions = [
|
|
{ kind: "equivalent", loader: false, reason: null },
|
|
{ kind: "fragment-change", loader: false, reason: "target_changed" },
|
|
{ kind: "fragment-change", loader: true, reason: null },
|
|
{ kind: "malformed", loader: false, reason: "protocol_error" },
|
|
{ kind: "malformed", loader: true, reason: "protocol_error" },
|
|
{ kind: "foreign-malformed", loader: true, reason: "target_not_allowed" },
|
|
{ kind: "foreign-loader-change", loader: true, reason: "target_changed" },
|
|
{ kind: "foreign", loader: false, reason: "target_changed" },
|
|
{ kind: "foreign", loader: true, reason: "target_not_allowed" },
|
|
{ kind: "missing", loader: true, reason: "protocol_error" },
|
|
{ kind: "frame-change", loader: true, reason: "target_changed" },
|
|
{ kind: "loader-change", loader: true, reason: "target_changed" },
|
|
{ kind: "unsupported", loader: true, reason: "target_not_allowed" },
|
|
] as const;
|
|
it.each(
|
|
actions.flatMap((action) => transitions.map((item) => ({ action, ...item }))),
|
|
)(
|
|
"checks parsed $kind final identity in $action with loader=$loader",
|
|
async ({ action, kind, loader, reason }) => {
|
|
const browser = await startRuntimeBrowser({
|
|
commandResult: ({ method }, origin, reads) => {
|
|
if (method !== "Page.getFrameTree") return undefined;
|
|
const changed = reads > 1;
|
|
const url = `${origin}/app?caller=retained`;
|
|
return {
|
|
frameTree: {
|
|
frame: {
|
|
id:
|
|
changed && kind === "frame-change"
|
|
? "replacement"
|
|
: "runtime-main",
|
|
...(loader
|
|
? {
|
|
loaderId:
|
|
changed &&
|
|
(kind === "loader-change" ||
|
|
kind === "foreign-loader-change")
|
|
? "replacement"
|
|
: "document-1",
|
|
}
|
|
: {}),
|
|
...(changed && kind === "missing"
|
|
? {}
|
|
: {
|
|
url:
|
|
changed &&
|
|
(kind === "foreign-malformed" ||
|
|
kind === "foreign" ||
|
|
kind === "foreign-loader-change")
|
|
? "https://foreign.example/app"
|
|
: changed && kind === "unsupported"
|
|
? "about:blank"
|
|
: changed && kind === "equivalent"
|
|
? `${url}#details`
|
|
: url,
|
|
}),
|
|
...(changed && kind === "equivalent"
|
|
? {}
|
|
: {
|
|
urlFragment:
|
|
changed &&
|
|
(kind === "malformed" || kind === "foreign-malformed")
|
|
? 7
|
|
: changed && kind === "fragment-change"
|
|
? "#changed"
|
|
: "#details",
|
|
}),
|
|
},
|
|
},
|
|
};
|
|
},
|
|
});
|
|
trackBrowser(browser);
|
|
const result = await run(browser, action);
|
|
if (reason === null) {
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.target.initial_url).toBe(
|
|
`${browser.allowedOrigin}/app?caller=retained#details`,
|
|
);
|
|
} else {
|
|
expect(result).toMatchObject({ ok: false, error: { reason } });
|
|
if (kind === "foreign" && loader && !result.ok) {
|
|
if (!(result.error instanceof BrowserObservationError))
|
|
throw result.error;
|
|
expect(result.error.detail).toBe(
|
|
`Selected target allowed-page moved to an unselected origin during ${result.error.operation}.`,
|
|
);
|
|
}
|
|
}
|
|
const methods = browser.commands.map(({ method }) => method);
|
|
expect(methods).toContain("Debugger.disable");
|
|
expect(methods).toContain("Target.detachFromTarget");
|
|
},
|
|
);
|