@flrande/browserctl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README-CN.md +1155 -0
- package/README.md +1155 -0
- package/apps/browserctl/src/commands/a11y-snapshot.ts +20 -0
- package/apps/browserctl/src/commands/act.ts +20 -0
- package/apps/browserctl/src/commands/common.test.ts +87 -0
- package/apps/browserctl/src/commands/common.ts +191 -0
- package/apps/browserctl/src/commands/console-list.ts +20 -0
- package/apps/browserctl/src/commands/cookie-clear.ts +18 -0
- package/apps/browserctl/src/commands/cookie-get.ts +18 -0
- package/apps/browserctl/src/commands/cookie-set.ts +22 -0
- package/apps/browserctl/src/commands/dialog-arm.ts +20 -0
- package/apps/browserctl/src/commands/dom-query-all.ts +18 -0
- package/apps/browserctl/src/commands/dom-query.ts +18 -0
- package/apps/browserctl/src/commands/download-trigger.ts +22 -0
- package/apps/browserctl/src/commands/download-wait.test.ts +67 -0
- package/apps/browserctl/src/commands/download-wait.ts +27 -0
- package/apps/browserctl/src/commands/element-screenshot.ts +20 -0
- package/apps/browserctl/src/commands/frame-list.ts +16 -0
- package/apps/browserctl/src/commands/frame-snapshot.ts +18 -0
- package/apps/browserctl/src/commands/network-wait-for.ts +100 -0
- package/apps/browserctl/src/commands/profile-list.ts +16 -0
- package/apps/browserctl/src/commands/profile-use.ts +18 -0
- package/apps/browserctl/src/commands/response-body.ts +24 -0
- package/apps/browserctl/src/commands/screenshot.ts +16 -0
- package/apps/browserctl/src/commands/snapshot.ts +16 -0
- package/apps/browserctl/src/commands/status.ts +10 -0
- package/apps/browserctl/src/commands/storage-get.ts +20 -0
- package/apps/browserctl/src/commands/storage-set.ts +22 -0
- package/apps/browserctl/src/commands/tab-close.ts +20 -0
- package/apps/browserctl/src/commands/tab-focus.ts +20 -0
- package/apps/browserctl/src/commands/tab-open.ts +19 -0
- package/apps/browserctl/src/commands/tabs.ts +13 -0
- package/apps/browserctl/src/commands/upload-arm.ts +26 -0
- package/apps/browserctl/src/daemon-client.test.ts +253 -0
- package/apps/browserctl/src/daemon-client.ts +632 -0
- package/apps/browserctl/src/e2e.test.ts +99 -0
- package/apps/browserctl/src/main.test.ts +215 -0
- package/apps/browserctl/src/main.ts +372 -0
- package/apps/browserctl/src/smoke.test.ts +16 -0
- package/apps/browserctl/src/smoke.ts +5 -0
- package/apps/browserd/src/bootstrap.ts +432 -0
- package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +275 -0
- package/apps/browserd/src/chrome-relay-extension-bridge.ts +506 -0
- package/apps/browserd/src/container.ts +1531 -0
- package/apps/browserd/src/main.test.ts +864 -0
- package/apps/browserd/src/main.ts +7 -0
- package/bin/browserctl.cjs +21 -0
- package/bin/browserd.cjs +21 -0
- package/extensions/chrome-relay/README.md +36 -0
- package/extensions/chrome-relay/background.js +1687 -0
- package/extensions/chrome-relay/manifest.json +15 -0
- package/extensions/chrome-relay/popup.html +369 -0
- package/extensions/chrome-relay/popup.js +972 -0
- package/package.json +51 -0
- package/packages/core/src/bootstrap.test.ts +10 -0
- package/packages/core/src/driver-registry.test.ts +45 -0
- package/packages/core/src/driver-registry.ts +22 -0
- package/packages/core/src/driver.ts +47 -0
- package/packages/core/src/index.ts +5 -0
- package/packages/core/src/ref-cache.test.ts +61 -0
- package/packages/core/src/ref-cache.ts +28 -0
- package/packages/core/src/session-store.test.ts +49 -0
- package/packages/core/src/session-store.ts +33 -0
- package/packages/core/src/types.ts +9 -0
- package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +634 -0
- package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +2206 -0
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +264 -0
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +521 -0
- package/packages/driver-chrome-relay/src/index.ts +26 -0
- package/packages/driver-managed/src/index.ts +22 -0
- package/packages/driver-managed/src/managed-driver.test.ts +59 -0
- package/packages/driver-managed/src/managed-driver.ts +125 -0
- package/packages/driver-managed/src/managed-local-driver.test.ts +506 -0
- package/packages/driver-managed/src/managed-local-driver.ts +2021 -0
- package/packages/driver-remote-cdp/src/index.ts +19 -0
- package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +617 -0
- package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +2042 -0
- package/packages/protocol/src/envelope.test.ts +25 -0
- package/packages/protocol/src/envelope.ts +31 -0
- package/packages/protocol/src/errors.test.ts +17 -0
- package/packages/protocol/src/errors.ts +11 -0
- package/packages/protocol/src/index.ts +3 -0
- package/packages/protocol/src/tools.ts +3 -0
- package/packages/transport-mcp-stdio/src/index.ts +3 -0
- package/packages/transport-mcp-stdio/src/sdk-server.ts +139 -0
- package/packages/transport-mcp-stdio/src/server.test.ts +281 -0
- package/packages/transport-mcp-stdio/src/server.ts +183 -0
- package/packages/transport-mcp-stdio/src/tool-map.ts +67 -0
- package/scripts/smoke.ps1 +127 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type A11ySnapshotCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runA11ySnapshotCommand(args: string[]): Promise<A11ySnapshotCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const selector = context.positional[1];
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<A11ySnapshotCommandResult>(
|
|
12
|
+
"browser.a11y.snapshot",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
...(typeof selector === "string" && selector.trim().length > 0
|
|
16
|
+
? { selector: selector.trim() }
|
|
17
|
+
: {})
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type ActCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runActCommand(args: string[]): Promise<ActCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const actionType = requirePositionalArg(context, 0, "actionType");
|
|
9
|
+
const targetId = requirePositionalArg(context, 1, "targetId");
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<ActCommandResult>(
|
|
12
|
+
"browser.act",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
action: {
|
|
16
|
+
type: actionType
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DAEMON_STARTUP_ARGUMENT,
|
|
5
|
+
buildToolArguments,
|
|
6
|
+
parseCommandContext
|
|
7
|
+
} from "./common";
|
|
8
|
+
|
|
9
|
+
const ORIGINAL_AUTH_TOKEN = process.env.BROWSERCTL_AUTH_TOKEN;
|
|
10
|
+
const ORIGINAL_BROWSER = process.env.BROWSERCTL_BROWSER;
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
if (ORIGINAL_AUTH_TOKEN === undefined) {
|
|
14
|
+
delete process.env.BROWSERCTL_AUTH_TOKEN;
|
|
15
|
+
} else {
|
|
16
|
+
process.env.BROWSERCTL_AUTH_TOKEN = ORIGINAL_AUTH_TOKEN;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (ORIGINAL_BROWSER === undefined) {
|
|
20
|
+
delete process.env.BROWSERCTL_BROWSER;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
process.env.BROWSERCTL_BROWSER = ORIGINAL_BROWSER;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("parseCommandContext", () => {
|
|
28
|
+
it("uses BROWSERCTL_AUTH_TOKEN by default", () => {
|
|
29
|
+
process.env.BROWSERCTL_AUTH_TOKEN = "env-token";
|
|
30
|
+
|
|
31
|
+
const context = parseCommandContext(["target:1"]);
|
|
32
|
+
|
|
33
|
+
expect(context.authToken).toBe("env-token");
|
|
34
|
+
expect(context.positional).toEqual(["target:1"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("allows --token to override the environment token", () => {
|
|
38
|
+
process.env.BROWSERCTL_AUTH_TOKEN = "env-token";
|
|
39
|
+
|
|
40
|
+
const context = parseCommandContext(["--token", "cli-token", "target:1"]);
|
|
41
|
+
|
|
42
|
+
expect(context.authToken).toBe("cli-token");
|
|
43
|
+
expect(context.positional).toEqual(["target:1"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("throws when --token is missing a value", () => {
|
|
47
|
+
expect(() => parseCommandContext(["--token"])).toThrow("Missing value for --token.");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("supports --browser edge preset and forwards daemon startup metadata", () => {
|
|
51
|
+
const context = parseCommandContext(["--browser", "edge", "target:1"]);
|
|
52
|
+
|
|
53
|
+
expect(context.daemonStartup).toEqual({
|
|
54
|
+
managedLocal: {
|
|
55
|
+
browserName: "chromium",
|
|
56
|
+
channel: "msedge"
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
expect(buildToolArguments(context)).toMatchObject({
|
|
60
|
+
[DAEMON_STARTUP_ARGUMENT]: {
|
|
61
|
+
managedLocal: {
|
|
62
|
+
browserName: "chromium",
|
|
63
|
+
channel: "msedge"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("supports BROWSERCTL_BROWSER env preset", () => {
|
|
70
|
+
process.env.BROWSERCTL_BROWSER = "edge";
|
|
71
|
+
|
|
72
|
+
const context = parseCommandContext(["target:1"]);
|
|
73
|
+
|
|
74
|
+
expect(context.daemonStartup).toEqual({
|
|
75
|
+
managedLocal: {
|
|
76
|
+
browserName: "chromium",
|
|
77
|
+
channel: "msedge"
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("throws on unsupported --browser values", () => {
|
|
83
|
+
expect(() => parseCommandContext(["--browser", "opera"])).toThrow(
|
|
84
|
+
"Invalid value for --browser: opera. Supported values: chromium, chrome, edge."
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export type ManagedLocalBrowserPreset = "chromium" | "chrome" | "edge";
|
|
2
|
+
|
|
3
|
+
export type DaemonStartupConfig = {
|
|
4
|
+
managedLocal: {
|
|
5
|
+
browserName: "chromium";
|
|
6
|
+
channel?: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CommandContext = {
|
|
11
|
+
sessionId: string;
|
|
12
|
+
profile?: string;
|
|
13
|
+
authToken?: string;
|
|
14
|
+
daemonStartup?: DaemonStartupConfig;
|
|
15
|
+
positional: string[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const DAEMON_STARTUP_ARGUMENT = "__browserctlDaemonStartup";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_SESSION_ID = "cli:local";
|
|
21
|
+
const SUPPORTED_BROWSER_PRESETS = new Set<ManagedLocalBrowserPreset>([
|
|
22
|
+
"chromium",
|
|
23
|
+
"chrome",
|
|
24
|
+
"edge"
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function resolveAuthToken(value: string | undefined): string | undefined {
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const trimmedValue = value.trim();
|
|
33
|
+
return trimmedValue.length > 0 ? trimmedValue : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function requireValue(args: string[], index: number, option: string): string {
|
|
37
|
+
const value = args[index + 1];
|
|
38
|
+
if (value === undefined || value.startsWith("--")) {
|
|
39
|
+
throw new Error(`Missing value for ${option}.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseBrowserPreset(
|
|
46
|
+
value: string | undefined,
|
|
47
|
+
optionName: string
|
|
48
|
+
): ManagedLocalBrowserPreset | undefined {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
54
|
+
if (normalizedValue.length === 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (SUPPORTED_BROWSER_PRESETS.has(normalizedValue as ManagedLocalBrowserPreset)) {
|
|
59
|
+
return normalizedValue as ManagedLocalBrowserPreset;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Invalid value for ${optionName}: ${value}. Supported values: chromium, chrome, edge.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveDaemonStartupConfig(
|
|
68
|
+
browserPreset: ManagedLocalBrowserPreset | undefined
|
|
69
|
+
): DaemonStartupConfig | undefined {
|
|
70
|
+
if (browserPreset === undefined) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (browserPreset === "edge") {
|
|
75
|
+
return {
|
|
76
|
+
managedLocal: {
|
|
77
|
+
browserName: "chromium",
|
|
78
|
+
channel: "msedge"
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (browserPreset === "chrome") {
|
|
84
|
+
return {
|
|
85
|
+
managedLocal: {
|
|
86
|
+
browserName: "chromium",
|
|
87
|
+
channel: "chrome"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
managedLocal: {
|
|
94
|
+
browserName: "chromium"
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function parseCommandContext(args: string[]): CommandContext {
|
|
100
|
+
const positional: string[] = [];
|
|
101
|
+
let sessionId = DEFAULT_SESSION_ID;
|
|
102
|
+
let profile: string | undefined;
|
|
103
|
+
let authToken = resolveAuthToken(process.env.BROWSERCTL_AUTH_TOKEN);
|
|
104
|
+
let browserPreset = parseBrowserPreset(process.env.BROWSERCTL_BROWSER, "BROWSERCTL_BROWSER");
|
|
105
|
+
|
|
106
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
107
|
+
const token = args[index];
|
|
108
|
+
|
|
109
|
+
if (token === "--session") {
|
|
110
|
+
sessionId = requireValue(args, index, "--session");
|
|
111
|
+
index += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (token === "--profile") {
|
|
116
|
+
profile = requireValue(args, index, "--profile");
|
|
117
|
+
index += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (token === "--token") {
|
|
122
|
+
authToken = resolveAuthToken(requireValue(args, index, "--token"));
|
|
123
|
+
if (authToken === undefined) {
|
|
124
|
+
throw new Error("Missing value for --token.");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
index += 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (token === "--browser") {
|
|
132
|
+
browserPreset = parseBrowserPreset(requireValue(args, index, "--browser"), "--browser");
|
|
133
|
+
index += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (token === "--") {
|
|
138
|
+
positional.push(...args.slice(index + 1));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
positional.push(token);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
sessionId,
|
|
147
|
+
profile,
|
|
148
|
+
authToken,
|
|
149
|
+
daemonStartup: resolveDaemonStartupConfig(browserPreset),
|
|
150
|
+
positional
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function buildToolArguments(
|
|
155
|
+
context: CommandContext,
|
|
156
|
+
args: Record<string, unknown> = {}
|
|
157
|
+
): Record<string, unknown> {
|
|
158
|
+
const baseArguments: Record<string, unknown> = {
|
|
159
|
+
sessionId: context.sessionId
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
if (context.profile !== undefined) {
|
|
163
|
+
baseArguments.profile = context.profile;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (context.authToken !== undefined) {
|
|
167
|
+
baseArguments.authToken = context.authToken;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (context.daemonStartup !== undefined) {
|
|
171
|
+
baseArguments[DAEMON_STARTUP_ARGUMENT] = context.daemonStartup;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
...baseArguments,
|
|
176
|
+
...args
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function requirePositionalArg(
|
|
181
|
+
context: CommandContext,
|
|
182
|
+
index: number,
|
|
183
|
+
name: string
|
|
184
|
+
): string {
|
|
185
|
+
const value = context.positional[index];
|
|
186
|
+
if (value === undefined) {
|
|
187
|
+
throw new Error(`Missing required argument: ${name}.`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type ConsoleListCommandResult = {
|
|
5
|
+
driver: string;
|
|
6
|
+
targetId: string;
|
|
7
|
+
entries: unknown[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function runConsoleListCommand(args: string[]): Promise<ConsoleListCommandResult> {
|
|
11
|
+
const context = parseCommandContext(args);
|
|
12
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
13
|
+
|
|
14
|
+
return await callDaemonTool<ConsoleListCommandResult>(
|
|
15
|
+
"browser.console.list",
|
|
16
|
+
buildToolArguments(context, {
|
|
17
|
+
targetId
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type CookieClearCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runCookieClearCommand(args: string[]): Promise<CookieClearCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const name = context.positional[1];
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<CookieClearCommandResult>(
|
|
12
|
+
"browser.cookie.clear",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
...(typeof name === "string" && name.trim().length > 0 ? { name: name.trim() } : {})
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type CookieGetCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runCookieGetCommand(args: string[]): Promise<CookieGetCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const name = context.positional[1];
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<CookieGetCommandResult>(
|
|
12
|
+
"browser.cookie.get",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
...(typeof name === "string" && name.trim().length > 0 ? { name: name.trim() } : {})
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type CookieSetCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runCookieSetCommand(args: string[]): Promise<CookieSetCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const name = requirePositionalArg(context, 1, "name");
|
|
10
|
+
const value = requirePositionalArg(context, 2, "value");
|
|
11
|
+
const url = context.positional[3];
|
|
12
|
+
|
|
13
|
+
return await callDaemonTool<CookieSetCommandResult>(
|
|
14
|
+
"browser.cookie.set",
|
|
15
|
+
buildToolArguments(context, {
|
|
16
|
+
targetId,
|
|
17
|
+
name,
|
|
18
|
+
value,
|
|
19
|
+
...(typeof url === "string" && url.trim().length > 0 ? { url: url.trim() } : {})
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type DialogArmCommandResult = {
|
|
5
|
+
driver: string;
|
|
6
|
+
targetId: string;
|
|
7
|
+
armed: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function runDialogArmCommand(args: string[]): Promise<DialogArmCommandResult> {
|
|
11
|
+
const context = parseCommandContext(args);
|
|
12
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
13
|
+
|
|
14
|
+
return await callDaemonTool<DialogArmCommandResult>(
|
|
15
|
+
"browser.dialog.arm",
|
|
16
|
+
buildToolArguments(context, {
|
|
17
|
+
targetId
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type DomQueryAllCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runDomQueryAllCommand(args: string[]): Promise<DomQueryAllCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const selector = requirePositionalArg(context, 1, "selector");
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<DomQueryAllCommandResult>(
|
|
12
|
+
"browser.dom.queryAll",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
selector
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type DomQueryCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runDomQueryCommand(args: string[]): Promise<DomQueryCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const selector = requirePositionalArg(context, 1, "selector");
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<DomQueryCommandResult>(
|
|
12
|
+
"browser.dom.query",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
selector
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type DownloadTriggerCommandResult = {
|
|
5
|
+
driver: string;
|
|
6
|
+
targetId: string;
|
|
7
|
+
triggered: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function runDownloadTriggerCommand(
|
|
11
|
+
args: string[]
|
|
12
|
+
): Promise<DownloadTriggerCommandResult> {
|
|
13
|
+
const context = parseCommandContext(args);
|
|
14
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
15
|
+
|
|
16
|
+
return await callDaemonTool<DownloadTriggerCommandResult>(
|
|
17
|
+
"browser.download.trigger",
|
|
18
|
+
buildToolArguments(context, {
|
|
19
|
+
targetId
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const { callDaemonToolMock } = vi.hoisted(() => ({
|
|
4
|
+
callDaemonToolMock: vi.fn()
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("../daemon-client", () => ({
|
|
8
|
+
callDaemonTool: callDaemonToolMock
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { runDownloadWaitCommand } from "./download-wait";
|
|
12
|
+
|
|
13
|
+
const ORIGINAL_AUTH_TOKEN = process.env.BROWSERCTL_AUTH_TOKEN;
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
callDaemonToolMock.mockReset();
|
|
17
|
+
|
|
18
|
+
if (ORIGINAL_AUTH_TOKEN === undefined) {
|
|
19
|
+
delete process.env.BROWSERCTL_AUTH_TOKEN;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
process.env.BROWSERCTL_AUTH_TOKEN = ORIGINAL_AUTH_TOKEN;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("runDownloadWaitCommand", () => {
|
|
27
|
+
it("forwards optional download path when provided", async () => {
|
|
28
|
+
callDaemonToolMock.mockResolvedValue({
|
|
29
|
+
driver: "managed",
|
|
30
|
+
targetId: "target:1",
|
|
31
|
+
download: {}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await runDownloadWaitCommand(["target:1", "downloads/file.bin"]);
|
|
35
|
+
|
|
36
|
+
expect(callDaemonToolMock).toHaveBeenCalledTimes(1);
|
|
37
|
+
expect(callDaemonToolMock).toHaveBeenCalledWith(
|
|
38
|
+
"browser.download.wait",
|
|
39
|
+
expect.objectContaining({
|
|
40
|
+
sessionId: "cli:local",
|
|
41
|
+
targetId: "target:1",
|
|
42
|
+
path: "downloads/file.bin"
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("uses --token over environment token", async () => {
|
|
48
|
+
process.env.BROWSERCTL_AUTH_TOKEN = "env-token";
|
|
49
|
+
callDaemonToolMock.mockResolvedValue({
|
|
50
|
+
driver: "managed",
|
|
51
|
+
targetId: "target:1",
|
|
52
|
+
download: {}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await runDownloadWaitCommand(["--token", "cli-token", "target:1"]);
|
|
56
|
+
|
|
57
|
+
expect(callDaemonToolMock).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(callDaemonToolMock).toHaveBeenCalledWith(
|
|
59
|
+
"browser.download.wait",
|
|
60
|
+
expect.objectContaining({
|
|
61
|
+
sessionId: "cli:local",
|
|
62
|
+
targetId: "target:1",
|
|
63
|
+
authToken: "cli-token"
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type DownloadWaitCommandResult = {
|
|
5
|
+
driver: string;
|
|
6
|
+
targetId: string;
|
|
7
|
+
download: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function runDownloadWaitCommand(args: string[]): Promise<DownloadWaitCommandResult> {
|
|
11
|
+
const context = parseCommandContext(args);
|
|
12
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
13
|
+
const path = context.positional[1];
|
|
14
|
+
|
|
15
|
+
return await callDaemonTool<DownloadWaitCommandResult>(
|
|
16
|
+
"browser.download.wait",
|
|
17
|
+
buildToolArguments(
|
|
18
|
+
context,
|
|
19
|
+
path === undefined
|
|
20
|
+
? { targetId }
|
|
21
|
+
: {
|
|
22
|
+
targetId,
|
|
23
|
+
path
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type ElementScreenshotCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runElementScreenshotCommand(
|
|
7
|
+
args: string[]
|
|
8
|
+
): Promise<ElementScreenshotCommandResult> {
|
|
9
|
+
const context = parseCommandContext(args);
|
|
10
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
11
|
+
const selector = requirePositionalArg(context, 1, "selector");
|
|
12
|
+
|
|
13
|
+
return await callDaemonTool<ElementScreenshotCommandResult>(
|
|
14
|
+
"browser.element.screenshot",
|
|
15
|
+
buildToolArguments(context, {
|
|
16
|
+
targetId,
|
|
17
|
+
selector
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type FrameListCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runFrameListCommand(args: string[]): Promise<FrameListCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
|
|
10
|
+
return await callDaemonTool<FrameListCommandResult>(
|
|
11
|
+
"browser.frame.list",
|
|
12
|
+
buildToolArguments(context, {
|
|
13
|
+
targetId
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type FrameSnapshotCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runFrameSnapshotCommand(args: string[]): Promise<FrameSnapshotCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
9
|
+
const frameId = requirePositionalArg(context, 1, "frameId");
|
|
10
|
+
|
|
11
|
+
return await callDaemonTool<FrameSnapshotCommandResult>(
|
|
12
|
+
"browser.frame.snapshot",
|
|
13
|
+
buildToolArguments(context, {
|
|
14
|
+
targetId,
|
|
15
|
+
frameId
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
}
|