test: share browser cdp fixtures

This commit is contained in:
Peter Steinberger
2026-04-20 20:33:13 +01:00
parent 8a09b40cb2
commit d033662145
2 changed files with 74 additions and 112 deletions

View File

@@ -28,6 +28,42 @@ type CdpReplyHandler = (
msg: { id?: number; method?: string; params?: Record<string, unknown> },
socket: WebSocket,
) => void;
type CdpMockMessage = Parameters<CdpReplyHandler>[0];
function sendCdpResult(socket: WebSocket, id: number | undefined, result: Record<string, unknown>) {
socket.send(JSON.stringify({ id, result }));
}
function replyToPageEnable(msg: CdpMockMessage, socket: WebSocket): boolean {
if (msg.method !== "Page.enable") {
return false;
}
sendCdpResult(socket, msg.id, {});
return true;
}
function replyWithScreenshotData(msg: CdpMockMessage, socket: WebSocket, data: string): boolean {
if (msg.method !== "Page.captureScreenshot") {
return false;
}
sendCdpResult(socket, msg.id, { data: Buffer.from(data).toString("base64") });
return true;
}
function replyToViewportCommandOrScreenshot(
msg: CdpMockMessage,
socket: WebSocket,
data: string,
): boolean {
if (
msg.method === "Emulation.setDeviceMetricsOverride" ||
msg.method === "Emulation.clearDeviceMetricsOverride"
) {
sendCdpResult(socket, msg.id, {});
return true;
}
return replyWithScreenshotData(msg, socket, data);
}
async function startMockWsServer(handle: CdpReplyHandler) {
const wss = new WebSocketServer({ port: 0, host: "127.0.0.1" });
@@ -60,6 +96,24 @@ describe("cdp internal", () => {
}
});
async function captureScreenshotAndObserveParams(
options: Omit<Parameters<typeof captureScreenshot>[0], "wsUrl">,
) {
const observed: Array<Record<string, unknown>> = [];
const server = await startMockWsServer((msg, socket) => {
if (replyToPageEnable(msg, socket)) {
return;
}
if (msg.method === "Page.captureScreenshot") {
observed.push(msg.params ?? {});
replyWithScreenshotData(msg, socket, "JPG");
}
});
wss = server.wss;
const buf = await captureScreenshot({ wsUrl: server.wsUrl, ...options });
return { buf, observed };
}
describe("captureScreenshot", () => {
it("captures a PNG without fullPage", async () => {
const server = await startMockWsServer((msg, socket) => {
@@ -104,24 +158,10 @@ describe("cdp internal", () => {
});
it("clamps out-of-range JPEG quality values into [0, 100]", async () => {
const observed: Array<Record<string, unknown>> = [];
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") {
observed.push(msg.params ?? {});
socket.send(
JSON.stringify({
id: msg.id,
result: { data: Buffer.from("JPG").toString("base64") },
}),
);
}
const { observed } = await captureScreenshotAndObserveParams({
format: "jpeg",
quality: 250,
});
wss = server.wss;
await captureScreenshot({ wsUrl: server.wsUrl, format: "jpeg", quality: 250 });
expect(observed[0]?.format).toBe("jpeg");
expect(observed[0]?.quality).toBe(100);
});
@@ -160,22 +200,9 @@ describe("cdp internal", () => {
);
return;
}
if (msg.method === "Emulation.setDeviceMetricsOverride") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
if (replyToViewportCommandOrScreenshot(msg, socket, "FULL")) {
return;
}
if (msg.method === "Emulation.clearDeviceMetricsOverride") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Page.captureScreenshot") {
socket.send(
JSON.stringify({
id: msg.id,
result: { data: Buffer.from("FULL").toString("base64") },
}),
);
}
});
wss = server.wss;
const buf = await captureScreenshot({ wsUrl: server.wsUrl, fullPage: true });
@@ -650,24 +677,7 @@ describe("cdp internal", () => {
describe("captureScreenshot branch coverage", () => {
it("uses the default jpeg quality when opts.quality is omitted", async () => {
const observed: Array<Record<string, unknown>> = [];
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") {
observed.push(msg.params ?? {});
socket.send(
JSON.stringify({
id: msg.id,
result: { data: Buffer.from("J").toString("base64") },
}),
);
}
});
wss = server.wss;
await captureScreenshot({ wsUrl: server.wsUrl, format: "jpeg" });
const { observed } = await captureScreenshotAndObserveParams({ format: "jpeg" });
expect(observed[0]?.quality).toBe(85);
});
@@ -720,22 +730,9 @@ describe("cdp internal", () => {
socket.send(JSON.stringify({ id: msg.id, result: { result: { value: {} } } }));
return;
}
if (msg.method === "Emulation.setDeviceMetricsOverride") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
if (replyToViewportCommandOrScreenshot(msg, socket, "C")) {
return;
}
if (msg.method === "Emulation.clearDeviceMetricsOverride") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Page.captureScreenshot") {
socket.send(
JSON.stringify({
id: msg.id,
result: { data: Buffer.from("C").toString("base64") },
}),
);
}
});
wss = server.wss;
const buf = await captureScreenshot({ wsUrl: server.wsUrl, fullPage: true });

View File

@@ -2,40 +2,13 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import {
getPwToolsCoreSessionMocks,
installPwToolsCoreTestHooks,
setPwToolsCoreCurrentPage,
setPwToolsCoreCurrentRefLocator,
} from "./pw-tools-core.test-harness.js";
let currentPage: Record<string, unknown> | null = null;
let currentRefLocator: Record<string, unknown> | null = null;
let pageState: {
console: unknown[];
armIdUpload: number;
armIdDialog: number;
armIdDownload: number;
} = {
console: [],
armIdUpload: 0,
armIdDialog: 0,
armIdDownload: 0,
};
const sessionMocks = vi.hoisted(() => ({
getPageForTargetId: vi.fn(async () => {
if (!currentPage) {
throw new Error("missing page");
}
return currentPage;
}),
ensurePageState: vi.fn(() => pageState),
forceDisconnectPlaywrightForTarget: vi.fn(async () => {}),
restoreRoleRefsForTarget: vi.fn(() => {}),
storeRoleRefsForTarget: vi.fn(() => {}),
refLocator: vi.fn(() => {
if (!currentRefLocator) {
throw new Error("missing locator");
}
return currentRefLocator;
}),
rememberRoleRefsForTarget: vi.fn(() => {}),
}));
const tmpDirMocks = vi.hoisted(() => ({
resolvePreferredOpenClawTmpDir: vi.fn(() => "/tmp/openclaw"),
}));
@@ -45,9 +18,11 @@ const chromeMocks = vi.hoisted(() => ({
const clientFetchMocks = vi.hoisted(() => ({
resolveBrowserRateLimitMessage: vi.fn(() => undefined),
}));
vi.mock("./pw-session.js", () => sessionMocks);
vi.mock("./chrome.js", () => chromeMocks);
vi.mock("./client-fetch.js", () => clientFetchMocks);
const sessionMocks = getPwToolsCoreSessionMocks();
let mod: Pick<
typeof import("./pw-tools-core.downloads.js"),
"downloadViaPlaywright" | "waitForDownloadViaPlaywright"
@@ -56,6 +31,8 @@ let mod: Pick<
let tmpDirModule: typeof import("../infra/tmp-openclaw-dir.js");
describe("pw-tools-core", () => {
installPwToolsCoreTestHooks();
beforeAll(async () => {
vi.doMock("./pw-session.js", () => sessionMocks);
vi.doMock("./chrome.js", () => chromeMocks);
@@ -75,18 +52,6 @@ describe("pw-tools-core", () => {
});
beforeEach(() => {
currentPage = null;
currentRefLocator = null;
pageState = {
console: [],
armIdUpload: 0,
armIdDialog: 0,
armIdDownload: 0,
};
for (const fn of Object.values(sessionMocks)) {
fn.mockClear();
}
for (const fn of Object.values(tmpDirMocks)) {
fn.mockClear();
}
@@ -144,7 +109,7 @@ describe("pw-tools-core", () => {
}
});
const off = vi.fn();
currentPage = { on, off };
setPwToolsCoreCurrentPage({ on, off });
return {
trigger: (download: unknown) => {
downloadHandler?.(download);
@@ -209,7 +174,7 @@ describe("pw-tools-core", () => {
const harness = createDownloadEventHarness();
const click = vi.fn(async () => {});
currentRefLocator = { click };
setPwToolsCoreCurrentRefLocator({ click });
const saveAs = vi.fn(async (outPath: string) => {
await fs.writeFile(outPath, "report-content", "utf8");
@@ -316,7 +281,7 @@ describe("pw-tools-core", () => {
}
});
const off = vi.fn();
currentPage = { on, off };
setPwToolsCoreCurrentPage({ on, off });
const resp = {
url: () => "https://example.com/api/data",