diff --git a/extensions/browser/src/browser/cdp.internal.test.ts b/extensions/browser/src/browser/cdp.internal.test.ts
index 1a6d0725c679..eb6bdb80fa60 100644
--- a/extensions/browser/src/browser/cdp.internal.test.ts
+++ b/extensions/browser/src/browser/cdp.internal.test.ts
@@ -6,18 +6,12 @@ import "../test-support/browser-security.mock.js";
import {
type AriaSnapshotNode,
captureScreenshot,
- captureScreenshotPng,
createTargetViaCdp,
- type DomSnapshotNode,
evaluateJavaScript,
formatAriaSnapshot,
- getDomText,
normalizeCdpWsUrl,
- type QueryMatch,
- querySelector,
type RawAXNode,
snapshotAria,
- snapshotDom,
snapshotRoleViaCdp,
} from "./cdp.js";
@@ -164,27 +158,6 @@ describe("cdp internal", () => {
expect(buf.toString("utf8")).toBe("PNGDATA");
});
- it("captureScreenshotPng forwards to the png captureScreenshot flow", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Page.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Page.captureScreenshot") {
- expect(msg.params?.format).toBe("png");
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { data: Buffer.from("WRAPPED").toString("base64") },
- }),
- );
- }
- });
- wss = server.wss;
- const buf = await captureScreenshotPng({ wsUrl: server.wsUrl });
- expect(buf.toString("utf8")).toBe("WRAPPED");
- });
-
it("clamps out-of-range JPEG quality values into [0, 100]", async () => {
const { observed } = await captureScreenshotAndObserveParams({
format: "jpeg",
@@ -730,257 +703,6 @@ describe("cdp internal", () => {
});
});
- describe("snapshotDom", () => {
- it("returns the nodes array from the evaluated expression", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- const fake: DomSnapshotNode[] = [{ ref: "n1", parentRef: null, depth: 0, tag: "html" }];
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { result: { value: { nodes: fake } } },
- }),
- );
- }
- });
- wss = server.wss;
- const snap = await snapshotDom({ wsUrl: server.wsUrl, limit: 10, maxTextChars: 200 });
- expect(snap.nodes[0]?.tag).toBe("html");
- });
-
- it("returns an empty nodes array when the value is not an object", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { result: { value: null } },
- }),
- );
- }
- });
- wss = server.wss;
- const snap = await snapshotDom({ wsUrl: server.wsUrl });
- expect(snap.nodes).toStrictEqual([]);
- });
-
- it("returns an empty nodes array when nodes is not an array", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { result: { value: { nodes: "not-an-array" } } },
- }),
- );
- }
- });
- wss = server.wss;
- const snap = await snapshotDom({ wsUrl: server.wsUrl });
- expect(snap.nodes).toStrictEqual([]);
- });
-
- it("uses default DOM snapshot budgets for non-finite options", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- const expression =
- typeof msg.params?.expression === "string" ? msg.params.expression : "";
- expect(expression).toContain("const maxNodes = 800;");
- expect(expression).toContain("const maxText = 220;");
- socket.send(JSON.stringify({ id: msg.id, result: { result: { value: { nodes: [] } } } }));
- }
- });
- wss = server.wss;
-
- const snap = await snapshotDom({
- wsUrl: server.wsUrl,
- limit: Number.NaN,
- maxTextChars: Number.NaN,
- });
-
- expect(snap.nodes).toStrictEqual([]);
- });
- });
-
- describe("getDomText", () => {
- it("returns the evaluated string for text format", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { result: { value: "plain body text" } },
- }),
- );
- }
- });
- wss = server.wss;
- const res = await getDomText({ wsUrl: server.wsUrl, format: "text", maxChars: 100 });
- expect(res.text).toBe("plain body text");
- });
-
- it("returns the html outerHTML for html format with a selector", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { result: { value: "
html
" } },
- }),
- );
- }
- });
- wss = server.wss;
- const res = await getDomText({
- wsUrl: server.wsUrl,
- format: "html",
- selector: "#foo",
- });
- expect(res.text).toBe("html
");
- });
-
- it("coerces numeric/boolean values to strings and falls back to empty for objects", async () => {
- const responses: unknown[] = [42, true, { shape: "object" }];
- let i = 0;
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(
- JSON.stringify({
- id: msg.id,
- result: { result: { value: responses[i++] } },
- }),
- );
- }
- });
- wss = server.wss;
- const num = await getDomText({ wsUrl: server.wsUrl, format: "text" });
- expect(num.text).toBe("42");
- const bool = await getDomText({ wsUrl: server.wsUrl, format: "text" });
- expect(bool.text).toBe("true");
- const obj = await getDomText({ wsUrl: server.wsUrl, format: "text" });
- expect(obj.text).toBe("");
- });
-
- it("uses the default text budget for non-finite maxChars", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- const expression =
- typeof msg.params?.expression === "string" ? msg.params.expression : "";
- expect(expression).toContain("const max = 200000;");
- socket.send(JSON.stringify({ id: msg.id, result: { result: { value: "ok" } } }));
- }
- });
- wss = server.wss;
-
- const res = await getDomText({
- wsUrl: server.wsUrl,
- format: "text",
- maxChars: Number.NaN,
- });
-
- expect(res.text).toBe("ok");
- });
- });
-
- describe("querySelector", () => {
- it("returns the matches array from the evaluated expression", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- const matches: QueryMatch[] = [{ index: 1, tag: "button", text: "OK" }];
- socket.send(JSON.stringify({ id: msg.id, result: { result: { value: matches } } }));
- }
- });
- wss = server.wss;
- const out = await querySelector({
- wsUrl: server.wsUrl,
- selector: "button",
- limit: 5,
- maxTextChars: 100,
- maxHtmlChars: 500,
- });
- expect(out.matches[0]?.tag).toBe("button");
- });
-
- it("returns an empty array when the value is not an array", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(JSON.stringify({ id: msg.id, result: { result: { value: "not-array" } } }));
- }
- });
- wss = server.wss;
- const out = await querySelector({ wsUrl: server.wsUrl, selector: "button" });
- expect(out.matches).toStrictEqual([]);
- });
-
- it("uses default query budgets for non-finite options", async () => {
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- const expression =
- typeof msg.params?.expression === "string" ? msg.params.expression : "";
- expect(expression).toContain("const lim = 20;");
- expect(expression).toContain("const maxText = 500;");
- expect(expression).toContain("const maxHtml = 1500;");
- socket.send(JSON.stringify({ id: msg.id, result: { result: { value: [] } } }));
- }
- });
- wss = server.wss;
-
- const out = await querySelector({
- wsUrl: server.wsUrl,
- selector: "button",
- limit: Number.NaN,
- maxTextChars: Number.NaN,
- maxHtmlChars: Number.NaN,
- });
-
- expect(out.matches).toStrictEqual([]);
- });
- });
-
describe("normalizeCdpWsUrl fill-in", () => {
it("respects an already-non-loopback ws hostname (no-rewrite branch)", () => {
// Covers the else side of the loopback/wildcard-guard in normalizeCdpWsUrl.
@@ -1287,21 +1009,4 @@ describe("cdp internal", () => {
});
});
- describe("getDomText branch coverage", () => {
- it("coerces a missing evaluated value to an empty string", async () => {
- // Covers the right-hand side of `evaluated.result?.value ?? ""`.
- const server = await startMockWsServer((msg, socket) => {
- if (msg.method === "Runtime.enable") {
- socket.send(JSON.stringify({ id: msg.id, result: {} }));
- return;
- }
- if (msg.method === "Runtime.evaluate") {
- socket.send(JSON.stringify({ id: msg.id, result: { result: {} } }));
- }
- });
- wss = server.wss;
- const res = await getDomText({ wsUrl: server.wsUrl, format: "text" });
- expect(res.text).toBe("");
- });
- });
});
diff --git a/extensions/browser/src/browser/cdp.ts b/extensions/browser/src/browser/cdp.ts
index e3eb6cb23852..07072baeba3e 100644
--- a/extensions/browser/src/browser/cdp.ts
+++ b/extensions/browser/src/browser/cdp.ts
@@ -65,20 +65,6 @@ export function normalizeCdpWsUrl(wsUrl: string, cdpUrl: string): string {
return ws.toString();
}
-/** Capture a PNG screenshot through CDP. */
-export async function captureScreenshotPng(opts: {
- wsUrl: string;
- fullPage?: boolean;
- timeoutMs?: number;
-}): Promise {
- return await captureScreenshot({
- wsUrl: opts.wsUrl,
- fullPage: opts.fullPage,
- format: "png",
- timeoutMs: opts.timeoutMs,
- });
-}
-
/** Capture a PNG or JPEG screenshot through CDP, optionally full-page. */
export async function captureScreenshot(opts: {
wsUrl: string;
@@ -979,200 +965,3 @@ export async function snapshotRoleViaCdp(opts: {
{ commandTimeoutMs: opts.timeoutMs ?? 5000 },
);
}
-
-/** Capture a raw DOM snapshot through CDP. */
-export async function snapshotDom(opts: {
- wsUrl: string;
- limit?: number;
- maxTextChars?: number;
-}): Promise<{
- nodes: DomSnapshotNode[];
-}> {
- const limit = resolveIntegerOption(opts.limit, 800, { min: 1, max: 5000 });
- const maxTextChars = resolveIntegerOption(opts.maxTextChars, 220, { min: 0, max: 5000 });
-
- const expression = `(() => {
- const maxNodes = ${JSON.stringify(limit)};
- const maxText = ${JSON.stringify(maxTextChars)};
- const lower = (value) => String(value || "").toLocaleLowerCase();
- const nodes = [];
- const root = document.documentElement;
- if (!root) return { nodes };
- const stack = [{ el: root, depth: 0, parentRef: null }];
- while (stack.length && nodes.length < maxNodes) {
- const cur = stack.pop();
- const el = cur.el;
- if (!el || el.nodeType !== 1) continue;
- const ref = "n" + String(nodes.length + 1);
- const tag = lower(el.tagName);
- const id = el.id ? String(el.id) : undefined;
- const className = el.className ? String(el.className).slice(0, 300) : undefined;
- const role = el.getAttribute && el.getAttribute("role") ? String(el.getAttribute("role")) : undefined;
- const name = el.getAttribute && el.getAttribute("aria-label") ? String(el.getAttribute("aria-label")) : undefined;
- let text = "";
- try { text = String(el.innerText || "").trim(); } catch {}
- if (maxText && text.length > maxText) text = text.slice(0, maxText) + "…";
- const href = (el.href !== undefined && el.href !== null) ? String(el.href) : undefined;
- const type = (el.type !== undefined && el.type !== null) ? String(el.type) : undefined;
- const value = (el.value !== undefined && el.value !== null) ? String(el.value).slice(0, 500) : undefined;
- nodes.push({
- ref,
- parentRef: cur.parentRef,
- depth: cur.depth,
- tag,
- ...(id ? { id } : {}),
- ...(className ? { className } : {}),
- ...(role ? { role } : {}),
- ...(name ? { name } : {}),
- ...(text ? { text } : {}),
- ...(href ? { href } : {}),
- ...(type ? { type } : {}),
- ...(value ? { value } : {}),
- });
- const children = el.children ? Array.from(el.children) : [];
- for (let i = children.length - 1; i >= 0; i--) {
- stack.push({ el: children[i], depth: cur.depth + 1, parentRef: ref });
- }
- }
- return { nodes };
- })()`;
-
- const evaluated = await evaluateJavaScript({
- wsUrl: opts.wsUrl,
- expression,
- awaitPromise: true,
- returnByValue: true,
- });
- const value = evaluated.result?.value;
- if (!value || typeof value !== "object") {
- return { nodes: [] };
- }
- const nodes = (value as { nodes?: unknown }).nodes;
- return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] };
-}
-
-/** Simplified DOM node returned by DOM snapshot helpers. */
-export type DomSnapshotNode = {
- ref: string;
- parentRef: string | null;
- depth: number;
- tag: string;
- id?: string;
- className?: string;
- role?: string;
- name?: string;
- text?: string;
- href?: string;
- type?: string;
- value?: string;
-};
-
-/** Extract visible DOM text from a CDP target. */
-export async function getDomText(opts: {
- wsUrl: string;
- format: "html" | "text";
- maxChars?: number;
- selector?: string;
-}): Promise<{ text: string }> {
- const maxChars = resolveIntegerOption(opts.maxChars, 200_000, { min: 0, max: 5_000_000 });
- const selectorExpr = opts.selector ? JSON.stringify(opts.selector) : "null";
- const expression = `(() => {
- const fmt = ${JSON.stringify(opts.format)};
- const max = ${JSON.stringify(maxChars)};
- const sel = ${selectorExpr};
- const pick = sel ? document.querySelector(sel) : null;
- let out = "";
- if (fmt === "text") {
- const el = pick || document.body || document.documentElement;
- try { out = String(el && el.innerText ? el.innerText : ""); } catch { out = ""; }
- } else {
- const el = pick || document.documentElement;
- try { out = String(el && el.outerHTML ? el.outerHTML : ""); } catch { out = ""; }
- }
- if (max && out.length > max) out = out.slice(0, max) + "\\n";
- return out;
- })()`;
-
- const evaluated = await evaluateJavaScript({
- wsUrl: opts.wsUrl,
- expression,
- awaitPromise: true,
- returnByValue: true,
- });
- const textValue = (evaluated.result?.value ?? "") as unknown;
- const text =
- typeof textValue === "string"
- ? textValue
- : typeof textValue === "number" || typeof textValue === "boolean"
- ? String(textValue)
- : "";
- return { text };
-}
-
-/** Query a selector in a CDP target and return matching node metadata. */
-export async function querySelector(opts: {
- wsUrl: string;
- selector: string;
- limit?: number;
- maxTextChars?: number;
- maxHtmlChars?: number;
-}): Promise<{
- matches: QueryMatch[];
-}> {
- const limit = resolveIntegerOption(opts.limit, 20, { min: 1, max: 200 });
- const maxText = resolveIntegerOption(opts.maxTextChars, 500, { min: 0, max: 5000 });
- const maxHtml = resolveIntegerOption(opts.maxHtmlChars, 1500, { min: 0, max: 20_000 });
-
- const expression = `(() => {
- const sel = ${JSON.stringify(opts.selector)};
- const lim = ${JSON.stringify(limit)};
- const maxText = ${JSON.stringify(maxText)};
- const maxHtml = ${JSON.stringify(maxHtml)};
- const lower = (value) => String(value || "").toLocaleLowerCase();
- const els = Array.from(document.querySelectorAll(sel)).slice(0, lim);
- return els.map((el, i) => {
- const tag = lower(el.tagName);
- const id = el.id ? String(el.id) : undefined;
- const className = el.className ? String(el.className).slice(0, 300) : undefined;
- let text = "";
- try { text = String(el.innerText || "").trim(); } catch {}
- if (maxText && text.length > maxText) text = text.slice(0, maxText) + "…";
- const value = (el.value !== undefined && el.value !== null) ? String(el.value).slice(0, 500) : undefined;
- const href = (el.href !== undefined && el.href !== null) ? String(el.href) : undefined;
- let outerHTML = "";
- try { outerHTML = String(el.outerHTML || ""); } catch {}
- if (maxHtml && outerHTML.length > maxHtml) outerHTML = outerHTML.slice(0, maxHtml) + "…";
- return {
- index: i + 1,
- tag,
- ...(id ? { id } : {}),
- ...(className ? { className } : {}),
- ...(text ? { text } : {}),
- ...(value ? { value } : {}),
- ...(href ? { href } : {}),
- ...(outerHTML ? { outerHTML } : {}),
- };
- });
- })()`;
-
- const evaluated = await evaluateJavaScript({
- wsUrl: opts.wsUrl,
- expression,
- awaitPromise: true,
- returnByValue: true,
- });
- const matches = evaluated.result?.value;
- return { matches: Array.isArray(matches) ? (matches as QueryMatch[]) : [] };
-}
-
-/** Selector match metadata returned by querySelector. */
-export type QueryMatch = {
- index: number;
- tag: string;
- id?: string;
- className?: string;
- text?: string;
- value?: string;
- href?: string;
- outerHTML?: string;
-};