@dyyz1993/agent-browser 0.13.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +108 -0
  2. package/bin/agent-browser-darwin-arm64 +0 -0
  3. package/bin/agent-browser-darwin-x64 +0 -0
  4. package/bin/agent-browser-linux-arm64 +0 -0
  5. package/bin/agent-browser-linux-x64 +0 -0
  6. package/bin/agent-browser-win32-x64.exe +0 -0
  7. package/dist/__tests__/e2e/utils/test-helpers.d.ts +1 -0
  8. package/dist/__tests__/e2e/utils/test-helpers.d.ts.map +1 -1
  9. package/dist/__tests__/e2e/utils/test-helpers.js +14 -1
  10. package/dist/__tests__/e2e/utils/test-helpers.js.map +1 -1
  11. package/dist/__tests__/utils/free-port.d.ts +2 -0
  12. package/dist/__tests__/utils/free-port.d.ts.map +1 -0
  13. package/dist/__tests__/utils/free-port.js +18 -0
  14. package/dist/__tests__/utils/free-port.js.map +1 -0
  15. package/dist/__tests__/utils/parseCli.d.ts.map +1 -1
  16. package/dist/__tests__/utils/parseCli.js +83 -9
  17. package/dist/__tests__/utils/parseCli.js.map +1 -1
  18. package/dist/actions.d.ts.map +1 -1
  19. package/dist/actions.js +298 -9
  20. package/dist/actions.js.map +1 -1
  21. package/dist/browser.d.ts +11 -1
  22. package/dist/browser.d.ts.map +1 -1
  23. package/dist/browser.js +75 -19
  24. package/dist/browser.js.map +1 -1
  25. package/dist/cli/commands.d.ts.map +1 -1
  26. package/dist/cli/commands.js +172 -15
  27. package/dist/cli/commands.js.map +1 -1
  28. package/dist/cli/connection.d.ts +13 -0
  29. package/dist/cli/connection.d.ts.map +1 -1
  30. package/dist/cli/connection.js +137 -48
  31. package/dist/cli/connection.js.map +1 -1
  32. package/dist/cli/flags.d.ts.map +1 -1
  33. package/dist/cli/flags.js +0 -1
  34. package/dist/cli/flags.js.map +1 -1
  35. package/dist/cli/help.d.ts.map +1 -1
  36. package/dist/cli/help.js +63 -22
  37. package/dist/cli/help.js.map +1 -1
  38. package/dist/cli/output.d.ts.map +1 -1
  39. package/dist/cli/output.js +0 -32
  40. package/dist/cli/output.js.map +1 -1
  41. package/dist/cli.js +20 -2
  42. package/dist/cli.js.map +1 -1
  43. package/dist/daemon.d.ts +1 -0
  44. package/dist/daemon.d.ts.map +1 -1
  45. package/dist/daemon.js +291 -264
  46. package/dist/daemon.js.map +1 -1
  47. package/dist/diff.d.ts.map +1 -1
  48. package/dist/diff.js +1 -1
  49. package/dist/diff.js.map +1 -1
  50. package/dist/flow/exporters/cypress.d.ts +9 -0
  51. package/dist/flow/exporters/cypress.d.ts.map +1 -0
  52. package/dist/flow/exporters/cypress.js +256 -0
  53. package/dist/flow/exporters/cypress.js.map +1 -0
  54. package/dist/flow/exporters/index.d.ts +6 -0
  55. package/dist/flow/exporters/index.d.ts.map +1 -0
  56. package/dist/flow/exporters/index.js +5 -0
  57. package/dist/flow/exporters/index.js.map +1 -0
  58. package/dist/flow/exporters/playwright.d.ts +20 -0
  59. package/dist/flow/exporters/playwright.d.ts.map +1 -0
  60. package/dist/flow/exporters/playwright.js +175 -0
  61. package/dist/flow/exporters/playwright.js.map +1 -0
  62. package/dist/flow/exporters/python.d.ts +20 -0
  63. package/dist/flow/exporters/python.d.ts.map +1 -0
  64. package/dist/flow/exporters/python.js +163 -0
  65. package/dist/flow/exporters/python.js.map +1 -0
  66. package/dist/flow/exporters/selenium.d.ts +9 -0
  67. package/dist/flow/exporters/selenium.d.ts.map +1 -0
  68. package/dist/flow/exporters/selenium.js +298 -0
  69. package/dist/flow/exporters/selenium.js.map +1 -0
  70. package/dist/flow/exporters/types.d.ts +13 -0
  71. package/dist/flow/exporters/types.d.ts.map +1 -0
  72. package/dist/flow/exporters/types.js +2 -0
  73. package/dist/flow/exporters/types.js.map +1 -0
  74. package/dist/flow/flow-executor.d.ts +57 -0
  75. package/dist/flow/flow-executor.d.ts.map +1 -0
  76. package/dist/flow/flow-executor.js +1263 -0
  77. package/dist/flow/flow-executor.js.map +1 -0
  78. package/dist/flow/index.d.ts +15 -0
  79. package/dist/flow/index.d.ts.map +1 -0
  80. package/dist/flow/index.js +10 -0
  81. package/dist/flow/index.js.map +1 -0
  82. package/dist/flow/output.d.ts +11 -0
  83. package/dist/flow/output.d.ts.map +1 -0
  84. package/dist/flow/output.js +84 -0
  85. package/dist/flow/output.js.map +1 -0
  86. package/dist/flow/plugin-system.d.ts +48 -0
  87. package/dist/flow/plugin-system.d.ts.map +1 -0
  88. package/dist/flow/plugin-system.js +132 -0
  89. package/dist/flow/plugin-system.js.map +1 -0
  90. package/dist/flow/plugins/file-output-plugin.d.ts +8 -0
  91. package/dist/flow/plugins/file-output-plugin.d.ts.map +1 -0
  92. package/dist/flow/plugins/file-output-plugin.js +31 -0
  93. package/dist/flow/plugins/file-output-plugin.js.map +1 -0
  94. package/dist/flow/plugins/index.d.ts +4 -0
  95. package/dist/flow/plugins/index.d.ts.map +1 -0
  96. package/dist/flow/plugins/index.js +4 -0
  97. package/dist/flow/plugins/index.js.map +1 -0
  98. package/dist/flow/plugins/logging-plugin.d.ts +7 -0
  99. package/dist/flow/plugins/logging-plugin.d.ts.map +1 -0
  100. package/dist/flow/plugins/logging-plugin.js +40 -0
  101. package/dist/flow/plugins/logging-plugin.js.map +1 -0
  102. package/dist/flow/plugins/webhook-plugin.d.ts +7 -0
  103. package/dist/flow/plugins/webhook-plugin.d.ts.map +1 -0
  104. package/dist/flow/plugins/webhook-plugin.js +24 -0
  105. package/dist/flow/plugins/webhook-plugin.js.map +1 -0
  106. package/dist/flow/presets/index.d.ts +10 -0
  107. package/dist/flow/presets/index.d.ts.map +1 -0
  108. package/dist/flow/presets/index.js +29 -0
  109. package/dist/flow/presets/index.js.map +1 -0
  110. package/dist/flow/recorder-to-flow.d.ts +70 -0
  111. package/dist/flow/recorder-to-flow.d.ts.map +1 -0
  112. package/dist/flow/recorder-to-flow.js +392 -0
  113. package/dist/flow/recorder-to-flow.js.map +1 -0
  114. package/dist/flow/site-manager.d.ts +24 -0
  115. package/dist/flow/site-manager.d.ts.map +1 -0
  116. package/dist/flow/site-manager.js +125 -0
  117. package/dist/flow/site-manager.js.map +1 -0
  118. package/dist/flow/types.d.ts +196 -0
  119. package/dist/flow/types.d.ts.map +1 -0
  120. package/dist/flow/types.js +2 -0
  121. package/dist/flow/types.js.map +1 -0
  122. package/dist/flow/yaml-parser.d.ts +15 -0
  123. package/dist/flow/yaml-parser.d.ts.map +1 -0
  124. package/dist/flow/yaml-parser.js +216 -0
  125. package/dist/flow/yaml-parser.js.map +1 -0
  126. package/dist/human-mouse.d.ts.map +1 -1
  127. package/dist/protocol.d.ts.map +1 -1
  128. package/dist/protocol.js +15 -11
  129. package/dist/protocol.js.map +1 -1
  130. package/dist/rc-config.d.ts.map +1 -1
  131. package/dist/rc-config.js +1 -2
  132. package/dist/rc-config.js.map +1 -1
  133. package/dist/recorder/inject.js +730 -332
  134. package/dist/snapshot-store.d.ts +83 -0
  135. package/dist/snapshot-store.d.ts.map +1 -0
  136. package/dist/snapshot-store.js +112 -0
  137. package/dist/snapshot-store.js.map +1 -0
  138. package/dist/snapshot.d.ts +6 -7
  139. package/dist/snapshot.d.ts.map +1 -1
  140. package/dist/snapshot.js +471 -17
  141. package/dist/snapshot.js.map +1 -1
  142. package/dist/stream-server-standalone.d.ts.map +1 -1
  143. package/dist/stream-server-standalone.js.map +1 -1
  144. package/dist/stream-server.d.ts.map +1 -1
  145. package/dist/stream-server.js +38 -13
  146. package/dist/stream-server.js.map +1 -1
  147. package/dist/test-live.js +5 -5
  148. package/dist/test-live.js.map +1 -1
  149. package/dist/types.d.ts +13 -9
  150. package/dist/types.d.ts.map +1 -1
  151. package/dist/types.js.map +1 -1
  152. package/dist/viewer-script.d.ts.map +1 -1
  153. package/dist/viewer-script.js +12 -6
  154. package/dist/viewer-script.js.map +1 -1
  155. package/package.json +18 -5
  156. package/skills/agent-browser/SKILL.md +151 -3
  157. package/dist/ios-actions.d.ts +0 -11
  158. package/dist/ios-actions.d.ts.map +0 -1
  159. package/dist/ios-actions.js +0 -228
  160. package/dist/ios-actions.js.map +0 -1
  161. package/dist/ios-manager.d.ts +0 -266
  162. package/dist/ios-manager.d.ts.map +0 -1
  163. package/dist/ios-manager.js +0 -1076
  164. package/dist/ios-manager.js.map +0 -1
