refactor: remove core browser test duplicates

This commit is contained in:
Peter Steinberger
2026-03-26 23:28:34 +00:00
parent 9a7ceceffa
commit 0ef2a9c8b5
80 changed files with 76 additions and 12320 deletions

View File

@@ -1,30 +0,0 @@
import { describe, expect, it } from "vitest";
import { readFields } from "../../../extensions/browser/src/cli/browser-cli-actions-input/shared.js";
describe("readFields", () => {
it.each([
{
name: "keeps explicit type",
fields: '[{"ref":"6","type":"textbox","value":"hello"}]',
expected: [{ ref: "6", type: "textbox", value: "hello" }],
},
{
name: "defaults missing type to text",
fields: '[{"ref":"7","value":"world"}]',
expected: [{ ref: "7", type: "text", value: "world" }],
},
{
name: "defaults blank type to text",
fields: '[{"ref":"8","type":" ","value":"blank"}]',
expected: [{ ref: "8", type: "text", value: "blank" }],
},
])("$name", async ({ fields, expected }) => {
await expect(readFields({ fields })).resolves.toEqual(expected);
});
it("requires ref", async () => {
await expect(readFields({ fields: '[{"type":"textbox","value":"world"}]' })).rejects.toThrow(
"fields[0] must include ref",
);
});
});

View File

@@ -1,155 +0,0 @@
import { Command } from "commander";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
const gatewayMocks = vi.hoisted(() => ({
callGatewayFromCli: vi.fn(async () => ({
ok: true,
format: "ai",
targetId: "t1",
url: "https://example.com",
snapshot: "ok",
})),
}));
vi.mock("./gateway-rpc.js", () => ({
callGatewayFromCli: gatewayMocks.callGatewayFromCli,
}));
const configMocks = vi.hoisted(() => ({
loadConfig: vi.fn(() => ({ browser: {} })),
}));
vi.mock("../config/config.js", () => configMocks);
const sharedMocks = vi.hoisted(() => ({
callBrowserRequest: vi.fn(
async (_opts: unknown, params: { path?: string; query?: Record<string, unknown> }) => {
const format = params.query?.format === "aria" ? "aria" : "ai";
if (format === "aria") {
return {
ok: true,
format: "aria",
targetId: "t1",
url: "https://example.com",
nodes: [],
};
}
return {
ok: true,
format: "ai",
targetId: "t1",
url: "https://example.com",
snapshot: "ok",
};
},
),
}));
vi.mock("../../extensions/browser/src/cli/browser-cli-shared.js", () => ({
callBrowserRequest: sharedMocks.callBrowserRequest,
}));
vi.mock("../../extensions/browser/src/core-api.js", async () => ({
...(await vi.importActual<object>("../../extensions/browser/src/core-api.js")),
defaultRuntime: runtime,
loadConfig: configMocks.loadConfig,
}));
let registerBrowserInspectCommands: typeof import("../../extensions/browser/src/cli/browser-cli-inspect.js").registerBrowserInspectCommands;
type SnapshotDefaultsCase = {
label: string;
args: string[];
expectMode: "efficient" | undefined;
};
describe("browser cli snapshot defaults", () => {
const runBrowserInspect = async (args: string[], withJson = false) => {
const program = new Command();
const browser = program.command("browser").option("--json", "JSON output", false);
registerBrowserInspectCommands(browser, () => ({}));
await program.parseAsync(withJson ? ["browser", "--json", ...args] : ["browser", ...args], {
from: "user",
});
const [, params] = sharedMocks.callBrowserRequest.mock.calls.at(-1) ?? [];
return params as { path?: string; query?: Record<string, unknown> } | undefined;
};
const runSnapshot = async (args: string[]) => await runBrowserInspect(["snapshot", ...args]);
beforeAll(async () => {
({ registerBrowserInspectCommands } =
await import("../../extensions/browser/src/cli/browser-cli-inspect.js"));
});
afterEach(() => {
vi.clearAllMocks();
resetRuntimeCapture();
configMocks.loadConfig.mockReturnValue({ browser: {} });
});
it.each<SnapshotDefaultsCase>([
{
label: "uses config snapshot defaults when mode is not provided",
args: [],
expectMode: "efficient",
},
{
label: "does not apply config snapshot defaults to aria snapshots",
args: ["--format", "aria"],
expectMode: undefined,
},
])("$label", async ({ args, expectMode }) => {
configMocks.loadConfig.mockReturnValue({
browser: { snapshotDefaults: { mode: "efficient" } },
});
if (args.includes("--format")) {
gatewayMocks.callGatewayFromCli.mockResolvedValueOnce({
ok: true,
format: "aria",
targetId: "t1",
url: "https://example.com",
snapshot: "ok",
});
}
const params = await runSnapshot(args);
expect(params?.path).toBe("/snapshot");
if (expectMode === undefined) {
expect((params?.query as { mode?: unknown } | undefined)?.mode).toBeUndefined();
} else {
expect(params?.query).toMatchObject({
format: "ai",
mode: expectMode,
});
}
});
it("does not set mode when config defaults are absent", async () => {
configMocks.loadConfig.mockReturnValue({ browser: {} });
const params = await runSnapshot([]);
expect((params?.query as { mode?: unknown } | undefined)?.mode).toBeUndefined();
});
it("applies explicit efficient mode without config defaults", async () => {
configMocks.loadConfig.mockReturnValue({ browser: {} });
const params = await runSnapshot(["--efficient"]);
expect(params?.query).toMatchObject({
format: "ai",
mode: "efficient",
});
});
it("sends screenshot request with trimmed target id and jpeg type", async () => {
const params = await runBrowserInspect(["screenshot", " tab-1 ", "--type", "jpeg"], true);
expect(params?.path).toBe("/screenshot");
expect((params as { body?: Record<string, unknown> } | undefined)?.body).toMatchObject({
targetId: "tab-1",
type: "jpeg",
fullPage: false,
});
});
});

