refactor(browser): split pw tools + agent routes

This commit is contained in:
Peter Steinberger
2026-01-14 05:39:44 +00:00
parent da6f07b7c1
commit 2b60ee96f2
27 changed files with 2781 additions and 2633 deletions

View File

@@ -0,0 +1,141 @@
import type { Page } from "playwright-core";
import {
buildRoleSnapshotFromAriaSnapshot,
getRoleSnapshotStats,
type RoleSnapshotOptions,
} from "./pw-role-snapshot.js";
import {
ensurePageState,
getPageForTargetId,
type WithSnapshotForAI,
} from "./pw-session.js";
export async function snapshotAiViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
timeoutMs?: number;
maxChars?: number;
}): Promise<{ snapshot: string; truncated?: boolean }> {
const page = await getPageForTargetId({
cdpUrl: opts.cdpUrl,
targetId: opts.targetId,
});
ensurePageState(page);
const maybe = page as unknown as WithSnapshotForAI;
if (!maybe._snapshotForAI) {
throw new Error(
"Playwright _snapshotForAI is not available. Upgrade playwright-core.",
);
}
const result = await maybe._snapshotForAI({
timeout: Math.max(
500,
Math.min(60_000, Math.floor(opts.timeoutMs ?? 5000)),
),
track: "response",
});
let snapshot = String(result?.full ?? "");
const maxChars = opts.maxChars;
const limit =
typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0
? Math.floor(maxChars)
: undefined;
if (limit && snapshot.length > limit) {
snapshot = `${snapshot.slice(0, limit)}\n\n[...TRUNCATED - page too large]`;
return { snapshot, truncated: true };
}
return { snapshot };
}
export async function snapshotRoleViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
selector?: string;
frameSelector?: string;
options?: RoleSnapshotOptions;
}): Promise<{
snapshot: string;
refs: Record<string, { role: string; name?: string; nth?: number }>;
stats: { lines: number; chars: number; refs: number; interactive: number };
}> {
const page = await getPageForTargetId({
cdpUrl: opts.cdpUrl,
targetId: opts.targetId,
});
const state = ensurePageState(page);
const frameSelector = opts.frameSelector?.trim() || "";
const selector = opts.selector?.trim() || "";
const locator = frameSelector
? selector
? page.frameLocator(frameSelector).locator(selector)
: page.frameLocator(frameSelector).locator(":root")
: selector
? page.locator(selector)
: page.locator(":root");
const ariaSnapshot = await locator.ariaSnapshot();
const built = buildRoleSnapshotFromAriaSnapshot(
String(ariaSnapshot ?? ""),
opts.options,
);
state.roleRefs = built.refs;
state.roleRefsFrameSelector = frameSelector || undefined;
return {
snapshot: built.snapshot,
refs: built.refs,
stats: getRoleSnapshotStats(built.snapshot, built.refs),
};
}
export async function navigateViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
url: string;
timeoutMs?: number;
}): Promise<{ url: string }> {
const url = String(opts.url ?? "").trim();
if (!url) throw new Error("url is required");
const page = await getPageForTargetId(opts);
ensurePageState(page);
await page.goto(url, {
timeout: Math.max(1000, Math.min(120_000, opts.timeoutMs ?? 20_000)),
});
return { url: page.url() };
}
export async function resizeViewportViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
width: number;
height: number;
}): Promise<void> {
const page = await getPageForTargetId(opts);
ensurePageState(page);
await page.setViewportSize({
width: Math.max(1, Math.floor(opts.width)),
height: Math.max(1, Math.floor(opts.height)),
});
}
export async function closePageViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
}): Promise<void> {
const page = await getPageForTargetId(opts);
ensurePageState(page);
await page.close();
}
export async function pdfViaPlaywright(opts: {
cdpUrl: string;
targetId?: string;
}): Promise<{ buffer: Buffer }> {
const page = await getPageForTargetId(opts);
ensurePageState(page);
const buffer = await (page as Page).pdf({ printBackground: true });
return { buffer };
}