package/dist/snapshot.js CHANGED
@@ -311,6 +311,381 @@ async function suggestSelectors(page) {
311
311
  }
312
312
  return selectors;
313
313
  }
314
+ export async function generateStableSelectors(page, refs) {
315
+ const result = {};
316
+ for (const [ref, data] of Object.entries(refs)) {
317
+ if (data.role === 'clickable' || data.role === 'focusable') {
318
+ if (data.selector && !data.selector.startsWith('getByRole')) {
319
+ result[ref] = { cssSelector: data.selector, xpath: '' };
320
+ }
321
+ continue;
322
+ }
323
+ try {
324
+ let locator;
325
+ if (data.name) {
326
+ locator = page.getByRole(data.role, {
327
+ name: data.name,
328
+ exact: true,
329
+ });
330
+ }
331
+ else {
332
+ locator = page.getByRole(data.role);
333
+ }
334
+ if (data.nth !== undefined) {
335
+ locator = locator.nth(data.nth);
336
+ }
337
+ const elementCount = await locator.count();
338
+ if (elementCount === 0)
339
+ continue;
340
+ const selectorData = await locator
341
+ .evaluate((el) => {
342
+ const UTILITY_CLASS_PATTERNS = [
343
+ /^_/,
344
+ /^css-/,
345
+ /^[a-z]{1,2}$/,
346
+ /^(active|disabled|hidden|visible|selected|hover|focus|current|open|closed)$/i,
347
+ /^(text-|font-|bg-|p-|m-|w-|h-|flex|grid|border|rounded|shadow|opacity|z-)/,
348
+ /^(sm:|md:|lg:|xl:|2xl:)/,
349
+ ];
350
+ const SEMANTIC_ATTRS = [
351
+ 'data-testid',
352
+ 'data-test',
353
+ 'data-cy',
354
+ 'name',
355
+ 'aria-label',
356
+ 'aria-labelledby',
357
+ 'role',
358
+ 'type',
359
+ 'placeholder',
360
+ 'title',
361
+ 'alt',
362
+ ];
363
+ function isHighEntropyClassName(className) {
364
+ if (!className || className.length < 4 || className.length > 15)
365
+ return false;
366
+ if (/^[a-zA-Z]+_[a-zA-Z]+_{2}[a-zA-Z0-9]+$/.test(className))
367
+ return true;
368
+ if (/^sc-[a-zA-Z0-9]+$/.test(className))
369
+ return true;
370
+ const hasUpper = /[A-Z]/.test(className);
371
+ const hasLower = /[a-z]/.test(className);
372
+ const hasDigit = /[0-9]/.test(className);
373
+ const hasSeparator = /[-_]/.test(className);
374
+ if (hasSeparator)
375
+ return false;
376
+ if (hasUpper && hasLower && hasDigit)
377
+ return true;
378
+ if (/^[A-Z][a-z0-9]+[A-Z]/.test(className) && className.length <= 12)
379
+ return true;
380
+ if (/^[a-z]/.test(className) && /[a-z][A-Z][a-z][A-Z]/.test(className))
381
+ return true;
382
+ return false;
383
+ }
384
+ function isUniqueSelector(selector) {
385
+ try {
386
+ return document.querySelectorAll(selector).length === 1;
387
+ }
388
+ catch {
389
+ return false;
390
+ }
391
+ }
392
+ function filterUsefulClasses(element) {
393
+ const htmlEl = element;
394
+ if (!htmlEl.className || typeof htmlEl.className !== 'string')
395
+ return [];
396
+ return htmlEl.className
397
+ .trim()
398
+ .split(/\s+/)
399
+ .filter((c) => {
400
+ if (!c)
401
+ return false;
402
+ if (UTILITY_CLASS_PATTERNS.some((p) => p.test(c)))
403
+ return false;
404
+ if (isHighEntropyClassName(c))
405
+ return false;
406
+ return true;
407
+ });
408
+ }
409
+ function tryIdSelector(element) {
410
+ const htmlEl = element;
411
+ if (htmlEl.id) {
412
+ const sel = '#' + CSS.escape(htmlEl.id);
413
+ if (isUniqueSelector(sel))
414
+ return sel;
415
+ }
416
+ return null;
417
+ }
418
+ function getMultiAttributeSelector(element) {
419
+ const tag = element.tagName.toLowerCase();
420
+ const attrs = [];
421
+ for (const attr of SEMANTIC_ATTRS) {
422
+ const value = element.getAttribute(attr);
423
+ if (value)
424
+ attrs.push({ attr, value });
425
+ }
426
+ if (attrs.length === 0)
427
+ return null;
428
+ for (const { attr, value } of attrs) {
429
+ const sel = tag + '[' + attr + '="' + CSS.escape(value) + '"]';
430
+ if (isUniqueSelector(sel))
431
+ return sel;
432
+ }
433
+ if (attrs.length >= 2) {
434
+ for (let i = 0; i < attrs.length; i++) {
435
+ for (let j = i + 1; j < attrs.length; j++) {
436
+ const sel = tag +
437
+ '[' +
438
+ attrs[i].attr +
439
+ '="' +
440
+ CSS.escape(attrs[i].value) +
441
+ '"]' +
442
+ '[' +
443
+ attrs[j].attr +
444
+ '="' +
445
+ CSS.escape(attrs[j].value) +
446
+ '"]';
447
+ if (isUniqueSelector(sel))
448
+ return sel;
449
+ }
450
+ }
451
+ }
452
+ return null;
453
+ }
454
+ function getAttributeClassComboSelector(element) {
455
+ const tag = element.tagName.toLowerCase();
456
+ const classes = filterUsefulClasses(element);
457
+ if (classes.length === 0)
458
+ return null;
459
+ classes.sort((a, b) => b.length - a.length);
460
+ const bestClass = classes[0];
461
+ for (const attr of SEMANTIC_ATTRS) {
462
+ const value = element.getAttribute(attr);
463
+ if (value) {
464
+ const sel = tag + '.' + CSS.escape(bestClass) + '[' + attr + '="' + CSS.escape(value) + '"]';
465
+ if (isUniqueSelector(sel))
466
+ return sel;
467
+ }
468
+ }
469
+ return null;
470
+ }
471
+ function getBestClassSelector(element) {
472
+ const classes = filterUsefulClasses(element);
473
+ if (classes.length === 0)
474
+ return null;
475
+ classes.sort((a, b) => b.length - a.length);
476
+ const tag = element.tagName.toLowerCase();
477
+ for (const cls of classes) {
478
+ const sel = tag + '.' + CSS.escape(cls);
479
+ if (isUniqueSelector(sel))
480
+ return sel;
481
+ }
482
+ for (let i = 2; i <= Math.min(3, classes.length); i++) {
483
+ const sel = tag +
484
+ '.' +
485
+ classes
486
+ .slice(0, i)
487
+ .map((c) => CSS.escape(c))
488
+ .join('.');
489
+ if (isUniqueSelector(sel))
490
+ return sel;
491
+ }
492
+ return null;
493
+ }
494
+ function getFeatureSelector(element) {
495
+ if (!element || element === document.body)
496
+ return null;
497
+ const htmlEl = element;
498
+ if (htmlEl.id)
499
+ return '#' + CSS.escape(htmlEl.id);
500
+ for (const attr of ['data-testid', 'data-test', 'name', 'role', 'aria-label']) {
501
+ const value = element.getAttribute(attr);
502
+ if (value)
503
+ return element.tagName.toLowerCase() + '[' + attr + '="' + CSS.escape(value) + '"]';
504
+ }
505
+ const classes = filterUsefulClasses(element);
506
+ if (classes.length > 0) {
507
+ classes.sort((a, b) => b.length - a.length);
508
+ const sel = element.tagName.toLowerCase() + '.' + CSS.escape(classes[0]);
509
+ if (isUniqueSelector(sel))
510
+ return sel;
511
+ }
512
+ return null;
513
+ }
514
+ function getBaseSelector(element) {
515
+ let sel = element.tagName.toLowerCase();
516
+ const classes = filterUsefulClasses(element);
517
+ if (classes.length > 0) {
518
+ classes.sort((a, b) => b.length - a.length);
519
+ sel +=
520
+ '.' +
521
+ classes
522
+ .slice(0, 2)
523
+ .map((c) => CSS.escape(c))
524
+ .join('.');
525
+ }
526
+ return sel;
527
+ }
528
+ function makeUniqueWithNth(element, baseSelector) {
529
+ const parent = element.parentElement;
530
+ if (!parent)
531
+ return baseSelector;
532
+ const siblings = Array.from(parent.children);
533
+ const sameTagSiblings = siblings.filter((s) => s.tagName === element.tagName);
534
+ if (sameTagSiblings.length === 1)
535
+ return baseSelector;
536
+ const index = siblings.indexOf(element) + 1;
537
+ return baseSelector + ':nth-child(' + index + ')';
538
+ }
539
+ function getSiblingBasedSelector(element) {
540
+ let prevSibling = element.previousElementSibling;
541
+ let attempts = 0;
542
+ while (prevSibling && attempts < 3) {
543
+ const siblingSelector = getFeatureSelector(prevSibling);
544
+ if (siblingSelector && isUniqueSelector(siblingSelector)) {
545
+ const elementSelector = getBaseSelector(element);
546
+ const combined = siblingSelector + ' + ' + elementSelector;
547
+ if (isUniqueSelector(combined))
548
+ return combined;
549
+ }
550
+ prevSibling = prevSibling.previousElementSibling;
551
+ attempts++;
552
+ }
553
+ return null;
554
+ }
555
+ function buildComposedSelector(element) {
556
+ const selfSelector = getBestClassSelector(element);
557
+ if (selfSelector && isUniqueSelector(selfSelector))
558
+ return selfSelector;
559
+ const parts = [];
560
+ let current = element;
561
+ let depth = 0;
562
+ const maxDepth = 3;
563
+ while (current && current !== document.body && depth < maxDepth) {
564
+ const featureSelector = getFeatureSelector(current);
565
+ if (featureSelector) {
566
+ parts.unshift(featureSelector);
567
+ const elementSelector = depth === 0 ? getBaseSelector(element) : getBaseSelector(current);
568
+ const fullSelector = parts.join(' > ') + (depth > 0 ? '' : ' > ' + elementSelector);
569
+ if (isUniqueSelector(fullSelector))
570
+ return fullSelector;
571
+ }
572
+ else {
573
+ const baseSelector = getBaseSelector(current);
574
+ const selector = makeUniqueWithNth(current, baseSelector);
575
+ parts.unshift(selector);
576
+ const fullSelector = parts.join(' > ');
577
+ if (isUniqueSelector(fullSelector))
578
+ return fullSelector;
579
+ }
580
+ current = current.parentElement;
581
+ depth++;
582
+ }
583
+ return parts.length > 0 ? parts.join(' > ') : null;
584
+ }
585
+ function tryNthChild(element) {
586
+ const baseSelector = getBaseSelector(element);
587
+ const uniqueSelector = makeUniqueWithNth(element, baseSelector);
588
+ try {
589
+ if (document.querySelectorAll(uniqueSelector).length === 1)
590
+ return uniqueSelector;
591
+ }
592
+ catch (_e) {
593
+ // Intentionally ignored: invalid CSS selector in tryNthChild
594
+ }
595
+ return null;
596
+ }
597
+ function buildUniquePath(element) {
598
+ const parts = [];
599
+ let current = element;
600
+ let depth = 0;
601
+ while (current && current !== document.body && depth < 5) {
602
+ const baseSelector = getBaseSelector(current);
603
+ const selector = makeUniqueWithNth(current, baseSelector);
604
+ parts.unshift(selector);
605
+ const fullSelector = parts.join(' > ');
606
+ if (isUniqueSelector(fullSelector))
607
+ return fullSelector;
608
+ current = current.parentElement;
609
+ depth++;
610
+ }
611
+ return parts.length > 0 ? parts.join(' > ') : null;
612
+ }
613
+ function generateXPath(element) {
614
+ const htmlEl = element;
615
+ if (htmlEl.id)
616
+ return '//*[@id="' + htmlEl.id + '"]';
617
+ const testId = element.getAttribute('data-testid');
618
+ if (testId)
619
+ return '//*[@data-testid="' + testId + '"]';
620
+ const nameAttr = element.getAttribute('name');
621
+ if (nameAttr)
622
+ return '//' + element.tagName.toLowerCase() + '[@name="' + nameAttr + '"]';
623
+ const parts = [];
624
+ let current = element;
625
+ let depth = 0;
626
+ while (current && depth < 5) {
627
+ const curHtml = current;
628
+ if (curHtml.id) {
629
+ parts.unshift('//*[@id="' + curHtml.id + '"]');
630
+ break;
631
+ }
632
+ const testId = current.getAttribute('data-testid');
633
+ if (testId) {
634
+ parts.unshift('//*[@data-testid="' + testId + '"]');
635
+ break;
636
+ }
637
+ const tagName = current.tagName.toLowerCase();
638
+ const parent = current.parentElement;
639
+ if (parent) {
640
+ const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
641
+ const index = siblings.indexOf(current) + 1;
642
+ parts.unshift(tagName + '[' + index + ']');
643
+ }
644
+ else {
645
+ parts.unshift(tagName);
646
+ }
647
+ current = current.parentElement;
648
+ depth++;
649
+ }
650
+ if (parts.length > 0 && !parts[0].startsWith('//'))
651
+ parts.unshift('//');
652
+ return parts.join('/');
653
+ }
654
+ let cssSelector = null;
655
+ cssSelector = tryIdSelector(el);
656
+ if (!cssSelector)
657
+ cssSelector = getMultiAttributeSelector(el);
658
+ if (!cssSelector)
659
+ cssSelector = getAttributeClassComboSelector(el);
660
+ if (!cssSelector)
661
+ cssSelector = getBestClassSelector(el);
662
+ if (!cssSelector)
663
+ cssSelector = getSiblingBasedSelector(el);
664
+ if (!cssSelector)
665
+ cssSelector = buildComposedSelector(el);
666
+ if (!cssSelector)
667
+ cssSelector = tryNthChild(el);
668
+ if (!cssSelector)
669
+ cssSelector = buildUniquePath(el);
670
+ if (!cssSelector)
671
+ cssSelector = el.tagName.toLowerCase();
672
+ const xpath = generateXPath(el);
673
+ return { cssSelector, xpath };
674
+ })
675
+ .catch(() => null);
676
+ if (selectorData) {
677
+ result[ref] = {
678
+ cssSelector: selectorData.cssSelector,
679
+ xpath: selectorData.xpath,
680
+ };
681
+ }
682
+ }
683
+ catch (_e) {
684
+ // Intentionally ignored: element ref generation failed for this element
685
+ }
686
+ }
687
+ return result;
688
+ }
314
689
  /**
315
690
  * Get enhanced snapshot with refs and optional filtering
316
691
  */
