agent-browser 0.0.0 → 0.1.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.
Files changed (54) hide show
  1. package/.prettierrc +7 -0
  2. package/LICENSE +201 -0
  3. package/README.md +274 -1
  4. package/bin/agent-browser +2 -0
  5. package/dist/actions.d.ts +7 -0
  6. package/dist/actions.d.ts.map +1 -0
  7. package/dist/actions.js +1138 -0
  8. package/dist/actions.js.map +1 -0
  9. package/dist/browser.d.ts +232 -0
  10. package/dist/browser.d.ts.map +1 -0
  11. package/dist/browser.js +477 -0
  12. package/dist/browser.js.map +1 -0
  13. package/dist/browser.test.d.ts +2 -0
  14. package/dist/browser.test.d.ts.map +1 -0
  15. package/dist/browser.test.js +136 -0
  16. package/dist/browser.test.js.map +1 -0
  17. package/dist/client.d.ts +17 -0
  18. package/dist/client.d.ts.map +1 -0
  19. package/dist/client.js +133 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/daemon.d.ts +29 -0
  22. package/dist/daemon.d.ts.map +1 -0
  23. package/dist/daemon.js +165 -0
  24. package/dist/daemon.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +1158 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/protocol.d.ts +26 -0
  30. package/dist/protocol.d.ts.map +1 -0
  31. package/dist/protocol.js +717 -0
  32. package/dist/protocol.js.map +1 -0
  33. package/dist/protocol.test.d.ts +2 -0
  34. package/dist/protocol.test.d.ts.map +1 -0
  35. package/dist/protocol.test.js +176 -0
  36. package/dist/protocol.test.js.map +1 -0
  37. package/dist/types.d.ts +604 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/types.js +2 -0
  40. package/dist/types.js.map +1 -0
  41. package/package.json +39 -8
  42. package/scripts/postinstall.js +17 -0
  43. package/src/actions.ts +1658 -0
  44. package/src/browser.test.ts +157 -0
  45. package/src/browser.ts +586 -0
  46. package/src/client.ts +150 -0
  47. package/src/daemon.ts +187 -0
  48. package/src/index.ts +1180 -0
  49. package/src/protocol.test.ts +216 -0
  50. package/src/protocol.ts +848 -0
  51. package/src/types.ts +913 -0
  52. package/tsconfig.json +19 -0
  53. package/vitest.config.ts +9 -0
  54. package/index.js +0 -2