View File

@@ -1,61 +0,0 @@
import { vi } from "vitest";
import { registerBrowserManageCommands } from "../../extensions/browser/src/cli/browser-cli-manage.js";
import { createBrowserProgram } from "./browser-cli-test-helpers.js";
type BrowserRequest = { path?: string };
type BrowserRuntimeOptions = { timeoutMs?: number };
export type BrowserManageCall = [unknown, BrowserRequest, BrowserRuntimeOptions | undefined];
const browserManageMocks = vi.hoisted(() => ({
callBrowserRequest: vi.fn<
(
opts: unknown,
req: BrowserRequest,
runtimeOpts?: BrowserRuntimeOptions,
) => Promise<Record<string, unknown>>
>(async (_opts: unknown, req: BrowserRequest) =>
req.path === "/"
? {
enabled: true,
running: true,
pid: 1,
cdpPort: 18800,
chosenBrowser: "chrome",
userDataDir: "/tmp/openclaw",
color: "blue",
headless: true,
attachOnly: false,
}
: {},
),
}));
vi.mock("../../extensions/browser/src/cli/browser-cli-shared.js", () => ({
callBrowserRequest: browserManageMocks.callBrowserRequest,
}));
vi.mock("../../extensions/browser/src/core-api.js", async () => ({
...(await vi.importActual<object>("../../extensions/browser/src/core-api.js")),
...(await (await import("./browser-cli-test-helpers.js")).createBrowserCliRuntimeMockModule()),
...(await (await import("./browser-cli-test-helpers.js")).createBrowserCliUtilsMockModule()),
}));
export function createBrowserManageProgram(params?: { withParentTimeout?: boolean }) {
const { program, browser, parentOpts } = createBrowserProgram();
if (params?.withParentTimeout) {
browser.option("--timeout <ms>", "Timeout in ms", "30000");
}
registerBrowserManageCommands(browser, parentOpts);
return program;
}
export function getBrowserManageCallBrowserRequestMock() {
return browserManageMocks.callBrowserRequest;
}
export function findBrowserManageCall(path: string): BrowserManageCall | undefined {
return browserManageMocks.callBrowserRequest.mock.calls.find(
(call) => (call[1] ?? {}).path === path,
) as BrowserManageCall | undefined;
}

View File