@@ -378,7 +753,83 @@ export async function getEnhancedSnapshot(page, options = {}) {
378
753
  if (options.path || options.attrs) {
379
754
  await enrichRefsWithPathsAndAttrs(page, refs, options);
380
755
  }
381
- return { tree: enhancedTree, refs };
756
+ let finalTree = enhancedTree;
757
+ if (options.selectors && Object.keys(refs).length > 0) {
758
+ const selectorMap = await buildCompactSelectors(page, refs, options);
759
+ if (selectorMap) {
760
+ finalTree += '\n## Selectors\n' + selectorMap;
761
+ }
762
+ }
763
+ return { tree: finalTree, refs };
764
+ }
765
+ async function buildCompactSelectors(page, refs, options) {
766
+ const entries = Object.entries(refs);
767
+ const parts = [];
768
+ const includeAll = options?.all ?? false;
769
+ for (const [ref, data] of entries) {
770
+ if (data.role === 'clickable' || data.role === 'focusable')
771
+ continue;
772
+ try {
773
+ let locator;
774
+ if (data.name) {
775
+ locator = page.getByRole(data.role, {
776
+ name: data.name,
777
+ exact: true,
778
+ });
779
+ }
780
+ else {
781
+ locator = page.getByRole(data.role);
782
+ }
783
+ if (data.nth !== undefined)
784
+ locator = locator.nth(data.nth);
785
+ if (!includeAll) {
786
+ const isReallyVisible = await locator
787
+ .evaluate((el) => {
788
+ const style = getComputedStyle(el);
789
+ const rect = el.getBoundingClientRect();
790
+ return !(style.display === 'none' ||
791
+ style.visibility === 'hidden' ||
792
+ parseFloat(style.opacity) === 0 ||
793
+ (rect.width === 0 && rect.height === 0) ||
794
+ rect.x + rect.width < 0 ||
795
+ rect.y + rect.height < 0);
796
+ })
797
+ .catch(() => false);
798
+ if (!isReallyVisible)
799
+ continue;
800
+ }
801
+ const attrs = await locator
802
+ .evaluate((el) => {
803
+ const htmlEl = el;
804
+ const r = {};
805
+ if (htmlEl.dataset.testid)
806
+ r['testid'] = `[data-testid="${htmlEl.dataset.testid}"]`;
807
+ if (htmlEl.id && !htmlEl.id.match(/^[:]/))
808
+ r['id'] = '#' + CSS.escape(htmlEl.id);
809
+ const nameAttr = htmlEl.getAttribute('name');
810
+ if (nameAttr)
811
+ r['name'] = `${htmlEl.tagName.toLowerCase()}[name="${nameAttr}"]`;
812
+ return r;
813
+ })
814
+ .catch(() => null);
815
+ if (!attrs)
816
+ continue;
817
+ let bestSelector = '';
818
+ if (attrs.testid)
819
+ bestSelector = attrs.testid;
820
+ else if (attrs.id)
821
+ bestSelector = attrs.id;
822
+ else if (attrs.name)
823
+ bestSelector = attrs.name;
824
+ if (bestSelector) {
825
+ parts.push(`${ref}: ${bestSelector}`);
826
+ }
827
+ }
828
+ catch {
829
+ // skip
830
+ }
831
+ }
832
+ return parts.join(' | ');
382
833
  }