package/src/actions.ts ADDED
@@ -0,0 +1,1658 @@
1
+ import type { Page, Frame } from 'playwright-core';
2
+ import type { BrowserManager } from './browser.js';
3
+ import type {
4
+ Command,
5
+ Response,
6
+ NavigateCommand,
7
+ ClickCommand,
8
+ TypeCommand,
9
+ FillCommand,
10
+ CheckCommand,
11
+ UncheckCommand,
12
+ UploadCommand,
13
+ DoubleClickCommand,
14
+ FocusCommand,
15
+ DragCommand,
16
+ FrameCommand,
17
+ GetByRoleCommand,
18
+ GetByTextCommand,
19
+ GetByLabelCommand,
20
+ GetByPlaceholderCommand,
21
+ PressCommand,
22
+ ScreenshotCommand,
23
+ EvaluateCommand,
24
+ WaitCommand,
25
+ ScrollCommand,
26
+ SelectCommand,
27
+ HoverCommand,
28
+ ContentCommand,
29
+ TabSwitchCommand,
30
+ TabCloseCommand,
31
+ WindowNewCommand,
32
+ CookiesSetCommand,
33
+ StorageGetCommand,
34
+ StorageSetCommand,
35
+ StorageClearCommand,
36
+ DialogCommand,
37
+ PdfCommand,
38
+ RouteCommand,
39
+ RequestsCommand,
40
+ DownloadCommand,
41
+ GeolocationCommand,
42
+ PermissionsCommand,
43
+ ViewportCommand,
44
+ DeviceCommand,
45
+ GetAttributeCommand,
46
+ GetTextCommand,
47
+ IsVisibleCommand,
48
+ IsEnabledCommand,
49
+ IsCheckedCommand,
50
+ CountCommand,
51
+ BoundingBoxCommand,
52
+ TraceStartCommand,
53
+ TraceStopCommand,
54
+ HarStopCommand,
55
+ StorageStateSaveCommand,
56
+ ConsoleCommand,
57
+ ErrorsCommand,
58
+ KeyboardCommand,
59
+ WheelCommand,
60
+ TapCommand,
61
+ ClipboardCommand,
62
+ HighlightCommand,
63
+ ClearCommand,
64
+ SelectAllCommand,
65
+ InnerTextCommand,
66
+ InnerHtmlCommand,
67
+ InputValueCommand,
68
+ SetValueCommand,
69
+ DispatchEventCommand,
70
+ AddScriptCommand,
71
+ AddStyleCommand,
72
+ EmulateMediaCommand,
73
+ OfflineCommand,
74
+ HeadersCommand,
75
+ GetByAltTextCommand,
76
+ GetByTitleCommand,
77
+ GetByTestIdCommand,
78
+ NthCommand,
79
+ WaitForUrlCommand,
80
+ WaitForLoadStateCommand,
81
+ SetContentCommand,
82
+ TimezoneCommand,
83
+ LocaleCommand,
84
+ HttpCredentialsCommand,
85
+ MouseMoveCommand,
86
+ MouseDownCommand,
87
+ MouseUpCommand,
88
+ WaitForFunctionCommand,
89
+ ScrollIntoViewCommand,
90
+ AddInitScriptCommand,
91
+ KeyDownCommand,
92
+ KeyUpCommand,
93
+ InsertTextCommand,
94
+ MultiSelectCommand,
95
+ WaitForDownloadCommand,
96
+ ResponseBodyCommand,
97
+ NavigateData,
98
+ ScreenshotData,
99
+ EvaluateData,
100
+ ContentData,
101
+ TabListData,
102
+ TabNewData,
103
+ TabSwitchData,
104
+ TabCloseData,
105
+ } from './types.js';
106
+ import { successResponse, errorResponse } from './protocol.js';
107
+
108
+ // Snapshot response type
109
+ interface SnapshotData {
110
+ snapshot: string;
111
+ }
112
+
113
+ /**
114
+ * Execute a command and return a response
115
+ */
116
+ export async function executeCommand(command: Command, browser: BrowserManager): Promise<Response> {
117
+ try {
118
+ switch (command.action) {
119
+ case 'launch':
120
+ return await handleLaunch(command, browser);
121
+ case 'navigate':
122
+ return await handleNavigate(command, browser);
123
+ case 'click':
124
+ return await handleClick(command, browser);
125
+ case 'type':
126
+ return await handleType(command, browser);
127
+ case 'fill':
128
+ return await handleFill(command, browser);
129
+ case 'check':
130
+ return await handleCheck(command, browser);
131
+ case 'uncheck':
132
+ return await handleUncheck(command, browser);
133
+ case 'upload':
134
+ return await handleUpload(command, browser);
135
+ case 'dblclick':
136
+ return await handleDoubleClick(command, browser);
137
+ case 'focus':
138
+ return await handleFocus(command, browser);
139
+ case 'drag':
140
+ return await handleDrag(command, browser);
141
+ case 'frame':
142
+ return await handleFrame(command, browser);
143
+ case 'mainframe':
144
+ return await handleMainFrame(command, browser);
145
+ case 'getbyrole':
146
+ return await handleGetByRole(command, browser);
147
+ case 'getbytext':
148
+ return await handleGetByText(command, browser);
149
+ case 'getbylabel':
150
+ return await handleGetByLabel(command, browser);
151
+ case 'getbyplaceholder':
152
+ return await handleGetByPlaceholder(command, browser);
153
+ case 'press':
154
+ return await handlePress(command, browser);
155
+ case 'screenshot':
156
+ return await handleScreenshot(command, browser);
157
+ case 'snapshot':
158
+ return await handleSnapshot(command, browser);
159
+ case 'evaluate':
160
+ return await handleEvaluate(command, browser);
161
+ case 'wait':
162
+ return await handleWait(command, browser);
163
+ case 'scroll':
164
+ return await handleScroll(command, browser);
165
+ case 'select':
166
+ return await handleSelect(command, browser);
167
+ case 'hover':
168
+ return await handleHover(command, browser);
169
+ case 'content':
170
+ return await handleContent(command, browser);
171
+ case 'close':
172
+ return await handleClose(command, browser);
173
+ case 'tab_new':
174
+ return await handleTabNew(command, browser);
175
+ case 'tab_list':
176
+ return await handleTabList(command, browser);
177
+ case 'tab_switch':
178
+ return await handleTabSwitch(command, browser);
179
+ case 'tab_close':
180
+ return await handleTabClose(command, browser);
181
+ case 'window_new':
182
+ return await handleWindowNew(command, browser);
183
+ case 'cookies_get':
184
+ return await handleCookiesGet(command, browser);
185
+ case 'cookies_set':
186
+ return await handleCookiesSet(command, browser);
187
+ case 'cookies_clear':
188
+ return await handleCookiesClear(command, browser);
189
+ case 'storage_get':
190
+ return await handleStorageGet(command, browser);
191
+ case 'storage_set':
192
+ return await handleStorageSet(command, browser);
193
+ case 'storage_clear':
194
+ return await handleStorageClear(command, browser);
195
+ case 'dialog':
196
+ return await handleDialog(command, browser);
197
+ case 'pdf':
198
+ return await handlePdf(command, browser);
199
+ case 'route':
200
+ return await handleRoute(command, browser);
201
+ case 'unroute':
202
+ return await handleUnroute(command, browser);
203
+ case 'requests':
204
+ return await handleRequests(command, browser);
205
+ case 'download':
206
+ return await handleDownload(command, browser);
207
+ case 'geolocation':
208
+ return await handleGeolocation(command, browser);
209
+ case 'permissions':
210
+ return await handlePermissions(command, browser);
211
+ case 'viewport':
212
+ return await handleViewport(command, browser);
213
+ case 'useragent':
214
+ return await handleUserAgent(command, browser);
215
+ case 'device':
216
+ return await handleDevice(command, browser);
217
+ case 'back':
218
+ return await handleBack(command, browser);
219
+ case 'forward':
220
+ return await handleForward(command, browser);
221
+ case 'reload':
222
+ return await handleReload(command, browser);
223
+ case 'url':
224
+ return await handleUrl(command, browser);
225
+ case 'title':
226
+ return await handleTitle(command, browser);
227
+ case 'getattribute':
228
+ return await handleGetAttribute(command, browser);
229
+ case 'gettext':
230
+ return await handleGetText(command, browser);
231
+ case 'isvisible':
232
+ return await handleIsVisible(command, browser);
233
+ case 'isenabled':
234
+ return await handleIsEnabled(command, browser);
235
+ case 'ischecked':
236
+ return await handleIsChecked(command, browser);
237
+ case 'count':
238
+ return await handleCount(command, browser);
239
+ case 'boundingbox':
240
+ return await handleBoundingBox(command, browser);
241
+ case 'video_start':
242
+ return await handleVideoStart(command, browser);
243
+ case 'video_stop':
244
+ return await handleVideoStop(command, browser);
245
+ case 'trace_start':
246
+ return await handleTraceStart(command, browser);
247
+ case 'trace_stop':
248
+ return await handleTraceStop(command, browser);
249
+ case 'har_start':
250
+ return await handleHarStart(command, browser);
251
+ case 'har_stop':
252
+ return await handleHarStop(command, browser);
253
+ case 'state_save':
254
+ return await handleStateSave(command, browser);
255
+ case 'state_load':
256
+ return await handleStateLoad(command, browser);
257
+ case 'console':
258
+ return await handleConsole(command, browser);
259
+ case 'errors':
260
+ return await handleErrors(command, browser);
261
+ case 'keyboard':
262
+ return await handleKeyboard(command, browser);
263
+ case 'wheel':
264
+ return await handleWheel(command, browser);
265
+ case 'tap':
266
+ return await handleTap(command, browser);
267
+ case 'clipboard':
268
+ return await handleClipboard(command, browser);
269
+ case 'highlight':
270
+ return await handleHighlight(command, browser);
271
+ case 'clear':
272
+ return await handleClear(command, browser);
273
+ case 'selectall':
274
+ return await handleSelectAll(command, browser);
275
+ case 'innertext':
276
+ return await handleInnerText(command, browser);
277
+ case 'innerhtml':
278
+ return await handleInnerHtml(command, browser);
279
+ case 'inputvalue':
280
+ return await handleInputValue(command, browser);
281
+ case 'setvalue':
282
+ return await handleSetValue(command, browser);
283
+ case 'dispatch':
284
+ return await handleDispatch(command, browser);
285
+ case 'evalhandle':
286
+ return await handleEvalHandle(command, browser);
287
+ case 'expose':
288
+ return await handleExpose(command, browser);
289
+ case 'addscript':
290
+ return await handleAddScript(command, browser);
291
+ case 'addstyle':
292
+ return await handleAddStyle(command, browser);
293
+ case 'emulatemedia':
294
+ return await handleEmulateMedia(command, browser);
295
+ case 'offline':
296
+ return await handleOffline(command, browser);
297
+ case 'headers':
298
+ return await handleHeaders(command, browser);
299
+ case 'pause':
300
+ return await handlePause(command, browser);
301
+ case 'getbyalttext':
302
+ return await handleGetByAltText(command, browser);
303
+ case 'getbytitle':
304
+ return await handleGetByTitle(command, browser);
305
+ case 'getbytestid':
306
+ return await handleGetByTestId(command, browser);
307
+ case 'nth':
308
+ return await handleNth(command, browser);
309
+ case 'waitforurl':
310
+ return await handleWaitForUrl(command, browser);
311
+ case 'waitforloadstate':
312
+ return await handleWaitForLoadState(command, browser);
313
+ case 'setcontent':
314
+ return await handleSetContent(command, browser);
315
+ case 'timezone':
316
+ return await handleTimezone(command, browser);
317
+ case 'locale':
318
+ return await handleLocale(command, browser);
319
+ case 'credentials':
320
+ return await handleCredentials(command, browser);
321
+ case 'mousemove':
322
+ return await handleMouseMove(command, browser);
323
+ case 'mousedown':
324
+ return await handleMouseDown(command, browser);
325
+ case 'mouseup':
326
+ return await handleMouseUp(command, browser);
327
+ case 'bringtofront':
328
+ return await handleBringToFront(command, browser);
329
+ case 'waitforfunction':
330
+ return await handleWaitForFunction(command, browser);
331
+ case 'scrollintoview':
332
+ return await handleScrollIntoView(command, browser);
333
+ case 'addinitscript':
334
+ return await handleAddInitScript(command, browser);
335
+ case 'keydown':
336
+ return await handleKeyDown(command, browser);
337
+ case 'keyup':
338
+ return await handleKeyUp(command, browser);
339
+ case 'inserttext':
340
+ return await handleInsertText(command, browser);
341
+ case 'multiselect':
342
+ return await handleMultiSelect(command, browser);
343
+ case 'waitfordownload':
344
+ return await handleWaitForDownload(command, browser);
345
+ case 'responsebody':
346
+ return await handleResponseBody(command, browser);
347
+ default: {
348
+ // TypeScript narrows to never here, but we handle it for safety
349
+ const unknownCommand = command as { id: string; action: string };
350
+ return errorResponse(unknownCommand.id, `Unknown action: ${unknownCommand.action}`);
351
+ }
352
+ }
353
+ } catch (error) {
354
+ const message = error instanceof Error ? error.message : String(error);
355
+ return errorResponse(command.id, message);
356
+ }
357
+ }
358
+
359
+ async function handleLaunch(
360
+ command: Command & { action: 'launch' },
361
+ browser: BrowserManager
362
+ ): Promise<Response> {
363
+ await browser.launch(command);
364
+ return successResponse(command.id, { launched: true });
365
+ }
366
+
367
+ async function handleNavigate(
368
+ command: NavigateCommand,
369
+ browser: BrowserManager
370
+ ): Promise<Response<NavigateData>> {
371
+ const page = browser.getPage();
372
+ await page.goto(command.url, {
373
+ waitUntil: command.waitUntil ?? 'load',
374
+ });
375
+
376
+ return successResponse(command.id, {
377
+ url: page.url(),
378
+ title: await page.title(),
379
+ });
380
+ }
381
+
382
+ async function handleClick(command: ClickCommand, browser: BrowserManager): Promise<Response> {
383
+ const page = browser.getPage();
384
+ await page.click(command.selector, {
385
+ button: command.button,
386
+ clickCount: command.clickCount,
387
+ delay: command.delay,
388
+ });
389
+
390
+ return successResponse(command.id, { clicked: true });
391
+ }
392
+
393
+ async function handleType(command: TypeCommand, browser: BrowserManager): Promise<Response> {
394
+ const page = browser.getPage();
395
+
396
+ if (command.clear) {
397
+ await page.fill(command.selector, '');
398
+ }
399
+
400
+ await page.type(command.selector, command.text, {
401
+ delay: command.delay,
402
+ });
403
+
404
+ return successResponse(command.id, { typed: true });
405
+ }
406
+
407
+ async function handlePress(command: PressCommand, browser: BrowserManager): Promise<Response> {
408
+ const page = browser.getPage();
409
+
410
+ if (command.selector) {
411
+ await page.press(command.selector, command.key);
412
+ } else {
413
+ await page.keyboard.press(command.key);
414
+ }
415
+
416
+ return successResponse(command.id, { pressed: true });
417
+ }
418
+
419
+ async function handleScreenshot(
420
+ command: ScreenshotCommand,
421
+ browser: BrowserManager
422
+ ): Promise<Response<ScreenshotData>> {
423
+ const page = browser.getPage();
424
+
425
+ const options: Parameters<Page['screenshot']>[0] = {
426
+ fullPage: command.fullPage,
427
+ type: command.format ?? 'png',
428
+ };
429
+
430
+ if (command.format === 'jpeg' && command.quality !== undefined) {
431
+ options.quality = command.quality;
432
+ }
433
+
434
+ let target: Page | ReturnType<Page['locator']> = page;
435
+ if (command.selector) {
436
+ target = page.locator(command.selector);
437
+ }
438
+
439
+ if (command.path) {
440
+ await target.screenshot({ ...options, path: command.path });
441
+ return successResponse(command.id, { path: command.path });
442
+ } else {
443
+ const buffer = await target.screenshot(options);
444
+ return successResponse(command.id, { base64: buffer.toString('base64') });
445
+ }
446
+ }
447
+
448
+ async function handleSnapshot(
449
+ command: Command & { action: 'snapshot' },
450
+ browser: BrowserManager
451
+ ): Promise<Response<SnapshotData>> {
452
+ const page = browser.getPage();
453
+ // Use ariaSnapshot which returns a string representation of the accessibility tree
454
+ const snapshot = await page.locator(':root').ariaSnapshot();
455
+
456
+ return successResponse(command.id, {
457
+ snapshot: snapshot ?? 'Empty page',
458
+ });
459
+ }
460
+
461
+ async function handleEvaluate(
462
+ command: EvaluateCommand,
463
+ browser: BrowserManager
464
+ ): Promise<Response<EvaluateData>> {
465
+ const page = browser.getPage();
466
+
467
+ // Evaluate the script directly as a string expression
468
+ const result = await page.evaluate(command.script);
469
+
470
+ return successResponse(command.id, { result });
471
+ }
472
+
473
+ async function handleWait(command: WaitCommand, browser: BrowserManager): Promise<Response> {
474
+ const page = browser.getPage();
475
+
476
+ if (command.selector) {
477
+ await page.waitForSelector(command.selector, {
478
+ state: command.state ?? 'visible',
479
+ timeout: command.timeout,
480
+ });
481
+ } else if (command.timeout) {
482
+ await page.waitForTimeout(command.timeout);
483
+ } else {
484
+ // Default: wait for load state
485
+ await page.waitForLoadState('load');
486
+ }
487
+
488
+ return successResponse(command.id, { waited: true });
489
+ }
490
+
491
+ async function handleScroll(command: ScrollCommand, browser: BrowserManager): Promise<Response> {
492
+ const page = browser.getPage();
493
+
494
+ if (command.selector) {
495
+ const element = page.locator(command.selector);
496
+ await element.scrollIntoViewIfNeeded();
497
+
498
+ if (command.x !== undefined || command.y !== undefined) {
499
+ await element.evaluate(
500
+ (el, { x, y }) => {
501
+ el.scrollBy(x ?? 0, y ?? 0);
502
+ },
503
+ { x: command.x, y: command.y }
504
+ );
505
+ }
506
+ } else {
507
+ // Scroll the page
508
+ let deltaX = command.x ?? 0;
509
+ let deltaY = command.y ?? 0;
510
+
511
+ if (command.direction) {
512
+ const amount = command.amount ?? 100;
513
+ switch (command.direction) {
514
+ case 'up':
515
+ deltaY = -amount;
516
+ break;
517
+ case 'down':
518
+ deltaY = amount;
519
+ break;
520
+ case 'left':
521
+ deltaX = -amount;
522
+ break;
523
+ case 'right':
524
+ deltaX = amount;
525
+ break;
526
+ }
527
+ }
528
+
529
+ await page.evaluate(`window.scrollBy(${deltaX}, ${deltaY})`);
530
+ }
531
+
532
+ return successResponse(command.id, { scrolled: true });
533
+ }
534
+
535
+ async function handleSelect(command: SelectCommand, browser: BrowserManager): Promise<Response> {
536
+ const page = browser.getPage();
537
+ const values = Array.isArray(command.values) ? command.values : [command.values];
538
+
539
+ await page.selectOption(command.selector, values);
540
+
541
+ return successResponse(command.id, { selected: values });
542
+ }
543
+
544
+ async function handleHover(command: HoverCommand, browser: BrowserManager): Promise<Response> {
545
+ const page = browser.getPage();
546
+ await page.hover(command.selector);
547
+
548
+ return successResponse(command.id, { hovered: true });
549
+ }
550
+
551
+ async function handleContent(
552
+ command: ContentCommand,
553
+ browser: BrowserManager
554
+ ): Promise<Response<ContentData>> {
555
+ const page = browser.getPage();
556
+
557
+ let html: string;
558
+ if (command.selector) {
559
+ html = await page.locator(command.selector).innerHTML();
560
+ } else {
561
+ html = await page.content();
562
+ }
563
+
564
+ return successResponse(command.id, { html });
565
+ }
566
+
567
+ async function handleClose(
568
+ command: Command & { action: 'close' },
569
+ browser: BrowserManager
570
+ ): Promise<Response> {
571
+ await browser.close();
572
+ return successResponse(command.id, { closed: true });
573
+ }
574
+
575
+ async function handleTabNew(
576
+ command: Command & { action: 'tab_new' },
577
+ browser: BrowserManager
578
+ ): Promise<Response<TabNewData>> {
579
+ const result = await browser.newTab();
580
+ return successResponse(command.id, result);
581
+ }
582
+
583
+ async function handleTabList(
584
+ command: Command & { action: 'tab_list' },
585
+ browser: BrowserManager
586
+ ): Promise<Response<TabListData>> {
587
+ const tabs = await browser.listTabs();
588
+ return successResponse(command.id, {
589
+ tabs,
590
+ active: browser.getActiveIndex(),
591
+ });
592
+ }
593
+
594
+ async function handleTabSwitch(
595
+ command: TabSwitchCommand,
596
+ browser: BrowserManager
597
+ ): Promise<Response<TabSwitchData>> {
598
+ const result = browser.switchTo(command.index);
599
+ const page = browser.getPage();
600
+ return successResponse(command.id, {
601
+ ...result,
602
+ title: await page.title(),
603
+ });
604
+ }
605
+
606
+ async function handleTabClose(
607
+ command: TabCloseCommand,
608
+ browser: BrowserManager
609
+ ): Promise<Response<TabCloseData>> {
610
+ const result = await browser.closeTab(command.index);
611
+ return successResponse(command.id, result);
612
+ }
613
+
614
+ async function handleWindowNew(
615
+ command: WindowNewCommand,
616
+ browser: BrowserManager
617
+ ): Promise<Response<TabNewData>> {
618
+ const result = await browser.newWindow(command.viewport);
619
+ return successResponse(command.id, result);
620
+ }
621
+
622
+ // New handlers for enhanced Playwright parity
623
+
624
+ async function handleFill(command: FillCommand, browser: BrowserManager): Promise<Response> {
625
+ const frame = browser.getFrame();
626
+ await frame.fill(command.selector, command.value);
627
+ return successResponse(command.id, { filled: true });
628
+ }
629
+
630
+ async function handleCheck(command: CheckCommand, browser: BrowserManager): Promise<Response> {
631
+ const frame = browser.getFrame();
632
+ await frame.check(command.selector);
633
+ return successResponse(command.id, { checked: true });
634
+ }
635
+
636
+ async function handleUncheck(command: UncheckCommand, browser: BrowserManager): Promise<Response> {
637
+ const frame = browser.getFrame();
638
+ await frame.uncheck(command.selector);
639
+ return successResponse(command.id, { unchecked: true });
640
+ }
641
+
642
+ async function handleUpload(command: UploadCommand, browser: BrowserManager): Promise<Response> {
643
+ const frame = browser.getFrame();
644
+ const files = Array.isArray(command.files) ? command.files : [command.files];
645
+ await frame.setInputFiles(command.selector, files);
646
+ return successResponse(command.id, { uploaded: files });
647
+ }
648
+
649
+ async function handleDoubleClick(
650
+ command: DoubleClickCommand,
651
+ browser: BrowserManager
652
+ ): Promise<Response> {
653
+ const frame = browser.getFrame();
654
+ await frame.dblclick(command.selector);
655
+ return successResponse(command.id, { clicked: true });
656
+ }
657
+
658
+ async function handleFocus(command: FocusCommand, browser: BrowserManager): Promise<Response> {
659
+ const frame = browser.getFrame();
660
+ await frame.focus(command.selector);
661
+ return successResponse(command.id, { focused: true });
662
+ }
663
+
664
+ async function handleDrag(command: DragCommand, browser: BrowserManager): Promise<Response> {
665
+ const frame = browser.getFrame();
666
+ await frame.dragAndDrop(command.source, command.target);
667
+ return successResponse(command.id, { dragged: true });
668
+ }
669
+
670
+ async function handleFrame(command: FrameCommand, browser: BrowserManager): Promise<Response> {
671
+ await browser.switchToFrame({
672
+ selector: command.selector,
673
+ name: command.name,
674
+ url: command.url,
675
+ });
676
+ return successResponse(command.id, { switched: true });
677
+ }
678
+
679
+ async function handleMainFrame(
680
+ command: Command & { action: 'mainframe' },
681
+ browser: BrowserManager
682
+ ): Promise<Response> {
683
+ browser.switchToMainFrame();
684
+ return successResponse(command.id, { switched: true });
685
+ }
686
+
687
+ async function handleGetByRole(
688
+ command: GetByRoleCommand,
689
+ browser: BrowserManager
690
+ ): Promise<Response> {
691
+ const page = browser.getPage();
692
+ const locator = page.getByRole(command.role as any, { name: command.name });
693
+
694
+ switch (command.subaction) {
695
+ case 'click':
696
+ await locator.click();
697
+ return successResponse(command.id, { clicked: true });
698
+ case 'fill':
699
+ await locator.fill(command.value ?? '');
700
+ return successResponse(command.id, { filled: true });
701
+ case 'check':
702
+ await locator.check();
703
+ return successResponse(command.id, { checked: true });
704
+ case 'hover':
705
+ await locator.hover();
706
+ return successResponse(command.id, { hovered: true });
707
+ }
708
+ }
709
+
710
+ async function handleGetByText(
711
+ command: GetByTextCommand,
712
+ browser: BrowserManager
713
+ ): Promise<Response> {
714
+ const page = browser.getPage();
715
+ const locator = page.getByText(command.text, { exact: command.exact });
716
+
717
+ switch (command.subaction) {
718
+ case 'click':
719
+ await locator.click();
720
+ return successResponse(command.id, { clicked: true });
721
+ case 'hover':
722
+ await locator.hover();
723
+ return successResponse(command.id, { hovered: true });
724
+ }
725
+ }
726
+
727
+ async function handleGetByLabel(
728
+ command: GetByLabelCommand,
729
+ browser: BrowserManager
730
+ ): Promise<Response> {
731
+ const page = browser.getPage();
732
+ const locator = page.getByLabel(command.label);
733
+
734
+ switch (command.subaction) {
735
+ case 'click':
736
+ await locator.click();
737
+ return successResponse(command.id, { clicked: true });
738
+ case 'fill':
739
+ await locator.fill(command.value ?? '');
740
+ return successResponse(command.id, { filled: true });
741
+ case 'check':
742
+ await locator.check();
743
+ return successResponse(command.id, { checked: true });
744
+ }
745
+ }
746
+
747
+ async function handleGetByPlaceholder(
748
+ command: GetByPlaceholderCommand,
749
+ browser: BrowserManager
750
+ ): Promise<Response> {
751
+ const page = browser.getPage();
752
+ const locator = page.getByPlaceholder(command.placeholder);
753
+
754
+ switch (command.subaction) {
755
+ case 'click':
756
+ await locator.click();
757
+ return successResponse(command.id, { clicked: true });
758
+ case 'fill':
759
+ await locator.fill(command.value ?? '');
760
+ return successResponse(command.id, { filled: true });
761
+ }
762
+ }
763
+
764
+ async function handleCookiesGet(
765
+ command: Command & { action: 'cookies_get'; urls?: string[] },
766
+ browser: BrowserManager
767
+ ): Promise<Response> {
768
+ const page = browser.getPage();
769
+ const context = page.context();
770
+ const cookies = await context.cookies(command.urls);
771
+ return successResponse(command.id, { cookies });
772
+ }
773
+
774
+ async function handleCookiesSet(
775
+ command: CookiesSetCommand,
776
+ browser: BrowserManager
777
+ ): Promise<Response> {
778
+ const page = browser.getPage();
779
+ const context = page.context();
780
+ await context.addCookies(command.cookies);
781
+ return successResponse(command.id, { set: true });
782
+ }
783
+
784
+ async function handleCookiesClear(
785
+ command: Command & { action: 'cookies_clear' },
786
+ browser: BrowserManager
787
+ ): Promise<Response> {
788
+ const page = browser.getPage();
789
+ const context = page.context();
790
+ await context.clearCookies();
791
+ return successResponse(command.id, { cleared: true });
792
+ }
793
+
794
+ async function handleStorageGet(
795
+ command: StorageGetCommand,
796
+ browser: BrowserManager
797
+ ): Promise<Response> {
798
+ const page = browser.getPage();
799
+ const storageType = command.type === 'local' ? 'localStorage' : 'sessionStorage';
800
+
801
+ if (command.key) {
802
+ const value = await page.evaluate(`${storageType}.getItem(${JSON.stringify(command.key)})`);
803
+ return successResponse(command.id, { key: command.key, value });
804
+ } else {
805
+ const data = await page.evaluate(`
806
+ (() => {
807
+ const storage = ${storageType};
808
+ const result = {};
809
+ for (let i = 0; i < storage.length; i++) {
810
+ const key = storage.key(i);
811
+ if (key) result[key] = storage.getItem(key);
812
+ }
813
+ return result;
814
+ })()
815
+ `);
816
+ return successResponse(command.id, { data });
817
+ }
818
+ }
819
+
820
+ async function handleStorageSet(
821
+ command: StorageSetCommand,
822
+ browser: BrowserManager
823
+ ): Promise<Response> {
824
+ const page = browser.getPage();
825
+ const storageType = command.type === 'local' ? 'localStorage' : 'sessionStorage';
826
+
827
+ await page.evaluate(
828
+ `${storageType}.setItem(${JSON.stringify(command.key)}, ${JSON.stringify(command.value)})`
829
+ );
830
+ return successResponse(command.id, { set: true });
831
+ }
832
+
833
+ async function handleStorageClear(
834
+ command: StorageClearCommand,
835
+ browser: BrowserManager
836
+ ): Promise<Response> {
837
+ const page = browser.getPage();
838
+ const storageType = command.type === 'local' ? 'localStorage' : 'sessionStorage';
839
+
840
+ await page.evaluate(`${storageType}.clear()`);
841
+ return successResponse(command.id, { cleared: true });
842
+ }
843
+
844
+ async function handleDialog(command: DialogCommand, browser: BrowserManager): Promise<Response> {
845
+ browser.setDialogHandler(command.response, command.promptText);
846
+ return successResponse(command.id, { handler: 'set', response: command.response });
847
+ }
848
+
849
+ async function handlePdf(command: PdfCommand, browser: BrowserManager): Promise<Response> {
850
+ const page = browser.getPage();
851
+ await page.pdf({
852
+ path: command.path,
853
+ format: command.format ?? 'Letter',
854
+ });
855
+ return successResponse(command.id, { path: command.path });
856
+ }
857
+
858
+ // Network & Request handlers
859
+
860
+ async function handleRoute(command: RouteCommand, browser: BrowserManager): Promise<Response> {
861
+ await browser.addRoute(command.url, {
862
+ response: command.response,
863
+ abort: command.abort,
864
+ });
865
+ return successResponse(command.id, { routed: command.url });
866
+ }
867
+
868
+ async function handleUnroute(
869
+ command: Command & { action: 'unroute'; url?: string },
870
+ browser: BrowserManager
871
+ ): Promise<Response> {
872
+ await browser.removeRoute(command.url);
873
+ return successResponse(command.id, { unrouted: command.url ?? 'all' });
874
+ }
875
+
876
+ async function handleRequests(
877
+ command: RequestsCommand,
878
+ browser: BrowserManager
879
+ ): Promise<Response> {
880
+ if (command.clear) {
881
+ browser.clearRequests();
882
+ return successResponse(command.id, { cleared: true });
883
+ }
884
+
885
+ // Start tracking if not already
886
+ browser.startRequestTracking();
887
+
888
+ const requests = browser.getRequests(command.filter);
889
+ return successResponse(command.id, { requests });
890
+ }
891
+
892
+ async function handleDownload(
893
+ command: DownloadCommand,
894
+ browser: BrowserManager
895
+ ): Promise<Response> {
896
+ const page = browser.getPage();
897
+
898
+ const [download] = await Promise.all([
899
+ page.waitForEvent('download'),
900
+ page.click(command.selector),
901
+ ]);
902
+
903
+ await download.saveAs(command.path);
904
+ return successResponse(command.id, {
905
+ path: command.path,
906
+ suggestedFilename: download.suggestedFilename(),
907
+ });
908
+ }
909
+
910
+ async function handleGeolocation(
911
+ command: GeolocationCommand,
912
+ browser: BrowserManager
913
+ ): Promise<Response> {
914
+ await browser.setGeolocation(command.latitude, command.longitude, command.accuracy);
915
+ return successResponse(command.id, {
916
+ latitude: command.latitude,
917
+ longitude: command.longitude,
918
+ });
919
+ }
920
+
921
+ async function handlePermissions(
922
+ command: PermissionsCommand,
923
+ browser: BrowserManager
924
+ ): Promise<Response> {
925
+ await browser.setPermissions(command.permissions, command.grant);
926
+ return successResponse(command.id, {
927
+ permissions: command.permissions,
928
+ granted: command.grant,
929
+ });
930
+ }
931
+
932
+ async function handleViewport(
933
+ command: ViewportCommand,
934
+ browser: BrowserManager
935
+ ): Promise<Response> {
936
+ await browser.setViewport(command.width, command.height);
937
+ return successResponse(command.id, {
938
+ width: command.width,
939
+ height: command.height,
940
+ });
941
+ }
942
+
943
+ async function handleUserAgent(
944
+ command: Command & { action: 'useragent'; userAgent: string },
945
+ browser: BrowserManager
946
+ ): Promise<Response> {
947
+ const page = browser.getPage();
948
+ const context = page.context();
949
+ // Note: Can't change user agent after context is created, but we can for new pages
950
+ return successResponse(command.id, {
951
+ note: 'User agent can only be set at launch time. Use device command instead.',
952
+ });
953
+ }
954
+
955
+ async function handleDevice(command: DeviceCommand, browser: BrowserManager): Promise<Response> {
956
+ const device = browser.getDevice(command.device);
957
+ if (!device) {
958
+ const available = browser.listDevices().slice(0, 10).join(', ');
959
+ throw new Error(`Unknown device: ${command.device}. Available: ${available}...`);
960
+ }
961
+
962
+ // Apply device viewport
963
+ await browser.setViewport(device.viewport.width, device.viewport.height);
964
+
965
+ return successResponse(command.id, {
966
+ device: command.device,
967
+ viewport: device.viewport,
968
+ userAgent: device.userAgent,
969
+ });
970
+ }
971
+
972
+ async function handleBack(
973
+ command: Command & { action: 'back' },
974
+ browser: BrowserManager
975
+ ): Promise<Response> {
976
+ const page = browser.getPage();
977
+ await page.goBack();
978
+ return successResponse(command.id, { url: page.url() });
979
+ }
980
+
981
+ async function handleForward(
982
+ command: Command & { action: 'forward' },
983
+ browser: BrowserManager
984
+ ): Promise<Response> {
985
+ const page = browser.getPage();
986
+ await page.goForward();
987
+ return successResponse(command.id, { url: page.url() });
988
+ }
989
+
990
+ async function handleReload(
991
+ command: Command & { action: 'reload' },
992
+ browser: BrowserManager
993
+ ): Promise<Response> {
994
+ const page = browser.getPage();
995
+ await page.reload();
996
+ return successResponse(command.id, { url: page.url() });
997
+ }
998
+
999
+ async function handleUrl(
1000
+ command: Command & { action: 'url' },
1001
+ browser: BrowserManager
1002
+ ): Promise<Response> {
1003
+ const page = browser.getPage();
1004
+ return successResponse(command.id, { url: page.url() });
1005
+ }
1006
+
1007
+ async function handleTitle(
1008
+ command: Command & { action: 'title' },
1009
+ browser: BrowserManager
1010
+ ): Promise<Response> {
1011
+ const page = browser.getPage();
1012
+ const title = await page.title();
1013
+ return successResponse(command.id, { title });
1014
+ }
1015
+
1016
+ async function handleGetAttribute(
1017
+ command: GetAttributeCommand,
1018
+ browser: BrowserManager
1019
+ ): Promise<Response> {
1020
+ const page = browser.getPage();
1021
+ const value = await page.getAttribute(command.selector, command.attribute);
1022
+ return successResponse(command.id, { attribute: command.attribute, value });
1023
+ }
1024
+
1025
+ async function handleGetText(command: GetTextCommand, browser: BrowserManager): Promise<Response> {
1026
+ const page = browser.getPage();
1027
+ const text = await page.textContent(command.selector);
1028
+ return successResponse(command.id, { text });
1029
+ }
1030
+
1031
+ async function handleIsVisible(
1032
+ command: IsVisibleCommand,
1033
+ browser: BrowserManager
1034
+ ): Promise<Response> {
1035
+ const page = browser.getPage();
1036
+ const visible = await page.isVisible(command.selector);
1037
+ return successResponse(command.id, { visible });
1038
+ }
1039
+
1040
+ async function handleIsEnabled(
1041
+ command: IsEnabledCommand,
1042
+ browser: BrowserManager
1043
+ ): Promise<Response> {
1044
+ const page = browser.getPage();
1045
+ const enabled = await page.isEnabled(command.selector);
1046
+ return successResponse(command.id, { enabled });
1047
+ }
1048
+
1049
+ async function handleIsChecked(
1050
+ command: IsCheckedCommand,
1051
+ browser: BrowserManager
1052
+ ): Promise<Response> {
1053
+ const page = browser.getPage();
1054
+ const checked = await page.isChecked(command.selector);
1055
+ return successResponse(command.id, { checked });
1056
+ }
1057
+
1058
+ async function handleCount(command: CountCommand, browser: BrowserManager): Promise<Response> {
1059
+ const page = browser.getPage();
1060
+ const count = await page.locator(command.selector).count();
1061
+ return successResponse(command.id, { count });
1062
+ }
1063
+
1064
+ async function handleBoundingBox(
1065
+ command: BoundingBoxCommand,
1066
+ browser: BrowserManager
1067
+ ): Promise<Response> {
1068
+ const page = browser.getPage();
1069
+ const box = await page.locator(command.selector).boundingBox();
1070
+ return successResponse(command.id, { box });
1071
+ }
1072
+
1073
+ // Advanced handlers
1074
+
1075
+ async function handleVideoStart(
1076
+ command: Command & { action: 'video_start'; path: string },
1077
+ browser: BrowserManager
1078
+ ): Promise<Response> {
1079
+ // Video recording requires context-level setup at launch
1080
+ // For now, return a note about this limitation
1081
+ return successResponse(command.id, {
1082
+ note: 'Video recording must be enabled at browser launch. Use --video flag when starting.',
1083
+ path: command.path,
1084
+ });
1085
+ }
1086
+
1087
+ async function handleVideoStop(
1088
+ command: Command & { action: 'video_stop' },
1089
+ browser: BrowserManager
1090
+ ): Promise<Response> {
1091
+ const page = browser.getPage();
1092
+ const video = page.video();
1093
+ if (video) {
1094
+ const path = await video.path();
1095
+ return successResponse(command.id, { path });
1096
+ }
1097
+ return successResponse(command.id, { note: 'No video recording active' });
1098
+ }
1099
+
1100
+ async function handleTraceStart(
1101
+ command: TraceStartCommand,
1102
+ browser: BrowserManager
1103
+ ): Promise<Response> {
1104
+ await browser.startTracing({
1105
+ screenshots: command.screenshots,
1106
+ snapshots: command.snapshots,
1107
+ });
1108
+ return successResponse(command.id, { started: true });
1109
+ }
1110
+
1111
+ async function handleTraceStop(
1112
+ command: TraceStopCommand,
1113
+ browser: BrowserManager
1114
+ ): Promise<Response> {
1115
+ await browser.stopTracing(command.path);
1116
+ return successResponse(command.id, { path: command.path });
1117
+ }
1118
+
1119
+ async function handleHarStart(
1120
+ command: Command & { action: 'har_start' },
1121
+ browser: BrowserManager
1122
+ ): Promise<Response> {
1123
+ await browser.startHarRecording();
1124
+ browser.startRequestTracking();
1125
+ return successResponse(command.id, { started: true });
1126
+ }
1127
+
1128
+ async function handleHarStop(command: HarStopCommand, browser: BrowserManager): Promise<Response> {
1129
+ // HAR recording is handled at context level
1130
+ // For now, we save tracked requests as a simplified HAR-like format
1131
+ const requests = browser.getRequests();
1132
+ return successResponse(command.id, {
1133
+ path: command.path,
1134
+ requestCount: requests.length,
1135
+ });
1136
+ }
1137
+
1138
+ async function handleStateSave(
1139
+ command: StorageStateSaveCommand,
1140
+ browser: BrowserManager
1141
+ ): Promise<Response> {
1142
+ await browser.saveStorageState(command.path);
1143
+ return successResponse(command.id, { path: command.path });
1144
+ }
1145
+
1146
+ async function handleStateLoad(
1147
+ command: Command & { action: 'state_load'; path: string },
1148
+ browser: BrowserManager
1149
+ ): Promise<Response> {
1150
+ // Storage state is loaded at context creation
1151
+ return successResponse(command.id, {
1152
+ note: 'Storage state must be loaded at browser launch. Use --state flag.',
1153
+ path: command.path,
1154
+ });
1155
+ }
1156
+
1157
+ async function handleConsole(command: ConsoleCommand, browser: BrowserManager): Promise<Response> {
1158
+ if (command.clear) {
1159
+ browser.clearConsoleMessages();
1160
+ return successResponse(command.id, { cleared: true });
1161
+ }
1162
+
1163
+ browser.startConsoleTracking();
1164
+ const messages = browser.getConsoleMessages();
1165
+ return successResponse(command.id, { messages });
1166
+ }
1167
+
1168
+ async function handleErrors(command: ErrorsCommand, browser: BrowserManager): Promise<Response> {
1169
+ if (command.clear) {
1170
+ browser.clearPageErrors();
1171
+ return successResponse(command.id, { cleared: true });
1172
+ }
1173
+
1174
+ browser.startErrorTracking();
1175
+ const errors = browser.getPageErrors();
1176
+ return successResponse(command.id, { errors });
1177
+ }
1178
+
1179
+ async function handleKeyboard(
1180
+ command: KeyboardCommand,
1181
+ browser: BrowserManager
1182
+ ): Promise<Response> {
1183
+ const page = browser.getPage();
1184
+ await page.keyboard.press(command.keys);
1185
+ return successResponse(command.id, { pressed: command.keys });
1186
+ }
1187
+
1188
+ async function handleWheel(command: WheelCommand, browser: BrowserManager): Promise<Response> {
1189
+ const page = browser.getPage();
1190
+
1191
+ if (command.selector) {
1192
+ const element = page.locator(command.selector);
1193
+ await element.hover();
1194
+ }
1195
+
1196
+ await page.mouse.wheel(command.deltaX ?? 0, command.deltaY ?? 0);
1197
+ return successResponse(command.id, { scrolled: true });
1198
+ }
1199
+
1200
+ async function handleTap(command: TapCommand, browser: BrowserManager): Promise<Response> {
1201
+ const page = browser.getPage();
1202
+ await page.tap(command.selector);
1203
+ return successResponse(command.id, { tapped: true });
1204
+ }
1205
+
1206
+ async function handleClipboard(
1207
+ command: ClipboardCommand,
1208
+ browser: BrowserManager
1209
+ ): Promise<Response> {
1210
+ const page = browser.getPage();
1211
+
1212
+ switch (command.operation) {
1213
+ case 'copy':
1214
+ await page.keyboard.press('Control+c');
1215
+ return successResponse(command.id, { copied: true });
1216
+ case 'paste':
1217
+ await page.keyboard.press('Control+v');
1218
+ return successResponse(command.id, { pasted: true });
1219
+ case 'read':
1220
+ const text = await page.evaluate('navigator.clipboard.readText()');
1221
+ return successResponse(command.id, { text });
1222
+ default:
1223
+ return errorResponse(command.id, 'Unknown clipboard operation');
1224
+ }
1225
+ }
1226
+
1227
+ async function handleHighlight(
1228
+ command: HighlightCommand,
1229
+ browser: BrowserManager
1230
+ ): Promise<Response> {
1231
+ const page = browser.getPage();
1232
+ await page.locator(command.selector).highlight();
1233
+ return successResponse(command.id, { highlighted: true });
1234
+ }
1235
+
1236
+ async function handleClear(command: ClearCommand, browser: BrowserManager): Promise<Response> {
1237
+ const page = browser.getPage();
1238
+ await page.locator(command.selector).clear();
1239
+ return successResponse(command.id, { cleared: true });
1240
+ }
1241
+
1242
+ async function handleSelectAll(
1243
+ command: SelectAllCommand,
1244
+ browser: BrowserManager
1245
+ ): Promise<Response> {
1246
+ const page = browser.getPage();
1247
+ await page.locator(command.selector).selectText();
1248
+ return successResponse(command.id, { selected: true });
1249
+ }
1250
+
1251
+ async function handleInnerText(
1252
+ command: InnerTextCommand,
1253
+ browser: BrowserManager
1254
+ ): Promise<Response> {
1255
+ const page = browser.getPage();
1256
+ const text = await page.locator(command.selector).innerText();
1257
+ return successResponse(command.id, { text });
1258
+ }
1259
+
1260
+ async function handleInnerHtml(
1261
+ command: InnerHtmlCommand,
1262
+ browser: BrowserManager
1263
+ ): Promise<Response> {
1264
+ const page = browser.getPage();
1265
+ const html = await page.locator(command.selector).innerHTML();
1266
+ return successResponse(command.id, { html });
1267
+ }
1268
+
1269
+ async function handleInputValue(
1270
+ command: InputValueCommand,
1271
+ browser: BrowserManager
1272
+ ): Promise<Response> {
1273
+ const page = browser.getPage();
1274
+ const value = await page.locator(command.selector).inputValue();
1275
+ return successResponse(command.id, { value });
1276
+ }
1277
+
1278
+ async function handleSetValue(
1279
+ command: SetValueCommand,
1280
+ browser: BrowserManager
1281
+ ): Promise<Response> {
1282
+ const page = browser.getPage();
1283
+ await page.locator(command.selector).fill(command.value);
1284
+ return successResponse(command.id, { set: true });
1285
+ }
1286
+
1287
+ async function handleDispatch(
1288
+ command: DispatchEventCommand,
1289
+ browser: BrowserManager
1290
+ ): Promise<Response> {
1291
+ const page = browser.getPage();
1292
+ await page.locator(command.selector).dispatchEvent(command.event, command.eventInit);
1293
+ return successResponse(command.id, { dispatched: command.event });
1294
+ }
1295
+
1296
+ async function handleEvalHandle(
1297
+ command: Command & { action: 'evalhandle'; script: string },
1298
+ browser: BrowserManager
1299
+ ): Promise<Response> {
1300
+ const page = browser.getPage();
1301
+ const handle = await page.evaluateHandle(command.script);
1302
+ const result = await handle.jsonValue().catch(() => 'Handle (non-serializable)');
1303
+ return successResponse(command.id, { result });
1304
+ }
1305
+
1306
+ async function handleExpose(
1307
+ command: Command & { action: 'expose'; name: string },
1308
+ browser: BrowserManager
1309
+ ): Promise<Response> {
1310
+ const page = browser.getPage();
1311
+ await page.exposeFunction(command.name, () => {
1312
+ // Exposed function - can be extended
1313
+ return `Function ${command.name} called`;
1314
+ });
1315
+ return successResponse(command.id, { exposed: command.name });
1316
+ }
1317
+
1318
+ async function handleAddScript(
1319
+ command: AddScriptCommand,
1320
+ browser: BrowserManager
1321
+ ): Promise<Response> {
1322
+ const page = browser.getPage();
1323
+
1324
+ if (command.content) {
1325
+ await page.addScriptTag({ content: command.content });
1326
+ } else if (command.url) {
1327
+ await page.addScriptTag({ url: command.url });
1328
+ }
1329
+
1330
+ return successResponse(command.id, { added: true });
1331
+ }
1332
+
1333
+ async function handleAddStyle(
1334
+ command: AddStyleCommand,
1335
+ browser: BrowserManager
1336
+ ): Promise<Response> {
1337
+ const page = browser.getPage();
1338
+
1339
+ if (command.content) {
1340
+ await page.addStyleTag({ content: command.content });
1341
+ } else if (command.url) {
1342
+ await page.addStyleTag({ url: command.url });
1343
+ }
1344
+
1345
+ return successResponse(command.id, { added: true });
1346
+ }
1347
+
1348
+ async function handleEmulateMedia(
1349
+ command: EmulateMediaCommand,
1350
+ browser: BrowserManager
1351
+ ): Promise<Response> {
1352
+ const page = browser.getPage();
1353
+ await page.emulateMedia({
1354
+ media: command.media,
1355
+ colorScheme: command.colorScheme,
1356
+ reducedMotion: command.reducedMotion,
1357
+ forcedColors: command.forcedColors,
1358
+ });
1359
+ return successResponse(command.id, { emulated: true });
1360
+ }
1361
+
1362
+ async function handleOffline(command: OfflineCommand, browser: BrowserManager): Promise<Response> {
1363
+ await browser.setOffline(command.offline);
1364
+ return successResponse(command.id, { offline: command.offline });
1365
+ }
1366
+
1367
+ async function handleHeaders(command: HeadersCommand, browser: BrowserManager): Promise<Response> {
1368
+ await browser.setExtraHeaders(command.headers);
1369
+ return successResponse(command.id, { set: true });
1370
+ }
1371
+
1372
+ async function handlePause(
1373
+ command: Command & { action: 'pause' },
1374
+ browser: BrowserManager
1375
+ ): Promise<Response> {
1376
+ const page = browser.getPage();
1377
+ await page.pause();
1378
+ return successResponse(command.id, { paused: true });
1379
+ }
1380
+
1381
+ async function handleGetByAltText(
1382
+ command: GetByAltTextCommand,
1383
+ browser: BrowserManager
1384
+ ): Promise<Response> {
1385
+ const page = browser.getPage();
1386
+ const locator = page.getByAltText(command.text, { exact: command.exact });
1387
+
1388
+ switch (command.subaction) {
1389
+ case 'click':
1390
+ await locator.click();
1391
+ return successResponse(command.id, { clicked: true });
1392
+ case 'hover':
1393
+ await locator.hover();
1394
+ return successResponse(command.id, { hovered: true });
1395
+ }
1396
+ }
1397
+
1398
+ async function handleGetByTitle(
1399
+ command: GetByTitleCommand,
1400
+ browser: BrowserManager
1401
+ ): Promise<Response> {
1402
+ const page = browser.getPage();
1403
+ const locator = page.getByTitle(command.text, { exact: command.exact });
1404
+
1405
+ switch (command.subaction) {
1406
+ case 'click':
1407
+ await locator.click();
1408
+ return successResponse(command.id, { clicked: true });
1409
+ case 'hover':
1410
+ await locator.hover();
1411
+ return successResponse(command.id, { hovered: true });
1412
+ }
1413
+ }
1414
+
1415
+ async function handleGetByTestId(
1416
+ command: GetByTestIdCommand,
1417
+ browser: BrowserManager
1418
+ ): Promise<Response> {
1419
+ const page = browser.getPage();
1420
+ const locator = page.getByTestId(command.testId);
1421
+
1422
+ switch (command.subaction) {
1423
+ case 'click':
1424
+ await locator.click();
1425
+ return successResponse(command.id, { clicked: true });
1426
+ case 'fill':
1427
+ await locator.fill(command.value ?? '');
1428
+ return successResponse(command.id, { filled: true });
1429
+ case 'check':
1430
+ await locator.check();
1431
+ return successResponse(command.id, { checked: true });
1432
+ case 'hover':
1433
+ await locator.hover();
1434
+ return successResponse(command.id, { hovered: true });
1435
+ }
1436
+ }
1437
+
1438
+ async function handleNth(command: NthCommand, browser: BrowserManager): Promise<Response> {
1439
+ const page = browser.getPage();
1440
+ const base = page.locator(command.selector);
1441
+ const locator = command.index === -1 ? base.last() : base.nth(command.index);
1442
+
1443
+ switch (command.subaction) {
1444
+ case 'click':
1445
+ await locator.click();
1446
+ return successResponse(command.id, { clicked: true });
1447
+ case 'fill':
1448
+ await locator.fill(command.value ?? '');
1449
+ return successResponse(command.id, { filled: true });
1450
+ case 'check':
1451
+ await locator.check();
1452
+ return successResponse(command.id, { checked: true });
1453
+ case 'hover':
1454
+ await locator.hover();
1455
+ return successResponse(command.id, { hovered: true });
1456
+ case 'text':
1457
+ const text = await locator.textContent();
1458
+ return successResponse(command.id, { text });
1459
+ }
1460
+ }
1461
+
1462
+ async function handleWaitForUrl(
1463
+ command: WaitForUrlCommand,
1464
+ browser: BrowserManager
1465
+ ): Promise<Response> {
1466
+ const page = browser.getPage();
1467
+ await page.waitForURL(command.url, { timeout: command.timeout });
1468
+ return successResponse(command.id, { url: page.url() });
1469
+ }
1470
+
1471
+ async function handleWaitForLoadState(
1472
+ command: WaitForLoadStateCommand,
1473
+ browser: BrowserManager
1474
+ ): Promise<Response> {
1475
+ const page = browser.getPage();
1476
+ await page.waitForLoadState(command.state, { timeout: command.timeout });
1477
+ return successResponse(command.id, { state: command.state });
1478
+ }
1479
+
1480
+ async function handleSetContent(
1481
+ command: SetContentCommand,
1482
+ browser: BrowserManager
1483
+ ): Promise<Response> {
1484
+ const page = browser.getPage();
1485
+ await page.setContent(command.html);
1486
+ return successResponse(command.id, { set: true });
1487
+ }
1488
+
1489
+ async function handleTimezone(
1490
+ command: TimezoneCommand,
1491
+ browser: BrowserManager
1492
+ ): Promise<Response> {
1493
+ // Timezone must be set at context level before navigation
1494
+ // This is a limitation - it sets for the current context
1495
+ const page = browser.getPage();
1496
+ await page.context().setGeolocation({ latitude: 0, longitude: 0 }); // Trigger context awareness
1497
+ return successResponse(command.id, {
1498
+ note: 'Timezone must be set at browser launch. Use --timezone flag.',
1499
+ timezone: command.timezone,
1500
+ });
1501
+ }
1502
+
1503
+ async function handleLocale(command: LocaleCommand, browser: BrowserManager): Promise<Response> {
1504
+ // Locale must be set at context creation
1505
+ return successResponse(command.id, {
1506
+ note: 'Locale must be set at browser launch. Use --locale flag.',
1507
+ locale: command.locale,
1508
+ });
1509
+ }
1510
+
1511
+ async function handleCredentials(
1512
+ command: HttpCredentialsCommand,
1513
+ browser: BrowserManager
1514
+ ): Promise<Response> {
1515
+ const context = browser.getPage().context();
1516
+ await context.setHTTPCredentials({
1517
+ username: command.username,
1518
+ password: command.password,
1519
+ });
1520
+ return successResponse(command.id, { set: true });
1521
+ }
1522
+
1523
+ async function handleMouseMove(
1524
+ command: MouseMoveCommand,
1525
+ browser: BrowserManager
1526
+ ): Promise<Response> {
1527
+ const page = browser.getPage();
1528
+ await page.mouse.move(command.x, command.y);
1529
+ return successResponse(command.id, { moved: true, x: command.x, y: command.y });
1530
+ }
1531
+
1532
+ async function handleMouseDown(
1533
+ command: MouseDownCommand,
1534
+ browser: BrowserManager
1535
+ ): Promise<Response> {
1536
+ const page = browser.getPage();
1537
+ await page.mouse.down({ button: command.button ?? 'left' });
1538
+ return successResponse(command.id, { down: true });
1539
+ }
1540
+
1541
+ async function handleMouseUp(command: MouseUpCommand, browser: BrowserManager): Promise<Response> {
1542
+ const page = browser.getPage();
1543
+ await page.mouse.up({ button: command.button ?? 'left' });
1544
+ return successResponse(command.id, { up: true });
1545
+ }
1546
+
1547
+ async function handleBringToFront(
1548
+ command: Command & { action: 'bringtofront' },
1549
+ browser: BrowserManager
1550
+ ): Promise<Response> {
1551
+ const page = browser.getPage();
1552
+ await page.bringToFront();
1553
+ return successResponse(command.id, { focused: true });
1554
+ }
1555
+
1556
+ async function handleWaitForFunction(
1557
+ command: WaitForFunctionCommand,
1558
+ browser: BrowserManager
1559
+ ): Promise<Response> {
1560
+ const page = browser.getPage();
1561
+ await page.waitForFunction(command.expression, { timeout: command.timeout });
1562
+ return successResponse(command.id, { waited: true });
1563
+ }
1564
+
1565
+ async function handleScrollIntoView(
1566
+ command: ScrollIntoViewCommand,
1567
+ browser: BrowserManager
1568
+ ): Promise<Response> {
1569
+ const page = browser.getPage();
1570
+ await page.locator(command.selector).scrollIntoViewIfNeeded();
1571
+ return successResponse(command.id, { scrolled: true });
1572
+ }
1573
+
1574
+ async function handleAddInitScript(
1575
+ command: AddInitScriptCommand,
1576
+ browser: BrowserManager
1577
+ ): Promise<Response> {
1578
+ const context = browser.getPage().context();
1579
+ await context.addInitScript(command.script);
1580
+ return successResponse(command.id, { added: true });
1581
+ }
1582
+
1583
+ async function handleKeyDown(command: KeyDownCommand, browser: BrowserManager): Promise<Response> {
1584
+ const page = browser.getPage();
1585
+ await page.keyboard.down(command.key);
1586
+ return successResponse(command.id, { down: true, key: command.key });
1587
+ }
1588
+
1589
+ async function handleKeyUp(command: KeyUpCommand, browser: BrowserManager): Promise<Response> {
1590
+ const page = browser.getPage();
1591
+ await page.keyboard.up(command.key);
1592
+ return successResponse(command.id, { up: true, key: command.key });
1593
+ }
1594
+
1595
+ async function handleInsertText(
1596
+ command: InsertTextCommand,
1597
+ browser: BrowserManager
1598
+ ): Promise<Response> {
1599
+ const page = browser.getPage();
1600
+ await page.keyboard.insertText(command.text);
1601
+ return successResponse(command.id, { inserted: true });
1602
+ }
1603
+
1604
+ async function handleMultiSelect(
1605
+ command: MultiSelectCommand,
1606
+ browser: BrowserManager
1607
+ ): Promise<Response> {
1608
+ const page = browser.getPage();
1609
+ const selected = await page.locator(command.selector).selectOption(command.values);
1610
+ return successResponse(command.id, { selected });
1611
+ }
1612
+
1613
+ async function handleWaitForDownload(
1614
+ command: WaitForDownloadCommand,
1615
+ browser: BrowserManager
1616
+ ): Promise<Response> {
1617
+ const page = browser.getPage();
1618
+ const download = await page.waitForEvent('download', { timeout: command.timeout });
1619
+
1620
+ let filePath: string;
1621
+ if (command.path) {
1622
+ filePath = command.path;
1623
+ await download.saveAs(filePath);
1624
+ } else {
1625
+ filePath = (await download.path()) || download.suggestedFilename();
1626
+ }
1627
+
1628
+ return successResponse(command.id, {
1629
+ path: filePath,
1630
+ filename: download.suggestedFilename(),
1631
+ url: download.url(),
1632
+ });
1633
+ }
1634
+
1635
+ async function handleResponseBody(
1636
+ command: ResponseBodyCommand,
1637
+ browser: BrowserManager
1638
+ ): Promise<Response> {
1639
+ const page = browser.getPage();
1640
+ const response = await page.waitForResponse((resp) => resp.url().includes(command.url), {
1641
+ timeout: command.timeout,
1642
+ });
1643
+
1644
+ const body = await response.text();
1645
+ let parsed: unknown = body;
1646
+
1647
+ try {
1648
+ parsed = JSON.parse(body);
1649
+ } catch {
1650
+ // Keep as string if not JSON
1651
+ }
1652
+
1653
+ return successResponse(command.id, {
1654
+ url: response.url(),
1655
+ status: response.status(),
1656
+ body: parsed,
1657
+ });
1658
+ }