@@ -1,182 +0,0 @@
import { beforeEach, describe, expect, it } from "vitest";
import {
createBrowserManageProgram,
getBrowserManageCallBrowserRequestMock,
} from "./browser-cli-manage.test-helpers.js";
import { getBrowserCliRuntime, getBrowserCliRuntimeCapture } from "./browser-cli-test-helpers.js";
describe("browser manage output", () => {
beforeEach(() => {
getBrowserManageCallBrowserRequestMock().mockClear();
getBrowserCliRuntimeCapture().resetRuntimeCapture();
});
it("shows chrome-mcp transport for existing-session status without fake CDP fields", async () => {
getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) =>
req.path === "/"
? {
enabled: true,
profile: "chrome-live",
driver: "existing-session",
transport: "chrome-mcp",
running: true,
cdpReady: true,
cdpHttp: true,
pid: 4321,
cdpPort: null,
cdpUrl: null,
chosenBrowser: null,
userDataDir: null,
color: "#00AA00",
headless: false,
noSandbox: false,
executablePath: null,
attachOnly: true,
}
: {},
);
const program = createBrowserManageProgram();
await program.parseAsync(["browser", "--browser-profile", "chrome-live", "status"], {
from: "user",
});
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
expect(output).toContain("transport: chrome-mcp");
expect(output).not.toContain("cdpPort:");
expect(output).not.toContain("cdpUrl:");
});
it("shows configured userDataDir for existing-session status", async () => {
getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) =>
req.path === "/"
? {
enabled: true,
profile: "brave-live",
driver: "existing-session",
transport: "chrome-mcp",
running: true,
cdpReady: true,
cdpHttp: true,
pid: 4321,
cdpPort: null,
cdpUrl: null,
chosenBrowser: null,
userDataDir: "/Users/test/Library/Application Support/BraveSoftware/Brave-Browser",
color: "#FB542B",
headless: false,
noSandbox: false,
executablePath: null,
attachOnly: true,
}
: {},
);
const program = createBrowserManageProgram();
await program.parseAsync(["browser", "--browser-profile", "brave-live", "status"], {
from: "user",
});
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
expect(output).toContain(
"userDataDir: /Users/test/Library/Application Support/BraveSoftware/Brave-Browser",
);
});
it("shows chrome-mcp transport in browser profiles output", async () => {
getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) =>
req.path === "/profiles"
? {
profiles: [
{
name: "chrome-live",
driver: "existing-session",
transport: "chrome-mcp",
running: true,
tabCount: 2,
isDefault: false,
isRemote: false,
cdpPort: null,
cdpUrl: null,
color: "#00AA00",
},
],
}
: {},
);
const program = createBrowserManageProgram();
await program.parseAsync(["browser", "profiles"], { from: "user" });
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
expect(output).toContain("chrome-live: running (2 tabs) [existing-session]");
expect(output).toContain("transport: chrome-mcp");
expect(output).not.toContain("port: 0");
});
it("shows chrome-mcp transport after creating an existing-session profile", async () => {
getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) =>
req.path === "/profiles/create"
? {
ok: true,
profile: "chrome-live",
transport: "chrome-mcp",
cdpPort: null,
cdpUrl: null,
userDataDir: null,
color: "#00AA00",
isRemote: false,
}
: {},
);
const program = createBrowserManageProgram();
await program.parseAsync(
["browser", "create-profile", "--name", "chrome-live", "--driver", "existing-session"],
{ from: "user" },
);
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
expect(output).toContain('Created profile "chrome-live"');
expect(output).toContain("transport: chrome-mcp");
expect(output).not.toContain("port: 0");
});
it("redacts sensitive remote cdpUrl details in status output", async () => {
getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) =>
req.path === "/"
? {
enabled: true,
profile: "remote",
driver: "openclaw",
transport: "cdp",
running: true,
cdpReady: true,
cdpHttp: true,
pid: null,
cdpPort: 9222,
cdpUrl:
"https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890",
chosenBrowser: null,
userDataDir: null,
color: "#00AA00",
headless: false,
noSandbox: false,
executablePath: null,
attachOnly: true,
}
: {},
);
const program = createBrowserManageProgram();
await program.parseAsync(["browser", "--browser-profile", "remote", "status"], {
from: "user",
});
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
expect(output).toContain("cdpUrl: https://example.com/chrome?token=supers…7890");
expect(output).not.toContain("alice");
expect(output).not.toContain("supersecretpasswordvalue1234");
expect(output).not.toContain("supersecrettokenvalue1234567890");
});
});

View File

