mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-18 08:31:49 -06:00
3a88142ddd
Summary: - The branch documents friendly browser tab references across docs, the browser skill, CLI help, and tool schema descriptions, and adds tests for target reference resolution and tab alias behavior. - PR surface: Source +24, Tests +328, Docs +9. Total +361 across 21 files. - Reproducibility: yes. for the documentation mismatch by source inspection: current main supports friendly ta ... schema/help surfaces still emphasize raw CDP target ids. Runtime behavior itself is not a new failing path. Automerge notes: - PR branch already contained follow-up commit before automerge: refactor(browser): share tab reference CLI help Validation: - ClawSweeper review passed for head118af80b0b. - Required merge gates passed before the squash merge. Prepared head SHA:118af80b0bReview: https://github.com/openclaw/openclaw/pull/88393#issuecomment-4583558133 Co-authored-by: FMLS <kfliuyang@gmail.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: hxy91819 Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
383 lines
12 KiB
TypeScript
383 lines
12 KiB
TypeScript
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 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("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 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,
|
|
};
|
|
}
|
|
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 tabs: 1 visible, use tab reference t1");
|
|
});
|
|
});
|