@flrande/browserctl 0.5.0-dev.22.1 → 0.6.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/dist/client.d.ts +34 -0
- package/dist/client.js +138 -0
- package/dist/commandRegistry.d.ts +16 -0
- package/dist/commandRegistry.js +21 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +24 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/runCli.d.ts +5 -0
- package/dist/runCli.js +170 -0
- package/package.json +32 -59
- package/INSTALL-CN.md +0 -92
- package/INSTALL.md +0 -92
- package/LICENSE +0 -21
- package/README-CN.md +0 -69
- package/README.md +0 -69
- package/apps/browserctl/src/commands/a11y-snapshot.ts +0 -20
- package/apps/browserctl/src/commands/act.test.ts +0 -71
- package/apps/browserctl/src/commands/act.ts +0 -64
- package/apps/browserctl/src/commands/command-wrappers.test.ts +0 -688
- package/apps/browserctl/src/commands/common.test.ts +0 -87
- package/apps/browserctl/src/commands/common.ts +0 -191
- package/apps/browserctl/src/commands/console-list.test.ts +0 -102
- package/apps/browserctl/src/commands/console-list.ts +0 -108
- package/apps/browserctl/src/commands/cookie-clear.ts +0 -18
- package/apps/browserctl/src/commands/cookie-get.ts +0 -18
- package/apps/browserctl/src/commands/cookie-set.ts +0 -22
- package/apps/browserctl/src/commands/dialog-arm.ts +0 -20
- package/apps/browserctl/src/commands/dom-query-all.ts +0 -18
- package/apps/browserctl/src/commands/dom-query.ts +0 -18
- package/apps/browserctl/src/commands/download-trigger.ts +0 -22
- package/apps/browserctl/src/commands/download-wait.test.ts +0 -67
- package/apps/browserctl/src/commands/download-wait.ts +0 -27
- package/apps/browserctl/src/commands/element-screenshot.ts +0 -20
- package/apps/browserctl/src/commands/frame-list.ts +0 -16
- package/apps/browserctl/src/commands/frame-snapshot.ts +0 -18
- package/apps/browserctl/src/commands/har-export.test.ts +0 -112
- package/apps/browserctl/src/commands/har-export.ts +0 -120
- package/apps/browserctl/src/commands/memory-delete.ts +0 -20
- package/apps/browserctl/src/commands/memory-inspect.ts +0 -20
- package/apps/browserctl/src/commands/memory-list.ts +0 -90
- package/apps/browserctl/src/commands/memory-mode-set.ts +0 -29
- package/apps/browserctl/src/commands/memory-purge.ts +0 -16
- package/apps/browserctl/src/commands/memory-resolve.ts +0 -56
- package/apps/browserctl/src/commands/memory-status.ts +0 -16
- package/apps/browserctl/src/commands/memory-ttl-set.ts +0 -28
- package/apps/browserctl/src/commands/memory-upsert.ts +0 -142
- package/apps/browserctl/src/commands/network-list.test.ts +0 -110
- package/apps/browserctl/src/commands/network-list.ts +0 -112
- package/apps/browserctl/src/commands/network-wait-for.test.ts +0 -90
- package/apps/browserctl/src/commands/network-wait-for.ts +0 -100
- package/apps/browserctl/src/commands/profile-list.ts +0 -16
- package/apps/browserctl/src/commands/profile-use.ts +0 -18
- package/apps/browserctl/src/commands/response-body.ts +0 -24
- package/apps/browserctl/src/commands/screenshot.ts +0 -16
- package/apps/browserctl/src/commands/session-drop.test.ts +0 -36
- package/apps/browserctl/src/commands/session-drop.ts +0 -16
- package/apps/browserctl/src/commands/session-list.test.ts +0 -81
- package/apps/browserctl/src/commands/session-list.ts +0 -70
- package/apps/browserctl/src/commands/snapshot.ts +0 -16
- package/apps/browserctl/src/commands/status.ts +0 -10
- package/apps/browserctl/src/commands/storage-get.ts +0 -20
- package/apps/browserctl/src/commands/storage-set.ts +0 -22
- package/apps/browserctl/src/commands/tab-close.ts +0 -20
- package/apps/browserctl/src/commands/tab-focus.ts +0 -20
- package/apps/browserctl/src/commands/tab-open.ts +0 -19
- package/apps/browserctl/src/commands/tabs.ts +0 -13
- package/apps/browserctl/src/commands/trace-get.test.ts +0 -61
- package/apps/browserctl/src/commands/trace-get.ts +0 -62
- package/apps/browserctl/src/commands/upload-arm.ts +0 -26
- package/apps/browserctl/src/commands/wait-element.test.ts +0 -80
- package/apps/browserctl/src/commands/wait-element.ts +0 -76
- package/apps/browserctl/src/commands/wait-text.test.ts +0 -110
- package/apps/browserctl/src/commands/wait-text.ts +0 -93
- package/apps/browserctl/src/commands/wait-url.test.ts +0 -80
- package/apps/browserctl/src/commands/wait-url.ts +0 -76
- package/apps/browserctl/src/daemon-client.test.ts +0 -512
- package/apps/browserctl/src/daemon-client.ts +0 -632
- package/apps/browserctl/src/e2e.test.ts +0 -103
- package/apps/browserctl/src/main.dispatch.test.ts +0 -461
- package/apps/browserctl/src/main.test.ts +0 -334
- package/apps/browserctl/src/main.ts +0 -957
- package/apps/browserctl/src/smoke.e2e.test.ts +0 -97
- package/apps/browserctl/src/test-port.ts +0 -26
- package/apps/browserd/src/bootstrap.ts +0 -432
- package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +0 -250
- package/apps/browserd/src/chrome-relay-extension-bridge.ts +0 -506
- package/apps/browserd/src/container.ts +0 -3088
- package/apps/browserd/src/main.test.ts +0 -1522
- package/apps/browserd/src/main.ts +0 -7
- package/apps/browserd/src/test-port.ts +0 -26
- package/apps/browserd/src/tool-matrix.test.ts +0 -887
- package/bin/browserctl.cjs +0 -21
- package/bin/browserd.cjs +0 -21
- package/extensions/chrome-relay/README-CN.md +0 -39
- package/extensions/chrome-relay/README.md +0 -39
- package/extensions/chrome-relay/background.js +0 -1687
- package/extensions/chrome-relay/manifest.json +0 -15
- package/extensions/chrome-relay/popup.html +0 -369
- package/extensions/chrome-relay/popup.js +0 -972
- package/packages/core/src/bootstrap.test.ts +0 -10
- package/packages/core/src/driver-registry.test.ts +0 -45
- package/packages/core/src/driver-registry.ts +0 -22
- package/packages/core/src/driver.ts +0 -47
- package/packages/core/src/index.ts +0 -6
- package/packages/core/src/navigation-memory.test.ts +0 -259
- package/packages/core/src/navigation-memory.ts +0 -360
- package/packages/core/src/ref-cache.test.ts +0 -61
- package/packages/core/src/ref-cache.ts +0 -28
- package/packages/core/src/session-store.test.ts +0 -82
- package/packages/core/src/session-store.ts +0 -138
- package/packages/core/src/types.ts +0 -9
- package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +0 -744
- package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +0 -2429
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +0 -264
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +0 -521
- package/packages/driver-chrome-relay/src/index.ts +0 -26
- package/packages/driver-managed/src/index.ts +0 -22
- package/packages/driver-managed/src/managed-driver.test.ts +0 -183
- package/packages/driver-managed/src/managed-driver.ts +0 -341
- package/packages/driver-managed/src/managed-local-driver.test.ts +0 -608
- package/packages/driver-managed/src/managed-local-driver.ts +0 -2243
- package/packages/driver-remote-cdp/src/index.ts +0 -19
- package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +0 -727
- package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +0 -2264
- package/packages/protocol/src/envelope.test.ts +0 -25
- package/packages/protocol/src/envelope.ts +0 -31
- package/packages/protocol/src/errors.test.ts +0 -17
- package/packages/protocol/src/errors.ts +0 -11
- package/packages/protocol/src/index.ts +0 -3
- package/packages/protocol/src/tools.ts +0 -3
- package/packages/transport-mcp-stdio/src/index.ts +0 -3
- package/packages/transport-mcp-stdio/src/sdk-server.ts +0 -139
- package/packages/transport-mcp-stdio/src/server.test.ts +0 -281
- package/packages/transport-mcp-stdio/src/server.ts +0 -183
- package/packages/transport-mcp-stdio/src/tool-map.ts +0 -84
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
import { createServer, type AddressInfo } from "node:net";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
-
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
-
|
|
8
|
-
import { DAEMON_STARTUP_ARGUMENT } from "./commands/common";
|
|
9
|
-
import { callDaemonTool, getDaemonStatus, stopDaemon } from "./daemon-client";
|
|
10
|
-
|
|
11
|
-
type CapturedRequest = {
|
|
12
|
-
id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
traceId: string;
|
|
15
|
-
arguments: Record<string, unknown>;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const ORIGINAL_DAEMON_PORT = process.env.BROWSERCTL_DAEMON_PORT;
|
|
19
|
-
const ORIGINAL_AUTH_TOKEN = process.env.BROWSERCTL_AUTH_TOKEN;
|
|
20
|
-
const ORIGINAL_DAEMON_STARTUP_TIMEOUT_MS = process.env.BROWSERCTL_DAEMON_STARTUP_TIMEOUT_MS;
|
|
21
|
-
const ORIGINAL_DAEMON_REQUEST_TIMEOUT_MS = process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS;
|
|
22
|
-
const TEST_PID_PORTS = new Set<number>();
|
|
23
|
-
|
|
24
|
-
function resolveRuntimeDirForTests(): string {
|
|
25
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
26
|
-
return resolve(dirname(currentFile), "../../../.browserctl-runtime");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function resolvePidFileForTests(port: number): string {
|
|
30
|
-
return join(resolveRuntimeDirForTests(), `daemon-${port}.pid`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function writePidRecordForTests(port: number, record: number | { pid: number; authToken?: string }): void {
|
|
34
|
-
mkdirSync(resolveRuntimeDirForTests(), { recursive: true });
|
|
35
|
-
writeFileSync(
|
|
36
|
-
resolvePidFileForTests(port),
|
|
37
|
-
typeof record === "number" ? String(record) : JSON.stringify(record),
|
|
38
|
-
{ encoding: "utf8" }
|
|
39
|
-
);
|
|
40
|
-
TEST_PID_PORTS.add(port);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
if (ORIGINAL_DAEMON_PORT === undefined) {
|
|
45
|
-
delete process.env.BROWSERCTL_DAEMON_PORT;
|
|
46
|
-
} else {
|
|
47
|
-
process.env.BROWSERCTL_DAEMON_PORT = ORIGINAL_DAEMON_PORT;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (ORIGINAL_AUTH_TOKEN === undefined) {
|
|
51
|
-
delete process.env.BROWSERCTL_AUTH_TOKEN;
|
|
52
|
-
} else {
|
|
53
|
-
process.env.BROWSERCTL_AUTH_TOKEN = ORIGINAL_AUTH_TOKEN;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (ORIGINAL_DAEMON_STARTUP_TIMEOUT_MS === undefined) {
|
|
57
|
-
delete process.env.BROWSERCTL_DAEMON_STARTUP_TIMEOUT_MS;
|
|
58
|
-
} else {
|
|
59
|
-
process.env.BROWSERCTL_DAEMON_STARTUP_TIMEOUT_MS = ORIGINAL_DAEMON_STARTUP_TIMEOUT_MS;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (ORIGINAL_DAEMON_REQUEST_TIMEOUT_MS === undefined) {
|
|
63
|
-
delete process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS;
|
|
64
|
-
} else {
|
|
65
|
-
process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS = ORIGINAL_DAEMON_REQUEST_TIMEOUT_MS;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for (const port of TEST_PID_PORTS) {
|
|
69
|
-
rmSync(resolvePidFileForTests(port), { force: true });
|
|
70
|
-
}
|
|
71
|
-
TEST_PID_PORTS.clear();
|
|
72
|
-
vi.restoreAllMocks();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
async function withDaemonHarness(
|
|
76
|
-
run: (state: { port: number; requests: CapturedRequest[] }) => Promise<void>,
|
|
77
|
-
options: {
|
|
78
|
-
responseDelayMs?: number;
|
|
79
|
-
} = {}
|
|
80
|
-
): Promise<void> {
|
|
81
|
-
const responseDelayMs = options.responseDelayMs ?? 0;
|
|
82
|
-
const requests: CapturedRequest[] = [];
|
|
83
|
-
const server = createServer((socket) => {
|
|
84
|
-
socket.setEncoding("utf8");
|
|
85
|
-
let buffer = "";
|
|
86
|
-
|
|
87
|
-
socket.on("data", (chunk: string) => {
|
|
88
|
-
buffer += chunk;
|
|
89
|
-
|
|
90
|
-
let lineBreakIndex = buffer.indexOf("\n");
|
|
91
|
-
while (lineBreakIndex >= 0) {
|
|
92
|
-
const line = buffer.slice(0, lineBreakIndex).trim();
|
|
93
|
-
buffer = buffer.slice(lineBreakIndex + 1);
|
|
94
|
-
|
|
95
|
-
if (line.length === 0) {
|
|
96
|
-
lineBreakIndex = buffer.indexOf("\n");
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const request = JSON.parse(line) as CapturedRequest;
|
|
101
|
-
requests.push(request);
|
|
102
|
-
const respond = () => {
|
|
103
|
-
socket.write(
|
|
104
|
-
`${JSON.stringify({
|
|
105
|
-
id: request.id,
|
|
106
|
-
ok: true,
|
|
107
|
-
traceId: request.traceId,
|
|
108
|
-
sessionId:
|
|
109
|
-
typeof request.arguments.sessionId === "string"
|
|
110
|
-
? request.arguments.sessionId
|
|
111
|
-
: "cli:test",
|
|
112
|
-
data: {
|
|
113
|
-
ok: true
|
|
114
|
-
}
|
|
115
|
-
})}\n`
|
|
116
|
-
);
|
|
117
|
-
};
|
|
118
|
-
if (responseDelayMs > 0) {
|
|
119
|
-
setTimeout(respond, responseDelayMs);
|
|
120
|
-
} else {
|
|
121
|
-
respond();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
lineBreakIndex = buffer.indexOf("\n");
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
await new Promise<void>((resolve, reject) => {
|
|
130
|
-
server.once("error", reject);
|
|
131
|
-
server.listen(0, "127.0.0.1", () => {
|
|
132
|
-
server.off("error", reject);
|
|
133
|
-
resolve();
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const address = server.address() as AddressInfo;
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
await run({ port: address.port, requests });
|
|
141
|
-
} finally {
|
|
142
|
-
await new Promise<void>((resolve, reject) => {
|
|
143
|
-
server.close((error) => {
|
|
144
|
-
if (error !== undefined) {
|
|
145
|
-
reject(error);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
resolve();
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function reservePortForTests(): Promise<number> {
|
|
156
|
-
const server = createServer();
|
|
157
|
-
|
|
158
|
-
await new Promise<void>((resolve, reject) => {
|
|
159
|
-
server.once("error", reject);
|
|
160
|
-
server.listen(0, "127.0.0.1", () => {
|
|
161
|
-
server.off("error", reject);
|
|
162
|
-
resolve();
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const address = server.address() as AddressInfo;
|
|
167
|
-
await new Promise<void>((resolve, reject) => {
|
|
168
|
-
server.close((error) => {
|
|
169
|
-
if (error !== undefined) {
|
|
170
|
-
reject(error);
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
resolve();
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
return address.port;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function startDaemonHarnessOnPort(
|
|
181
|
-
port: number,
|
|
182
|
-
requests: CapturedRequest[],
|
|
183
|
-
options: {
|
|
184
|
-
responseDelayMs?: number;
|
|
185
|
-
} = {}
|
|
186
|
-
): Promise<() => Promise<void>> {
|
|
187
|
-
const responseDelayMs = options.responseDelayMs ?? 0;
|
|
188
|
-
const server = createServer((socket) => {
|
|
189
|
-
socket.setEncoding("utf8");
|
|
190
|
-
let buffer = "";
|
|
191
|
-
|
|
192
|
-
socket.on("data", (chunk: string) => {
|
|
193
|
-
buffer += chunk;
|
|
194
|
-
|
|
195
|
-
let lineBreakIndex = buffer.indexOf("\n");
|
|
196
|
-
while (lineBreakIndex >= 0) {
|
|
197
|
-
const line = buffer.slice(0, lineBreakIndex).trim();
|
|
198
|
-
buffer = buffer.slice(lineBreakIndex + 1);
|
|
199
|
-
|
|
200
|
-
if (line.length === 0) {
|
|
201
|
-
lineBreakIndex = buffer.indexOf("\n");
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const request = JSON.parse(line) as CapturedRequest;
|
|
206
|
-
requests.push(request);
|
|
207
|
-
const respond = () => {
|
|
208
|
-
socket.write(
|
|
209
|
-
`${JSON.stringify({
|
|
210
|
-
id: request.id,
|
|
211
|
-
ok: true,
|
|
212
|
-
traceId: request.traceId,
|
|
213
|
-
sessionId:
|
|
214
|
-
typeof request.arguments.sessionId === "string"
|
|
215
|
-
? request.arguments.sessionId
|
|
216
|
-
: "cli:test",
|
|
217
|
-
data: {
|
|
218
|
-
ok: true
|
|
219
|
-
}
|
|
220
|
-
})}\n`
|
|
221
|
-
);
|
|
222
|
-
};
|
|
223
|
-
if (responseDelayMs > 0) {
|
|
224
|
-
setTimeout(respond, responseDelayMs);
|
|
225
|
-
} else {
|
|
226
|
-
respond();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
lineBreakIndex = buffer.indexOf("\n");
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
await new Promise<void>((resolve, reject) => {
|
|
235
|
-
server.once("error", reject);
|
|
236
|
-
server.listen(port, "127.0.0.1", () => {
|
|
237
|
-
server.off("error", reject);
|
|
238
|
-
resolve();
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
return async () =>
|
|
243
|
-
await new Promise<void>((resolve, reject) => {
|
|
244
|
-
server.close((error) => {
|
|
245
|
-
if (error !== undefined) {
|
|
246
|
-
reject(error);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
resolve();
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async function importDaemonClientWithMockedSpawn(
|
|
255
|
-
spawnImplementation: () => {
|
|
256
|
-
pid: number;
|
|
257
|
-
unref(): void;
|
|
258
|
-
}
|
|
259
|
-
): Promise<{
|
|
260
|
-
callDaemonToolWithMock: typeof callDaemonTool;
|
|
261
|
-
spawnMock: ReturnType<typeof vi.fn>;
|
|
262
|
-
}> {
|
|
263
|
-
vi.resetModules();
|
|
264
|
-
const spawnMock = vi.fn(spawnImplementation);
|
|
265
|
-
vi.doMock("node:child_process", () => ({
|
|
266
|
-
spawn: spawnMock
|
|
267
|
-
}));
|
|
268
|
-
const daemonClientModule = await import("./daemon-client");
|
|
269
|
-
return {
|
|
270
|
-
callDaemonToolWithMock: daemonClientModule.callDaemonTool,
|
|
271
|
-
spawnMock
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
describe("daemon client auth token forwarding", () => {
|
|
276
|
-
it("includes env token in daemon status probes", async () => {
|
|
277
|
-
await withDaemonHarness(async ({ port, requests }) => {
|
|
278
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
279
|
-
process.env.BROWSERCTL_AUTH_TOKEN = "env-token";
|
|
280
|
-
|
|
281
|
-
const status = await getDaemonStatus();
|
|
282
|
-
|
|
283
|
-
expect(status.running).toBe(true);
|
|
284
|
-
expect(requests).toHaveLength(1);
|
|
285
|
-
expect(requests[0]).toMatchObject({
|
|
286
|
-
name: "browser.status",
|
|
287
|
-
arguments: {
|
|
288
|
-
sessionId: "cli:daemon-status",
|
|
289
|
-
authToken: "env-token"
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it("includes env token in tool calls when not explicitly provided", async () => {
|
|
296
|
-
await withDaemonHarness(async ({ port, requests }) => {
|
|
297
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
298
|
-
process.env.BROWSERCTL_AUTH_TOKEN = "env-token";
|
|
299
|
-
|
|
300
|
-
await callDaemonTool("browser.tab.list", {
|
|
301
|
-
sessionId: "cli:test"
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
expect(requests).toHaveLength(1);
|
|
305
|
-
expect(requests[0]).toMatchObject({
|
|
306
|
-
name: "browser.tab.list",
|
|
307
|
-
arguments: {
|
|
308
|
-
sessionId: "cli:test",
|
|
309
|
-
authToken: "env-token"
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("preserves explicit authToken in tool calls", async () => {
|
|
316
|
-
await withDaemonHarness(async ({ port, requests }) => {
|
|
317
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
318
|
-
process.env.BROWSERCTL_AUTH_TOKEN = "env-token";
|
|
319
|
-
|
|
320
|
-
await callDaemonTool("browser.tab.list", {
|
|
321
|
-
sessionId: "cli:test",
|
|
322
|
-
authToken: "cli-token"
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
expect(requests).toHaveLength(1);
|
|
326
|
-
expect(requests[0]).toMatchObject({
|
|
327
|
-
name: "browser.tab.list",
|
|
328
|
-
arguments: {
|
|
329
|
-
sessionId: "cli:test",
|
|
330
|
-
authToken: "cli-token"
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("does not forward internal daemon startup metadata as tool arguments", async () => {
|
|
337
|
-
await withDaemonHarness(async ({ port, requests }) => {
|
|
338
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
339
|
-
|
|
340
|
-
await callDaemonTool("browser.tab.list", {
|
|
341
|
-
sessionId: "cli:test",
|
|
342
|
-
[DAEMON_STARTUP_ARGUMENT]: {
|
|
343
|
-
managedLocal: {
|
|
344
|
-
browserName: "chromium",
|
|
345
|
-
channel: "msedge"
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
expect(requests).toHaveLength(1);
|
|
351
|
-
expect(requests[0].arguments).toMatchObject({
|
|
352
|
-
sessionId: "cli:test"
|
|
353
|
-
});
|
|
354
|
-
expect(requests[0].arguments).not.toHaveProperty(DAEMON_STARTUP_ARGUMENT);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it("reuses persisted daemon auth token when env token is missing", async () => {
|
|
359
|
-
await withDaemonHarness(async ({ port, requests }) => {
|
|
360
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
361
|
-
delete process.env.BROWSERCTL_AUTH_TOKEN;
|
|
362
|
-
writePidRecordForTests(port, {
|
|
363
|
-
pid: 12345,
|
|
364
|
-
authToken: "persisted-token"
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
const status = await getDaemonStatus();
|
|
368
|
-
|
|
369
|
-
expect(status.running).toBe(true);
|
|
370
|
-
expect(requests).toHaveLength(1);
|
|
371
|
-
expect(requests[0]).toMatchObject({
|
|
372
|
-
name: "browser.status",
|
|
373
|
-
arguments: {
|
|
374
|
-
sessionId: "cli:daemon-status",
|
|
375
|
-
authToken: "persisted-token"
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it("avoids killing stale pid records when daemon cannot be verified", async () => {
|
|
382
|
-
const unreachablePort = 45999;
|
|
383
|
-
writePidRecordForTests(unreachablePort, {
|
|
384
|
-
pid: 70001,
|
|
385
|
-
authToken: "persisted-token"
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
const killSpy = vi.spyOn(process, "kill").mockImplementation(() => true);
|
|
389
|
-
const stopResult = await stopDaemon(unreachablePort);
|
|
390
|
-
|
|
391
|
-
expect(stopResult).toMatchObject({
|
|
392
|
-
stopped: false,
|
|
393
|
-
port: unreachablePort,
|
|
394
|
-
pid: 70001
|
|
395
|
-
});
|
|
396
|
-
expect(killSpy).not.toHaveBeenCalled();
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
describe("daemon client timeout env branches", () => {
|
|
401
|
-
it("applies BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS to daemon responses", async () => {
|
|
402
|
-
await withDaemonHarness(
|
|
403
|
-
async ({ port }) => {
|
|
404
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
405
|
-
process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS = "10";
|
|
406
|
-
|
|
407
|
-
await expect(
|
|
408
|
-
callDaemonTool("browser.tab.list", {
|
|
409
|
-
sessionId: "cli:timeout"
|
|
410
|
-
})
|
|
411
|
-
).rejects.toThrow("Timed out waiting for daemon response after 10ms");
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
responseDelayMs: 80
|
|
415
|
-
}
|
|
416
|
-
);
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it("falls back to default request timeout when env value is invalid", async () => {
|
|
420
|
-
await withDaemonHarness(
|
|
421
|
-
async ({ port, requests }) => {
|
|
422
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
423
|
-
process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS = "-1";
|
|
424
|
-
|
|
425
|
-
await expect(
|
|
426
|
-
callDaemonTool("browser.tab.list", {
|
|
427
|
-
sessionId: "cli:timeout-fallback"
|
|
428
|
-
})
|
|
429
|
-
).resolves.toEqual({
|
|
430
|
-
ok: true
|
|
431
|
-
});
|
|
432
|
-
expect(requests).toHaveLength(1);
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
responseDelayMs: 80
|
|
436
|
-
}
|
|
437
|
-
);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it("applies BROWSERCTL_DAEMON_STARTUP_TIMEOUT_MS while polling spawned daemon readiness", async () => {
|
|
441
|
-
const unavailablePort = await reservePortForTests();
|
|
442
|
-
TEST_PID_PORTS.add(unavailablePort);
|
|
443
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(unavailablePort);
|
|
444
|
-
process.env.BROWSERCTL_DAEMON_STARTUP_TIMEOUT_MS = "50";
|
|
445
|
-
process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS = "20";
|
|
446
|
-
|
|
447
|
-
const { callDaemonToolWithMock, spawnMock } = await importDaemonClientWithMockedSpawn(() => ({
|
|
448
|
-
pid: 424242,
|
|
449
|
-
unref: vi.fn()
|
|
450
|
-
}));
|
|
451
|
-
|
|
452
|
-
try {
|
|
453
|
-
const startedAt = Date.now();
|
|
454
|
-
await expect(
|
|
455
|
-
callDaemonToolWithMock("browser.status", {
|
|
456
|
-
sessionId: "cli:startup-timeout"
|
|
457
|
-
})
|
|
458
|
-
).rejects.toThrow(`Daemon did not become ready on port ${unavailablePort}:`);
|
|
459
|
-
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
460
|
-
expect(Date.now() - startedAt).toBeLessThan(1_500);
|
|
461
|
-
} finally {
|
|
462
|
-
vi.doUnmock("node:child_process");
|
|
463
|
-
vi.resetModules();
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
it("falls back to default startup timeout when env value is invalid", async () => {
|
|
468
|
-
const port = await reservePortForTests();
|
|
469
|
-
TEST_PID_PORTS.add(port);
|
|
470
|
-
process.env.BROWSERCTL_DAEMON_PORT = String(port);
|
|
471
|
-
process.env.BROWSERCTL_DAEMON_STARTUP_TIMEOUT_MS = "-1";
|
|
472
|
-
process.env.BROWSERCTL_DAEMON_REQUEST_TIMEOUT_MS = "50";
|
|
473
|
-
|
|
474
|
-
const requests: CapturedRequest[] = [];
|
|
475
|
-
let closeServer: (() => Promise<void>) | undefined;
|
|
476
|
-
let startupError: unknown;
|
|
477
|
-
const startupTimer = setTimeout(() => {
|
|
478
|
-
void startDaemonHarnessOnPort(port, requests)
|
|
479
|
-
.then((close) => {
|
|
480
|
-
closeServer = close;
|
|
481
|
-
})
|
|
482
|
-
.catch((error) => {
|
|
483
|
-
startupError = error;
|
|
484
|
-
});
|
|
485
|
-
}, 300);
|
|
486
|
-
|
|
487
|
-
const { callDaemonToolWithMock, spawnMock } = await importDaemonClientWithMockedSpawn(() => ({
|
|
488
|
-
pid: 434343,
|
|
489
|
-
unref: vi.fn()
|
|
490
|
-
}));
|
|
491
|
-
|
|
492
|
-
try {
|
|
493
|
-
await expect(
|
|
494
|
-
callDaemonToolWithMock("browser.status", {
|
|
495
|
-
sessionId: "cli:startup-fallback"
|
|
496
|
-
})
|
|
497
|
-
).resolves.toEqual({
|
|
498
|
-
ok: true
|
|
499
|
-
});
|
|
500
|
-
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
501
|
-
expect(requests.some((request) => request.name === "browser.status")).toBe(true);
|
|
502
|
-
expect(startupError).toBeUndefined();
|
|
503
|
-
} finally {
|
|
504
|
-
clearTimeout(startupTimer);
|
|
505
|
-
if (closeServer !== undefined) {
|
|
506
|
-
await closeServer();
|
|
507
|
-
}
|
|
508
|
-
vi.doUnmock("node:child_process");
|
|
509
|
-
vi.resetModules();
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
});
|