@@ -1,56 +0,0 @@
import { beforeEach, describe, expect, it } from "vitest";
import {
createBrowserManageProgram,
findBrowserManageCall,
getBrowserManageCallBrowserRequestMock,
} from "./browser-cli-manage.test-helpers.js";
import { getBrowserCliRuntimeCapture } from "./browser-cli-test-helpers.js";
describe("browser manage start timeout option", () => {
beforeEach(() => {
getBrowserManageCallBrowserRequestMock().mockClear();
getBrowserCliRuntimeCapture().resetRuntimeCapture();
});
it("uses parent --timeout for browser start instead of hardcoded 15s", async () => {
const program = createBrowserManageProgram({ withParentTimeout: true });
await program.parseAsync(["browser", "--timeout", "60000", "start"], { from: "user" });
const startCall = findBrowserManageCall("/start");
expect(startCall).toBeDefined();
expect(startCall?.[0]).toMatchObject({ timeout: "60000" });
expect(startCall?.[2]).toBeUndefined();
});
it("uses a longer built-in timeout for browser status", async () => {
const program = createBrowserManageProgram({ withParentTimeout: true });
await program.parseAsync(["browser", "status"], { from: "user" });
const statusCall = findBrowserManageCall("/");
expect(statusCall?.[2]).toEqual({ timeoutMs: 45_000 });
});
it("uses a longer built-in timeout for browser tabs", async () => {
const program = createBrowserManageProgram({ withParentTimeout: true });
await program.parseAsync(["browser", "tabs"], { from: "user" });
const tabsCall = findBrowserManageCall("/tabs");
expect(tabsCall?.[2]).toEqual({ timeoutMs: 45_000 });
});
it("uses a longer built-in timeout for browser profiles", async () => {
const program = createBrowserManageProgram({ withParentTimeout: true });
await program.parseAsync(["browser", "profiles"], { from: "user" });
const profilesCall = findBrowserManageCall("/profiles");
expect(profilesCall?.[2]).toEqual({ timeoutMs: 45_000 });
});
it("uses a longer built-in timeout for browser open", async () => {
const program = createBrowserManageProgram({ withParentTimeout: true });
await program.parseAsync(["browser", "open", "https://example.com"], { from: "user" });
const openCall = findBrowserManageCall("/tabs/open");
expect(openCall?.[2]).toEqual({ timeoutMs: 45_000 });
});
});

View File

@@ -1,173 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { registerBrowserStateCommands } from "../../extensions/browser/src/cli/browser-cli-state.js";
import {
createBrowserProgram as createBrowserProgramShared,
getBrowserCliRuntime,
getBrowserCliRuntimeCapture,
} from "./browser-cli-test-helpers.js";
const mocks = vi.hoisted(() => ({
callBrowserRequest: vi.fn(async (..._args: unknown[]) => ({ ok: true })),
runBrowserResizeWithOutput: vi.fn(async (_params: unknown) => {}),
}));
vi.mock("../../extensions/browser/src/cli/browser-cli-shared.js", () => ({
callBrowserRequest: mocks.callBrowserRequest,
}));
vi.mock("../../extensions/browser/src/cli/browser-cli-resize.js", () => ({
runBrowserResizeWithOutput: mocks.runBrowserResizeWithOutput,
}));
vi.mock("../../extensions/browser/src/core-api.js", async () => ({
...(await vi.importActual<object>("../../extensions/browser/src/core-api.js")),
...(await (await import("./browser-cli-test-helpers.js")).createBrowserCliRuntimeMockModule()),
...(await (await import("./browser-cli-test-helpers.js")).createBrowserCliUtilsMockModule()),
}));
describe("browser state option collisions", () => {
const createStateProgram = ({ withGatewayUrl = false } = {}) => {
const { program, browser, parentOpts } = createBrowserProgramShared({ withGatewayUrl });
registerBrowserStateCommands(browser, parentOpts);
return program;
};
const getLastRequest = () => {
const call = mocks.callBrowserRequest.mock.calls.at(-1);
expect(call).toBeDefined();
if (!call) {
throw new Error("expected browser request call");
}
return call[1] as { body?: Record<string, unknown> };
};
const runBrowserCommand = async (argv: string[]) => {
const program = createStateProgram();
await program.parseAsync(["browser", ...argv], { from: "user" });
};
const runBrowserCommandAndGetRequest = async (argv: string[]) => {
await runBrowserCommand(argv);
return getLastRequest();
};
beforeEach(() => {
mocks.callBrowserRequest.mockClear();
mocks.runBrowserResizeWithOutput.mockClear();
getBrowserCliRuntimeCapture().resetRuntimeCapture();
getBrowserCliRuntime().exit.mockImplementation(() => {});
});
it("forwards parent-captured --target-id on `browser cookies set`", async () => {
const request = await runBrowserCommandAndGetRequest([
"cookies",
"set",
"session",
"abc",
"--url",
"https://example.com",
"--target-id",
"tab-1",
]);
expect((request as { body?: { targetId?: string } }).body?.targetId).toBe("tab-1");
});
it("resolves --url via parent when addGatewayClientOptions captures it", async () => {
const program = createStateProgram({ withGatewayUrl: true });
await program.parseAsync(
[
"browser",
"--url",
"ws://gw",
"cookies",
"set",
"session",
"abc",
"--url",
"https://example.com",
],
{ from: "user" },
);
const call = mocks.callBrowserRequest.mock.calls.at(-1);
expect(call).toBeDefined();
const request = call![1] as { body?: { cookie?: { url?: string } } };
expect(request.body?.cookie?.url).toBe("https://example.com");
});
it("inherits --url from parent when subcommand does not provide it", async () => {
const program = createStateProgram({ withGatewayUrl: true });
await program.parseAsync(
["browser", "--url", "https://inherited.example.com", "cookies", "set", "session", "abc"],
{ from: "user" },
);
const call = mocks.callBrowserRequest.mock.calls.at(-1);
expect(call).toBeDefined();
const request = call![1] as { body?: { cookie?: { url?: string } } };
expect(request.body?.cookie?.url).toBe("https://inherited.example.com");
});
it("accepts legacy parent `--json` by parsing payload via positional headers fallback", async () => {
const request = (await runBrowserCommandAndGetRequest([
"set",
"headers",
"--json",
'{"x-auth":"ok"}',
])) as {
body?: { headers?: Record<string, string> };
};
expect(request.body?.headers).toEqual({ "x-auth": "ok" });
});
it("filters non-string header values from JSON payload", async () => {
const request = (await runBrowserCommandAndGetRequest([
"set",
"headers",
"--json",
'{"x-auth":"ok","retry":3,"enabled":true}',
])) as {
body?: { headers?: Record<string, string> };
};
expect(request.body?.headers).toEqual({ "x-auth": "ok" });
});
it("errors when set offline receives an invalid value", async () => {
await runBrowserCommand(["set", "offline", "maybe"]);
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
expect.stringContaining("Expected on|off"),
);
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
});
it("errors when set media receives an invalid value", async () => {
await runBrowserCommand(["set", "media", "sepia"]);
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
expect.stringContaining("Expected dark|light|none"),
);
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
});
it("errors when headers JSON is missing", async () => {
await runBrowserCommand(["set", "headers"]);
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
expect.stringContaining("Missing headers JSON"),
);
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
});
it("errors when headers JSON is not an object", async () => {
await runBrowserCommand(["set", "headers", "--json", "[]"]);
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
expect.stringContaining("Headers JSON must be a JSON object"),
);
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
});
});

