@flrande/browserctl 0.1.0 → 0.2.0-dev.12.1
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/INSTALL-CN.md +28 -0
- package/INSTALL.md +28 -0
- package/README-CN.md +27 -1131
- package/README.md +27 -1131
- package/apps/browserctl/src/commands/command-wrappers.test.ts +386 -0
- package/apps/browserctl/src/commands/network-wait-for.test.ts +90 -0
- package/apps/browserctl/src/e2e.test.ts +9 -5
- package/apps/browserctl/src/main.dispatch.test.ts +256 -0
- package/apps/browserctl/src/smoke.e2e.test.ts +97 -0
- package/apps/browserctl/src/test-port.ts +26 -0
- package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +6 -31
- package/apps/browserd/src/container.ts +12 -10
- package/apps/browserd/src/main.test.ts +81 -46
- package/apps/browserd/src/test-port.ts +26 -0
- package/apps/browserd/src/tool-matrix.test.ts +398 -0
- package/extensions/chrome-relay/README-CN.md +39 -0
- package/extensions/chrome-relay/README.md +3 -0
- package/package.json +6 -4
- package/apps/browserctl/src/smoke.test.ts +0 -16
- package/apps/browserctl/src/smoke.ts +0 -5
- package/scripts/smoke.ps1 +0 -127
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
2
|
import { PassThrough } from "node:stream";
|
|
3
3
|
import { createConnection } from "node:net";
|
|
4
4
|
|
|
5
5
|
import { createContainer, loadBrowserdConfig } from "./container";
|
|
6
6
|
import { bootstrapBrowserd } from "./bootstrap";
|
|
7
|
+
import { reserveLoopbackPort } from "./test-port";
|
|
7
8
|
|
|
8
9
|
function waitForNextJsonLine(stream: PassThrough): Promise<Record<string, unknown>> {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
@@ -78,11 +79,33 @@ function sendTcpToolRequest(
|
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
let testRelayPort = 0;
|
|
83
|
+
let testTcpPort = 0;
|
|
84
|
+
|
|
85
|
+
beforeEach(async () => {
|
|
86
|
+
testRelayPort = await reserveLoopbackPort();
|
|
87
|
+
testTcpPort = await reserveLoopbackPort();
|
|
88
|
+
while (testTcpPort === testRelayPort) {
|
|
89
|
+
testTcpPort = await reserveLoopbackPort();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
function createTestEnv(
|
|
94
|
+
overrides: Record<string, string | undefined> = {}
|
|
95
|
+
): Record<string, string | undefined> {
|
|
96
|
+
return {
|
|
97
|
+
BROWSERD_CHROME_RELAY_URL: `http://127.0.0.1:${testRelayPort}`,
|
|
98
|
+
...overrides
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
describe("browserd container", () => {
|
|
82
103
|
it("uses secure defaults for new security config fields", () => {
|
|
83
104
|
const config = loadBrowserdConfig({});
|
|
84
105
|
|
|
85
|
-
expect(config.defaultDriver).toBe("
|
|
106
|
+
expect(config.defaultDriver).toBe("chrome-relay");
|
|
107
|
+
expect(config.chromeRelayMode).toBe("extension");
|
|
108
|
+
expect(config.chromeRelayExtensionToken).toBe("browserctl-relay");
|
|
86
109
|
expect(config.managedLocalEnabled).toBe(true);
|
|
87
110
|
expect(config.uploadRoot).toBeUndefined();
|
|
88
111
|
expect(config.downloadRoot).toBeUndefined();
|
|
@@ -91,12 +114,13 @@ describe("browserd container", () => {
|
|
|
91
114
|
});
|
|
92
115
|
|
|
93
116
|
it("registers managed-local, managed, chrome-relay, and remote-cdp drivers by default", () => {
|
|
94
|
-
const c = createContainer();
|
|
117
|
+
const c = createContainer(loadBrowserdConfig(createTestEnv()));
|
|
95
118
|
|
|
96
119
|
expect(c.drivers.has("managed-local")).toBe(true);
|
|
97
120
|
expect(c.drivers.has("managed")).toBe(true);
|
|
98
121
|
expect(c.drivers.has("chrome-relay")).toBe(true);
|
|
99
122
|
expect(c.drivers.has("remote-cdp")).toBe(true);
|
|
123
|
+
c.close();
|
|
100
124
|
});
|
|
101
125
|
|
|
102
126
|
it("uses env override in loadBrowserdConfig", () => {
|
|
@@ -110,7 +134,7 @@ describe("browserd container", () => {
|
|
|
110
134
|
|
|
111
135
|
expect(config.remoteCdpUrl).toBe("http://127.0.0.1:9333/devtools/browser/override");
|
|
112
136
|
expect(config.chromeRelayUrl).toBe("http://127.0.0.1:9223");
|
|
113
|
-
expect(config.defaultDriver).toBe("
|
|
137
|
+
expect(config.defaultDriver).toBe("chrome-relay");
|
|
114
138
|
expect(config.managedLocalEnabled).toBe(true);
|
|
115
139
|
expect(config.uploadRoot).toBe("C:\\safe\\uploads");
|
|
116
140
|
expect(config.downloadRoot).toBe("C:\\safe\\downloads");
|
|
@@ -132,31 +156,42 @@ describe("browserd container", () => {
|
|
|
132
156
|
expect(config.chromeRelayExtensionRequestTimeoutMs).toBe(7000);
|
|
133
157
|
});
|
|
134
158
|
|
|
135
|
-
it("
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
).
|
|
159
|
+
it("respects explicit cdp relay mode override", () => {
|
|
160
|
+
const config = loadBrowserdConfig({
|
|
161
|
+
BROWSERD_CHROME_RELAY_MODE: "cdp"
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(config.chromeRelayMode).toBe("cdp");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("uses default extension relay token when extension mode is enabled", () => {
|
|
168
|
+
const config = loadBrowserdConfig({
|
|
169
|
+
BROWSERD_CHROME_RELAY_MODE: "extension"
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(config.chromeRelayMode).toBe("extension");
|
|
173
|
+
expect(config.chromeRelayExtensionToken).toBe("browserctl-relay");
|
|
141
174
|
});
|
|
142
175
|
|
|
143
|
-
it("
|
|
176
|
+
it("keeps chrome-relay as default when managed-local is explicitly disabled", () => {
|
|
144
177
|
const config = loadBrowserdConfig({
|
|
145
178
|
BROWSERD_MANAGED_LOCAL_ENABLED: "false"
|
|
146
179
|
});
|
|
147
180
|
|
|
148
181
|
expect(config.managedLocalEnabled).toBe(false);
|
|
149
|
-
expect(config.defaultDriver).toBe("
|
|
182
|
+
expect(config.defaultDriver).toBe("chrome-relay");
|
|
150
183
|
});
|
|
151
184
|
|
|
152
185
|
it("does not register managed-local driver when explicitly disabled", () => {
|
|
153
186
|
const c = createContainer(
|
|
154
187
|
loadBrowserdConfig({
|
|
188
|
+
...createTestEnv(),
|
|
155
189
|
BROWSERD_MANAGED_LOCAL_ENABLED: "false"
|
|
156
190
|
})
|
|
157
191
|
);
|
|
158
192
|
|
|
159
193
|
expect(c.drivers.has("managed-local")).toBe(false);
|
|
194
|
+
c.close();
|
|
160
195
|
});
|
|
161
196
|
});
|
|
162
197
|
|
|
@@ -165,7 +200,7 @@ describe("browserd bootstrap", () => {
|
|
|
165
200
|
const input = new PassThrough();
|
|
166
201
|
const output = new PassThrough();
|
|
167
202
|
|
|
168
|
-
const runtime = bootstrapBrowserd({ input, output });
|
|
203
|
+
const runtime = bootstrapBrowserd({ env: createTestEnv(), input, output });
|
|
169
204
|
|
|
170
205
|
expect(runtime.mcpStdioStarted).toBe(true);
|
|
171
206
|
expect(runtime.container.drivers.has("managed")).toBe(true);
|
|
@@ -179,13 +214,13 @@ describe("browserd bootstrap", () => {
|
|
|
179
214
|
});
|
|
180
215
|
|
|
181
216
|
it("supports tcp transport mode for persistent daemon use", async () => {
|
|
182
|
-
const port =
|
|
217
|
+
const port = testTcpPort;
|
|
183
218
|
const runtime = bootstrapBrowserd({
|
|
184
|
-
env: {
|
|
219
|
+
env: createTestEnv({
|
|
185
220
|
BROWSERD_TRANSPORT: "tcp",
|
|
186
221
|
BROWSERD_PORT: String(port),
|
|
187
222
|
BROWSERD_AUTH_TOKEN: "tcp-token"
|
|
188
|
-
}
|
|
223
|
+
})
|
|
189
224
|
});
|
|
190
225
|
|
|
191
226
|
const response = await sendTcpToolRequest(port, {
|
|
@@ -215,10 +250,10 @@ describe("browserd bootstrap", () => {
|
|
|
215
250
|
try {
|
|
216
251
|
expect(() => {
|
|
217
252
|
runtime = bootstrapBrowserd({
|
|
218
|
-
env: {
|
|
253
|
+
env: createTestEnv({
|
|
219
254
|
BROWSERD_TRANSPORT: "tcp",
|
|
220
|
-
BROWSERD_PORT:
|
|
221
|
-
}
|
|
255
|
+
BROWSERD_PORT: String(testTcpPort)
|
|
256
|
+
})
|
|
222
257
|
});
|
|
223
258
|
}).toThrow("BROWSERD_AUTH_TOKEN");
|
|
224
259
|
} finally {
|
|
@@ -229,7 +264,7 @@ describe("browserd bootstrap", () => {
|
|
|
229
264
|
it("processes line-delimited requests and writes response with id", async () => {
|
|
230
265
|
const input = new PassThrough();
|
|
231
266
|
const output = new PassThrough();
|
|
232
|
-
const runtime = bootstrapBrowserd({ input, output, stdioProtocol: "legacy" });
|
|
267
|
+
const runtime = bootstrapBrowserd({ env: createTestEnv(), input, output, stdioProtocol: "legacy" });
|
|
233
268
|
|
|
234
269
|
const response = await sendToolRequest(input, output, {
|
|
235
270
|
id: "request-1",
|
|
@@ -247,7 +282,7 @@ describe("browserd bootstrap", () => {
|
|
|
247
282
|
expect(response.data).toMatchObject({
|
|
248
283
|
kind: "browserd",
|
|
249
284
|
ready: true,
|
|
250
|
-
driver: "
|
|
285
|
+
driver: "chrome-relay"
|
|
251
286
|
});
|
|
252
287
|
|
|
253
288
|
runtime.close();
|
|
@@ -259,10 +294,10 @@ describe("browserd bootstrap", () => {
|
|
|
259
294
|
const input = new PassThrough();
|
|
260
295
|
const output = new PassThrough();
|
|
261
296
|
const runtime = bootstrapBrowserd({
|
|
262
|
-
env: {
|
|
297
|
+
env: createTestEnv({
|
|
263
298
|
BROWSERD_MANAGED_LOCAL_ENABLED: "true",
|
|
264
299
|
BROWSERD_DEFAULT_DRIVER: "managed-local"
|
|
265
|
-
},
|
|
300
|
+
}),
|
|
266
301
|
input,
|
|
267
302
|
output,
|
|
268
303
|
stdioProtocol: "legacy"
|
|
@@ -297,10 +332,10 @@ describe("browserd bootstrap", () => {
|
|
|
297
332
|
const input = new PassThrough();
|
|
298
333
|
const output = new PassThrough();
|
|
299
334
|
const runtime = bootstrapBrowserd({
|
|
300
|
-
env: {
|
|
335
|
+
env: createTestEnv({
|
|
301
336
|
BROWSERD_DEFAULT_DRIVER: "managed-local",
|
|
302
337
|
BROWSERD_MANAGED_LOCAL_ENABLED: "false"
|
|
303
|
-
},
|
|
338
|
+
}),
|
|
304
339
|
input,
|
|
305
340
|
output,
|
|
306
341
|
stdioProtocol: "legacy"
|
|
@@ -331,9 +366,9 @@ describe("browserd bootstrap", () => {
|
|
|
331
366
|
const input = new PassThrough();
|
|
332
367
|
const output = new PassThrough();
|
|
333
368
|
const runtime = bootstrapBrowserd({
|
|
334
|
-
env: {
|
|
369
|
+
env: createTestEnv({
|
|
335
370
|
BROWSERD_AUTH_TOKEN: "secret-token"
|
|
336
|
-
},
|
|
371
|
+
}),
|
|
337
372
|
input,
|
|
338
373
|
output,
|
|
339
374
|
stdioProtocol: "legacy"
|
|
@@ -362,9 +397,9 @@ describe("browserd bootstrap", () => {
|
|
|
362
397
|
const input = new PassThrough();
|
|
363
398
|
const output = new PassThrough();
|
|
364
399
|
const runtime = bootstrapBrowserd({
|
|
365
|
-
env: {
|
|
400
|
+
env: createTestEnv({
|
|
366
401
|
BROWSERD_AUTH_TOKEN: "secret-token"
|
|
367
|
-
},
|
|
402
|
+
}),
|
|
368
403
|
input,
|
|
369
404
|
output,
|
|
370
405
|
stdioProtocol: "legacy"
|
|
@@ -394,11 +429,11 @@ describe("browserd bootstrap", () => {
|
|
|
394
429
|
const input = new PassThrough();
|
|
395
430
|
const output = new PassThrough();
|
|
396
431
|
const runtime = bootstrapBrowserd({
|
|
397
|
-
env: {
|
|
432
|
+
env: createTestEnv({
|
|
398
433
|
BROWSERD_DEFAULT_DRIVER: "managed",
|
|
399
434
|
BROWSERD_AUTH_TOKEN: "secret-token",
|
|
400
435
|
BROWSERD_AUTH_SCOPES: "read"
|
|
401
|
-
},
|
|
436
|
+
}),
|
|
402
437
|
input,
|
|
403
438
|
output,
|
|
404
439
|
stdioProtocol: "legacy"
|
|
@@ -429,11 +464,11 @@ describe("browserd bootstrap", () => {
|
|
|
429
464
|
const input = new PassThrough();
|
|
430
465
|
const output = new PassThrough();
|
|
431
466
|
const runtime = bootstrapBrowserd({
|
|
432
|
-
env: {
|
|
467
|
+
env: createTestEnv({
|
|
433
468
|
BROWSERD_DEFAULT_DRIVER: "managed",
|
|
434
469
|
BROWSERD_AUTH_TOKEN: "secret-token",
|
|
435
470
|
BROWSERD_AUTH_SCOPES: "read,act"
|
|
436
|
-
},
|
|
471
|
+
}),
|
|
437
472
|
input,
|
|
438
473
|
output,
|
|
439
474
|
stdioProtocol: "legacy"
|
|
@@ -494,10 +529,10 @@ describe("browserd bootstrap", () => {
|
|
|
494
529
|
const input = new PassThrough();
|
|
495
530
|
const output = new PassThrough();
|
|
496
531
|
const runtime = bootstrapBrowserd({
|
|
497
|
-
env: {
|
|
532
|
+
env: createTestEnv({
|
|
498
533
|
BROWSERD_DEFAULT_DRIVER: "managed",
|
|
499
534
|
BROWSERD_UPLOAD_ROOT: "C:\\allowed\\uploads"
|
|
500
|
-
},
|
|
535
|
+
}),
|
|
501
536
|
input,
|
|
502
537
|
output,
|
|
503
538
|
stdioProtocol: "legacy"
|
|
@@ -539,9 +574,9 @@ describe("browserd bootstrap", () => {
|
|
|
539
574
|
const input = new PassThrough();
|
|
540
575
|
const output = new PassThrough();
|
|
541
576
|
const runtime = bootstrapBrowserd({
|
|
542
|
-
env: {
|
|
577
|
+
env: createTestEnv({
|
|
543
578
|
BROWSERD_DEFAULT_DRIVER: "managed"
|
|
544
|
-
},
|
|
579
|
+
}),
|
|
545
580
|
input,
|
|
546
581
|
output,
|
|
547
582
|
stdioProtocol: "legacy"
|
|
@@ -584,10 +619,10 @@ describe("browserd bootstrap", () => {
|
|
|
584
619
|
const input = new PassThrough();
|
|
585
620
|
const output = new PassThrough();
|
|
586
621
|
const runtime = bootstrapBrowserd({
|
|
587
|
-
env: {
|
|
622
|
+
env: createTestEnv({
|
|
588
623
|
BROWSERD_DEFAULT_DRIVER: "managed",
|
|
589
624
|
BROWSERD_DOWNLOAD_ROOT: "C:\\allowed\\downloads"
|
|
590
|
-
},
|
|
625
|
+
}),
|
|
591
626
|
input,
|
|
592
627
|
output,
|
|
593
628
|
stdioProtocol: "legacy"
|
|
@@ -646,9 +681,9 @@ describe("browserd bootstrap", () => {
|
|
|
646
681
|
const input = new PassThrough();
|
|
647
682
|
const output = new PassThrough();
|
|
648
683
|
const runtime = bootstrapBrowserd({
|
|
649
|
-
env: {
|
|
684
|
+
env: createTestEnv({
|
|
650
685
|
BROWSERD_DEFAULT_DRIVER: "managed"
|
|
651
|
-
},
|
|
686
|
+
}),
|
|
652
687
|
input,
|
|
653
688
|
output,
|
|
654
689
|
stdioProtocol: "legacy"
|
|
@@ -690,9 +725,9 @@ describe("browserd bootstrap", () => {
|
|
|
690
725
|
const input = new PassThrough();
|
|
691
726
|
const output = new PassThrough();
|
|
692
727
|
const runtime = bootstrapBrowserd({
|
|
693
|
-
env: {
|
|
728
|
+
env: createTestEnv({
|
|
694
729
|
BROWSERD_DEFAULT_DRIVER: "managed"
|
|
695
|
-
},
|
|
730
|
+
}),
|
|
696
731
|
input,
|
|
697
732
|
output,
|
|
698
733
|
stdioProtocol: "legacy"
|
|
@@ -739,9 +774,9 @@ describe("browserd bootstrap", () => {
|
|
|
739
774
|
const input = new PassThrough();
|
|
740
775
|
const output = new PassThrough();
|
|
741
776
|
const runtime = bootstrapBrowserd({
|
|
742
|
-
env: {
|
|
777
|
+
env: createTestEnv({
|
|
743
778
|
BROWSERD_DEFAULT_DRIVER: "managed"
|
|
744
|
-
},
|
|
779
|
+
}),
|
|
745
780
|
input,
|
|
746
781
|
output,
|
|
747
782
|
stdioProtocol: "legacy"
|
|
@@ -819,7 +854,7 @@ describe("browserd bootstrap", () => {
|
|
|
819
854
|
it("preserves id and trace/session metadata when queue-level error handling runs", async () => {
|
|
820
855
|
const input = new PassThrough();
|
|
821
856
|
const output = new PassThrough();
|
|
822
|
-
const runtime = bootstrapBrowserd({ input, output, stdioProtocol: "legacy" });
|
|
857
|
+
const runtime = bootstrapBrowserd({ env: createTestEnv(), input, output, stdioProtocol: "legacy" });
|
|
823
858
|
|
|
824
859
|
const originalWrite = output.write.bind(output);
|
|
825
860
|
let shouldThrow = true;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createServer } from "node:net";
|
|
2
|
+
|
|
3
|
+
export async function reserveLoopbackPort(): Promise<number> {
|
|
4
|
+
return await new Promise<number>((resolve, reject) => {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
|
|
7
|
+
server.once("error", reject);
|
|
8
|
+
server.listen(0, "127.0.0.1", () => {
|
|
9
|
+
const address = server.address();
|
|
10
|
+
if (typeof address !== "object" || address === null) {
|
|
11
|
+
server.close(() => reject(new Error("Failed to reserve loopback port.")));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const port = address.port;
|
|
16
|
+
server.close((error) => {
|
|
17
|
+
if (error !== undefined) {
|
|
18
|
+
reject(error);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
resolve(port);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|