mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 17:43:05 +00:00
refactor(browser): dedupe browser and cli command wiring
This commit is contained in:
@@ -13,6 +13,31 @@ export function registerBrowserElementCommands(
|
||||
browser: Command,
|
||||
parentOpts: (cmd: Command) => BrowserParentOpts,
|
||||
) {
|
||||
const runElementAction = async (params: {
|
||||
cmd: Command;
|
||||
body: Record<string, unknown>;
|
||||
successMessage: string | ((result: unknown) => string);
|
||||
timeoutMs?: number;
|
||||
}): Promise<void> => {
|
||||
const { parent, profile } = resolveBrowserActionContext(params.cmd, parentOpts);
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: params.body,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
const successMessage =
|
||||
typeof params.successMessage === "function"
|
||||
? params.successMessage(result)
|
||||
: params.successMessage;
|
||||
logBrowserActionResult(parent, result, successMessage);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
browser
|
||||
.command("click")
|
||||
.description("Click an element by ref from snapshot")
|
||||
@@ -22,7 +47,6 @@ export function registerBrowserElementCommands(
|
||||
.option("--button <left|right|middle>", "Mouse button to use")
|
||||
.option("--modifiers <list>", "Comma-separated modifiers (Shift,Alt,Meta)")
|
||||
.action(async (ref: string | undefined, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) {
|
||||
return;
|
||||
@@ -33,25 +57,22 @@ export function registerBrowserElementCommands(
|
||||
.map((v: string) => v.trim())
|
||||
.filter(Boolean)
|
||||
: undefined;
|
||||
try {
|
||||
const result = await callBrowserAct<{ url?: string }>({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "click",
|
||||
ref: refValue,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
doubleClick: Boolean(opts.double),
|
||||
button: opts.button?.trim() || undefined,
|
||||
modifiers,
|
||||
},
|
||||
});
|
||||
const suffix = result.url ? ` on ${result.url}` : "";
|
||||
logBrowserActionResult(parent, result, `clicked ref ${refValue}${suffix}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: {
|
||||
kind: "click",
|
||||
ref: refValue,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
doubleClick: Boolean(opts.double),
|
||||
button: opts.button?.trim() || undefined,
|
||||
modifiers,
|
||||
},
|
||||
successMessage: (result) => {
|
||||
const url = (result as { url?: unknown }).url;
|
||||
const suffix = typeof url === "string" && url ? ` on ${url}` : "";
|
||||
return `clicked ref ${refValue}${suffix}`;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -63,29 +84,22 @@ export function registerBrowserElementCommands(
|
||||
.option("--slowly", "Type slowly (human-like)", false)
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string | undefined, text: string, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "type",
|
||||
ref: refValue,
|
||||
text,
|
||||
submit: Boolean(opts.submit),
|
||||
slowly: Boolean(opts.slowly),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
});
|
||||
logBrowserActionResult(parent, result, `typed into ref ${refValue}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: {
|
||||
kind: "type",
|
||||
ref: refValue,
|
||||
text,
|
||||
submit: Boolean(opts.submit),
|
||||
slowly: Boolean(opts.slowly),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `typed into ref ${refValue}`,
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -94,18 +108,11 @@ export function registerBrowserElementCommands(
|
||||
.argument("<key>", "Key to press (e.g. Enter)")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (key: string, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: { kind: "press", key, targetId: opts.targetId?.trim() || undefined },
|
||||
});
|
||||
logBrowserActionResult(parent, result, `pressed ${key}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: { kind: "press", key, targetId: opts.targetId?.trim() || undefined },
|
||||
successMessage: `pressed ${key}`,
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -114,18 +121,11 @@ export function registerBrowserElementCommands(
|
||||
.argument("<ref>", "Ref id from snapshot")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: { kind: "hover", ref, targetId: opts.targetId?.trim() || undefined },
|
||||
});
|
||||
logBrowserActionResult(parent, result, `hovered ref ${ref}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: { kind: "hover", ref, targetId: opts.targetId?.trim() || undefined },
|
||||
successMessage: `hovered ref ${ref}`,
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -137,28 +137,22 @@ export function registerBrowserElementCommands(
|
||||
Number(v),
|
||||
)
|
||||
.action(async (ref: string | undefined, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
const refValue = requireRef(ref);
|
||||
if (!refValue) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "scrollIntoView",
|
||||
ref: refValue,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
},
|
||||
timeoutMs: Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined,
|
||||
});
|
||||
logBrowserActionResult(parent, result, `scrolled into view: ${refValue}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : undefined;
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: {
|
||||
kind: "scrollIntoView",
|
||||
ref: refValue,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
timeoutMs,
|
||||
},
|
||||
timeoutMs,
|
||||
successMessage: `scrolled into view: ${refValue}`,
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -168,23 +162,16 @@ export function registerBrowserElementCommands(
|
||||
.argument("<endRef>", "End ref id")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (startRef: string, endRef: string, opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "drag",
|
||||
startRef,
|
||||
endRef,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
});
|
||||
logBrowserActionResult(parent, result, `dragged ${startRef} → ${endRef}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: {
|
||||
kind: "drag",
|
||||
startRef,
|
||||
endRef,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `dragged ${startRef} → ${endRef}`,
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -194,22 +181,15 @@ export function registerBrowserElementCommands(
|
||||
.argument("<values...>", "Option values to select")
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (ref: string, values: string[], opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const result = await callBrowserAct({
|
||||
parent,
|
||||
profile,
|
||||
body: {
|
||||
kind: "select",
|
||||
ref,
|
||||
values,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
});
|
||||
logBrowserActionResult(parent, result, `selected ${values.join(", ")}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
await runElementAction({
|
||||
cmd,
|
||||
body: {
|
||||
kind: "select",
|
||||
ref,
|
||||
values,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `selected ${values.join(", ")}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,36 @@ async function normalizeUploadPaths(paths: string[]): Promise<string[]> {
|
||||
return result.paths;
|
||||
}
|
||||
|
||||
async function runBrowserPostAction<T>(params: {
|
||||
parent: BrowserParentOpts;
|
||||
profile: string | undefined;
|
||||
path: string;
|
||||
body: Record<string, unknown>;
|
||||
timeoutMs: number;
|
||||
describeSuccess: (result: T) => string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const result = await callBrowserRequest<T>(
|
||||
params.parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: params.path,
|
||||
query: params.profile ? { profile: params.profile } : undefined,
|
||||
body: params.body,
|
||||
},
|
||||
{ timeoutMs: params.timeoutMs },
|
||||
);
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.describeSuccess(result));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerBrowserFilesAndDownloadsCommands(
|
||||
browser: Command,
|
||||
parentOpts: (cmd: Command) => BrowserParentOpts,
|
||||
@@ -35,31 +65,19 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
request: { path: string; body: Record<string, unknown> },
|
||||
) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts);
|
||||
const result = await callBrowserRequest<{ download: { path: string } }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: request.path,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
...request.body,
|
||||
targetId,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`downloaded: ${shortenHomePath(result.download.path)}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts);
|
||||
await runBrowserPostAction<{ download: { path: string } }>({
|
||||
parent,
|
||||
profile,
|
||||
path: request.path,
|
||||
body: {
|
||||
...request.body,
|
||||
targetId,
|
||||
timeoutMs,
|
||||
},
|
||||
timeoutMs: timeoutMs ?? 20000,
|
||||
describeSuccess: (result) => `downloaded: ${shortenHomePath(result.download.path)}`,
|
||||
});
|
||||
};
|
||||
|
||||
browser
|
||||
@@ -80,35 +98,23 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
)
|
||||
.action(async (paths: string[], opts, cmd) => {
|
||||
const { parent, profile } = resolveBrowserActionContext(cmd, parentOpts);
|
||||
try {
|
||||
const normalizedPaths = await normalizeUploadPaths(paths);
|
||||
const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts);
|
||||
const result = await callBrowserRequest<{ download: { path: string } }>(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/hooks/file-chooser",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
paths: normalizedPaths,
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
inputRef: opts.inputRef?.trim() || undefined,
|
||||
element: opts.element?.trim() || undefined,
|
||||
targetId,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`upload armed for ${paths.length} file(s)`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
const normalizedPaths = await normalizeUploadPaths(paths);
|
||||
const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts);
|
||||
await runBrowserPostAction({
|
||||
parent,
|
||||
profile,
|
||||
path: "/hooks/file-chooser",
|
||||
body: {
|
||||
paths: normalizedPaths,
|
||||
ref: opts.ref?.trim() || undefined,
|
||||
inputRef: opts.inputRef?.trim() || undefined,
|
||||
element: opts.element?.trim() || undefined,
|
||||
targetId,
|
||||
timeoutMs,
|
||||
},
|
||||
timeoutMs: timeoutMs ?? 20000,
|
||||
describeSuccess: () => `upload armed for ${paths.length} file(s)`,
|
||||
});
|
||||
});
|
||||
|
||||
browser
|
||||
@@ -177,31 +183,19 @@ export function registerBrowserFilesAndDownloadsCommands(
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts);
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/hooks/dialog",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
accept,
|
||||
promptText: opts.prompt?.trim() || undefined,
|
||||
targetId,
|
||||
timeoutMs,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: timeoutMs ?? 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log("dialog armed");
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
const { timeoutMs, targetId } = resolveTimeoutAndTarget(opts);
|
||||
await runBrowserPostAction({
|
||||
parent,
|
||||
profile,
|
||||
path: "/hooks/dialog",
|
||||
body: {
|
||||
accept,
|
||||
promptText: opts.prompt?.trim() || undefined,
|
||||
targetId,
|
||||
timeoutMs,
|
||||
},
|
||||
timeoutMs: timeoutMs ?? 20000,
|
||||
describeSuccess: () => "dialog armed",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerBrowserManageCommands } from "./browser-cli-manage.js";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { createBrowserProgram } from "./browser-cli-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
callBrowserRequest: vi.fn(async (_opts: unknown, req: { path?: string }) =>
|
||||
@@ -44,13 +43,8 @@ vi.mock("../runtime.js", () => ({
|
||||
|
||||
describe("browser manage start timeout option", () => {
|
||||
function createProgram() {
|
||||
const program = new Command();
|
||||
const browser = program
|
||||
.command("browser")
|
||||
.option("--browser-profile <name>", "Browser profile")
|
||||
.option("--json", "Output JSON", false)
|
||||
.option("--timeout <ms>", "Timeout in ms", "30000");
|
||||
const parentOpts = (cmd: Command) => cmd.parent?.opts?.() as BrowserParentOpts;
|
||||
const { program, browser, parentOpts } = createBrowserProgram();
|
||||
browser.option("--timeout <ms>", "Timeout in ms", "30000");
|
||||
registerBrowserManageCommands(browser, parentOpts);
|
||||
return program;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,24 @@ function resolveTargetId(rawTargetId: unknown, command: Command): string | undef
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
async function runMutationRequest(params: {
|
||||
parent: BrowserParentOpts;
|
||||
request: Parameters<typeof callBrowserRequest>[1];
|
||||
successMessage: string;
|
||||
}) {
|
||||
try {
|
||||
const result = await callBrowserRequest(params.parent, params.request, { timeoutMs: 20000 });
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.successMessage);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerBrowserCookiesAndStorageCommands(
|
||||
browser: Command,
|
||||
parentOpts: (cmd: Command) => BrowserParentOpts,
|
||||
@@ -81,29 +99,19 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/cookies/set",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId,
|
||||
cookie: { name, value, url },
|
||||
},
|
||||
await runMutationRequest({
|
||||
parent,
|
||||
request: {
|
||||
method: "POST",
|
||||
path: "/cookies/set",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId,
|
||||
cookie: { name, value, url },
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`cookie set: ${name}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
},
|
||||
successMessage: `cookie set: ${name}`,
|
||||
});
|
||||
});
|
||||
|
||||
cookies
|
||||
@@ -114,28 +122,18 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
const targetId = resolveTargetId(opts.targetId, cmd);
|
||||
try {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/cookies/clear",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId,
|
||||
},
|
||||
await runMutationRequest({
|
||||
parent,
|
||||
request: {
|
||||
method: "POST",
|
||||
path: "/cookies/clear",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log("cookies cleared");
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
},
|
||||
successMessage: "cookies cleared",
|
||||
});
|
||||
});
|
||||
|
||||
const storage = browser.command("storage").description("Read/write localStorage/sessionStorage");
|
||||
@@ -187,30 +185,20 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
const parent = parentOpts(cmd2);
|
||||
const profile = parent?.browserProfile;
|
||||
const targetId = resolveTargetId(opts.targetId, cmd2);
|
||||
try {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: `/storage/${kind}/set`,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
key,
|
||||
value,
|
||||
targetId,
|
||||
},
|
||||
await runMutationRequest({
|
||||
parent,
|
||||
request: {
|
||||
method: "POST",
|
||||
path: `/storage/${kind}/set`,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
key,
|
||||
value,
|
||||
targetId,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`${kind}Storage set: ${key}`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
},
|
||||
successMessage: `${kind}Storage set: ${key}`,
|
||||
});
|
||||
});
|
||||
|
||||
cmd
|
||||
@@ -221,28 +209,18 @@ export function registerBrowserCookiesAndStorageCommands(
|
||||
const parent = parentOpts(cmd2);
|
||||
const profile = parent?.browserProfile;
|
||||
const targetId = resolveTargetId(opts.targetId, cmd2);
|
||||
try {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: `/storage/${kind}/clear`,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId,
|
||||
},
|
||||
await runMutationRequest({
|
||||
parent,
|
||||
request: {
|
||||
method: "POST",
|
||||
path: `/storage/${kind}/clear`,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
targetId,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`${kind}Storage cleared`);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(danger(String(err)));
|
||||
defaultRuntime.exit(1);
|
||||
}
|
||||
},
|
||||
successMessage: `${kind}Storage cleared`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Command } from "commander";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { registerBrowserStateCommands } from "./browser-cli-state.js";
|
||||
import { createBrowserProgram as createBrowserProgramShared } from "./browser-cli-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
callBrowserRequest: vi.fn(async (..._args: unknown[]) => ({ ok: true })),
|
||||
@@ -26,16 +25,8 @@ vi.mock("../runtime.js", () => ({
|
||||
}));
|
||||
|
||||
describe("browser state option collisions", () => {
|
||||
const createBrowserProgram = ({ withGatewayUrl = false } = {}) => {
|
||||
const program = new Command();
|
||||
const browser = program
|
||||
.command("browser")
|
||||
.option("--browser-profile <name>", "Browser profile")
|
||||
.option("--json", "Output JSON", false);
|
||||
if (withGatewayUrl) {
|
||||
browser.option("--url <url>", "Gateway WebSocket URL");
|
||||
}
|
||||
const parentOpts = (cmd: Command) => cmd.parent?.opts?.() as BrowserParentOpts;
|
||||
const createStateProgram = ({ withGatewayUrl = false } = {}) => {
|
||||
const { program, browser, parentOpts } = createBrowserProgramShared({ withGatewayUrl });
|
||||
registerBrowserStateCommands(browser, parentOpts);
|
||||
return program;
|
||||
};
|
||||
@@ -50,7 +41,7 @@ describe("browser state option collisions", () => {
|
||||
};
|
||||
|
||||
const runBrowserCommand = async (argv: string[]) => {
|
||||
const program = createBrowserProgram();
|
||||
const program = createStateProgram();
|
||||
await program.parseAsync(["browser", ...argv], { from: "user" });
|
||||
};
|
||||
|
||||
@@ -83,7 +74,7 @@ describe("browser state option collisions", () => {
|
||||
});
|
||||
|
||||
it("resolves --url via parent when addGatewayClientOptions captures it", async () => {
|
||||
const program = createBrowserProgram({ withGatewayUrl: true });
|
||||
const program = createStateProgram({ withGatewayUrl: true });
|
||||
await program.parseAsync(
|
||||
[
|
||||
"browser",
|
||||
@@ -105,7 +96,7 @@ describe("browser state option collisions", () => {
|
||||
});
|
||||
|
||||
it("inherits --url from parent when subcommand does not provide it", async () => {
|
||||
const program = createBrowserProgram({ withGatewayUrl: true });
|
||||
const program = createStateProgram({ withGatewayUrl: true });
|
||||
await program.parseAsync(
|
||||
["browser", "--url", "https://inherited.example.com", "cookies", "set", "session", "abc"],
|
||||
{ from: "user" },
|
||||
|
||||
19
src/cli/browser-cli-test-helpers.ts
Normal file
19
src/cli/browser-cli-test-helpers.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Command } from "commander";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user