@nuanu-ai/agentbrowse 0.2.21 → 0.2.23

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 (106) hide show
  1. package/README.md +52 -9
  2. package/dist/commands/act.d.ts.map +1 -1
  3. package/dist/commands/act.js +100 -73
  4. package/dist/commands/action-acceptance.d.ts +4 -1
  5. package/dist/commands/action-acceptance.d.ts.map +1 -1
  6. package/dist/commands/action-acceptance.js +127 -37
  7. package/dist/commands/action-executor-helpers.d.ts.map +1 -1
  8. package/dist/commands/action-executor-helpers.js +12 -4
  9. package/dist/commands/action-fallbacks.d.ts.map +1 -1
  10. package/dist/commands/action-result-resolution.d.ts.map +1 -1
  11. package/dist/commands/action-result-resolution.js +1 -1
  12. package/dist/commands/action-value-projection.d.ts.map +1 -1
  13. package/dist/commands/close.d.ts +12 -1
  14. package/dist/commands/close.d.ts.map +1 -1
  15. package/dist/commands/close.js +19 -21
  16. package/dist/commands/datepicker-action-executor.d.ts.map +1 -1
  17. package/dist/commands/datepicker-action-executor.js +1 -1
  18. package/dist/commands/descriptor-validation.d.ts.map +1 -1
  19. package/dist/commands/descriptor-validation.js +13 -172
  20. package/dist/commands/extract-scoped-dialog-text.d.ts.map +1 -1
  21. package/dist/commands/extract-scoped-dialog-text.js +1 -4
  22. package/dist/commands/extract-snapshot-sanitizer.d.ts.map +1 -1
  23. package/dist/commands/extract-stagehand-executor.d.ts.map +1 -1
  24. package/dist/commands/extract.d.ts.map +1 -1
  25. package/dist/commands/extract.js +23 -4
  26. package/dist/commands/launch.d.ts +22 -1
  27. package/dist/commands/launch.d.ts.map +1 -1
  28. package/dist/commands/launch.js +122 -59
  29. package/dist/commands/observe-accessibility.d.ts +22 -0
  30. package/dist/commands/observe-accessibility.d.ts.map +1 -0
  31. package/dist/commands/observe-accessibility.js +497 -0
  32. package/dist/commands/observe-display-label.d.ts +4 -0
  33. package/dist/commands/observe-display-label.d.ts.map +1 -0
  34. package/dist/commands/observe-display-label.js +26 -0
  35. package/dist/commands/observe-dom-label-contract.d.ts +2 -0
  36. package/dist/commands/observe-dom-label-contract.d.ts.map +1 -0
  37. package/dist/commands/observe-dom-label-contract.js +521 -0
  38. package/dist/commands/observe-fallback-semantics.d.ts +6 -0
  39. package/dist/commands/observe-fallback-semantics.d.ts.map +1 -0
  40. package/dist/commands/observe-fallback-semantics.js +86 -0
  41. package/dist/commands/observe-inventory.d.ts +23 -18
  42. package/dist/commands/observe-inventory.d.ts.map +1 -1
  43. package/dist/commands/observe-inventory.js +172 -719
  44. package/dist/commands/observe-label-policy.d.ts +8 -0
  45. package/dist/commands/observe-label-policy.d.ts.map +1 -0
  46. package/dist/commands/observe-label-policy.js +21 -0
  47. package/dist/commands/observe-page-state.d.ts +1 -1
  48. package/dist/commands/observe-page-state.d.ts.map +1 -1
  49. package/dist/commands/observe-persistence.d.ts.map +1 -1
  50. package/dist/commands/observe-persistence.js +10 -3
  51. package/dist/commands/observe-projection.d.ts +2 -1
  52. package/dist/commands/observe-projection.d.ts.map +1 -1
  53. package/dist/commands/observe-projection.js +5 -2
  54. package/dist/commands/observe-semantics.d.ts.map +1 -1
  55. package/dist/commands/observe-semantics.js +18 -1
  56. package/dist/commands/observe-signals.d.ts +48 -0
  57. package/dist/commands/observe-signals.d.ts.map +1 -0
  58. package/dist/commands/observe-signals.js +461 -0
  59. package/dist/commands/observe-stagehand.d.ts.map +1 -1
  60. package/dist/commands/observe-stagehand.js +5 -18
  61. package/dist/commands/observe-surfaces.d.ts.map +1 -1
  62. package/dist/commands/observe-surfaces.js +5 -4
  63. package/dist/commands/observe.d.ts +0 -6
  64. package/dist/commands/observe.d.ts.map +1 -1
  65. package/dist/commands/observe.js +30 -6
  66. package/dist/commands/observe.test-harness.d.ts +0 -6
  67. package/dist/commands/observe.test-harness.d.ts.map +1 -1
  68. package/dist/commands/screenshot.d.ts.map +1 -1
  69. package/dist/commands/select-action-executor.d.ts.map +1 -1
  70. package/dist/commands/select-action-executor.js +1 -1
  71. package/dist/commands/semantic-observe.d.ts.map +1 -1
  72. package/dist/commands/semantic-observe.js +1 -4
  73. package/dist/commands/status.d.ts.map +1 -1
  74. package/dist/commands/text-input-action-executor.d.ts.map +1 -1
  75. package/dist/commands/text-input-action-executor.js +1 -1
  76. package/dist/control-semantics.d.ts.map +1 -1
  77. package/dist/control-semantics.js +2 -6
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +30 -9
  80. package/dist/owned-process.d.ts +4 -0
  81. package/dist/owned-process.d.ts.map +1 -0
  82. package/dist/owned-process.js +57 -0
  83. package/dist/playwright-runtime.d.ts.map +1 -1
  84. package/dist/playwright-runtime.js +3 -4
  85. package/dist/runtime-state.d.ts +3 -0
  86. package/dist/runtime-state.d.ts.map +1 -1
  87. package/dist/runtime-state.js +3 -0
  88. package/dist/secrets/catalog-applicability.d.ts.map +1 -1
  89. package/dist/secrets/form-matcher.d.ts.map +1 -1
  90. package/dist/secrets/form-matcher.js +2 -1
  91. package/dist/secrets/mock-agentpay-backend.d.ts +1 -1
  92. package/dist/secrets/mock-agentpay-backend.d.ts.map +1 -1
  93. package/dist/secrets/mock-agentpay-backend.js +2 -4
  94. package/dist/secrets/mock-agentpay-cabinet.d.ts.map +1 -1
  95. package/dist/secrets/mock-agentpay-cabinet.js +6 -1
  96. package/dist/secrets/protected-field-values.d.ts.map +1 -1
  97. package/dist/secrets/protected-field-values.js +1 -1
  98. package/dist/secrets/protected-fill.d.ts.map +1 -1
  99. package/dist/secrets/protected-fill.js +16 -4
  100. package/dist/session.d.ts +13 -0
  101. package/dist/session.d.ts.map +1 -1
  102. package/dist/session.js +52 -7
  103. package/dist/solver/captcha-detector.d.ts.map +1 -1
  104. package/dist/solver/types.d.ts +2 -0
  105. package/dist/solver/types.d.ts.map +1 -1
  106. package/package.json +3 -3
@@ -1,9 +1,9 @@
1
1
  import { inferAcceptancePolicyFromFacts, inferAllowedActionsFromFacts, inferAvailabilityFromFacts, inferControlFamilyFromFacts, } from '../control-semantics.js';
