// Browser tests cover browser cli manage plugin behavior. import { beforeEach, describe, expect, it } from "vitest"; import { createBrowserManageProgram, getBrowserManageCallBrowserRequestMock, } from "./browser-cli-manage.test-helpers.js"; import { getBrowserCliRuntime, getBrowserCliRuntimeCapture } from "./browser-cli.test-support.js"; function lastRuntimeLog(): string { const calls = getBrowserCliRuntime().log.mock.calls; const value = calls[calls.length - 1]?.[0]; if (typeof value !== "string") { throw new Error("expected browser CLI runtime log"); } return value; } 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, headlessSource: "default", noSandbox: false, executablePath: null, attachOnly: true, } : {}, ); const program = createBrowserManageProgram(); await program.parseAsync(["browser", "--browser-profile", "chrome-live", "status"], { from: "user", }); const output = lastRuntimeLog(); expect(output).toContain("transport: chrome-mcp"); expect(output).toContain("headless: false (default)"); 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 = lastRuntimeLog(); expect(output).toContain( "userDataDir: /Users/test/Library/Application Support/BraveSoftware/Brave-Browser", ); }); it("shows configured cdpUrl for existing-session status", 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: "https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890", chosenBrowser: null, userDataDir: "/Users/test/Library/Application Support/BraveSoftware/Brave-Browser", 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 = lastRuntimeLog(); expect(output).toContain("transport: chrome-mcp"); expect(output).toContain("cdpUrl: https://example.com/chrome?token=supers…7890"); expect(output).not.toContain("userDataDir:"); expect(output).not.toContain("alice"); expect(output).not.toContain("supersecretpasswordvalue1234"); expect(output).not.toContain("supersecrettokenvalue1234567890"); }); 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 = lastRuntimeLog(); expect(output).toContain("chrome-live: running (2 tabs) [existing-session]"); expect(output).toContain("transport: chrome-mcp"); expect(output).not.toContain("port: 0"); }); it("redacts remote cdpUrl details in browser profiles output", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => req.path === "/profiles" ? { profiles: [ { name: "remote", driver: "openclaw", transport: "cdp", running: true, tabCount: 1, isDefault: false, isRemote: true, cdpPort: null, cdpUrl: "https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890", color: "#00AA00", }, ], } : {}, ); const program = createBrowserManageProgram(); await program.parseAsync(["browser", "profiles"], { from: "user" }); const output = lastRuntimeLog(); 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"); }); 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 = lastRuntimeLog(); expect(output).toContain('Created profile "chrome-live"'); expect(output).toContain("transport: chrome-mcp"); expect(output).not.toContain("port: 0"); }); it("shows cdpUrl after creating an existing-session endpoint profile", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => req.path === "/profiles/create" ? { ok: true, profile: "chrome-live", transport: "chrome-mcp", cdpPort: null, cdpUrl: "https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890", userDataDir: null, color: "#00AA00", isRemote: true, } : {}, ); const program = createBrowserManageProgram(); await program.parseAsync( [ "browser", "create-profile", "--name", "chrome-live", "--driver", "existing-session", "--cdp-url", "https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890", ], { from: "user" }, ); const output = lastRuntimeLog(); expect(output).toContain('Created profile "chrome-live"'); expect(output).toContain("transport: chrome-mcp"); 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"); }); it("redacts remote cdpUrl details after creating a remote profile", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => req.path === "/profiles/create" ? { ok: true, profile: "remote", transport: "cdp", cdpPort: null, cdpUrl: "https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890", userDataDir: null, color: "#00AA00", isRemote: true, } : {}, ); const program = createBrowserManageProgram(); await program.parseAsync( [ "browser", "create-profile", "--name", "remote", "--cdp-url", "https://alice:supersecretpasswordvalue1234@example.com/chrome?token=supersecrettokenvalue1234567890", ], { from: "user" }, ); const output = lastRuntimeLog(); 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"); }); 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 = lastRuntimeLog(); 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"); }); it("prints managed graphics facts in status output", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => req.path === "/" ? { enabled: true, profile: "openclaw", driver: "openclaw", transport: "cdp", running: true, cdpReady: true, cdpHttp: true, pid: 4321, cdpPort: 18800, cdpUrl: "http://127.0.0.1:18800", chosenBrowser: "chromium", userDataDir: null, color: "#00AA00", headless: true, noSandbox: false, executablePath: null, attachOnly: false, graphics: { status: "available", observedAt: 123, acceleration: "hardware", renderer: "ANGLE (Intel)", vendor: "Intel", version: "OpenGL ES 3.0", backend: "(gl=angle,angle=metal)", devices: [], featureStatus: {}, disabledFeatures: [], driverBugWorkarounds: [], videoDecoding: [], videoEncoding: [], }, } : {}, ); const program = createBrowserManageProgram(); await program.parseAsync(["browser", "status"], { from: "user" }); expect(lastRuntimeLog()).toContain( "graphics: hardware; renderer ANGLE (Intel); backend (gl=angle,angle=metal)", ); }); it("prints suggested tab references while keeping raw target ids visible", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => req.path === "/tabs" ? { running: true, tabs: [ { targetId: "RAW_TARGET_1", suggestedTargetId: "docs", tabId: "t1", label: "docs", title: "Docs", url: "https://docs.example.com", }, ], } : {}, ); const program = createBrowserManageProgram(); await program.parseAsync(["browser", "tabs"], { from: "user" }); const output = lastRuntimeLog(); expect(output).toContain("use: docs"); expect(output).toContain("tab: t1"); expect(output).toContain("label:docs"); expect(output).toContain("id: RAW_TARGET_1"); }); it("rejects non-integer tab indexes without calling browser actions", async () => { const program = createBrowserManageProgram(); await expect( program.parseAsync(["browser", "tab", "select", "1.9"], { from: "user" }), ).rejects.toThrow("__exit__:1"); expect(getBrowserCliRuntimeCapture().runtimeErrors.at(-1)).toContain( "index must be a positive integer", ); getBrowserCliRuntimeCapture().resetRuntimeCapture(); await expect( program.parseAsync(["browser", "tab", "close", "abc"], { from: "user" }), ).rejects.toThrow("__exit__:1"); expect(getBrowserCliRuntimeCapture().runtimeErrors.at(-1)).toContain( "index must be a positive integer", ); expect(getBrowserManageCallBrowserRequestMock()).not.toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ path: "/tabs/action" }), expect.anything(), ); }); it("accepts signed decimal tab indexes", async () => { const program = createBrowserManageProgram(); await program.parseAsync(["browser", "tab", "select", "+2"], { from: "user" }); expect(getBrowserManageCallBrowserRequestMock()).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ path: "/tabs/action", body: { action: "select", index: 1 }, }), expect.anything(), ); }); it("prints a readable browser doctor report", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => { if (req.path === "/") { return { enabled: true, profile: "openclaw", driver: "openclaw", transport: "cdp", running: true, cdpReady: true, cdpHttp: true, pid: 4321, cdpPort: 18792, cdpUrl: "http://127.0.0.1:18792", chosenBrowser: "chrome", userDataDir: null, color: "#00AA00", headless: false, noSandbox: false, executablePath: null, attachOnly: false, graphics: { status: "available", observedAt: 123, acceleration: "software", renderer: "ANGLE (Google, SwiftShader Device)", vendor: "Google Inc.", version: "OpenGL ES 3.0", backend: "(gl=angle,angle=swiftshader)", devices: [], featureStatus: {}, disabledFeatures: [], driverBugWorkarounds: [], videoDecoding: [], videoEncoding: [], }, }; } if (req.path === "/profiles") { return { profiles: [{ name: "openclaw", running: true }] }; } if (req.path === "/tabs") { return { running: true, tabs: [ { targetId: "abc", tabId: "t1", suggestedTargetId: "t1", title: "Example", url: "https://example.com", }, ], }; } return {}; }); const program = createBrowserManageProgram(); await program.parseAsync(["browser", "doctor"], { from: "user" }); const output = lastRuntimeLog(); expect(output).toContain("OK gateway: browser control endpoint reachable"); expect(output).toContain("OK graphics: software"); expect(output).toContain("OK tabs: 1 visible, use tab reference t1"); }); it("prints a readable browser doctor failure when gateway auth SecretRefs are unavailable", async () => { const error = Object.assign(new Error("gateway.auth.password unavailable"), { code: "GATEWAY_SECRET_REF_UNAVAILABLE", name: "GatewaySecretRefUnavailableError", }); getBrowserManageCallBrowserRequestMock().mockRejectedValueOnce(error); const program = createBrowserManageProgram(); await expect(program.parseAsync(["browser", "doctor"], { from: "user" })).rejects.toThrow( "__exit__:1", ); const output = lastRuntimeLog(); expect(output).toContain( "FAIL gateway: Gateway auth SecretRef is unavailable in this command path", ); expect(output).toContain("OPENCLAW_GATEWAY_TOKEN"); expect(output).not.toContain("GatewaySecretRefUnavailableError"); }); });