383
834
  async function enrichRefsWithPathsAndAttrs(page, refs, options) {
384
835
  if (Object.keys(refs).length === 0) {
@@ -663,9 +1114,10 @@ async function enrichRefsWithPathsAndAttrs(page, refs, options) {
663
1114
  const injectScript = `
664
1115
  window.__AGENT_BROWSER_REFS__ = ${JSON.stringify(refs)};
665
1116
  `;
666
- await page.evaluate(injectScript);
1117
+ if ('evaluate' in page) {
1118
+ await page.evaluate(injectScript);
1119
+ }
667
1120
  // Evaluate the function in the browser context
668
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
669
1121
  const elementData = await page.evaluate(() => {
670
1122
  const STYLE_CLASS_PATTERNS = [
671
1123
  /^(flex|grid|block|inline|hidden)$/,
@@ -692,7 +1144,6 @@ async function enrichRefsWithPathsAndAttrs(page, refs, options) {
692
1144
  'aside',
693
1145
  'form',
694
1146
  ]);
695
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
696
1147
  function getSemanticClass(element) {
697
1148
  const className = element.getAttribute('class');
698
1149
  if (!className)
@@ -705,7 +1156,6 @@ async function enrichRefsWithPathsAndAttrs(page, refs, options) {
705
1156
  const selectedClasses = classes.slice(0, 2);
706
1157
  return selectedClasses.map((cls) => 'contains(@class, "' + cls + '")').join(' and ');
707
1158
  }
708
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
709
1159
  function getElementIndex(element) {
710
1160
  const parent = element.parentElement;
711
1161
  if (!parent)
@@ -870,7 +1320,6 @@ async function enrichRefsWithPathsAndAttrs(page, refs, options) {
870
1320
  return null;
871
1321
  }
872
1322
  const results = {};
873
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
874
1323
  const refEntries = Object.entries(window.__AGENT_BROWSER_REFS__ || {});
875
1324
  for (const [ref, data] of refEntries) {
876
1325
  const targetRole = data.role;
@@ -941,13 +1390,14 @@ async function enrichRefsWithPathsAndAttrs(page, refs, options) {
941
1390
  return;
942
1391
  }
943
1392
  for (const [ref, data] of Object.entries(elementData)) {
1393
+ const typedData = data;
944
1394
  if (refs[ref] && data) {
945
1395
  if (options.path) {
946
- refs[ref].xpath = data.xpath;
947
- refs[ref].cssPath = data.cssPath;
1396
+ refs[ref].xpath = typedData.xpath;
1397
+ refs[ref].cssPath = typedData.cssPath;
948
1398
  }
949
1399
  if (options.attrs) {
950
- refs[ref].attributes = data.attributes;
1400
+ refs[ref].attributes = typedData.attributes;
951
1401
  }
952
1402
  }
953
1403
  }
@@ -1232,8 +1682,9 @@ export function getSemanticClass(element) {
1232
1682
  return selectedClasses.map((cls) => `contains(@class, "${cls}")`).join(' and ');
1233
1683
  }
1234
1684
  export function generateXPath(element, maxDepth = 5) {
1235
- if (element.id) {
1236
- return `//*[@id="${element.id}"]`;
1685
+ const elemId = element.id;
1686
+ if (elemId) {
1687
+ return `//*[@id="${elemId}"]`;
1237
1688
  }
1238
1689
  const testId = element.getAttribute('data-testid');
1239
1690
  if (testId) {
@@ -1254,8 +1705,9 @@ function buildRelativeXPath(element, maxDepth) {
1254
1705
  let current = element;
1255
1706
  let depth = 0;
1256
1707
  while (current && depth < maxDepth) {
1257
- if (current.id) {
1258
- path.unshift(`//*[@id="${current.id}"]`);
1708
+ const currentId = current.id;
1709
+ if (currentId) {
1710
+ path.unshift(`//*[@id="${currentId}"]`);
1259
1711
  break;
1260
1712
  }
1261
1713
  const testId = current.getAttribute('data-testid');
@@ -1287,8 +1739,9 @@ function getElementIndex(element) {
1287
1739
  return siblings.indexOf(element) + 1;
1288
1740
  }
1289
1741
  export function generateCSSPath(element, maxDepth = 5) {
1290
- if (element.id) {
1291
- return `#${element.id}`;
1742
+ const elemId = element.id;
1743
+ if (elemId) {
1744
+ return `#${elemId}`;
1292
1745
  }
1293
1746
  const testId = element.getAttribute('data-testid');
1294
1747
  if (testId) {
@@ -1298,8 +1751,9 @@ export function generateCSSPath(element, maxDepth = 5) {
1298
1751
  let current = element;
1299
1752
  let depth = 0;
1300
1753
  while (current && depth < maxDepth) {
1301
- if (current.id) {
1302
- path.unshift(`#${current.id}`);
1754
+ const currentId = current.id;
1755
+ if (currentId) {
1756
+ path.unshift(`#${currentId}`);
1303
1757
  break;
1304
1758
  }
1305
1759
  const testId = current.getAttribute('data-testid');