mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(browser): document stable tab references (#88393)
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>
This commit is contained in:
@@ -142,6 +142,9 @@ the optional label, and the raw `targetId`. Agents should pass
|
||||
`suggestedTargetId` back into `focus`, `close`, snapshots, and actions. You can
|
||||
assign a label with `open --label`, `tab new --label`, or `tab label`; labels,
|
||||
tab ids, raw target ids, and unique target-id prefixes are all accepted.
|
||||
The request field is still named `targetId` for compatibility, but it accepts
|
||||
these tab references. Treat raw target ids as diagnostic handles, not durable
|
||||
agent memory.
|
||||
When Chromium replaces the underlying raw target during a navigation or form
|
||||
submit, OpenClaw keeps the stable `tabId`/label attached to the replacement tab
|
||||
when it can prove the match. Raw target ids remain volatile; prefer
|
||||
|
||||
@@ -34,6 +34,11 @@ one-shot headless launch for local managed profiles without changing persisted
|
||||
browser config; attach-only, remote CDP, and existing-session profiles reject
|
||||
that override because OpenClaw does not launch those browser processes.
|
||||
|
||||
For tab endpoints, `targetId` is the compatibility field name. Prefer passing
|
||||
`suggestedTargetId` from `GET /tabs` or `POST /tabs/open`; labels and `tabId`
|
||||
handles such as `t1` are also accepted. Raw CDP target ids and unique raw
|
||||
target-id prefixes still work, but they are volatile diagnostic handles.
|
||||
|
||||
If shared-secret gateway auth is configured, browser HTTP routes require auth too:
|
||||
|
||||
- `Authorization: Bearer <gateway token>`
|
||||
|
||||
@@ -17,8 +17,9 @@ Use this skill when you need the `browser` tool for anything beyond a single pag
|
||||
- `action="tabs"` before opening a new tab if retries/timeouts may have left windows behind.
|
||||
2. Prefer stable tab handles:
|
||||
- Open important tabs with `label`, for example `label="meet"`.
|
||||
- Use `tabId` handles like `t1` or labels like `meet` as `targetId` in later calls.
|
||||
- Avoid relying on raw DevTools `targetId` unless the tool just returned it.
|
||||
- After `action="tabs"` or `action="open"`, store `suggestedTargetId` and pass it as `targetId` in later calls.
|
||||
- `suggestedTargetId` is the label when one exists, otherwise the stable `tabId` handle like `t1`.
|
||||
- Avoid relying on raw DevTools `targetId` except for immediate diagnostics; it can change under Chromium target replacement.
|
||||
3. Read before you click:
|
||||
- Use `action="snapshot"` on the intended `targetId`.
|
||||
- Use the same `targetId` for follow-up actions so refs stay on the same tab.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ACT_MAX_VIEWPORT_DIMENSION } from "./browser/act-policy.js";
|
||||
import { BrowserToolSchema } from "./browser-tool.schema.js";
|
||||
import { ACT_MAX_VIEWPORT_DIMENSION } from "./browser/act-policy.js";
|
||||
|
||||
type SchemaRecord = Record<string, { maximum?: number; properties?: SchemaRecord }>;
|
||||
type SchemaProperty = {
|
||||
description?: string;
|
||||
maximum?: number;
|
||||
properties?: SchemaRecord;
|
||||
};
|
||||
type BrowserSchemaRecord = Record<string, SchemaProperty>;
|
||||
|
||||
describe("browser tool schema", () => {
|
||||
it("advertises the viewport resize maximum on nested and flattened act params", () => {
|
||||
@@ -14,4 +20,13 @@ describe("browser tool schema", () => {
|
||||
expect(requestProperties.width.maximum).toBe(ACT_MAX_VIEWPORT_DIMENSION);
|
||||
expect(requestProperties.height.maximum).toBe(ACT_MAX_VIEWPORT_DIMENSION);
|
||||
});
|
||||
|
||||
it("describes targetId as a compatible tab reference", () => {
|
||||
const properties = BrowserToolSchema.properties as BrowserSchemaRecord;
|
||||
const requestProperties = properties.request.properties as BrowserSchemaRecord;
|
||||
|
||||
expect(properties.targetId.description).toContain("Prefer suggestedTargetId");
|
||||
expect(properties.targetId.description).toContain("raw CDP targetId");
|
||||
expect(requestProperties.targetId.description).toBe(properties.targetId.description);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,13 +51,16 @@ const BROWSER_SNAPSHOT_REFS = ["role", "aria"] as const;
|
||||
|
||||
const BROWSER_IMAGE_TYPES = ["png", "jpeg"] as const;
|
||||
|
||||
const TAB_REFERENCE_DESCRIPTION =
|
||||
"Tab reference. Prefer suggestedTargetId, tabId, or label from tabs output; raw CDP targetId and unique raw prefixes remain supported for compatibility.";
|
||||
|
||||
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
|
||||
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
||||
// The discriminator (kind) determines which properties are relevant; runtime validates.
|
||||
const BrowserActSchema = Type.Object({
|
||||
kind: stringEnum(BROWSER_ACT_KINDS),
|
||||
// Common fields
|
||||
targetId: Type.Optional(Type.String()),
|
||||
targetId: Type.Optional(Type.String({ description: TAB_REFERENCE_DESCRIPTION })),
|
||||
ref: Type.Optional(Type.String()),
|
||||
// click
|
||||
doubleClick: Type.Optional(Type.Boolean()),
|
||||
@@ -103,7 +106,7 @@ export const BrowserToolSchema = Type.Object({
|
||||
profile: Type.Optional(Type.String()),
|
||||
targetUrl: Type.Optional(Type.String()),
|
||||
url: Type.Optional(Type.String()),
|
||||
targetId: Type.Optional(Type.String()),
|
||||
targetId: Type.Optional(Type.String({ description: TAB_REFERENCE_DESCRIPTION })),
|
||||
label: Type.Optional(Type.String()),
|
||||
limit: optionalPositiveIntegerSchema(),
|
||||
maxChars: optionalNonNegativeIntegerSchema(),
|
||||
|
||||
@@ -16,6 +16,9 @@ const { registerBrowserTabRoutes } = await import("./tabs.js");
|
||||
type ProfileContext = ReturnType<typeof createProfileContext>;
|
||||
type TabFixture = {
|
||||
targetId: string;
|
||||
suggestedTargetId?: string;
|
||||
tabId?: string;
|
||||
label?: string;
|
||||
title: string;
|
||||
url: string;
|
||||
type: "page";
|
||||
@@ -286,6 +289,31 @@ describe("browser tab routes", () => {
|
||||
expect(navigationGuardMocks.assertBrowserNavigationResultAllowed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves friendly tab references before focusing tabs", async () => {
|
||||
const profileCtx = createProfileWithTabs([
|
||||
publicTab({
|
||||
targetId: "T1_RAW",
|
||||
suggestedTargetId: "docs",
|
||||
tabId: "t1",
|
||||
label: "docs",
|
||||
}),
|
||||
]);
|
||||
|
||||
const labelResponse = await callTabsFocus({
|
||||
profileCtx,
|
||||
body: { targetId: "docs" },
|
||||
});
|
||||
const tabIdResponse = await callTabsFocus({
|
||||
profileCtx,
|
||||
body: { targetId: "t1" },
|
||||
});
|
||||
|
||||
expect(labelResponse.statusCode).toBe(200);
|
||||
expect(tabIdResponse.statusCode).toBe(200);
|
||||
expect(profileCtx.focusTab).toHaveBeenNthCalledWith(1, "T1_RAW");
|
||||
expect(profileCtx.focusTab).toHaveBeenNthCalledWith(2, "T1_RAW");
|
||||
});
|
||||
|
||||
it("blocks /tabs/action select when target tab URL fails SSRF checks", async () => {
|
||||
navigationGuardMocks.assertBrowserNavigationResultAllowed.mockRejectedValueOnce(
|
||||
new Error("blocked"),
|
||||
|
||||
@@ -396,4 +396,212 @@ describe("browser server-context tab selection state", () => {
|
||||
undefined,
|
||||
]);
|
||||
});
|
||||
|
||||
it("assigns stable tab ids and prefers labels as suggested target ids", async () => {
|
||||
const fetchMock = vi.fn(async (url: unknown) => {
|
||||
const value = String(url);
|
||||
if (!value.includes("/json/list")) {
|
||||
throw new Error(`unexpected fetch: ${value}`);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{
|
||||
id: "DOCS_RAW",
|
||||
title: "Docs",
|
||||
url: "https://docs.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/DOCS_RAW",
|
||||
type: "page",
|
||||
},
|
||||
{
|
||||
id: "APP_RAW",
|
||||
title: "App",
|
||||
url: "https://app.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/APP_RAW",
|
||||
type: "page",
|
||||
},
|
||||
],
|
||||
} as unknown as Response;
|
||||
});
|
||||
|
||||
global.fetch = withBrowserFetchPreconnect(fetchMock);
|
||||
const state = makeState("openclaw");
|
||||
const ctx = createTestBrowserRouteContext({ getState: () => state });
|
||||
const openclaw = ctx.forProfile("openclaw");
|
||||
|
||||
expect(await openclaw.listTabs()).toEqual([
|
||||
expect.objectContaining({
|
||||
targetId: "DOCS_RAW",
|
||||
tabId: "t1",
|
||||
suggestedTargetId: "t1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
targetId: "APP_RAW",
|
||||
tabId: "t2",
|
||||
suggestedTargetId: "t2",
|
||||
}),
|
||||
]);
|
||||
|
||||
await expect(openclaw.labelTab("t1", "docs")).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
targetId: "DOCS_RAW",
|
||||
tabId: "t1",
|
||||
label: "docs",
|
||||
suggestedTargetId: "docs",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("carries a stale alias to a single replacement target", async () => {
|
||||
let listCount = 0;
|
||||
const fetchMock = vi.fn(async (url: unknown) => {
|
||||
const value = String(url);
|
||||
if (!value.includes("/json/list")) {
|
||||
throw new Error(`unexpected fetch: ${value}`);
|
||||
}
|
||||
listCount += 1;
|
||||
const secondList = listCount > 1;
|
||||
return {
|
||||
ok: true,
|
||||
json: async () =>
|
||||
secondList
|
||||
? [
|
||||
{
|
||||
id: "FIRST_RAW",
|
||||
title: "First",
|
||||
url: "https://first.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/FIRST_RAW",
|
||||
type: "page",
|
||||
},
|
||||
{
|
||||
id: "THIRD_RAW",
|
||||
title: "Third",
|
||||
url: "https://third.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/THIRD_RAW",
|
||||
type: "page",
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
id: "FIRST_RAW",
|
||||
title: "First",
|
||||
url: "https://first.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/FIRST_RAW",
|
||||
type: "page",
|
||||
},
|
||||
{
|
||||
id: "SECOND_RAW",
|
||||
title: "Second",
|
||||
url: "https://second.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/SECOND_RAW",
|
||||
type: "page",
|
||||
},
|
||||
],
|
||||
} as unknown as Response;
|
||||
});
|
||||
|
||||
global.fetch = withBrowserFetchPreconnect(fetchMock);
|
||||
const state = makeState("openclaw");
|
||||
const ctx = createTestBrowserRouteContext({ getState: () => state });
|
||||
const openclaw = ctx.forProfile("openclaw");
|
||||
|
||||
expect((await openclaw.listTabs()).map((tab) => tab.tabId)).toEqual(["t1", "t2"]);
|
||||
expect(await openclaw.listTabs()).toEqual([
|
||||
expect.objectContaining({ targetId: "FIRST_RAW", tabId: "t1" }),
|
||||
expect.objectContaining({ targetId: "THIRD_RAW", tabId: "t2" }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("carries stable aliases across confident raw target replacement", async () => {
|
||||
let listCount = 0;
|
||||
const fetchMock = vi.fn(async (url: unknown) => {
|
||||
const value = String(url);
|
||||
if (!value.includes("/json/list")) {
|
||||
throw new Error(`unexpected fetch: ${value}`);
|
||||
}
|
||||
listCount += 1;
|
||||
const targetId = listCount > 1 ? "NEW_RAW" : "OLD_RAW";
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{
|
||||
id: targetId,
|
||||
title: "Checkout",
|
||||
url: "https://shop.example.com/checkout",
|
||||
webSocketDebuggerUrl: `ws://127.0.0.1/devtools/page/${targetId}`,
|
||||
type: "page",
|
||||
},
|
||||
],
|
||||
} as unknown as Response;
|
||||
});
|
||||
|
||||
global.fetch = withBrowserFetchPreconnect(fetchMock);
|
||||
const state = makeState("openclaw");
|
||||
const ctx = createTestBrowserRouteContext({ getState: () => state });
|
||||
const openclaw = ctx.forProfile("openclaw");
|
||||
|
||||
await expect(openclaw.labelTab("OLD_RAW", "checkout")).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
targetId: "OLD_RAW",
|
||||
tabId: "t1",
|
||||
suggestedTargetId: "checkout",
|
||||
}),
|
||||
);
|
||||
const profileState = state.profiles.get("openclaw");
|
||||
if (!profileState) {
|
||||
throw new Error("expected profile state");
|
||||
}
|
||||
profileState.lastTargetId = "OLD_RAW";
|
||||
|
||||
await expect(openclaw.listTabs()).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
targetId: "NEW_RAW",
|
||||
tabId: "t1",
|
||||
label: "checkout",
|
||||
suggestedTargetId: "checkout",
|
||||
}),
|
||||
]);
|
||||
expect(state.profiles.get("openclaw")?.lastTargetId).toBe("NEW_RAW");
|
||||
});
|
||||
|
||||
it("resolves friendly tab references before backend focus and close calls", async () => {
|
||||
const fetchMock = vi.fn(async (url: unknown) => {
|
||||
const value = String(url);
|
||||
if (value.includes("/json/list")) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => [
|
||||
{
|
||||
id: "DOCS_RAW",
|
||||
title: "Docs",
|
||||
url: "https://docs.example.com",
|
||||
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/DOCS_RAW",
|
||||
type: "page",
|
||||
},
|
||||
],
|
||||
} as unknown as Response;
|
||||
}
|
||||
if (value.includes("/json/activate/DOCS_RAW") || value.includes("/json/close/DOCS_RAW")) {
|
||||
return { ok: true } as unknown as Response;
|
||||
}
|
||||
throw new Error(`unexpected fetch: ${value}`);
|
||||
});
|
||||
|
||||
global.fetch = withBrowserFetchPreconnect(fetchMock);
|
||||
const state = makeState("openclaw");
|
||||
const ctx = createTestBrowserRouteContext({ getState: () => state });
|
||||
const openclaw = ctx.forProfile("openclaw");
|
||||
|
||||
await openclaw.labelTab("DOCS_RAW", "docs");
|
||||
await expect(openclaw.ensureTabAvailable("t1")).resolves.toEqual(
|
||||
expect.objectContaining({ targetId: "DOCS_RAW" }),
|
||||
);
|
||||
await openclaw.focusTab("docs");
|
||||
await openclaw.closeTab("t1");
|
||||
|
||||
expect(fetchCallUrls(fetchMock).some((url) => url.includes("/json/activate/DOCS_RAW"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(fetchCallUrls(fetchMock).some((url) => url.includes("/json/close/DOCS_RAW"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
48
extensions/browser/src/browser/target-id.test.ts
Normal file
48
extensions/browser/src/browser/target-id.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveTargetIdFromTabs } from "./target-id.js";
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
targetId: "ABCDEF123456",
|
||||
suggestedTargetId: "docs",
|
||||
tabId: "t1",
|
||||
label: "docs",
|
||||
},
|
||||
{
|
||||
targetId: "ABC999",
|
||||
suggestedTargetId: "t2",
|
||||
tabId: "t2",
|
||||
},
|
||||
];
|
||||
|
||||
describe("resolveTargetIdFromTabs", () => {
|
||||
it("resolves friendly tab references before falling back to raw target prefixes", () => {
|
||||
expect(resolveTargetIdFromTabs("docs", tabs)).toEqual({
|
||||
ok: true,
|
||||
targetId: "ABCDEF123456",
|
||||
});
|
||||
expect(resolveTargetIdFromTabs("t2", tabs)).toEqual({
|
||||
ok: true,
|
||||
targetId: "ABC999",
|
||||
});
|
||||
expect(resolveTargetIdFromTabs("ABCDEF123456", tabs)).toEqual({
|
||||
ok: true,
|
||||
targetId: "ABCDEF123456",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps unique raw target-id prefixes as compatibility input", () => {
|
||||
expect(resolveTargetIdFromTabs("ABCDEF", tabs)).toEqual({
|
||||
ok: true,
|
||||
targetId: "ABCDEF123456",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects ambiguous raw target-id prefixes", () => {
|
||||
expect(resolveTargetIdFromTabs("ABC", tabs)).toEqual({
|
||||
ok: false,
|
||||
reason: "ambiguous",
|
||||
matches: ["ABCDEF123456", "ABC999"],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
parseBrowserNonNegativeIntegerOption,
|
||||
parseBrowserPositiveIntegerOption,
|
||||
type BrowserParentOpts,
|
||||
@@ -65,7 +66,7 @@ export function registerBrowserElementCommands(
|
||||
.command("click")
|
||||
.description("Click an element by ref from snapshot")
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option("--double", "Double click", false)
|
||||
.option("--button <left|right|middle>", "Mouse button to use")
|
||||
.option("--modifiers <list>", "Comma-separated modifiers (Shift,Alt,Meta)")
|
||||
@@ -103,7 +104,7 @@ export function registerBrowserElementCommands(
|
||||
.description("Click viewport coordinates")
|
||||
.argument("<x>", "Viewport x coordinate")
|
||||
.argument("<y>", "Viewport y coordinate")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option("--double", "Double click", false)
|
||||
.option("--button <left|right|middle>", "Mouse button to use")
|
||||
.option("--delay-ms <ms>", "Delay between mouse down/up", (v: string) =>
|
||||
@@ -141,7 +142,7 @@ export function registerBrowserElementCommands(
|
||||
.argument("<text>", "Text to type")
|
||||
.option("--submit", "Press Enter after typing", false)
|
||||
.option("--slowly", "Type slowly (human-like)", false)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (ref: string | undefined, text: string, opts, cmd) => {
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) {
|
||||
@@ -165,7 +166,7 @@ export function registerBrowserElementCommands(
|
||||
.command("press")
|
||||
.description("Press a key")
|
||||
.argument("<key>", "Key to press (e.g. Enter)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (key: string, opts, cmd) => {
|
||||
await runElementAction({
|
||||
cmd,
|
||||
@@ -178,7 +179,7 @@ export function registerBrowserElementCommands(
|
||||
.command("hover")
|
||||
.description("Hover an element by ai ref")
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (ref: string, opts, cmd) => {
|
||||
await runElementAction({
|
||||
cmd,
|
||||
@@ -191,7 +192,7 @@ export function registerBrowserElementCommands(
|
||||
.command("scrollintoview")
|
||||
.description("Scroll an element into view by ref from snapshot")
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option("--timeout-ms <ms>", "How long to wait for scroll (default: 20000)", (v: string) =>
|
||||
parseBrowserPositiveIntegerOption(v, "--timeout-ms"),
|
||||
)
|
||||
@@ -219,7 +220,7 @@ export function registerBrowserElementCommands(
|
||||
.description("Drag from one ref to another")
|
||||
.argument("<startRef>", "Start ref id")
|
||||
.argument("<endRef>", "End ref id")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (startRef: string, endRef: string, opts, cmd) => {
|
||||
await runElementAction({
|
||||
cmd,
|
||||
@@ -238,7 +239,7 @@ export function registerBrowserElementCommands(
|
||||
.description("Select option(s) in a select element")
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.argument("<values...>", "Option values to select")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (ref: string, values: string[], opts, cmd) => {
|
||||
await runElementAction({
|
||||
cmd,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
parseBrowserPositiveIntegerOption,
|
||||
type BrowserParentOpts,
|
||||
@@ -95,7 +96,7 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
.option("--ref <ref>", "Ref id from snapshot to click after arming")
|
||||
.option("--input-ref <ref>", "Ref id for <input type=file> to set directly")
|
||||
.option("--element <selector>", "CSS selector for <input type=file>")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option(
|
||||
"--timeout-ms <ms>",
|
||||
"How long to wait for the next file chooser (default: 120000)",
|
||||
@@ -134,7 +135,7 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
"[path]",
|
||||
"Save path within openclaw temp downloads dir (default: /tmp/openclaw/downloads/...; fallback: os.tmpdir()/openclaw/downloads/...)",
|
||||
)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option(
|
||||
"--timeout-ms <ms>",
|
||||
"How long to wait for the next download (default: 120000)",
|
||||
@@ -157,7 +158,7 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
"<path>",
|
||||
"Save path within openclaw temp downloads dir (e.g. report.pdf or /tmp/openclaw/downloads/report.pdf)",
|
||||
)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option(
|
||||
"--timeout-ms <ms>",
|
||||
"How long to wait for the download to start (default: 120000)",
|
||||
@@ -180,7 +181,7 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
.option("--dismiss", "Dismiss the dialog", false)
|
||||
.option("--prompt <text>", "Prompt response text")
|
||||
.option("--dialog-id <id>", "Pending dialog id from snapshot/browser state")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option(
|
||||
"--timeout-ms <ms>",
|
||||
"How long to wait for the next dialog (default: 120000)",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
parseBrowserNonNegativeIntegerOption,
|
||||
parseBrowserPositiveIntegerOption,
|
||||
type BrowserParentOpts,
|
||||
@@ -39,7 +40,7 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
.description("Fill a form with JSON field descriptors")
|
||||
.option("--fields <json>", "JSON array of field objects")
|
||||
.option("--fields-file <path>", "Read JSON array from a file")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
@@ -80,7 +81,7 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
"How long to wait for each condition (default: 20000)",
|
||||
(v: string) => parseBrowserPositiveIntegerOption(v, "--timeout-ms"),
|
||||
)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (selector: string | undefined, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
@@ -130,7 +131,7 @@ export function registerBrowserFormWaitEvalCommands(
|
||||
"How long to allow the evaluate function to run (default: 20000)",
|
||||
(v: string) => parseBrowserPositiveIntegerOption(v, "--timeout-ms"),
|
||||
)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
if (!opts.fn) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runti
|
||||
import { ACT_MAX_VIEWPORT_DIMENSION } from "../../browser/act-policy.js";
|
||||
import { runBrowserResizeWithOutput } from "../browser-cli-resize.js";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
parseBrowserPositiveIntegerValue,
|
||||
type BrowserParentOpts,
|
||||
@@ -33,7 +34,7 @@ export function registerBrowserNavigationCommands(
|
||||
.command("navigate")
|
||||
.description("Navigate the current tab to a URL")
|
||||
.argument("<url>", "URL to navigate to")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (url: string, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
@@ -66,7 +67,7 @@ export function registerBrowserNavigationCommands(
|
||||
.description("Resize the viewport")
|
||||
.argument("<width>", "Viewport width")
|
||||
.argument("<height>", "Viewport height")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (width: string, height: string, opts, cmd) => {
|
||||
const normalizedWidth = parsePositiveInteger(width, "width");
|
||||
const normalizedHeight = parsePositiveInteger(height, "height");
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { runCommandWithRuntime } from "../core-api.js";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
parseBrowserPositiveIntegerOption,
|
||||
type BrowserParentOpts,
|
||||
@@ -23,7 +24,7 @@ export function registerBrowserActionObserveCommands(
|
||||
.command("console")
|
||||
.description("Get recent console messages")
|
||||
.option("--level <level>", "Filter by level (error, warn, info)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -52,7 +53,7 @@ export function registerBrowserActionObserveCommands(
|
||||
browser
|
||||
.command("pdf")
|
||||
.description("Save page as PDF")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -79,7 +80,7 @@ export function registerBrowserActionObserveCommands(
|
||||
.command("responsebody")
|
||||
.description("Wait for a network response and return its body")
|
||||
.argument("<url>", "URL (exact, substring, or glob like **/api)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option(
|
||||
"--timeout-ms <ms>",
|
||||
"How long to wait for the response (default: 20000)",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { runCommandWithRuntime } from "../core-api.js";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
type BrowserParentOpts,
|
||||
} from "./browser-cli-shared.js";
|
||||
import { danger, defaultRuntime, shortenHomePath } from "./core-api.js";
|
||||
|
||||
const BROWSER_DEBUG_TIMEOUT_MS = 20000;
|
||||
@@ -75,7 +79,7 @@ export function registerBrowserDebugCommands(
|
||||
.command("highlight")
|
||||
.description("Highlight an element by ref")
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (ref: string, opts, cmd) => {
|
||||
await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
|
||||
const result = await callDebugRequest(parent, {
|
||||
@@ -98,7 +102,7 @@ export function registerBrowserDebugCommands(
|
||||
.command("errors")
|
||||
.description("Get recent page errors")
|
||||
.option("--clear", "Clear stored errors after reading", false)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
|
||||
const result = await callDebugRequest<{
|
||||
@@ -132,7 +136,7 @@ export function registerBrowserDebugCommands(
|
||||
.description("Get recent network requests (best-effort)")
|
||||
.option("--filter <text>", "Only show URLs that contain this substring")
|
||||
.option("--clear", "Clear stored requests after reading", false)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
|
||||
const result = await callDebugRequest<{
|
||||
@@ -179,7 +183,7 @@ export function registerBrowserDebugCommands(
|
||||
trace
|
||||
.command("start")
|
||||
.description("Start trace recording")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option("--no-screenshots", "Disable screenshots")
|
||||
.option("--no-snapshots", "Disable snapshots")
|
||||
.option("--sources", "Include sources (bigger traces)", false)
|
||||
@@ -210,7 +214,7 @@ export function registerBrowserDebugCommands(
|
||||
"--out <path>",
|
||||
"Output path within openclaw temp dir (e.g. trace.zip or /tmp/openclaw/trace.zip)",
|
||||
)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
|
||||
const result = await callDebugRequest<{ path: string }>(parent, {
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
parseBrowserNonNegativeIntegerValue,
|
||||
parseBrowserPositiveIntegerValue,
|
||||
@@ -42,7 +43,7 @@ export function registerBrowserInspectCommands(
|
||||
browser
|
||||
.command("screenshot")
|
||||
.description("Capture a screenshot (prints the saved path)")
|
||||
.argument("[targetId]", "CDP target id (or unique prefix)")
|
||||
.argument("[targetId]", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option("--full-page", "Capture full scrollable page", false)
|
||||
.option("--ref <ref>", "ARIA ref from ai snapshot")
|
||||
.option("--element <selector>", "CSS selector for element screenshot")
|
||||
@@ -84,7 +85,7 @@ export function registerBrowserInspectCommands(
|
||||
.command("snapshot")
|
||||
.description("Capture a snapshot (default: ai; aria is the accessibility tree)")
|
||||
.option("--format <aria|ai>", "Snapshot format (default: ai)", "ai")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.option("--limit <n>", "Max nodes (default: 500/800)")
|
||||
.option("--mode <efficient>", "Snapshot preset (efficient)")
|
||||
.option("--efficient", "Use the efficient snapshot preset", false)
|
||||
|
||||
@@ -261,6 +261,35 @@ describe("browser manage output", () => {
|
||||
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();
|
||||
|
||||
@@ -348,6 +377,6 @@ describe("browser manage output", () => {
|
||||
|
||||
const output = lastRuntimeLog();
|
||||
expect(output).toContain("OK gateway: browser control endpoint reachable");
|
||||
expect(output).toContain("OK tabs: 1 visible, use target t1");
|
||||
expect(output).toContain("OK tabs: 1 visible, use tab reference t1");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { runCommandWithRuntime } from "../core-api.js";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
parseBrowserPositiveIntegerValue,
|
||||
type BrowserParentOpts,
|
||||
@@ -132,8 +133,12 @@ function logBrowserTabs(tabs: BrowserTab[], json?: boolean) {
|
||||
defaultRuntime.log(
|
||||
tabs
|
||||
.map((t, i) => {
|
||||
const alias = [t.tabId, t.label ? `label:${t.label}` : undefined].filter(Boolean).join(" ");
|
||||
return `${i + 1}. ${t.title || "(untitled)"}${alias ? ` [${alias}]` : ""}\n ${t.url}\n id: ${t.targetId}`;
|
||||
const labelHandle = t.label ? `label:${t.label}` : undefined;
|
||||
const suggested = t.suggestedTargetId ? `use: ${t.suggestedTargetId}` : undefined;
|
||||
const handles = [suggested, t.tabId ? `tab: ${t.tabId}` : undefined, labelHandle]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return `${i + 1}. ${t.title || "(untitled)"}${handles ? ` [${handles}]` : ""}\n ${t.url}\n id: ${t.targetId}`;
|
||||
})
|
||||
.join("\n"),
|
||||
);
|
||||
@@ -215,7 +220,7 @@ async function runBrowserDoctor(parent: BrowserParentOpts, profile?: string, dee
|
||||
checks.push({
|
||||
name: "tabs",
|
||||
ok: true,
|
||||
detail: `${tabs.length} visible${tabs.length > 0 && tabs[0]?.suggestedTargetId ? `, use target ${tabs[0].suggestedTargetId}` : ""}`,
|
||||
detail: `${tabs.length} visible${tabs.length > 0 && tabs[0]?.suggestedTargetId ? `, use tab reference ${tabs[0].suggestedTargetId}` : ""}`,
|
||||
});
|
||||
} catch (err) {
|
||||
checks.push({
|
||||
@@ -480,7 +485,7 @@ export function registerBrowserManageCommands(
|
||||
tab
|
||||
.command("label")
|
||||
.description("Assign a friendly label to a tab")
|
||||
.argument("<targetId>", "Target id, tab id, label, or unique target id prefix")
|
||||
.argument("<targetId>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.argument("<label>", "Friendly label")
|
||||
.action(async (targetId: string, label: string, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
@@ -571,8 +576,8 @@ export function registerBrowserManageCommands(
|
||||
|
||||
browser
|
||||
.command("focus")
|
||||
.description("Focus a tab by target id, tab id, label, or unique target id prefix")
|
||||
.argument("<targetId>", "Target id, tab id, label, or unique target id prefix")
|
||||
.description("Focus a tab by tab reference")
|
||||
.argument("<targetId>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (targetId: string, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -596,8 +601,8 @@ export function registerBrowserManageCommands(
|
||||
|
||||
browser
|
||||
.command("close")
|
||||
.description("Close a tab (target id optional)")
|
||||
.argument("[targetId]", "Target id, tab id, label, or unique target id prefix (optional)")
|
||||
.description("Close a tab (tab reference optional)")
|
||||
.argument("[targetId]", `${BROWSER_TAB_REFERENCE_HELP} (optional)`)
|
||||
.action(async (targetId: string | undefined, _opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
|
||||
@@ -15,6 +15,9 @@ export type BrowserParentOpts = GatewayRpcOpts & {
|
||||
browserProfile?: string;
|
||||
};
|
||||
|
||||
export const BROWSER_TAB_REFERENCE_HELP =
|
||||
"Tab reference: suggested target id, tab id, label, raw target id, or unique raw prefix";
|
||||
|
||||
type BrowserRequestParams = {
|
||||
method: "GET" | "POST" | "DELETE";
|
||||
path: string;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
type BrowserParentOpts,
|
||||
} from "./browser-cli-shared.js";
|
||||
import { danger, defaultRuntime, inheritOptionFromParent } from "./core-api.js";
|
||||
|
||||
function resolveUrl(opts: { url?: string }, command: Command): string | undefined {
|
||||
@@ -41,35 +45,33 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
) {
|
||||
const cookies = browser.command("cookies").description("Read/write cookies");
|
||||
|
||||
cookies
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
const targetId = resolveTargetId(opts.targetId, cmd);
|
||||
try {
|
||||
const result = await callBrowserRequest<{ cookies?: unknown[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/cookies",
|
||||
query: {
|
||||
targetId,
|
||||
profile,
|
||||
},
|
||||
cookies.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP).action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
const targetId = resolveTargetId(opts.targetId, cmd);
|
||||
try {
|
||||
const result = await callBrowserRequest<{ cookies?: unknown[] }>(
|
||||
parent,
|
||||
{
|
||||
method: "GET",
|
||||
path: "/cookies",
|
||||
query: {
|
||||
targetId,
|
||||
profile,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
defaultRuntime.writeJson(result.cookies ?? []);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.writeJson(result);
|
||||
return;
|
||||
}
|
||||
});
|
||||
defaultRuntime.writeJson(result.cookies ?? []);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
cookies
|
||||
.command("set")
|
||||
@@ -77,7 +79,7 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.argument("<name>", "Cookie name")
|
||||
.argument("<value>", "Cookie value")
|
||||
.option("--url <url>", "Cookie URL scope (recommended)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (name: string, value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -106,7 +108,7 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
cookies
|
||||
.command("clear")
|
||||
.description("Clear all cookies")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -134,7 +136,7 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.command("get")
|
||||
.description(`Get ${kind}Storage (all keys or one key)`)
|
||||
.argument("[key]", "Key (optional)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (key: string | undefined, opts, cmd2) => {
|
||||
const parent = parentOpts(cmd2);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -169,7 +171,7 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
.description(`Set a ${kind}Storage key`)
|
||||
.argument("<key>", "Key")
|
||||
.argument("<value>", "Value")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (key: string, value: string, opts, cmd2) => {
|
||||
const parent = parentOpts(cmd2);
|
||||
const profile = parent?.browserProfile;
|
||||
@@ -193,7 +195,7 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
cmd
|
||||
.command("clear")
|
||||
.description(`Clear all ${kind}Storage keys`)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (opts, cmd2) => {
|
||||
const parent = parentOpts(cmd2);
|
||||
const profile = parent?.browserProfile;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ACT_MAX_VIEWPORT_DIMENSION } from "../browser/act-policy.js";
|
||||
import { runCommandWithRuntime } from "../core-api.js";
|
||||
import { runBrowserResizeWithOutput } from "./browser-cli-resize.js";
|
||||
import {
|
||||
BROWSER_TAB_REFERENCE_HELP,
|
||||
callBrowserRequest,
|
||||
parseBrowserPositiveIntegerValue,
|
||||
type BrowserParentOpts,
|
||||
@@ -96,7 +97,7 @@ export function registerBrowserStateCommands(
|
||||
.description("Set viewport size (alias for resize)")
|
||||
.argument("<width>", "Viewport width")
|
||||
.argument("<height>", "Viewport height")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (widthRaw: string, heightRaw: string, opts, cmd) => {
|
||||
const width = parsePositiveInteger(widthRaw, "width");
|
||||
const height = parsePositiveInteger(heightRaw, "height");
|
||||
@@ -122,7 +123,7 @@ export function registerBrowserStateCommands(
|
||||
.command("offline")
|
||||
.description("Toggle offline mode")
|
||||
.argument("<on|off>", "on/off")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const offline = parseOnOff(value);
|
||||
@@ -147,7 +148,7 @@ export function registerBrowserStateCommands(
|
||||
.description("Set extra HTTP headers (JSON object)")
|
||||
.argument("[headersJson]", "JSON object of headers (alternative to --headers-json)")
|
||||
.option("--headers-json <json>", "JSON object of headers")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (headersJson: string | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
await runBrowserCommand(async () => {
|
||||
@@ -194,7 +195,7 @@ export function registerBrowserStateCommands(
|
||||
.option("--clear", "Clear credentials", false)
|
||||
.argument("[username]", "Username")
|
||||
.argument("[password]", "Password")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (username: string | undefined, password: string | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
await runBrowserSetRequest({
|
||||
@@ -218,7 +219,7 @@ export function registerBrowserStateCommands(
|
||||
.argument("[longitude]", "Longitude")
|
||||
.option("--accuracy <m>", "Accuracy in meters")
|
||||
.option("--origin <origin>", "Origin to grant permissions for")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(
|
||||
async (latitudeRaw: string | undefined, longitudeRaw: string | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
@@ -252,7 +253,7 @@ export function registerBrowserStateCommands(
|
||||
.command("media")
|
||||
.description("Emulate prefers-color-scheme")
|
||||
.argument("<dark|light|none>", "dark/light/none")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const v = normalizeOptionalLowercaseString(value);
|
||||
@@ -278,7 +279,7 @@ export function registerBrowserStateCommands(
|
||||
.command("timezone")
|
||||
.description("Override timezone (CDP)")
|
||||
.argument("<timezoneId>", "Timezone ID (e.g. America/New_York)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (timezoneId: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
await runBrowserSetRequest({
|
||||
@@ -296,7 +297,7 @@ export function registerBrowserStateCommands(
|
||||
.command("locale")
|
||||
.description("Override locale (CDP)")
|
||||
.argument("<locale>", "Locale (e.g. en-US)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (locale: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
await runBrowserSetRequest({
|
||||
@@ -314,7 +315,7 @@ export function registerBrowserStateCommands(
|
||||
.command("device")
|
||||
.description('Apply a Playwright device descriptor (e.g. "iPhone 14")')
|
||||
.argument("<name>", "Device name (Playwright devices)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.option("--target-id <id>", BROWSER_TAB_REFERENCE_HELP)
|
||||
.action(async (name: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
await runBrowserSetRequest({
|
||||
|
||||
@@ -53,8 +53,8 @@ const browserCommandGroupDefinitions: readonly BrowserCommandGroupDefinition[] =
|
||||
command("tabs", "List open tabs"),
|
||||
command("tab", "Tab shortcuts (index-based)"),
|
||||
command("open", "Open a URL in a new tab"),
|
||||
command("focus", "Focus a tab by target id, tab id, label, or unique target id prefix"),
|
||||
command("close", "Close a tab (target id optional)"),
|
||||
command("focus", "Focus a tab by tab reference"),
|
||||
command("close", "Close a tab (tab reference optional)"),
|
||||
command("profiles", "List all browser profiles"),
|
||||
command("create-profile", "Create a new browser profile"),
|
||||
command("delete-profile", "Delete a browser profile"),
|
||||
|
||||
Reference in New Issue
Block a user