2
2
  import { LOCATOR_DOM_SIGNATURE_SCRIPT, normalizePageSignature, readLocatorDomSignature, } from './descriptor-validation.js';
3
+ import { OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT } from './observe-dom-label-contract.js';
3
4
  import { TRANSPARENT_ACTIONABLE_CONTROL_HELPER_SCRIPT } from './user-actionable.js';
4
5
  const DOM_TARGET_COLLECTION_LIMIT = 640;
5
6
  const DOM_TARGET_OUTPUT_LIMIT = 480;
6
- const DOM_SIGNAL_COLLECTION_LIMIT = 24;
7
7
  export function inferStructuredCellVariantFromEvidence(evidence) {
8
8
  const role = (evidence.role ?? '').toLowerCase();
9
9
  const surfaceKind = (evidence.surfaceKind ?? '').toLowerCase();
@@ -14,8 +14,7 @@ export function inferStructuredCellVariantFromEvidence(evidence) {
14
14
  const hasSeatMetadata = Boolean(evidence.hasSeatAttribute || evidence.hasSeatRowAttribute || evidence.hasSeatColumnAttribute);
15
15
  const seatIdentityLike = /(?:\bseat\s*\d{1,3}[a-z]?\b|место\s*\d{1,3}[a-z]?\b|\b\d{1,3}[a-z]\b|\b[a-z]\d{1,3}\b)/i.test(normalizedLabel);
16
16
  const seatClassLike = /seat|cabin|fare|row/.test(className);
17
- const compactDateCellLabel = /^(?:\d{1,2}|january|february|march|april|may|june|july|august|september|october|november|december|январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)$/i.test(normalizedLabel) ||
18
- /^(?:\d{1,2}[./-]\d{1,2}(?:[./-]\d{2,4})?)$/.test(normalizedLabel);
17
+ const compactDateCellLabel = /^(?:\d{1,2}|january|february|march|april|may|june|july|august|september|october|november|december|январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)$/i.test(normalizedLabel) || /^(?:\d{1,2}[./-]\d{1,2}(?:[./-]\d{2,4})?)$/.test(normalizedLabel);
19
18
  const dateLike = surfaceKind === 'datepicker' ||
20
19
  ((role === 'gridcell' || structuredSurface || hasDateMetadata) && compactDateCellLabel);
21
20
  if (dateLike) {
@@ -48,7 +47,8 @@ function normalizeInheritedPageSignature(pageSignature) {
48
47
  : undefined;
49
48
  }
50
49
  function applyInheritedDomTargetMetadata(target, options) {
51
- const normalizedFramePath = normalizeInheritedFramePath(options?.framePath) ?? normalizeInheritedFramePath(target.framePath);
50
+ const normalizedFramePath = normalizeInheritedFramePath(options?.framePath) ??
51
+ normalizeInheritedFramePath(target.framePath);
52
52
  const normalizedFrameUrl = normalizeInheritedFrameUrl(options?.frameUrl) ?? target.frameUrl;
53
53
  const normalizedPageSignature = normalizeInheritedPageSignature(options?.pageSignature) ?? target.pageSignature;
54
54
  return {
@@ -84,19 +84,11 @@ function enrichObservedTargetSemantics(target) {
84
84
  ...target,
85
85
  capability,
86
86
  availability,
87
+ controlFamily,
87
88
  allowedActions,
88
89
  acceptancePolicy,
89
90
  };
90
91
  }
91
- function applyInheritedSignalMetadata(signal, options) {
92
- const normalizedFramePath = normalizeInheritedFramePath(options?.framePath) ?? normalizeInheritedFramePath(signal.framePath);
93
- const normalizedFrameUrl = normalizeInheritedFrameUrl(options?.frameUrl) ?? signal.frameUrl;
94
- return {
95
- ...signal,
96
- framePath: normalizedFramePath,
97
- frameUrl: normalizedFrameUrl,
98
- };
99
- }
100
92
  function hasStagehandDomFacts(domFacts) {
101
93
  return Boolean(domFacts &&
102
94
  (domFacts.kind ||
@@ -163,23 +155,11 @@ export async function readStagehandLocatorSnapshot(locator) {
163
155
  return best;
164
156
  }
165
157
  const STAGEHAND_DOM_FACTS_SCRIPT = String.raw `
158
+ ${OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT}
166
159
  if (!(element instanceof HTMLElement)) {
167
160
  return null;
168
161
  }
169
162
 
170
- const normalizeText = (value) => (value ?? '').replace(/\s+/g, ' ').trim();
171
- const isVisible = (candidate) => {
172
- if (!(candidate instanceof HTMLElement)) {
173
- return false;
174
- }
175
- const style = candidate.ownerDocument?.defaultView?.getComputedStyle(candidate);
176
- if (!style || style.display === 'none' || style.visibility === 'hidden') {
177
- return false;
178
- }
179
- const rect = candidate.getBoundingClientRect();
180
- return rect.width > 0 && rect.height > 0;
181
- };
182
-
183
163
  const inputLike = element;
184
164
  const disabledProperty =
185
165
  typeof inputLike.disabled === 'boolean' ? inputLike.disabled : false;
@@ -209,47 +189,10 @@ const STAGEHAND_DOM_FACTS_SCRIPT = String.raw `
209
189
  const states = {};
210
190
  const directValue =
211
191
  'value' in inputLike && typeof inputLike.value === 'string'
212
- ? normalizeText(inputLike.value)
192
+ ? observedNormalizeDescriptorText(inputLike.value)
213
193
  : undefined;
214
- const directText = normalizeText(element.innerText || element.textContent || '');
215
- let currentValue =
216
- directValue ||
217
- (element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea'
218
- ? undefined
219
- : directText || undefined);
220
-
221
- const popupBacked =
222
- element.getAttribute('role') === 'combobox' ||
223
- element.hasAttribute('aria-haspopup') ||
224
- element.hasAttribute('aria-controls') ||
225
- readonly;
226
- if (!currentValue && popupBacked && element.parentElement) {
227
- const siblingValues = Array.from(element.parentElement.children)
228
- .filter((candidate) => candidate !== element)
229
- .filter((candidate) => isVisible(candidate))
230
- .filter((candidate) => {
231
- const tag = candidate.tagName.toLowerCase();
232
- if (tag === 'label' || tag === 'legend') {
233
- return false;
234
- }
235
- const role = candidate.getAttribute('role') || '';
236
- return (
237
- role === 'listbox' ||
238
- role === 'button' ||
239
- role === 'option' ||
240
- candidate.hasAttribute('aria-selected') ||
241
- candidate.hasAttribute('aria-current')
242
- );
243
- })
244
- .map((candidate) => normalizeText(candidate.innerText || candidate.textContent || ''))
245
- .filter(
246
- (value, index, all) =>
247
- Boolean(value) && value.length <= 40 && all.indexOf(value) === index
248
- );
249
- if (siblingValues.length === 1) {
250
- currentValue = siblingValues[0];
251
- }
252
- }
194
+ const directText = observedNormalizeDescriptorText(element.innerText || element.textContent || '');
195
+ const currentValue = observedPopupCurrentValueOf(element);
253
196
 
254
197
  if (expanded !== undefined) states.expanded = expanded;
255
198
  if (selected !== undefined) states.selected = selected;
@@ -407,9 +350,10 @@ async function collectDomTargetsFromDocument(context, options) {
407
350
  return undefined;
408
351
  };
409
352
  ${TRANSPARENT_ACTIONABLE_CONTROL_HELPER_SCRIPT}
353
+ ${OBSERVE_DOM_LABEL_CONTRACT_HELPER_SCRIPT}
410
354
  ${INFER_STRUCTURED_CELL_VARIANT_HELPER_SCRIPT}
411
355
 
412
- const normalizeDescriptorText = (value) => (value || '').replace(/\s+/g, ' ').trim();
356
+ const normalizeDescriptorText = (value) => observedNormalizeDescriptorText(value);
413
357
 
414
358
  const isVisible = (element) => {
415
359
  const style = window.getComputedStyle(element);
@@ -636,38 +580,12 @@ async function collectDomTargetsFromDocument(context, options) {
636
580
  return values.length > 0 ? values.join(' ') : undefined;
637
581
  };
638
582
 
639
- const MACHINE_DESCRIPTOR_TEXT_RE =
640
- /(?:window\.[a-z0-9_$]+|apiary(?:sleepingqueue|markerportal)|document\.getelementbyid\(|addeventlistener\(|push\(\[|["']widgets["']\s*:|["']meta["']\s*:)/i;
641
-
642
- const looksLikeMachineDescriptorText = (value) => {
643
- const normalized = normalizeDescriptorText(value || '');
644
- if (!normalized) {
645
- return false;
646
- }
647
-
648
- if (MACHINE_DESCRIPTOR_TEXT_RE.test(normalized)) {
649
- return true;
650
- }
651
-
652
- if (normalized.length < 160) {
653
- return false;
654
- }
655
-
656
- const syntaxCount =
657
- (normalized.match(/[{}[\]();=]/g) || []).length +
658
- (normalized.match(/["']/g) || []).length;
659
- return syntaxCount >= 24 && syntaxCount / normalized.length >= 0.08;
660
- };
661
-
662
- const safeVisibleDescriptorTextOf = (element) => {
583
+ const visibleDescriptorTextOf = (element) => {
663
584
  const text = normalizeDescriptorText(element?.innerText || '');
664
- if (!text || looksLikeMachineDescriptorText(text)) {
665
- return undefined;
666
- }
667
- return text;
585
+ return text || undefined;
668
586
  };
669
587
 
670
- const directVisibleChildDescriptorTextOf = (element) => {
588
+ const visibleChildDescriptorTextOf = (element) => {
671
589
  if (!isHTMLElementNode(element)) {
672
590
  return undefined;
673
591
  }
@@ -676,7 +594,7 @@ async function collectDomTargetsFromDocument(context, options) {
676
594
  const seen = new Set();
677
595
  const pushValue = (value) => {
678
596
  const normalized = normalizeDescriptorText(value || '');
679
- if (!normalized || looksLikeMachineDescriptorText(normalized) || seen.has(normalized)) {
597
+ if (!normalized || seen.has(normalized)) {
680
598
  return;
681
599
  }
682
600
  seen.add(normalized);
@@ -712,18 +630,9 @@ async function collectDomTargetsFromDocument(context, options) {
712
630
  return buttonLikeText;
713
631
  }
714
632
  const altValue = imageAltTextOf(element);
715
- const visibleText = safeVisibleDescriptorTextOf(element);
716
- const childText = container ? directVisibleChildDescriptorTextOf(element) : undefined;
717
- const rawTextContent = normalizeDescriptorText(element?.textContent || '');
718
- const textContentFallback =
719
- !container &&
720
- rawTextContent &&
721
- rawTextContent !== visibleText &&
722
- rawTextContent !== childText &&
723
- !looksLikeMachineDescriptorText(rawTextContent)
724
- ? rawTextContent
725
- : undefined;
726
- const textValue = visibleText || childText || textContentFallback;
633
+ const visibleText = visibleDescriptorTextOf(element);
634
+ const childText = container ? visibleChildDescriptorTextOf(element) : undefined;
635
+ const textValue = visibleText || childText;
727
636
 
728
637
  if (!textValue && !altValue) {
729
638
  return undefined;
@@ -737,139 +646,13 @@ async function collectDomTargetsFromDocument(context, options) {
737
646
  return (altValue + ' ' + textValue).trim();
738
647
  };
739
648
 
740
- const isMeaningfulLabel = (value) => {
741
- const normalized = (value || '').replace(/\s+/g, ' ').trim();
742
- return Boolean(normalized) && normalized !== '[object Object]';
743
- };
744
-
745
- const inputTypeOf = (element) => {
746
- if (!isHTMLInputNode(element)) {
747
- return '';
748
- }
749
- return (element.getAttribute('type') || 'text').trim().toLowerCase();
750
- };
751
-
752
- const isButtonLikeInput = (element) => {
753
- return isHTMLInputNode(element) && ['button', 'submit', 'reset'].includes(inputTypeOf(element));
754
- };
755
-
756
- const LOOSE_ACTION_TEXT_RE =
757
- /^(?:pay|buy|continue|submit|search|book|reserve|checkout|next|done|оплат|куп|продолж|дальше|поиск|заброни)/i;
758
-
759
- const tokenizeSemanticText = (value) => {
760
- const normalized = (value || '')
761
- .replace(/([a-z])([A-Z])/g, '$1 $2')
762
- .replace(/[^a-zA-Z0-9\u0400-\u04FF]+/g, ' ')
763
- .toLowerCase()
764
- .trim();
765
- if (!normalized) return [];
766
- return normalized
767
- .split(/\s+/)
768
- .filter((token) => token.length >= 2 && token !== 'input' && token !== 'field');
769
- };
770
-
771
- const fieldSemanticTokensOf = (element) => {
772
- const tokens = new Set();
773
- const pushValue = (value) => {
774
- for (const token of tokenizeSemanticText(value)) {
775
- tokens.add(token);
776
- }
777
- };
778
-
779
- const inputType = inputTypeOf(element);
780
- const autocomplete = (element.getAttribute('autocomplete') || '').trim().toLowerCase();
781
-
782
- if (isButtonLikeInput(element)) {
783
- return tokens;
784
- }
785
-
786
- pushValue(element.getAttribute('placeholder'));
787
- pushValue(element.getAttribute('name'));
788
- pushValue(element.getAttribute('id'));
789
- pushValue(autocomplete);
790
- pushValue(inputType);
791
-
792
- if (inputType === 'email' || autocomplete.includes('email')) {
793
- pushValue('email mail e-mail');
794
- }
795
- if (
796
- inputType === 'tel' ||
797
- autocomplete.startsWith('tel') ||
798
- autocomplete.includes('phone')
799
- ) {
800
- pushValue('phone telephone mobile tel номер телефон');
801
- }
802
- if (inputType === 'password' || autocomplete.includes('password')) {
803
- pushValue('password pass пароль');
804
- }
805
- if (inputType === 'search' || autocomplete.includes('search')) {
806
- pushValue('search find поиск найти');
807
- }
808
- if (
809
- inputType === 'date' ||
810
- autocomplete.includes('bday') ||
811
- autocomplete.includes('birth') ||
812
- autocomplete.includes('dob')
813
- ) {
814
- pushValue('date birth birthday dob дата рождения');
815
- }
816
-
817
- return tokens;
818
- };
819
-
820
- const isLooseFieldLabelCompatible = (element, candidateText) => {
821
- const normalizedCandidate = normalizeDescriptorText(candidateText || '');
822
- if (!normalizedCandidate) return false;
823
- if (LOOSE_ACTION_TEXT_RE.test(normalizedCandidate)) {
824
- return false;
825
- }
826
-
827
- const semanticTokens = fieldSemanticTokensOf(element);
828
- if (semanticTokens.size === 0) {
829
- return true;
830
- }
831
-
832
- const candidateTokens = tokenizeSemanticText(normalizedCandidate);
833
- if (candidateTokens.length === 0) {
834
- return false;
835
- }
836
-
837
- return candidateTokens.some((token) => semanticTokens.has(token));
838
- };
839
-
840
- const isFieldLikeControl = (element) => {
841
- const tag = element.tagName.toLowerCase();
842
- const explicitRole = (element.getAttribute('role') || '').trim().toLowerCase();
843
- if (tag === 'input') {
844
- return !isButtonLikeInput(element);
845
- }
846
- return (
847
- tag === 'textarea' ||
848
- tag === 'select' ||
849
- explicitRole === 'textbox' ||
850
- explicitRole === 'combobox' ||
851
- explicitRole === 'searchbox' ||
852
- explicitRole === 'spinbutton'
853
- );
854
- };
855
-
856
- const precedesElementInDocument = (candidate, element) => {
857
- return Boolean(candidate.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING);
858
- };
649
+ const isMeaningfulLabel = (value) => observedIsMeaningfulLabel(value);
859
650
 
860
- const ariaLabelledbyTextOf = (element) => {
861
- const labelledBy = element.getAttribute('aria-labelledby')?.trim();
862
- if (!labelledBy) return undefined;
651
+ const inputTypeOf = (element) => observedInputTypeOf(element);
863
652
 
864
- const text = labelledBy
865
- .split(/\s+/)
866
- .map((id) => textOf(document.getElementById(id)))
867
- .filter(Boolean)
868
- .join(' ')
869
- .trim();
653
+ const isButtonLikeInput = (element) => observedIsButtonLikeInput(element);
870
654
 
871
- return isMeaningfulLabel(text) ? text : undefined;
872
- };
655
+ const popupCurrentValueOf = (element) => observedPopupCurrentValueOf(element);
873
656
 
874
657
  const describedByTextOf = (element) => {
875
658
  const describedBy = element.getAttribute('aria-describedby')?.trim();
@@ -885,105 +668,9 @@ async function collectDomTargetsFromDocument(context, options) {
885
668
  return isMeaningfulLabel(text) ? text : undefined;
886
669
  };
887
670
 
888
- const nearestFieldLabelOf = (element) => {
889
- const restrictLooseCandidatesToPreceding = isFieldLikeControl(element);
890
- const anchors = [
891
- composedParentElement(element),
892
- composedParentElement(composedParentElement(element)),
893
- composedParentElement(composedParentElement(composedParentElement(element))),
894
- ].filter(Boolean);
895
-
896
- for (const anchor of anchors) {
897
- if (!isHTMLElementNode(anchor)) continue;
898
-
899
- const explicitLabels = Array.from(
900
- anchor.querySelectorAll('label, legend, [class*="label"], [data-testid*="label"]')
901
- ).filter((candidate) => {
902
- return (
903
- isHTMLElementNode(candidate) &&
904
- candidate !== element &&
905
- !candidate.contains(element) &&
906
- !element.contains(candidate)
907
- );
908
- });
909
-
910
- for (const candidate of explicitLabels) {
911
- if (
912
- restrictLooseCandidatesToPreceding &&
913
- !candidate.matches('label, legend') &&
914
- !precedesElementInDocument(candidate, element)
915
- ) {
916
- continue;
917
- }
918
- const candidateText = textOf(candidate);
919
- if (isMeaningfulLabel(candidateText)) {
920
- return candidateText;
921
- }
922
- }
923
-
924
- for (const child of Array.from(anchor.children).slice(0, 8)) {
925
- if (!isHTMLElementNode(child)) continue;
926
- if (child === element || child.contains(element) || element.contains(child)) continue;
927
- if (restrictLooseCandidatesToPreceding && !precedesElementInDocument(child, element)) {
928
- continue;
929
- }
930
-
931
- const candidateText = textOf(child);
932
- if (isMeaningfulLabel(candidateText)) {
933
- return candidateText;
934
- }
935
- }
936
- }
937
-
938
- return undefined;
939
- };
940
-
941
- const labelOf = (element) => {
942
- const ariaLabel = element.getAttribute('aria-label')?.trim();
943
- if (isMeaningfulLabel(ariaLabel)) return ariaLabel;
944
-
945
- const customLabel = element.getAttribute('label')?.trim();
946
- if (isMeaningfulLabel(customLabel)) return customLabel;
947
-
948
- const ariaLabelledbyText = ariaLabelledbyTextOf(element);
949
- if (isMeaningfulLabel(ariaLabelledbyText)) return ariaLabelledbyText;
950
-
951
- const labels = element.labels;
952
- if (labels && labels.length > 0) {
953
- const labelText = textOf(labels[0]);
954
- if (isMeaningfulLabel(labelText)) return labelText;
955
- }
956
-
957
- const title = element.getAttribute('title')?.trim();
958
- if (isMeaningfulLabel(title)) return title;
959
-
960
- const skipNearestFieldLabel = isButtonLikeInput(element);
961
- const preferFieldLabelBeforeVisibleText = isFieldLikeControl(element);
962
- const text = textOf(element);
963
- if (!preferFieldLabelBeforeVisibleText && isMeaningfulLabel(text)) return text;
964
-
965
- const placeholder = element.getAttribute('placeholder')?.trim();
966
- const nearestFieldLabel = nearestFieldLabelOf(element);
967
- if (
968
- !skipNearestFieldLabel &&
969
- isMeaningfulLabel(nearestFieldLabel) &&
970
- (!preferFieldLabelBeforeVisibleText ||
971
- isLooseFieldLabelCompatible(element, nearestFieldLabel))
972
- ) {
973
- return nearestFieldLabel;
974
- }
975
-
976
- if (isMeaningfulLabel(placeholder)) return placeholder;
977
-
978
- if (!skipNearestFieldLabel && isMeaningfulLabel(nearestFieldLabel)) return nearestFieldLabel;
979
-
980
- if (isMeaningfulLabel(text)) return text;
671
+ const explicitLabelOf = (element) => observedExplicitLabelOf(element);
981
672
 
982
- const syntheticLabel = stableLabelOf(element);
983
- if (isMeaningfulLabel(syntheticLabel)) return syntheticLabel;
984
-
985
- return undefined;
986
- };
673
+ const looseFieldLabelOf = (element) => observedLooseFieldLabelOf(element);
987
674
 
988
675
  const VALIDATION_TEXT_RE =
989
676
  /(?:required|invalid|incorrect|too\s+(?:short|long)|must|error|format|please\s+(?:enter|select|choose|fill)|невер|ошиб|обязател|заполн|введите|укажите|выберите|долж|нужно|формат|цифр|символ)/i;
@@ -1023,58 +710,7 @@ async function collectDomTargetsFromDocument(context, options) {
1023
710
  return undefined;
1024
711
  };
1025
712
 
1026
- const stableLabelOf = (element) => {
1027
- const ariaLabel = element.getAttribute('aria-label')?.trim();
1028
- if (isMeaningfulLabel(ariaLabel)) return ariaLabel;
1029
-
1030
- const customLabel = element.getAttribute('label')?.trim();
1031
- if (isMeaningfulLabel(customLabel)) return customLabel;
1032
-
1033
- const title = element.getAttribute('title')?.trim();
1034
- if (isMeaningfulLabel(title)) return title;
1035
-
1036
- const ariaLabelledbyText = ariaLabelledbyTextOf(element);
1037
- if (isMeaningfulLabel(ariaLabelledbyText)) return ariaLabelledbyText;
1038
-
1039
- const labels = element.labels;
1040
- if (labels && labels.length > 0) {
1041
- const labelText = textOf(labels[0]);
1042
- if (isMeaningfulLabel(labelText)) return labelText;
1043
- }
1044
-
1045
- const text = textOf(element);
1046
- if (isMeaningfulLabel(text)) return text;
1047
-
1048
- const skipNearestFieldLabel = isButtonLikeInput(element);
1049
- const nearestFieldLabel = nearestFieldLabelOf(element);
1050
- if (!skipNearestFieldLabel && isMeaningfulLabel(nearestFieldLabel)) return nearestFieldLabel;
1051
-
1052
- return syntheticLabelOf(element);
1053
- };
1054
-
1055
- const inferRole = (element) => {
1056
- const explicitRole = element.getAttribute('role')?.trim();
1057
- if (explicitRole) return explicitRole;
1058
-
1059
- const labelBackedChoice = labelBackedChoiceControlOf(element);
1060
- if (isHTMLInputNode(labelBackedChoice)) {
1061
- const inputType = (labelBackedChoice.type || '').toLowerCase();
1062
- if (inputType === 'radio') return 'radio';
1063
- if (inputType === 'checkbox') return 'checkbox';
1064
- }
1065
-
1066
- const tag = element.tagName.toLowerCase();
1067
- if (tag === 'button') return 'button';
1068
- if (tag === 'a' && element.getAttribute('href')) return 'link';
1069
- if (tag === 'select') return 'combobox';
1070
- if (tag === 'textarea') return 'textbox';
1071
- if (tag === 'input') {
1072
- const inputType = (element.getAttribute('type') || 'text').toLowerCase();
1073
- if (['button', 'submit', 'reset'].includes(inputType)) return 'button';
1074
- return 'textbox';
1075
- }
1076
- return undefined;
1077
- };
713
+ const inferRole = (element) => observedInferRole(element) || undefined;
1078
714
 
1079
715
  const kindOf = (element) => {
1080
716
  const tag = element.tagName.toLowerCase();
@@ -1299,22 +935,6 @@ async function collectDomTargetsFromDocument(context, options) {
1299
935
  };
1300
936
  };
1301
937
 
1302
- const landmarkLabelOf = (container) => {
1303
- if (!container) return undefined;
1304
-
1305
- const ariaLabel = container.getAttribute?.('aria-label')?.trim();
1306
- if (ariaLabel) return ariaLabel;
1307
-
1308
- const heading = container.querySelector?.(headingSelector);
1309
- const headingText = textOf(heading);
1310
- if (headingText) return headingText;
1311
-
1312
- const text = textOf(container, { container: true });
1313
- if (text && text.length <= 140) return text;
1314
- if (text) return text.slice(0, 140);
1315
- return undefined;
1316
- };
1317
-
1318
938
  const isStructuredContainer = (element) => {
1319
939
  if (!isHTMLElementNode(element)) return false;
1320
940
 
@@ -1389,7 +1009,7 @@ async function collectDomTargetsFromDocument(context, options) {
1389
1009
  const cursorClick = style.cursor === 'pointer';
1390
1010
  if (!explicitClick && !cursorClick) return false;
1391
1011
 
1392
- const descriptorText = stableLabelOf(element) || textOf(element);
1012
+ const descriptorText = explicitLabelOf(element) || textOf(element) || syntheticLabelOf(element);
1393
1013
  if (!descriptorText) return false;
1394
1014
 
1395
1015
  const interactiveDescendants = visibleInteractiveDescendantCountOf(element);
@@ -1458,12 +1078,6 @@ async function collectDomTargetsFromDocument(context, options) {
1458
1078
  return undefined;
1459
1079
  };
1460
1080
 
1461
- const containerTextOf = (container) => {
1462
- const text = textOf(container, { container: true });
1463
- if (!text) return undefined;
1464
- return text.length <= 240 ? text : text.slice(0, 240);
1465
- };
1466
-
1467
1081
  const groupOf = (element) => {
1468
1082
  if (element.matches?.(collectionSelector)) {
1469
1083
  return element;
@@ -1532,70 +1146,6 @@ async function collectDomTargetsFromDocument(context, options) {
1532
1146
 
1533
1147
  const normalizeText = (value) => (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
1534
1148
 
1535
- const contextualHintOf = (element) => {
1536
- const excluded = new Set(
1537
- [labelOf(element), textOf(element)]
1538
- .map((value) => normalizeText(value))
1539
- .filter(Boolean)
1540
- );
1541
-
1542
- const seen = new Set();
1543
- const pushCandidate = (candidates, value) => {
1544
- const text = (value || '').replace(/\s+/g, ' ').trim();
1545
- const normalized = normalizeText(text);
1546
- if (!normalized || excluded.has(normalized) || seen.has(normalized)) {
1547
- return;
1548
- }
1549
- if (text.length < 4 || text.length > 120) {
1550
- return;
1551
- }
1552
- seen.add(normalized);
1553
- candidates.push(text);
1554
- };
1555
-
1556
- const describeNode = (node, candidates) => {
1557
- if (!isHTMLElementNode(node)) return;
1558
-
1559
- const heading = node.querySelector?.(headingSelector);
1560
- if (isHTMLElementNode(heading) && heading !== element && !heading.contains(element)) {
1561
- pushCandidate(candidates, textOf(heading));
1562
- }
1563
-
1564
- for (const child of Array.from(node.children).slice(0, 8)) {
1565
- if (!isHTMLElementNode(child)) continue;
1566
- if (child === element || child.contains(element) || element.contains(child)) continue;
1567
- pushCandidate(candidates, textOf(child));
1568
- }
1569
-
1570
- pushCandidate(candidates, landmarkLabelOf(node));
1571
- pushCandidate(candidates, textOf(node, { container: true }));
1572
- };
1573
-
1574
- const anchors = [
1575
- itemOf(element),
1576
- groupOf(element),
1577
- containerOf(element),
1578
- composedParentElement(element),
1579
- composedParentElement(composedParentElement(element)),
1580
- ].filter(Boolean);
1581
-
1582
- for (const anchor of anchors) {
1583
- const candidates = [];
1584
- pushCandidate(candidates, describedByTextOf(element));
1585
- describeNode(anchor, candidates);
1586
- if (candidates.length > 0) {
1587
- return candidates[0];
1588
- }
1589
- }
1590
-
1591
- const describedBy = describedByTextOf(element);
1592
- if (describedBy) {
1593
- return describedBy;
1594
- }
1595
-
1596
- return undefined;
1597
- };
1598
-
1599
1149
  const VALIDATION_CLASS_RE = /\b(?:error|invalid|warning|danger|alert|failed)\b/i;
1600
1150
  const validationFieldSelectors =
1601
1151
  'input, textarea, select, [role="textbox"], [contenteditable="true"], [aria-invalid="true"]';
@@ -1825,7 +1375,7 @@ async function collectDomTargetsFromDocument(context, options) {
1825
1375
  const role = inferRole(element) || '';
1826
1376
  const className = (element.getAttribute('class') || '').toLowerCase();
1827
1377
  const surfaceKind = inferSurfaceKind(surface);
1828
- const label = labelOf(element) || textOf(element) || '';
1378
+ const label = explicitLabelOf(element) || textOf(element) || syntheticLabelOf(element) || '';
1829
1379
  const normalizedLabel = label.replace(/\s+/g, ' ').trim();
1830
1380
  const explicitDateCellMetadata =
1831
1381
  element.hasAttribute('data-day') ||
@@ -1909,6 +1459,49 @@ async function collectDomTargetsFromDocument(context, options) {
1909
1459
  return undefined;
1910
1460
  };
1911
1461
 
1462
+ const siblingModalBackdropOf = (surface) => {
1463
+ let current = surface;
1464
+ let depth = 0;
1465
+ while (current && depth < 16) {
1466
+ const parent = composedParentElement(current);
1467
+ if (isHTMLElementNode(parent)) {
1468
+ const siblings = Array.from(parent.children).filter(
1469
+ (candidate) => candidate !== current && isHTMLElementNode(candidate)
1470
+ );
1471
+ for (const sibling of siblings) {
1472
+ if (!isVisible(sibling)) {
1473
+ continue;
1474
+ }
1475
+
1476
+ const style = window.getComputedStyle(sibling);
1477
+ const position = (style.position || '').toLowerCase();
1478
+ const rect = sibling.getBoundingClientRect();
1479
+ const viewportArea = Math.max(window.innerWidth * window.innerHeight, 1);
1480
+ const coverage = (rect.width * rect.height) / viewportArea;
1481
+ const backgroundVisible =
1482
+ style.backgroundColor &&
1483
+ style.backgroundColor !== 'transparent' &&
1484
+ style.backgroundColor !== 'rgba(0, 0, 0, 0)';
1485
+ const opacity = parseFloat(style.opacity || '1');
1486
+
1487
+ if (
1488
+ position === 'fixed' &&
1489
+ coverage > 0.45 &&
1490
+ backgroundVisible &&
1491
+ opacity >= 0.05
1492
+ ) {
1493
+ return sibling;
1494
+ }
1495
+ }
1496
+ }
1497
+
1498
+ current = parent;
1499
+ depth += 1;
1500
+ }
1501
+
1502
+ return undefined;
1503
+ };
1504
+
1912
1505
  const surfacePositionTraitsOf = (surface) => {
1913
1506
  if (!isHTMLElementNode(surface) || !isVisible(surface)) return undefined;
1914
1507
 
@@ -1949,7 +1542,7 @@ async function collectDomTargetsFromDocument(context, options) {
1949
1542
  hasCardChrome &&
1950
1543
  coverage <= 0.35 &&
1951
1544
  interactiveCount >= 1 &&
1952
- modalBackdropAncestorOf(surface)
1545
+ (modalBackdropAncestorOf(surface) || siblingModalBackdropOf(surface))
1953
1546
  ) {
1954
1547
  return { kind: 'floating-panel', priority: 90 };
1955
1548
  }
@@ -1980,15 +1573,97 @@ async function collectDomTargetsFromDocument(context, options) {
1980
1573
  return role || surface.tagName.toLowerCase();
1981
1574
  };
1982
1575
 
1576
+ const surfaceFallbackLabelOf = (surface, surfaceKind) => {
1577
+ if (!isHTMLElementNode(surface)) {
1578
+ return undefined;
1579
+ }
1580
+
1581
+ const kind = (surfaceKind || '').toLowerCase();
1582
+ const overlayLike =
1583
+ kind === 'dialog' ||
1584
+ kind === 'floating-panel' ||
1585
+ kind === 'sticky-panel' ||
1586
+ kind === 'listbox' ||
1587
+ kind === 'menu' ||
1588
+ kind === 'grid' ||
1589
+ kind === 'tabpanel' ||
1590
+ kind === 'popover' ||
1591
+ kind === 'dropdown' ||
1592
+ kind === 'datepicker' ||
1593
+ kind === 'form';
1594
+ if (!overlayLike) {
1595
+ return undefined;
1596
+ }
1597
+
1598
+ const directLabel = normalizeDescriptorText(
1599
+ surface.getAttribute('aria-label') || surface.getAttribute('title') || ''
1600
+ );
1601
+ if (directLabel) {
1602
+ return directLabel;
1603
+ }
1604
+
1605
+ const heading = surface.querySelector(
1606
+ 'h1, h2, h3, h4, h5, h6, [role="heading"], legend, strong'
1607
+ );
1608
+ if (!isHTMLElementNode(heading) || !isVisible(heading)) {
1609
+ return undefined;
1610
+ }
1611
+
1612
+ return normalizeDescriptorText(heading.innerText || heading.textContent || '') || undefined;
1613
+ };
1614
+
1615
+ const contextLabelOf = (element) => {
1616
+ if (!isHTMLElementNode(element)) {
1617
+ return undefined;
1618
+ }
1619
+
1620
+ const ariaLabel = normalizeDescriptorText(element.getAttribute('aria-label') || '');
1621
+ if (ariaLabel) {
1622
+ return ariaLabel;
1623
+ }
1624
+
1625
+ const ariaLabelledby = element.getAttribute('aria-labelledby')?.trim();
1626
+ if (ariaLabelledby) {
1627
+ const labelledByText = normalizeDescriptorText(
1628
+ ariaLabelledby
1629
+ .split(/\s+/)
1630
+ .map((id) => textOf(document.getElementById(id)))
1631
+ .filter(Boolean)
1632
+ .join(' ')
1633
+ );
1634
+ if (labelledByText) {
1635
+ return labelledByText;
1636
+ }
1637
+ }
1638
+
1639
+ const heading = element.querySelector(headingSelector);
1640
+ if (isHTMLElementNode(heading) && heading !== element && !heading.contains(element)) {
1641
+ const headingText = normalizeDescriptorText(heading.innerText || heading.textContent || '');
1642
+ if (headingText) {
1643
+ return headingText;
1644
+ }
1645
+ }
1646
+
1647
+ const text = textOf(element, { container: true });
1648
+ if (!text) {
1649
+ return undefined;
1650
+ }
1651
+ return text.length <= 140 ? text : text.slice(0, 140);
1652
+ };
1653
+
1983
1654
  const contextNodeOf = (element) => {
1984
1655
  if (!element) return undefined;
1985
1656
 
1986
1657
  const kind = element.getAttribute?.('role')?.trim() || element.tagName?.toLowerCase?.();
1987
- const label = landmarkLabelOf(element);
1988
- const text = containerTextOf(element);
1989
-
1990
- if (!kind && !label && !text) return undefined;
1991
- return { kind: kind || undefined, label, text };
1658
+ const label = contextLabelOf(element);
1659
+ const selector = buildSelector(element);
1660
+ if (!kind && !label && !selector) return undefined;
1661
+ return {
1662
+ kind: kind || undefined,
1663
+ label,
1664
+ text: undefined,
1665
+ selector,
1666
+ };
1992
1667
  };
1993
1668
 
1994
1669
  const pageSignature =
@@ -2328,7 +2003,6 @@ async function collectDomTargetsFromDocument(context, options) {
2328
2003
  variant: 'seat-cell',
2329
2004
  row,
2330
2005
  column,
2331
- zone: landmarkLabelOf(ownerSurface) || landmarkLabelOf(surface) || undefined,
2332
2006
  cellLabel: label,
2333
2007
  },
2334
2008
  surface: ownerSurface,
@@ -2442,7 +2116,7 @@ async function collectDomTargetsFromDocument(context, options) {
2442
2116
 
2443
2117
  let current = composedParentElement(element);
2444
2118
  let depth = 0;
2445
- while (current && depth < 8) {
2119
+ while (current && depth < 16) {
2446
2120
  pushCandidate(current, depth + 2);
2447
2121
  current = composedParentElement(current);
2448
2122
  depth += 1;
@@ -2518,9 +2192,13 @@ async function collectDomTargetsFromDocument(context, options) {
2518
2192
  (isHTMLElementNode(overlaySurface) ? overlaySurface : localSurface || selfSurface);
2519
2193
  const surfaceSelectors = surfaceSelectorsOf(element, localSurface || selfSurface);
2520
2194
  const structure = visualSeatGrid?.structure || inferStructuredCell(element, surface);
2521
- const stableLabel = visualSeatGrid?.label || stableLabelOf(element);
2195
+ const fallbackLabel = explicitLabelOf(element) || looseFieldLabelOf(element);
2196
+ const currentValue = popupCurrentValueOf(element);
2522
2197
  const role = inferRole(element);
2523
2198
  const surfaceKind = visualSeatGrid?.surfaceKind || surfaceKindOf(surface);
2199
+ const fallbackSurfaceLabel =
2200
+ (visualSeatGrid?.hintText ? 'Seat map' : undefined) ||
2201
+ surfaceFallbackLabelOf(surface, surfaceKind);
2524
2202
  const form = composedClosest(element, 'form');
2525
2203
  const testIdAttribute = element.hasAttribute('data-testid')
2526
2204
  ? 'data-testid'
@@ -2529,9 +2207,11 @@ async function collectDomTargetsFromDocument(context, options) {
2529
2207
  : undefined;
2530
2208
  return {
2531
2209
  kind: inferredKind,
2532
- label: visualSeatGrid?.label || labelOf(element) || stableLabel,
2210
+ label: undefined,
2211
+ fallbackLabel: visualSeatGrid?.label || fallbackLabel,
2533
2212
  interactionHint: visualSeatGrid?.interactionHint || (genericClickable ? 'click' : undefined),
2534
2213
  role,
2214
+ currentValue: currentValue || undefined,
2535
2215
  text: textOf(element),
2536
2216
  placeholder: element.getAttribute('placeholder')?.trim() || undefined,
2537
2217
  inputName: element.getAttribute('name')?.trim() || undefined,
@@ -2554,7 +2234,8 @@ async function collectDomTargetsFromDocument(context, options) {
2554
2234
  ? { ...(stateOf(element) || {}), ...(visualSeatGrid?.states || {}) }
2555
2235
  : undefined,
2556
2236
  surfaceKind,
2557
- surfaceLabel: landmarkLabelOf(surface),
2237
+ surfaceLabel: undefined,
2238
+ fallbackSurfaceLabel,
2558
2239
  surfaceSelector: surface ? buildSelector(surface) : undefined,
2559
2240
  surfaceSelectors,
2560
2241
  surfacePriority: surfacePriorityOf(surface),
@@ -2576,7 +2257,8 @@ async function collectDomTargetsFromDocument(context, options) {
2576
2257
  lane: laneOf(rect),
2577
2258
  band: bandOf(rect),
2578
2259
  },
2579
- hintText: visualSeatGrid?.hintText || contextualHintOf(element),
2260
+ hintText: undefined,
2261
+ fallbackHintText: visualSeatGrid?.hintText,
2580
2262
  visual: visualOf(element),
2581
2263
  },
2582
2264
  };
@@ -2727,188 +2409,6 @@ async function collectDomTargetsFromDocument(context, options) {
2727
2409
  pageSignature: options?.pageSignature,
2728
2410
  })));
2729
2411
  }
2730
- async function collectPageSignalsFromDocument(context, options) {
2731
- const inheritedFramePath = JSON.stringify(options?.framePath ?? []);
2732
- const inheritedFrameUrl = JSON.stringify(options?.frameUrl ?? '');
2733
- const observedSignals = await context.evaluate(String.raw `(() => {
2734
- const inheritedFramePath = ${inheritedFramePath};
2735
- const inheritedFrameUrl = ${inheritedFrameUrl};
2736
- const limit = ${DOM_SIGNAL_COLLECTION_LIMIT};
2737
- const interactiveSelector =
2738
- 'button, a[href], input:not([type="hidden"]), textarea, select, [role="button"], [role="link"], [role="textbox"], [role="combobox"], [role="option"], [role="gridcell"], [contenteditable="true"], [tabindex]:not([tabindex="-1"])';
2739
- const explicitSelector = [
2740
- '[role="alert"]',
2741
- '[role="status"]',
2742
- '[aria-live="assertive"]',
2743
- '[aria-live="polite"]',
2744
- '[role="dialog"]',
2745
- '[aria-modal="true"]',
2746
- '[class*="toast"]',
2747
- '[class*="snackbar"]',
2748
- '[class*="banner"]',
2749
- '[class*="notice"]',
2750
- '[class*="success"]',
2751
- '[class*="error"]',
2752
- '[class*="warning"]',
2753
- '[data-testid*="toast"]',
2754
- '[data-testid*="banner"]',
2755
- '[data-testid*="alert"]',
2756
- '[data-testid*="success"]',
2757
- '[data-testid*="error"]',
2758
- '[aria-busy="true"]',
2759
- '[role="progressbar"]',
2760
- 'button[disabled]',
2761
- ].join(', ');
2762
- const candidateSelector = 'h1, h2, h3, p, section, article, div, [role="region"]';
2763
- const outcomeTextRe =
2764
- /(?:thanks|thank you|success(?:ful|fully)?|receipt|order|confirmed|complete(?:d)?|declin(?:e|ed)|fail(?:ed|ure)|error|unable|try again|verification|verify|challenge|captcha|processing|pending|approved|denied)/i;
2765
-
2766
- const normalizeText = (value) => (value ?? '').replace(/\s+/g, ' ').trim();
2767
- const sampleText = (value, maxLength) => {
2768
- const normalized = normalizeText(value);
2769
- if (!normalized) {
2770
- return '';
2771
- }
2772
- if (normalized.length <= maxLength) {
2773
- return normalized;
2774
- }
2775
- return normalized.slice(0, maxLength - 1).trimEnd() + '…';
2776
- };
2777
- const isVisible = (element) => {
2778
- if (!(element instanceof HTMLElement)) {
2779
- return false;
2780
- }
2781
- const style = element.ownerDocument?.defaultView?.getComputedStyle(element);
2782
- if (!style || style.display === 'none' || style.visibility === 'hidden') {
2783
- return false;
2784
- }
2785
- const rect = element.getBoundingClientRect();
2786
- return rect.width > 0 && rect.height > 0;
2787
- };
2788
- const textOf = (element) => {
2789
- if (!(element instanceof HTMLElement)) {
2790
- return '';
2791
- }
2792
- return sampleText(element.innerText || element.textContent || '', 240);
2793
- };
2794
- const hasVisibleInteractiveDescendant = (element) => {
2795
- if (!(element instanceof HTMLElement)) {
2796
- return false;
2797
- }
2798
- return Array.from(element.querySelectorAll(interactiveSelector)).some(
2799
- (candidate) => candidate !== element && candidate instanceof HTMLElement && isVisible(candidate)
2800
- );
2801
- };
2802
- const hasNestedOutcomeCandidate = (element) => {
2803
- if (!(element instanceof HTMLElement)) {
2804
- return false;
2805
- }
2806
- return Array.from(
2807
- element.querySelectorAll(
2808
- 'h1, h2, h3, p, [role="alert"], [role="status"], [aria-live="assertive"], [aria-live="polite"]'
2809
- )
2810
- ).some((candidate) => {
2811
- if (!(candidate instanceof HTMLElement) || candidate === element || !isVisible(candidate)) {
2812
- return false;
2813
- }
2814
- return outcomeTextRe.test(normalizeText(candidate.innerText || candidate.textContent || ''));
2815
- });
2816
- };
2817
- const signalKindOf = (element, text) => {
2818
- const role = element.getAttribute('role') || '';
2819
- const ariaLive = element.getAttribute('aria-live') || '';
2820
- const classBlob =
2821
- ((element.getAttribute('class') || '') + ' ' + Object.values(element.dataset || {}).join(' ')).toLowerCase();
2822
-
2823
- if (role === 'dialog' || element.getAttribute('aria-modal') === 'true') {
2824
- return 'dialog';
2825
- }
2826
- if (role === 'alert' || ariaLive === 'assertive') {
2827
- return 'alert';
2828
- }
2829
- if (
2830
- role === 'status' ||
2831
- ariaLive === 'polite' ||
2832
- element.hasAttribute('aria-busy') ||
2833
- element.matches('button[disabled], [role="progressbar"]')
2834
- ) {
2835
- return 'status';
2836
- }
2837
- if (/toast|snackbar|banner|notice|warning|error|success/.test(classBlob)) {
2838
- return 'notice';
2839
- }
2840
- return 'notice';
2841
- };
2842
-
2843
- const seen = new Set();
2844
- const signals = [];
2845
- const pushSignal = (kind, text) => {
2846
- const normalized = sampleText(text, 240);
2847
- if (!normalized) {
2848
- return;
2849
- }
2850
- const key = kind + '|' + normalized.toLowerCase();
2851
- if (seen.has(key)) {
2852
- return;
2853
- }
2854
- seen.add(key);
2855
- signals.push({
2856
- kind,
2857
- text: normalized,
2858
- framePath: inheritedFramePath.length > 0 ? inheritedFramePath : undefined,
2859
- frameUrl: inheritedFrameUrl || undefined,
2860
- source: 'dom',
2861
- });
2862
- if (signals.length > limit) {
2863
- signals.length = limit;
2864
- }
2865
- };
2866
-
2867
- for (const element of Array.from(document.querySelectorAll(explicitSelector))) {
2868
- if (!(element instanceof HTMLElement) || !isVisible(element)) {
2869
- continue;
2870
- }
2871
- const text = textOf(element);
2872
- if (!text) {
2873
- continue;
2874
- }
2875
- pushSignal(signalKindOf(element, text), text);
2876
- if (signals.length >= limit) {
2877
- return signals;
2878
- }
2879
- }
2880
-
2881
- for (const element of Array.from(document.querySelectorAll(candidateSelector))) {
2882
- if (!(element instanceof HTMLElement) || !isVisible(element)) {
2883
- continue;
2884
- }
2885
- const text = textOf(element);
2886
- if (text.length < 12 || text.length > 240 || !outcomeTextRe.test(text)) {
2887
- continue;
2888
- }
2889
- if (hasVisibleInteractiveDescendant(element) || hasNestedOutcomeCandidate(element)) {
2890
- continue;
2891
- }
2892
- pushSignal(signalKindOf(element, text), text);
2893
- if (signals.length >= limit) {
2894
- break;
2895
- }
2896
- }
2897
-
2898
- return signals;
2899
- })()`);
2900
- if (!Array.isArray(observedSignals)) {
2901
- return [];
2902
- }
2903
- return observedSignals.filter((signal) => Boolean(signal &&
2904
- typeof signal === 'object' &&
2905
- typeof signal.kind === 'string' &&
2906
- typeof signal.text === 'string' &&
2907
- signal.text.length > 0)).map((signal) => applyInheritedSignalMetadata(signal, {
2908
- framePath: options?.framePath,
2909
- frameUrl: options?.frameUrl,
2910
- }));
2911
- }
2912
2412
  const FRAME_HOST_DESCRIPTOR_SCRIPT = String.raw `
2913
2413
  const ownerWindowOf = (node) => node?.ownerDocument?.defaultView || window;
2914
2414
  const isHTMLElementNode = (value) => {
@@ -3111,7 +2611,7 @@ const FRAME_HOST_DESCRIPTOR_SCRIPT = String.raw `
3111
2611
  const selector = isSelectorUniqueFor(element, structuralSelector) ? structuralSelector : null;
3112
2612
  return descriptorOf(selector);
3113
2613
  `;
3114
- async function readFrameHostDescriptor(frame) {
2614
+ export async function readFrameHostDescriptor(frame) {
3115
2615
  const frameElement = await frame.frameElement().catch(() => null);
3116
2616
  if (!frameElement) {
3117
2617
  return null;
@@ -3178,55 +2678,8 @@ export async function collectDomTargets(page, options) {
3178
2678
  await walk(page.mainFrame());
3179
2679
  return collected;
3180
2680
  }
3181
- export async function collectPageSignals(page) {
3182
- if (typeof page.mainFrame !== 'function') {
3183
- return collectPageSignalsFromDocument(page);
3184
- }
3185
- const collected = [];
3186
- const seen = new Set();
3187
- const pushSignals = (signals) => {
3188
- for (const signal of signals) {
3189
- const key = [signal.kind, signal.text.toLowerCase(), signal.framePath?.join('>') ?? 'top'].join('|');
3190
- if (seen.has(key)) {
3191
- continue;
3192
- }
3193
- seen.add(key);
3194
- collected.push(signal);
3195
- if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
3196
- break;
3197
- }
3198
- }
3199
- };
3200
- const walk = async (frame, framePath) => {
3201
- if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
3202
- return;
3203
- }
3204
- const frameUrl = frame.url().trim() || undefined;
3205
- const signals = await collectPageSignalsFromDocument(frame, {
3206
- framePath,
3207
- frameUrl,
3208
- }).catch(() => []);
3209
- pushSignals(signals);
3210
- if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
3211
- return;
3212
- }
3213
- for (const childFrame of frame.childFrames().slice(0, 20)) {
3214
- if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
3215
- break;
3216
- }
3217
- const frameHost = await readFrameHostDescriptor(childFrame);
3218
- if (!frameHost?.selector || !frameHost.userVisible) {
3219
- continue;
3220
- }
3221
- await walk(childFrame, [...(framePath ?? []), frameHost.selector]);
3222
- }
3223
- };
3224
- await walk(page.mainFrame());
3225
- return collected;
3226
- }
3227
2681
  export const __testDomTargetCollection = {
3228
2682
  collectDomTargetsFromDocument,
3229
- collectPageSignalsFromDocument,
3230
2683
  inferStructuredCellVariantFromEvidence,
3231
2684
  locatorDomSignatureScript: LOCATOR_DOM_SIGNATURE_SCRIPT,
3232
2685
  };