View File

@@ -1,60 +0,0 @@
import { Command } from "commander";
import type { GatewayRpcOpts } from "./gateway-rpc.js";
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
import type { CliRuntimeCapture } from "./test-runtime-capture.js";
type BrowserParentOpts = GatewayRpcOpts & {
json?: boolean;
browserProfile?: string;
};
export function createBrowserProgram(params?: { withGatewayUrl?: boolean }): {
program: Command;
browser: Command;
parentOpts: (cmd: Command) => BrowserParentOpts;
} {
const program = new Command();
const browser = program
.command("browser")
.option("--browser-profile <name>", "Browser profile")
.option("--json", "Output JSON", false);
if (params?.withGatewayUrl) {
browser.option("--url <url>", "Gateway WebSocket URL");
}
const parentOpts = (cmd: Command) => cmd.parent?.opts?.() as BrowserParentOpts;
return { program, browser, parentOpts };
}
const browserCliRuntimeState = { capture: null as CliRuntimeCapture | null };
export function getBrowserCliRuntimeCapture(): CliRuntimeCapture {
if (!browserCliRuntimeState.capture) {
throw new Error("runtime capture not initialized");
}
return browserCliRuntimeState.capture;
}
export function getBrowserCliRuntime() {
return getBrowserCliRuntimeCapture().defaultRuntime;
}
export async function mockBrowserCliDefaultRuntime() {
browserCliRuntimeState.capture ??= createCliRuntimeCapture();
return { defaultRuntime: browserCliRuntimeState.capture.defaultRuntime };
}
export async function runCommandWithRuntimeMock(
_runtime: unknown,
action: () => Promise<void>,
onError: (err: unknown) => void,
) {
return await action().catch(onError);
}
export async function createBrowserCliUtilsMockModule() {
return { runCommandWithRuntime: runCommandWithRuntimeMock };
}
export async function createBrowserCliRuntimeMockModule() {
return await mockBrowserCliDefaultRuntime();
}