@flrande/browserctl 0.4.0-dev.15.1 → 0.5.0-dev.19.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/apps/browserctl/src/commands/act.test.ts +71 -0
- package/apps/browserctl/src/commands/act.ts +45 -1
- package/apps/browserctl/src/commands/command-wrappers.test.ts +302 -0
- package/apps/browserctl/src/commands/console-list.test.ts +102 -0
- package/apps/browserctl/src/commands/console-list.ts +89 -1
- package/apps/browserctl/src/commands/har-export.test.ts +112 -0
- package/apps/browserctl/src/commands/har-export.ts +120 -0
- package/apps/browserctl/src/commands/memory-delete.ts +20 -0
- package/apps/browserctl/src/commands/memory-inspect.ts +20 -0
- package/apps/browserctl/src/commands/memory-list.ts +90 -0
- package/apps/browserctl/src/commands/memory-mode-set.ts +29 -0
- package/apps/browserctl/src/commands/memory-purge.ts +16 -0
- package/apps/browserctl/src/commands/memory-resolve.ts +56 -0
- package/apps/browserctl/src/commands/memory-status.ts +16 -0
- package/apps/browserctl/src/commands/memory-ttl-set.ts +28 -0
- package/apps/browserctl/src/commands/memory-upsert.ts +142 -0
- package/apps/browserctl/src/commands/network-list.test.ts +110 -0
- package/apps/browserctl/src/commands/network-list.ts +112 -0
- package/apps/browserctl/src/commands/session-drop.test.ts +36 -0
- package/apps/browserctl/src/commands/session-drop.ts +16 -0
- package/apps/browserctl/src/commands/session-list.test.ts +81 -0
- package/apps/browserctl/src/commands/session-list.ts +70 -0
- package/apps/browserctl/src/commands/trace-get.test.ts +61 -0
- package/apps/browserctl/src/commands/trace-get.ts +62 -0
- package/apps/browserctl/src/commands/wait-element.test.ts +80 -0
- package/apps/browserctl/src/commands/wait-element.ts +76 -0
- package/apps/browserctl/src/commands/wait-text.test.ts +110 -0
- package/apps/browserctl/src/commands/wait-text.ts +93 -0
- package/apps/browserctl/src/commands/wait-url.test.ts +80 -0
- package/apps/browserctl/src/commands/wait-url.ts +76 -0
- package/apps/browserctl/src/main.dispatch.test.ts +206 -1
- package/apps/browserctl/src/main.test.ts +30 -0
- package/apps/browserctl/src/main.ts +246 -4
- package/apps/browserd/src/container.ts +1603 -48
- package/apps/browserd/src/main.test.ts +538 -1
- package/apps/browserd/src/tool-matrix.test.ts +492 -3
- package/package.json +5 -1
- package/packages/core/src/driver.ts +1 -1
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/navigation-memory.test.ts +259 -0
- package/packages/core/src/navigation-memory.ts +360 -0
- package/packages/core/src/session-store.test.ts +33 -0
- package/packages/core/src/session-store.ts +111 -6
- package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +112 -2
- package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +233 -10
- package/packages/driver-managed/src/managed-driver.test.ts +124 -0
- package/packages/driver-managed/src/managed-driver.ts +233 -17
- package/packages/driver-managed/src/managed-local-driver.test.ts +104 -2
- package/packages/driver-managed/src/managed-local-driver.ts +232 -10
- package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +112 -2
- package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +232 -10
- package/packages/transport-mcp-stdio/src/tool-map.ts +18 -1
|
@@ -41,7 +41,9 @@ function sendToolRequest(
|
|
|
41
41
|
return responsePromise;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async function createManagedLegacyRuntime(
|
|
44
|
+
async function createManagedLegacyRuntime(
|
|
45
|
+
overrides: Record<string, string | undefined> = {}
|
|
46
|
+
) {
|
|
45
47
|
const relayPort = await reserveLoopbackPort();
|
|
46
48
|
|
|
47
49
|
const input = new PassThrough();
|
|
@@ -49,7 +51,8 @@ async function createManagedLegacyRuntime() {
|
|
|
49
51
|
const runtime = bootstrapBrowserd({
|
|
50
52
|
env: {
|
|
51
53
|
BROWSERD_DEFAULT_DRIVER: "managed",
|
|
52
|
-
BROWSERD_CHROME_RELAY_URL: `http://127.0.0.1:${relayPort}
|
|
54
|
+
BROWSERD_CHROME_RELAY_URL: `http://127.0.0.1:${relayPort}`,
|
|
55
|
+
...overrides
|
|
53
56
|
},
|
|
54
57
|
input,
|
|
55
58
|
output,
|
|
@@ -77,6 +80,9 @@ describe("browserd tool matrix", () => {
|
|
|
77
80
|
"browser.dom.queryAll",
|
|
78
81
|
"browser.element.screenshot",
|
|
79
82
|
"browser.a11y.snapshot",
|
|
83
|
+
"browser.wait.element",
|
|
84
|
+
"browser.wait.text",
|
|
85
|
+
"browser.wait.url",
|
|
80
86
|
"browser.cookie.get",
|
|
81
87
|
"browser.cookie.set",
|
|
82
88
|
"browser.cookie.clear",
|
|
@@ -87,7 +93,11 @@ describe("browserd tool matrix", () => {
|
|
|
87
93
|
"browser.act",
|
|
88
94
|
"browser.dialog.arm",
|
|
89
95
|
"browser.download.trigger",
|
|
90
|
-
"browser.network.
|
|
96
|
+
"browser.network.list",
|
|
97
|
+
"browser.network.harExport",
|
|
98
|
+
"browser.network.waitFor",
|
|
99
|
+
"browser.console.list",
|
|
100
|
+
"browser.session.drop"
|
|
91
101
|
] as const;
|
|
92
102
|
|
|
93
103
|
for (const [index, toolName] of toolNames.entries()) {
|
|
@@ -112,6 +122,392 @@ describe("browserd tool matrix", () => {
|
|
|
112
122
|
}
|
|
113
123
|
});
|
|
114
124
|
|
|
125
|
+
it("routes browser.memory tools with validation and deterministic state", async () => {
|
|
126
|
+
const { input, output, runtime } = await createManagedLegacyRuntime();
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const statusResponse = await sendToolRequest(input, output, {
|
|
130
|
+
id: "request-memory-status",
|
|
131
|
+
name: "browser.memory.status",
|
|
132
|
+
traceId: "trace:memory:status",
|
|
133
|
+
arguments: {
|
|
134
|
+
sessionId: "session:memory"
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
expect(statusResponse.ok).toBe(true);
|
|
138
|
+
const statusPayload = statusResponse.data as {
|
|
139
|
+
path?: unknown;
|
|
140
|
+
mode?: unknown;
|
|
141
|
+
ttlDays?: unknown;
|
|
142
|
+
totalEntries?: unknown;
|
|
143
|
+
};
|
|
144
|
+
expect(typeof statusPayload.path).toBe("string");
|
|
145
|
+
expect((statusPayload.path as string).length).toBeGreaterThan(0);
|
|
146
|
+
expect(statusPayload.mode).toBe("ask");
|
|
147
|
+
expect(typeof statusPayload.ttlDays).toBe("number");
|
|
148
|
+
expect(typeof statusPayload.totalEntries).toBe("number");
|
|
149
|
+
|
|
150
|
+
const resolveMissingArgResponse = await sendToolRequest(input, output, {
|
|
151
|
+
id: "request-memory-resolve-missing",
|
|
152
|
+
name: "browser.memory.resolve",
|
|
153
|
+
traceId: "trace:memory:resolve:missing",
|
|
154
|
+
arguments: {
|
|
155
|
+
sessionId: "session:memory"
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
expect(resolveMissingArgResponse.ok).toBe(false);
|
|
159
|
+
expect(resolveMissingArgResponse.error).toMatchObject({
|
|
160
|
+
code: "E_INVALID_ARG"
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const upsertMissingArgResponse = await sendToolRequest(input, output, {
|
|
164
|
+
id: "request-memory-upsert-missing",
|
|
165
|
+
name: "browser.memory.upsert",
|
|
166
|
+
traceId: "trace:memory:upsert:missing",
|
|
167
|
+
arguments: {
|
|
168
|
+
sessionId: "session:memory"
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
expect(upsertMissingArgResponse.ok).toBe(false);
|
|
172
|
+
expect(upsertMissingArgResponse.error).toMatchObject({
|
|
173
|
+
code: "E_INVALID_ARG"
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const upsertFreeTextSignalResponse = await sendToolRequest(input, output, {
|
|
177
|
+
id: "request-memory-upsert-free-text-signal",
|
|
178
|
+
name: "browser.memory.upsert",
|
|
179
|
+
traceId: "trace:memory:upsert:free-text-signal",
|
|
180
|
+
arguments: {
|
|
181
|
+
sessionId: "session:memory",
|
|
182
|
+
domain: "example.com",
|
|
183
|
+
profileId: "managed",
|
|
184
|
+
intentKey: "checkout",
|
|
185
|
+
signals: [
|
|
186
|
+
{
|
|
187
|
+
kind: "urlPattern",
|
|
188
|
+
value: "this is copied page body text that should never be stored"
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
confirmed: true
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
expect(upsertFreeTextSignalResponse.ok).toBe(false);
|
|
195
|
+
expect(upsertFreeTextSignalResponse.error).toMatchObject({
|
|
196
|
+
code: "E_INVALID_ARG"
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const upsertResponse = await sendToolRequest(input, output, {
|
|
200
|
+
id: "request-memory-upsert",
|
|
201
|
+
name: "browser.memory.upsert",
|
|
202
|
+
traceId: "trace:memory:upsert",
|
|
203
|
+
arguments: {
|
|
204
|
+
sessionId: "session:memory",
|
|
205
|
+
domain: "example.com",
|
|
206
|
+
profileId: "managed",
|
|
207
|
+
intentKey: "checkout",
|
|
208
|
+
signals: [
|
|
209
|
+
{
|
|
210
|
+
kind: "urlPattern",
|
|
211
|
+
value: "/checkout"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
confidence: 0.8,
|
|
215
|
+
confirmed: true
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
expect(upsertResponse.ok).toBe(true);
|
|
219
|
+
const upsertPayload = upsertResponse.data as {
|
|
220
|
+
created?: unknown;
|
|
221
|
+
entry?: {
|
|
222
|
+
id?: unknown;
|
|
223
|
+
domain?: unknown;
|
|
224
|
+
profileId?: unknown;
|
|
225
|
+
intentKey?: unknown;
|
|
226
|
+
confidence?: unknown;
|
|
227
|
+
hitCount?: unknown;
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
expect(upsertPayload.created).toBe(true);
|
|
231
|
+
expect(upsertPayload.entry?.domain).toBe("example.com");
|
|
232
|
+
expect(upsertPayload.entry?.profileId).toBe("managed");
|
|
233
|
+
expect(upsertPayload.entry?.intentKey).toBe("checkout");
|
|
234
|
+
expect(upsertPayload.entry?.confidence).toBe(0.8);
|
|
235
|
+
expect(upsertPayload.entry?.hitCount).toBe(0);
|
|
236
|
+
expect(typeof upsertPayload.entry?.id).toBe("string");
|
|
237
|
+
expect((upsertPayload.entry?.id as string).length).toBeGreaterThan(0);
|
|
238
|
+
|
|
239
|
+
const memoryId = upsertPayload.entry?.id as string;
|
|
240
|
+
|
|
241
|
+
const listResponse = await sendToolRequest(input, output, {
|
|
242
|
+
id: "request-memory-list",
|
|
243
|
+
name: "browser.memory.list",
|
|
244
|
+
traceId: "trace:memory:list",
|
|
245
|
+
arguments: {
|
|
246
|
+
sessionId: "session:memory"
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
expect(listResponse.ok).toBe(true);
|
|
250
|
+
expect(listResponse.data).toMatchObject({
|
|
251
|
+
entries: [{ id: memoryId }]
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const inspectResponse = await sendToolRequest(input, output, {
|
|
255
|
+
id: "request-memory-inspect",
|
|
256
|
+
name: "browser.memory.inspect",
|
|
257
|
+
traceId: "trace:memory:inspect",
|
|
258
|
+
arguments: {
|
|
259
|
+
sessionId: "session:memory",
|
|
260
|
+
id: memoryId
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
expect(inspectResponse.ok).toBe(true);
|
|
264
|
+
expect(inspectResponse.data).toMatchObject({
|
|
265
|
+
entry: {
|
|
266
|
+
id: memoryId
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const resolveResponse = await sendToolRequest(input, output, {
|
|
271
|
+
id: "request-memory-resolve",
|
|
272
|
+
name: "browser.memory.resolve",
|
|
273
|
+
traceId: "trace:memory:resolve",
|
|
274
|
+
arguments: {
|
|
275
|
+
sessionId: "session:memory",
|
|
276
|
+
domain: "example.com",
|
|
277
|
+
profileId: "managed",
|
|
278
|
+
intentKey: "checkout"
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
expect(resolveResponse.ok).toBe(true);
|
|
282
|
+
expect(resolveResponse.data).toMatchObject({
|
|
283
|
+
hit: true,
|
|
284
|
+
entry: {
|
|
285
|
+
id: memoryId,
|
|
286
|
+
hitCount: 1
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const deleteResponse = await sendToolRequest(input, output, {
|
|
291
|
+
id: "request-memory-delete",
|
|
292
|
+
name: "browser.memory.delete",
|
|
293
|
+
traceId: "trace:memory:delete",
|
|
294
|
+
arguments: {
|
|
295
|
+
sessionId: "session:memory",
|
|
296
|
+
id: memoryId
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
expect(deleteResponse.ok).toBe(true);
|
|
300
|
+
expect(deleteResponse.data).toMatchObject({
|
|
301
|
+
deleted: true
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const purgeResponse = await sendToolRequest(input, output, {
|
|
305
|
+
id: "request-memory-purge",
|
|
306
|
+
name: "browser.memory.purge",
|
|
307
|
+
traceId: "trace:memory:purge",
|
|
308
|
+
arguments: {
|
|
309
|
+
sessionId: "session:memory"
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
expect(purgeResponse.ok).toBe(true);
|
|
313
|
+
expect(purgeResponse.data).toMatchObject({
|
|
314
|
+
purged: 0
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const modeSetResponse = await sendToolRequest(input, output, {
|
|
318
|
+
id: "request-memory-mode-set",
|
|
319
|
+
name: "browser.memory.mode.set",
|
|
320
|
+
traceId: "trace:memory:mode:set",
|
|
321
|
+
arguments: {
|
|
322
|
+
sessionId: "session:memory",
|
|
323
|
+
mode: "ask"
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
expect(modeSetResponse.ok).toBe(true);
|
|
327
|
+
expect(modeSetResponse.data).toMatchObject({
|
|
328
|
+
mode: "ask"
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const modeOffResponse = await sendToolRequest(input, output, {
|
|
332
|
+
id: "request-memory-mode-set-off",
|
|
333
|
+
name: "browser.memory.mode.set",
|
|
334
|
+
traceId: "trace:memory:mode:set:off",
|
|
335
|
+
arguments: {
|
|
336
|
+
sessionId: "session:memory",
|
|
337
|
+
mode: "off"
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
expect(modeOffResponse.ok).toBe(true);
|
|
341
|
+
expect(modeOffResponse.data).toMatchObject({
|
|
342
|
+
mode: "off"
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const policyRejectResponse = await sendToolRequest(input, output, {
|
|
346
|
+
id: "request-memory-upsert-policy-reject",
|
|
347
|
+
name: "browser.memory.upsert",
|
|
348
|
+
traceId: "trace:memory:upsert:policy:reject",
|
|
349
|
+
arguments: {
|
|
350
|
+
sessionId: "session:memory",
|
|
351
|
+
domain: "example.com",
|
|
352
|
+
profileId: "managed",
|
|
353
|
+
intentKey: "checkout",
|
|
354
|
+
signals: [
|
|
355
|
+
{
|
|
356
|
+
kind: "urlPattern",
|
|
357
|
+
value: "/checkout"
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
confidence: 0.6
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
expect(policyRejectResponse.ok).toBe(false);
|
|
364
|
+
expect(policyRejectResponse.error).toMatchObject({
|
|
365
|
+
code: "E_PERMISSION"
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const ttlSetResponse = await sendToolRequest(input, output, {
|
|
369
|
+
id: "request-memory-ttl-set",
|
|
370
|
+
name: "browser.memory.ttl.set",
|
|
371
|
+
traceId: "trace:memory:ttl:set",
|
|
372
|
+
arguments: {
|
|
373
|
+
sessionId: "session:memory",
|
|
374
|
+
ttlDays: 7
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
expect(ttlSetResponse.ok).toBe(true);
|
|
378
|
+
expect(ttlSetResponse.data).toMatchObject({
|
|
379
|
+
ttlDays: 7
|
|
380
|
+
});
|
|
381
|
+
} finally {
|
|
382
|
+
runtime.close();
|
|
383
|
+
input.end();
|
|
384
|
+
output.end();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("records memory-hit marker in browser.trace.get after browser.memory.resolve", async () => {
|
|
389
|
+
const { input, output, runtime } = await createManagedLegacyRuntime();
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const upsertResponse = await sendToolRequest(input, output, {
|
|
393
|
+
id: "request-memory-hit-upsert",
|
|
394
|
+
name: "browser.memory.upsert",
|
|
395
|
+
traceId: "trace:memory:hit:upsert",
|
|
396
|
+
arguments: {
|
|
397
|
+
sessionId: "session:memory-hit",
|
|
398
|
+
domain: "example.com",
|
|
399
|
+
profileId: "managed",
|
|
400
|
+
intentKey: "checkout",
|
|
401
|
+
signals: [
|
|
402
|
+
{
|
|
403
|
+
kind: "urlPattern",
|
|
404
|
+
value: "/checkout"
|
|
405
|
+
}
|
|
406
|
+
],
|
|
407
|
+
confidence: 0.9,
|
|
408
|
+
confirmed: true
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
expect(upsertResponse.ok).toBe(true);
|
|
412
|
+
|
|
413
|
+
const resolveResponse = await sendToolRequest(input, output, {
|
|
414
|
+
id: "request-memory-hit-resolve",
|
|
415
|
+
name: "browser.memory.resolve",
|
|
416
|
+
traceId: "trace:memory:hit:resolve",
|
|
417
|
+
arguments: {
|
|
418
|
+
sessionId: "session:memory-hit",
|
|
419
|
+
domain: "example.com",
|
|
420
|
+
profileId: "managed",
|
|
421
|
+
intentKey: "checkout"
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
expect(resolveResponse.ok).toBe(true);
|
|
425
|
+
expect(resolveResponse.data).toMatchObject({
|
|
426
|
+
hit: true
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const traceGetResponse = await sendToolRequest(input, output, {
|
|
430
|
+
id: "request-memory-hit-trace-get",
|
|
431
|
+
name: "browser.trace.get",
|
|
432
|
+
traceId: "trace:memory:hit:trace-get",
|
|
433
|
+
arguments: {
|
|
434
|
+
sessionId: "session:memory-hit",
|
|
435
|
+
limit: 20
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
expect(traceGetResponse.ok).toBe(true);
|
|
439
|
+
const tracePayload = traceGetResponse.data as {
|
|
440
|
+
keyResponses?: Array<{
|
|
441
|
+
kind?: string;
|
|
442
|
+
data?: {
|
|
443
|
+
memoryHit?: boolean;
|
|
444
|
+
};
|
|
445
|
+
}>;
|
|
446
|
+
};
|
|
447
|
+
expect(tracePayload.keyResponses?.some((response) =>
|
|
448
|
+
response.kind === "memory.resolve" && response.data?.memoryHit === true
|
|
449
|
+
)).toBe(true);
|
|
450
|
+
} finally {
|
|
451
|
+
runtime.close();
|
|
452
|
+
input.end();
|
|
453
|
+
output.end();
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("keeps browser.memory disabled when BROWSERD_MEMORY_ENABLED is false", async () => {
|
|
458
|
+
const { input, output, runtime } = await createManagedLegacyRuntime({
|
|
459
|
+
BROWSERD_MEMORY_ENABLED: "false"
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
try {
|
|
463
|
+
const statusResponse = await sendToolRequest(input, output, {
|
|
464
|
+
id: "request-memory-disabled-status",
|
|
465
|
+
name: "browser.memory.status",
|
|
466
|
+
traceId: "trace:memory:disabled:status",
|
|
467
|
+
arguments: {
|
|
468
|
+
sessionId: "session:memory:disabled"
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
expect(statusResponse.ok).toBe(true);
|
|
473
|
+
expect(statusResponse.data).toMatchObject({
|
|
474
|
+
mode: "off"
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const modeSetResponse = await sendToolRequest(input, output, {
|
|
478
|
+
id: "request-memory-disabled-mode-set",
|
|
479
|
+
name: "browser.memory.mode.set",
|
|
480
|
+
traceId: "trace:memory:disabled:mode:set",
|
|
481
|
+
arguments: {
|
|
482
|
+
sessionId: "session:memory:disabled",
|
|
483
|
+
mode: "auto"
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
expect(modeSetResponse.ok).toBe(false);
|
|
488
|
+
expect(modeSetResponse.error).toMatchObject({
|
|
489
|
+
code: "E_PERMISSION"
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const statusAfterResponse = await sendToolRequest(input, output, {
|
|
493
|
+
id: "request-memory-disabled-status-after",
|
|
494
|
+
name: "browser.memory.status",
|
|
495
|
+
traceId: "trace:memory:disabled:status:after",
|
|
496
|
+
arguments: {
|
|
497
|
+
sessionId: "session:memory:disabled"
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
expect(statusAfterResponse.ok).toBe(true);
|
|
501
|
+
expect(statusAfterResponse.data).toMatchObject({
|
|
502
|
+
mode: "off"
|
|
503
|
+
});
|
|
504
|
+
} finally {
|
|
505
|
+
runtime.close();
|
|
506
|
+
input.end();
|
|
507
|
+
output.end();
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
115
511
|
it("returns E_DRIVER_UNAVAILABLE for structured action tools on managed driver", async () => {
|
|
116
512
|
const { input, output, runtime } = await createManagedLegacyRuntime();
|
|
117
513
|
|
|
@@ -159,6 +555,27 @@ describe("browserd tool matrix", () => {
|
|
|
159
555
|
targetId
|
|
160
556
|
}
|
|
161
557
|
},
|
|
558
|
+
{
|
|
559
|
+
name: "browser.wait.element",
|
|
560
|
+
arguments: {
|
|
561
|
+
sessionId: "session:unavailable",
|
|
562
|
+
targetId,
|
|
563
|
+
selector: "#ready",
|
|
564
|
+
timeoutMs: 5,
|
|
565
|
+
pollMs: 1
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "browser.wait.text",
|
|
570
|
+
arguments: {
|
|
571
|
+
sessionId: "session:unavailable",
|
|
572
|
+
targetId,
|
|
573
|
+
text: "ready",
|
|
574
|
+
selector: "#status",
|
|
575
|
+
timeoutMs: 5,
|
|
576
|
+
pollMs: 1
|
|
577
|
+
}
|
|
578
|
+
},
|
|
162
579
|
{
|
|
163
580
|
name: "browser.cookie.get",
|
|
164
581
|
arguments: {
|
|
@@ -374,6 +791,78 @@ describe("browserd tool matrix", () => {
|
|
|
374
791
|
code: "E_TIMEOUT"
|
|
375
792
|
});
|
|
376
793
|
|
|
794
|
+
const networkListResponse = await sendToolRequest(input, output, {
|
|
795
|
+
id: "request-flow-network-list",
|
|
796
|
+
name: "browser.network.list",
|
|
797
|
+
traceId: "trace:matrix:network-list",
|
|
798
|
+
arguments: {
|
|
799
|
+
sessionId: "session:flow",
|
|
800
|
+
targetId
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
expect(networkListResponse.ok).toBe(true);
|
|
804
|
+
expect(networkListResponse.data).toMatchObject({
|
|
805
|
+
driver: "managed",
|
|
806
|
+
targetId,
|
|
807
|
+
requests: []
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
const harExportResponse = await sendToolRequest(input, output, {
|
|
811
|
+
id: "request-flow-har-export",
|
|
812
|
+
name: "browser.network.harExport",
|
|
813
|
+
traceId: "trace:matrix:har-export",
|
|
814
|
+
arguments: {
|
|
815
|
+
sessionId: "session:flow",
|
|
816
|
+
targetId
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
expect(harExportResponse.ok).toBe(true);
|
|
820
|
+
expect(harExportResponse.data).toMatchObject({
|
|
821
|
+
driver: "managed",
|
|
822
|
+
targetId,
|
|
823
|
+
har: {
|
|
824
|
+
log: {
|
|
825
|
+
entries: []
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
const traceGetResponse = await sendToolRequest(input, output, {
|
|
831
|
+
id: "request-flow-trace-get",
|
|
832
|
+
name: "browser.trace.get",
|
|
833
|
+
traceId: "trace:matrix:trace-get",
|
|
834
|
+
arguments: {
|
|
835
|
+
sessionId: "session:flow",
|
|
836
|
+
limit: 20
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
expect(traceGetResponse.ok).toBe(true);
|
|
840
|
+
expect(traceGetResponse.data).toMatchObject({
|
|
841
|
+
sessionId: "session:flow"
|
|
842
|
+
});
|
|
843
|
+
const tracePayload = traceGetResponse.data as {
|
|
844
|
+
steps?: Array<{ tool?: string }>;
|
|
845
|
+
};
|
|
846
|
+
expect(Array.isArray(tracePayload.steps)).toBe(true);
|
|
847
|
+
expect(tracePayload.steps?.some((step) => step.tool === "browser.tab.open")).toBe(true);
|
|
848
|
+
|
|
849
|
+
const waitUrlResponse = await sendToolRequest(input, output, {
|
|
850
|
+
id: "request-flow-waiturl",
|
|
851
|
+
name: "browser.wait.url",
|
|
852
|
+
traceId: "trace:matrix:waiturl",
|
|
853
|
+
arguments: {
|
|
854
|
+
sessionId: "session:flow",
|
|
855
|
+
targetId,
|
|
856
|
+
urlPattern: "/never-match",
|
|
857
|
+
timeoutMs: 5,
|
|
858
|
+
pollMs: 1
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
expect(waitUrlResponse.ok).toBe(false);
|
|
862
|
+
expect(waitUrlResponse.error).toMatchObject({
|
|
863
|
+
code: "E_TIMEOUT"
|
|
864
|
+
});
|
|
865
|
+
|
|
377
866
|
const closeResponse = await sendToolRequest(input, output, {
|
|
378
867
|
id: "request-flow-close",
|
|
379
868
|
name: "browser.tab.close",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flrande/browserctl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-dev.19.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"bin": {
|
|
6
6
|
"browserctl": "bin/browserctl.cjs",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"playwright-core": "^1.52.0",
|
|
33
33
|
"tsx": "^4.21.0",
|
|
34
34
|
"ws": "^8.19.0",
|
|
35
|
+
"yaml": "^2.8.1",
|
|
35
36
|
"zod": "^4.3.6"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
@@ -44,6 +45,9 @@
|
|
|
44
45
|
"test:unit": "vitest run --config vitest.config.ts",
|
|
45
46
|
"test:contract": "vitest run --config vitest.contract.config.ts",
|
|
46
47
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
48
|
+
"test:e2e:smoke": "vitest run --config vitest.e2e.smoke.config.ts",
|
|
49
|
+
"test:e2e:full": "vitest run --config vitest.e2e.full.config.ts",
|
|
50
|
+
"test:e2e:stress": "vitest run --config vitest.e2e.stress.config.ts",
|
|
47
51
|
"test:smoke": "vitest run --config vitest.smoke.config.ts",
|
|
48
52
|
"test:all": "pnpm run test:unit && pnpm run test:contract && pnpm run test:e2e && pnpm run test:smoke",
|
|
49
53
|
"build": "npm pack --dry-run",
|
|
@@ -42,6 +42,6 @@ export interface BrowserDriver<
|
|
|
42
42
|
|
|
43
43
|
armUpload(targetId: TargetId, files: string[], profile?: ProfileId): Promise<void>;
|
|
44
44
|
armDialog(targetId: TargetId, profile?: ProfileId): Promise<void>;
|
|
45
|
-
waitDownload(targetId: TargetId, profile?: ProfileId): Promise<TDownload>;
|
|
45
|
+
waitDownload(targetId: TargetId, profile?: ProfileId, path?: string): Promise<TDownload>;
|
|
46
46
|
triggerDownload(targetId: TargetId, profile?: ProfileId): Promise<void>;
|
|
47
47
|
}
|