feat: add remote CDP browser support

This commit is contained in:
Peter Steinberger
2026-01-01 22:44:52 +01:00
parent 73d0e2cb81
commit bd8a0a9f8f
21 changed files with 400 additions and 157 deletions

View File

@@ -4,6 +4,7 @@ import { fetch as realFetch } from "undici";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let testPort = 0;
let cdpBaseUrl = "";
let reachable = false;
let cfgAttachOnly = false;
let createTargetId: string | null = null;
@@ -99,6 +100,7 @@ vi.mock("./chrome.js", () => ({
vi.mock("./cdp.js", () => ({
createTargetViaCdp: cdpMocks.createTargetViaCdp,
normalizeCdpWsUrl: vi.fn((wsUrl: string) => wsUrl),
snapshotAria: cdpMocks.snapshotAria,
}));
@@ -159,6 +161,7 @@ describe("browser control server", () => {
for (const fn of Object.values(cdpMocks)) fn.mockClear();
testPort = await getFreePort();
cdpBaseUrl = `http://127.0.0.1:${testPort + 1}`;
// Minimal CDP JSON endpoints used by the server.
let putNewCalls = 0;
@@ -293,7 +296,7 @@ describe("browser control server", () => {
expect(snapAi.ok).toBe(true);
expect(snapAi.format).toBe("ai");
expect(pwMocks.snapshotAiViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
});
@@ -305,7 +308,7 @@ describe("browser control server", () => {
expect(nav.ok).toBe(true);
expect(typeof nav.targetId).toBe("string");
expect(pwMocks.navigateViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
url: "https://example.com",
});
@@ -322,7 +325,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(click.ok).toBe(true);
expect(pwMocks.clickViaPlaywright).toHaveBeenNthCalledWith(1, {
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: "1",
doubleClick: false,
@@ -351,7 +354,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(type.ok).toBe(true);
expect(pwMocks.typeViaPlaywright).toHaveBeenNthCalledWith(1, {
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: "1",
text: "",
@@ -366,7 +369,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(press.ok).toBe(true);
expect(pwMocks.pressKeyViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
key: "Enter",
});
@@ -378,7 +381,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(hover.ok).toBe(true);
expect(pwMocks.hoverViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: "2",
});
@@ -390,7 +393,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(drag.ok).toBe(true);
expect(pwMocks.dragViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
startRef: "3",
endRef: "4",
@@ -403,7 +406,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(select.ok).toBe(true);
expect(pwMocks.selectOptionViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: "5",
values: ["a", "b"],
@@ -419,7 +422,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(fill.ok).toBe(true);
expect(pwMocks.fillFormViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
fields: [{ ref: "6", type: "textbox", value: "hello" }],
});
@@ -431,7 +434,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(resize.ok).toBe(true);
expect(pwMocks.resizeViewportViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
width: 800,
height: 600,
@@ -444,7 +447,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(wait.ok).toBe(true);
expect(pwMocks.waitForViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
timeMs: 5,
text: undefined,
@@ -459,7 +462,7 @@ describe("browser control server", () => {
expect(evalRes.ok).toBe(true);
expect(evalRes.result).toBe("ok");
expect(pwMocks.evaluateViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
fn: "() => 1",
ref: undefined,
@@ -472,7 +475,7 @@ describe("browser control server", () => {
}).then((r) => r.json());
expect(upload).toMatchObject({ ok: true });
expect(pwMocks.armFileUploadViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
paths: ["/tmp/a.txt"],
timeoutMs: 1234,
@@ -485,13 +488,13 @@ describe("browser control server", () => {
}).then((r) => r.json());
expect(uploadWithRef).toMatchObject({ ok: true });
expect(pwMocks.armFileUploadViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
paths: ["/tmp/b.txt"],
timeoutMs: undefined,
});
expect(pwMocks.clickViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: "e12",
});
@@ -503,7 +506,7 @@ describe("browser control server", () => {
}).then((r) => r.json());
expect(uploadWithInputRef).toMatchObject({ ok: true });
expect(pwMocks.setInputFilesViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
inputRef: "e99",
element: undefined,
@@ -520,7 +523,7 @@ describe("browser control server", () => {
}).then((r) => r.json());
expect(uploadWithElement).toMatchObject({ ok: true });
expect(pwMocks.setInputFilesViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
inputRef: undefined,
element: "input[type=file]",
@@ -534,7 +537,7 @@ describe("browser control server", () => {
}).then((r) => r.json());
expect(dialog).toMatchObject({ ok: true });
expect(pwMocks.armDialogViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
accept: true,
promptText: undefined,
@@ -547,7 +550,7 @@ describe("browser control server", () => {
expect(consoleRes.ok).toBe(true);
expect(Array.isArray(consoleRes.messages)).toBe(true);
expect(pwMocks.getConsoleMessagesViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
level: "error",
});
@@ -568,7 +571,7 @@ describe("browser control server", () => {
expect(shot.ok).toBe(true);
expect(typeof shot.path).toBe("string");
expect(pwMocks.takeScreenshotViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: undefined,
element: "body",
@@ -583,7 +586,7 @@ describe("browser control server", () => {
}).then((r) => r.json())) as { ok: boolean };
expect(close.ok).toBe(true);
expect(pwMocks.closePageViaPlaywright).toHaveBeenCalledWith({
cdpPort: testPort + 1,
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
});