@nuanu-ai/agentbrowse 0.2.7 → 0.2.8

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 (191) hide show
  1. package/README.md +36 -8
  2. package/dist/agentpay-stagehand-llm.d.ts.map +1 -1
  3. package/dist/agentpay-stagehand-llm.js +5 -1
  4. package/dist/commands/act.d.ts +6 -2
  5. package/dist/commands/act.d.ts.map +1 -1
  6. package/dist/commands/act.js +840 -55
  7. package/dist/commands/act.test-harness.d.ts +19 -0
  8. package/dist/commands/act.test-harness.d.ts.map +1 -0
  9. package/dist/commands/act.test-harness.js +245 -0
  10. package/dist/commands/action-acceptance.d.ts +90 -0
  11. package/dist/commands/action-acceptance.d.ts.map +1 -0
  12. package/dist/commands/action-acceptance.js +1411 -0
  13. package/dist/commands/action-artifacts.d.ts +33 -0
  14. package/dist/commands/action-artifacts.d.ts.map +1 -0
  15. package/dist/commands/action-artifacts.js +104 -0
  16. package/dist/commands/action-execution-guards.d.ts +5 -0
  17. package/dist/commands/action-execution-guards.d.ts.map +1 -0
  18. package/dist/commands/action-execution-guards.js +3 -0
  19. package/dist/commands/action-executor-helpers.d.ts +21 -0
  20. package/dist/commands/action-executor-helpers.d.ts.map +1 -0
  21. package/dist/commands/action-executor-helpers.js +242 -0
  22. package/dist/commands/action-executor.d.ts +12 -0
  23. package/dist/commands/action-executor.d.ts.map +1 -0
  24. package/dist/commands/action-executor.js +45 -0
  25. package/dist/commands/action-fallbacks.d.ts +6 -0
  26. package/dist/commands/action-fallbacks.d.ts.map +1 -0
  27. package/dist/commands/action-fallbacks.js +43 -0
  28. package/dist/commands/action-value-projection.d.ts +32 -0
  29. package/dist/commands/action-value-projection.d.ts.map +1 -0
  30. package/dist/commands/action-value-projection.js +151 -0
  31. package/dist/commands/browse-actions.d.ts +4 -0
  32. package/dist/commands/browse-actions.d.ts.map +1 -0
  33. package/dist/commands/browse-actions.js +4 -0
  34. package/dist/commands/captcha-solve.d.ts.map +1 -1
  35. package/dist/commands/captcha-solve.js +13 -3
  36. package/dist/commands/click-action-executor.d.ts +10 -0
  37. package/dist/commands/click-action-executor.d.ts.map +1 -0
  38. package/dist/commands/click-action-executor.js +68 -0
  39. package/dist/commands/create-intent.d.ts +6 -0
  40. package/dist/commands/create-intent.d.ts.map +1 -0
  41. package/dist/commands/create-intent.js +75 -0
  42. package/dist/commands/datepicker-action-executor.d.ts +12 -0
  43. package/dist/commands/datepicker-action-executor.d.ts.map +1 -0
  44. package/dist/commands/datepicker-action-executor.js +218 -0
  45. package/dist/commands/descriptor-validation.d.ts +27 -0
  46. package/dist/commands/descriptor-validation.d.ts.map +1 -0
  47. package/dist/commands/descriptor-validation.js +333 -0
  48. package/dist/commands/extract-scope-resolution.d.ts +20 -0
  49. package/dist/commands/extract-scope-resolution.d.ts.map +1 -0
  50. package/dist/commands/extract-scope-resolution.js +100 -0
  51. package/dist/commands/extract-stagehand-executor.d.ts +17 -0
  52. package/dist/commands/extract-stagehand-executor.d.ts.map +1 -0
  53. package/dist/commands/extract-stagehand-executor.js +18 -0
  54. package/dist/commands/extract.d.ts +3 -2
  55. package/dist/commands/extract.d.ts.map +1 -1
  56. package/dist/commands/extract.js +256 -39
  57. package/dist/commands/fill-secret.d.ts +7 -0
  58. package/dist/commands/fill-secret.d.ts.map +1 -0
  59. package/dist/commands/fill-secret.js +371 -0
  60. package/dist/commands/get-secrets-catalog.d.ts +6 -0
  61. package/dist/commands/get-secrets-catalog.d.ts.map +1 -0
  62. package/dist/commands/get-secrets-catalog.js +23 -0
  63. package/dist/commands/launch.d.ts.map +1 -1
  64. package/dist/commands/launch.js +41 -7
  65. package/dist/commands/navigate.d.ts +2 -1
  66. package/dist/commands/navigate.d.ts.map +1 -1
  67. package/dist/commands/navigate.js +49 -12
  68. package/dist/commands/observe-inventory.d.ts +109 -0
  69. package/dist/commands/observe-inventory.d.ts.map +1 -0
  70. package/dist/commands/observe-inventory.js +2837 -0
  71. package/dist/commands/observe-persistence.d.ts +14 -0
  72. package/dist/commands/observe-persistence.d.ts.map +1 -0
  73. package/dist/commands/observe-persistence.js +170 -0
  74. package/dist/commands/observe-projection.d.ts +84 -0
  75. package/dist/commands/observe-projection.d.ts.map +1 -0
  76. package/dist/commands/observe-projection.js +140 -0
  77. package/dist/commands/observe-protected.d.ts +5 -0
  78. package/dist/commands/observe-protected.d.ts.map +1 -0
  79. package/dist/commands/observe-protected.js +18 -0
  80. package/dist/commands/observe-semantics.d.ts +10 -0
  81. package/dist/commands/observe-semantics.d.ts.map +1 -0
  82. package/dist/commands/observe-semantics.js +338 -0
  83. package/dist/commands/observe-stagehand.d.ts +48 -0
  84. package/dist/commands/observe-stagehand.d.ts.map +1 -0
  85. package/dist/commands/observe-stagehand.js +105 -0
  86. package/dist/commands/observe-surfaces.d.ts +9 -0
  87. package/dist/commands/observe-surfaces.d.ts.map +1 -0
  88. package/dist/commands/observe-surfaces.js +195 -0
  89. package/dist/commands/observe.d.ts +47 -1
  90. package/dist/commands/observe.d.ts.map +1 -1
  91. package/dist/commands/observe.js +173 -20
  92. package/dist/commands/observe.test-harness.d.ts +67 -0
  93. package/dist/commands/observe.test-harness.d.ts.map +1 -0
  94. package/dist/commands/observe.test-harness.js +107 -0
  95. package/dist/commands/poll-intent.d.ts +6 -0
  96. package/dist/commands/poll-intent.d.ts.map +1 -0
  97. package/dist/commands/poll-intent.js +57 -0
  98. package/dist/commands/screenshot.d.ts +2 -1
  99. package/dist/commands/screenshot.d.ts.map +1 -1
  100. package/dist/commands/screenshot.js +44 -12
  101. package/dist/commands/select-action-executor.d.ts +10 -0
  102. package/dist/commands/select-action-executor.d.ts.map +1 -0
  103. package/dist/commands/select-action-executor.js +91 -0
  104. package/dist/commands/semantic-observe.d.ts +24 -0
  105. package/dist/commands/semantic-observe.d.ts.map +1 -0
  106. package/dist/commands/semantic-observe.js +344 -0
  107. package/dist/commands/status.d.ts.map +1 -1
  108. package/dist/commands/status.js +75 -2
  109. package/dist/commands/structured-grid-action-executor.d.ts +3 -0
  110. package/dist/commands/structured-grid-action-executor.d.ts.map +1 -0
  111. package/dist/commands/structured-grid-action-executor.js +4 -0
  112. package/dist/commands/target-resolution.d.ts +4 -0
  113. package/dist/commands/target-resolution.d.ts.map +1 -0
  114. package/dist/commands/target-resolution.js +33 -0
  115. package/dist/commands/text-input-action-executor.d.ts +5 -0
  116. package/dist/commands/text-input-action-executor.d.ts.map +1 -0
  117. package/dist/commands/text-input-action-executor.js +116 -0
  118. package/dist/commands/user-actionable.d.ts +4 -0
  119. package/dist/commands/user-actionable.d.ts.map +1 -0
  120. package/dist/commands/user-actionable.js +95 -0
  121. package/dist/control-semantics.d.ts +29 -0
  122. package/dist/control-semantics.d.ts.map +1 -0
  123. package/dist/control-semantics.js +299 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +95 -32
  126. package/dist/output.d.ts +14 -2
  127. package/dist/output.d.ts.map +1 -1
  128. package/dist/output.js +17 -29
  129. package/dist/playwright-runtime.d.ts +35 -0
  130. package/dist/playwright-runtime.d.ts.map +1 -0
  131. package/dist/playwright-runtime.js +224 -0
  132. package/dist/runtime-resolution.d.ts +9 -0
  133. package/dist/runtime-resolution.d.ts.map +1 -0
  134. package/dist/runtime-resolution.js +19 -0
  135. package/dist/runtime-state.d.ts +217 -0
  136. package/dist/runtime-state.d.ts.map +1 -0
  137. package/dist/runtime-state.js +629 -0
  138. package/dist/secrets/backend.d.ts +32 -0
  139. package/dist/secrets/backend.d.ts.map +1 -0
  140. package/dist/secrets/backend.js +169 -0
  141. package/dist/secrets/catalog-applicability.d.ts +5 -0
  142. package/dist/secrets/catalog-applicability.d.ts.map +1 -0
  143. package/dist/secrets/catalog-applicability.js +59 -0
  144. package/dist/secrets/catalog-sync.d.ts +14 -0
  145. package/dist/secrets/catalog-sync.d.ts.map +1 -0
  146. package/dist/secrets/catalog-sync.js +35 -0
  147. package/dist/secrets/field-policy.d.ts +3 -0
  148. package/dist/secrets/field-policy.d.ts.map +1 -0
  149. package/dist/secrets/field-policy.js +3 -0
  150. package/dist/secrets/fill-ordering.d.ts +11 -0
  151. package/dist/secrets/fill-ordering.d.ts.map +1 -0
  152. package/dist/secrets/fill-ordering.js +44 -0
  153. package/dist/secrets/form-matcher.d.ts +60 -0
  154. package/dist/secrets/form-matcher.d.ts.map +1 -0
  155. package/dist/secrets/form-matcher.js +596 -0
  156. package/dist/secrets/intent-output.d.ts +11 -0
  157. package/dist/secrets/intent-output.d.ts.map +1 -0
  158. package/dist/secrets/intent-output.js +64 -0
  159. package/dist/secrets/mock-agentpay-backend.d.ts +13 -0
  160. package/dist/secrets/mock-agentpay-backend.d.ts.map +1 -0
  161. package/dist/secrets/mock-agentpay-backend.js +87 -0
  162. package/dist/secrets/mock-agentpay-cabinet.d.ts +43 -0
  163. package/dist/secrets/mock-agentpay-cabinet.d.ts.map +1 -0
  164. package/dist/secrets/mock-agentpay-cabinet.js +195 -0
  165. package/dist/secrets/protected-artifact-guard.d.ts +25 -0
  166. package/dist/secrets/protected-artifact-guard.d.ts.map +1 -0
  167. package/dist/secrets/protected-artifact-guard.js +26 -0
  168. package/dist/secrets/protected-bindings.d.ts +10 -0
  169. package/dist/secrets/protected-bindings.d.ts.map +1 -0
  170. package/dist/secrets/protected-bindings.js +17 -0
  171. package/dist/secrets/protected-field-values.d.ts +13 -0
  172. package/dist/secrets/protected-field-values.d.ts.map +1 -0
  173. package/dist/secrets/protected-field-values.js +100 -0
  174. package/dist/secrets/protected-fill.d.ts +47 -0
  175. package/dist/secrets/protected-fill.d.ts.map +1 -0
  176. package/dist/secrets/protected-fill.js +512 -0
  177. package/dist/secrets/types.d.ts +84 -0
  178. package/dist/secrets/types.d.ts.map +1 -0
  179. package/dist/secrets/types.js +27 -0
  180. package/dist/session.d.ts +22 -0
  181. package/dist/session.d.ts.map +1 -1
  182. package/dist/session.js +74 -2
  183. package/dist/solver/browser-launcher.d.ts.map +1 -1
  184. package/dist/solver/browser-launcher.js +6 -3
  185. package/dist/stagehand-runtime.d.ts +4 -0
  186. package/dist/stagehand-runtime.d.ts.map +1 -0
  187. package/dist/stagehand-runtime.js +10 -0
  188. package/dist/stagehand.d.ts +0 -5
  189. package/dist/stagehand.d.ts.map +1 -1
  190. package/dist/stagehand.js +0 -6
  191. package/package.json +5 -2
@@ -0,0 +1,2837 @@
1
+ import { inferAcceptancePolicyFromFacts, inferAllowedActionsFromFacts, inferAvailabilityFromFacts, inferControlFamilyFromFacts, } from '../control-semantics.js';
2
+ import { LOCATOR_DOM_SIGNATURE_SCRIPT, normalizePageSignature, readLocatorDomSignature, } from './descriptor-validation.js';
3
+ import { TRANSPARENT_ACTIONABLE_CONTROL_HELPER_SCRIPT } from './user-actionable.js';
4
+ const DOM_TARGET_COLLECTION_LIMIT = 320;
5
+ const DOM_SIGNAL_COLLECTION_LIMIT = 24;
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+ function normalizeInheritedFramePath(framePath) {
10
+ return Array.isArray(framePath) && framePath.length > 0 ? [...framePath] : undefined;
11
+ }
12
+ function normalizeInheritedFrameUrl(frameUrl) {
13
+ return typeof frameUrl === 'string' && frameUrl.trim().length > 0 ? frameUrl : undefined;
14
+ }
15
+ function normalizeInheritedPageSignature(pageSignature) {
16
+ return typeof pageSignature === 'string' && pageSignature.trim().length > 0
17
+ ? pageSignature
18
+ : undefined;
19
+ }
20
+ function applyInheritedDomTargetMetadata(target, options) {
21
+ const normalizedFramePath = normalizeInheritedFramePath(options?.framePath) ?? normalizeInheritedFramePath(target.framePath);
22
+ const normalizedFrameUrl = normalizeInheritedFrameUrl(options?.frameUrl) ?? target.frameUrl;
23
+ const normalizedPageSignature = normalizeInheritedPageSignature(options?.pageSignature) ?? target.pageSignature;
24
+ return {
25
+ ...target,
26
+ framePath: normalizedFramePath,
27
+ frameUrl: normalizedFrameUrl,
28
+ pageSignature: normalizedPageSignature,
29
+ };
30
+ }
31
+ function enrichObservedTargetSemantics(target) {
32
+ const facts = {
33
+ kind: target.kind,
34
+ role: target.role,
35
+ label: target.label,
36
+ interactionHint: target.interactionHint,
37
+ placeholder: target.placeholder,
38
+ inputName: target.inputName,
39
+ inputType: target.inputType,
40
+ autocomplete: target.autocomplete,
41
+ states: target.states,
42
+ };
43
+ const allowedActions = inferAllowedActionsFromFacts(facts);
44
+ const acceptancePolicy = inferAcceptancePolicyFromFacts(facts, allowedActions);
45
+ const controlFamily = inferControlFamilyFromFacts(facts, allowedActions);
46
+ const availability = inferAvailabilityFromFacts(facts.states, undefined, {
47
+ readonlyInteractive: controlFamily === 'select' ||
48
+ controlFamily === 'datepicker' ||
49
+ acceptancePolicy === 'selection' ||
50
+ acceptancePolicy === 'date-selection',
51
+ });
52
+ const capability = allowedActions.length > 0 ? 'actionable' : 'informational';
53
+ return {
54
+ ...target,
55
+ capability,
56
+ availability,
57
+ allowedActions,
58
+ acceptancePolicy,
59
+ };
60
+ }
61
+ function applyInheritedSignalMetadata(signal, options) {
62
+ const normalizedFramePath = normalizeInheritedFramePath(options?.framePath) ?? normalizeInheritedFramePath(signal.framePath);
63
+ const normalizedFrameUrl = normalizeInheritedFrameUrl(options?.frameUrl) ?? signal.frameUrl;
64
+ return {
65
+ ...signal,
66
+ framePath: normalizedFramePath,
67
+ frameUrl: normalizedFrameUrl,
68
+ };
69
+ }
70
+ function hasStagehandDomFacts(domFacts) {
71
+ return Boolean(domFacts &&
72
+ (domFacts.kind ||
73
+ domFacts.role ||
74
+ domFacts.placeholder ||
75
+ domFacts.value ||
76
+ domFacts.text ||
77
+ domFacts.currentValue ||
78
+ (domFacts.states && Object.keys(domFacts.states).length > 0)));
79
+ }
80
+ function scoreStagehandLocatorSnapshot(snapshot) {
81
+ let score = 0;
82
+ if (snapshot.domSignature) {
83
+ score += 1;
84
+ }
85
+ if (snapshot.domFacts?.kind) {
86
+ score += 1;
87
+ }
88
+ if (snapshot.domFacts?.placeholder) {
89
+ score += 1;
90
+ }
91
+ if (snapshot.domFacts?.value) {
92
+ score += 1;
93
+ }
94
+ if (snapshot.domFacts?.text) {
95
+ score += 1;
96
+ }
97
+ if (snapshot.domFacts?.currentValue) {
98
+ score += 2;
99
+ }
100
+ if (snapshot.domFacts?.role) {
101
+ score += 3;
102
+ }
103
+ if (snapshot.domFacts?.states && Object.keys(snapshot.domFacts.states).length > 0) {
104
+ score += 3;
105
+ }
106
+ return score;
107
+ }
108
+ async function readStagehandLocatorSnapshotOnce(locator) {
109
+ return {
110
+ domSignature: await readLocatorDomSignature(locator).catch(() => null),
111
+ domFacts: await readStagehandDomFacts(locator).catch(() => null),
112
+ };
113
+ }
114
+ export async function readStagehandLocatorSnapshot(locator) {
115
+ const target = locator.first();
116
+ let best = await readStagehandLocatorSnapshotOnce(target);
117
+ if (hasStagehandDomFacts(best.domFacts)) {
118
+ return best;
119
+ }
120
+ await target.waitFor({ state: 'attached', timeout: 1200 }).catch(() => undefined);
121
+ for (const delayMs of [0, 100, 250]) {
122
+ if (delayMs > 0) {
123
+ await sleep(delayMs);
124
+ }
125
+ const snapshot = await readStagehandLocatorSnapshotOnce(target);
126
+ if (scoreStagehandLocatorSnapshot(snapshot) > scoreStagehandLocatorSnapshot(best)) {
127
+ best = snapshot;
128
+ }
129
+ if (hasStagehandDomFacts(snapshot.domFacts)) {
130
+ return snapshot;
131
+ }
132
+ }
133
+ return best;
134
+ }
135
+ const STAGEHAND_DOM_FACTS_SCRIPT = String.raw `
136
+ if (!(element instanceof HTMLElement)) {
137
+ return null;
138
+ }
139
+
140
+ const normalizeText = (value) => (value ?? '').replace(/\s+/g, ' ').trim();
141
+ const isVisible = (candidate) => {
142
+ if (!(candidate instanceof HTMLElement)) {
143
+ return false;
144
+ }
145
+ const style = candidate.ownerDocument?.defaultView?.getComputedStyle(candidate);
146
+ if (!style || style.display === 'none' || style.visibility === 'hidden') {
147
+ return false;
148
+ }
149
+ const rect = candidate.getBoundingClientRect();
150
+ return rect.width > 0 && rect.height > 0;
151
+ };
152
+
153
+ const inputLike = element;
154
+ const disabledProperty =
155
+ typeof inputLike.disabled === 'boolean' ? inputLike.disabled : false;
156
+ const readonlyProperty =
157
+ 'readOnly' in inputLike && typeof inputLike.readOnly === 'boolean' ? inputLike.readOnly : false;
158
+ const disabled =
159
+ element.getAttribute('aria-disabled') === 'true' || Boolean(disabledProperty);
160
+ const readonly =
161
+ element.hasAttribute('readonly') ||
162
+ element.getAttribute('aria-readonly') === 'true' ||
163
+ Boolean(readonlyProperty);
164
+ const expandedValue = element.getAttribute('aria-expanded');
165
+ const selectedValue = element.getAttribute('aria-selected');
166
+ const checkedValue = element.getAttribute('aria-checked');
167
+ const pressedValue = element.getAttribute('aria-pressed');
168
+ const expanded =
169
+ expandedValue === 'true' ? true : expandedValue === 'false' ? false : undefined;
170
+ const selected =
171
+ selectedValue === 'true' ? true : selectedValue === 'false' ? false : undefined;
172
+ const checked =
173
+ checkedValue === 'true' ? true : checkedValue === 'false' ? false : undefined;
174
+ const pressed =
175
+ pressedValue === 'true' ? true : pressedValue === 'false' ? false : undefined;
176
+ const current =
177
+ element.getAttribute('aria-current') ??
178
+ (document.activeElement === element ? true : undefined);
179
+ const states = {};
180
+ const directValue =
181
+ 'value' in inputLike && typeof inputLike.value === 'string'
182
+ ? normalizeText(inputLike.value)
183
+ : undefined;
184
+ const directText = normalizeText(element.innerText || element.textContent || '');
185
+ let currentValue =
186
+ directValue ||
187
+ (element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea'
188
+ ? undefined
189
+ : directText || undefined);
190
+
191
+ const popupBacked =
192
+ element.getAttribute('role') === 'combobox' ||
193
+ element.hasAttribute('aria-haspopup') ||
194
+ element.hasAttribute('aria-controls') ||
195
+ readonly;
196
+ if (!currentValue && popupBacked && element.parentElement) {
197
+ const siblingValues = Array.from(element.parentElement.children)
198
+ .filter((candidate) => candidate !== element)
199
+ .filter((candidate) => isVisible(candidate))
200
+ .filter((candidate) => {
201
+ const tag = candidate.tagName.toLowerCase();
202
+ if (tag === 'label' || tag === 'legend') {
203
+ return false;
204
+ }
205
+ const role = candidate.getAttribute('role') || '';
206
+ return (
207
+ role === 'listbox' ||
208
+ role === 'button' ||
209
+ role === 'option' ||
210
+ candidate.hasAttribute('aria-selected') ||
211
+ candidate.hasAttribute('aria-current')
212
+ );
213
+ })
214
+ .map((candidate) => normalizeText(candidate.innerText || candidate.textContent || ''))
215
+ .filter(
216
+ (value, index, all) =>
217
+ Boolean(value) && value.length <= 40 && all.indexOf(value) === index
218
+ );
219
+ if (siblingValues.length === 1) {
220
+ currentValue = siblingValues[0];
221
+ }
222
+ }
223
+
224
+ if (expanded !== undefined) states.expanded = expanded;
225
+ if (selected !== undefined) states.selected = selected;
226
+ if (checked !== undefined) states.checked = checked;
227
+ if (pressed !== undefined) states.pressed = pressed;
228
+ if (current !== undefined) states.current = current;
229
+ if (readonly) states.readonly = true;
230
+ if (disabled) states.disabled = true;
231
+ if (element.getAttribute('aria-disabled') === 'true') states.ariaDisabled = true;
232
+
233
+ return {
234
+ kind: element.tagName.toLowerCase(),
235
+ role: element.getAttribute('role') || undefined,
236
+ placeholder: element.getAttribute('placeholder') || undefined,
237
+ inputName: element.getAttribute('name') || undefined,
238
+ inputType: element.getAttribute('type') || undefined,
239
+ autocomplete: element.getAttribute('autocomplete') || undefined,
240
+ value: directValue || undefined,
241
+ text: directText || undefined,
242
+ currentValue: currentValue || undefined,
243
+ states: Object.keys(states).length > 0 ? states : undefined,
244
+ };
245
+ `;
246
+ function readStagehandDomFactsInBrowser(element) {
247
+ return Function('element', STAGEHAND_DOM_FACTS_SCRIPT)(element);
248
+ }
249
+ async function readStagehandDomFacts(locator) {
250
+ return locator
251
+ .evaluate((element, source) => Function('element', source)(element), STAGEHAND_DOM_FACTS_SCRIPT)
252
+ .catch(() => null);
253
+ }
254
+ export function normalizeStagehandSelector(selector) {
255
+ if (!selector.startsWith('xpath=')) {
256
+ return { selector };
257
+ }
258
+ let remaining = selector.slice('xpath='.length).trim();
259
+ const framePath = [];
260
+ const frameBoundaryPattern = /^(.*?\/iframe\[\d+\])\/html\[1\]\/body\[1\](\/.*)$/;
261
+ while (true) {
262
+ const boundary = remaining.match(frameBoundaryPattern);
263
+ if (!boundary) {
264
+ break;
265
+ }
266
+ framePath.push(`xpath=${boundary[1]}`);
267
+ remaining = `/html[1]/body[1]${boundary[2]}`;
268
+ }
269
+ return framePath.length > 0
270
+ ? {
271
+ selector: `xpath=${remaining}`,
272
+ framePath,
273
+ }
274
+ : { selector };
275
+ }
276
+ async function collectDomTargetsFromDocument(context, options) {
277
+ const includeActivationAffordances = options?.includeActivationAffordances === true;
278
+ const inheritedFramePath = JSON.stringify(options?.framePath ?? []);
279
+ const inheritedFrameUrl = JSON.stringify(options?.frameUrl ?? '');
280
+ const inheritedPageSignature = JSON.stringify(options?.pageSignature ?? '');
281
+ const observedTargets = await context.evaluate(String.raw `(() => {
282
+ const includeActivationAffordances = ${includeActivationAffordances ? 'true' : 'false'};
283
+ const inheritedFramePath = ${inheritedFramePath};
284
+ const inheritedFrameUrl = ${inheritedFrameUrl};
285
+ const inheritedPageSignature = ${inheritedPageSignature};
286
+ const domSignatureOf = (element) => {
287
+ ${LOCATOR_DOM_SIGNATURE_SCRIPT}
288
+ };
289
+ const selector =
290
+ '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"])';
291
+ const contextSelector =
292
+ 'main, aside, section, form, article, nav, [role="dialog"], [role="listbox"], [role="menu"], [role="grid"], [role="region"], [role="tabpanel"]';
293
+ const collectionSelector =
294
+ 'ul, ol, table, tbody, [role="list"], [role="listbox"], [role="menu"], [role="grid"], [role="tablist"], [role="radiogroup"]';
295
+ const itemSelector =
296
+ 'li, tr, td, article, [role="option"], [role="listitem"], [role="row"], [role="gridcell"], [role="tab"], [role="menuitem"], [role="radio"]';
297
+ const headingSelector = 'h1, h2, h3, h4, h5, h6, [role="heading"], legend';
298
+ const collectorElementLimit = ${DOM_TARGET_COLLECTION_LIMIT};
299
+ const collectorOutputLimit = 180;
300
+
301
+ const cssEscape = (value) =>
302
+ typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
303
+ ? CSS.escape(value)
304
+ : value.replace(/["\\]/g, '\\$&');
305
+
306
+ const ownerWindowOf = (node) => node?.ownerDocument?.defaultView || window;
307
+ const isHTMLElementNode = (value) => {
308
+ const view = ownerWindowOf(value);
309
+ return Boolean(view && value instanceof view.HTMLElement);
310
+ };
311
+ const isHTMLInputNode = (value) => {
312
+ const view = ownerWindowOf(value);
313
+ return Boolean(view && value instanceof view.HTMLInputElement);
314
+ };
315
+ const isHTMLLabelNode = (value) => {
316
+ const view = ownerWindowOf(value);
317
+ return Boolean(view && value instanceof view.HTMLLabelElement);
318
+ };
319
+ const isHTMLTextAreaNode = (value) => {
320
+ const view = ownerWindowOf(value);
321
+ return Boolean(view && value instanceof view.HTMLTextAreaElement);
322
+ };
323
+ const isHTMLSelectNode = (value) => {
324
+ const view = ownerWindowOf(value);
325
+ return Boolean(view && value instanceof view.HTMLSelectElement);
326
+ };
327
+ const isHTMLIFrameNode = (value) => {
328
+ const view = ownerWindowOf(value);
329
+ return Boolean(view && value instanceof view.HTMLIFrameElement);
330
+ };
331
+ const isShadowRootNode = (value) => {
332
+ const view = ownerWindowOf(value);
333
+ return Boolean(view && value instanceof view.ShadowRoot);
334
+ };
335
+ const composedParentElement = (element) => {
336
+ if (!isHTMLElementNode(element)) return undefined;
337
+ if (element.parentElement) return element.parentElement;
338
+ const root = element.getRootNode?.();
339
+ if (isShadowRootNode(root) && isHTMLElementNode(root.host)) {
340
+ return root.host;
341
+ }
342
+ return undefined;
343
+ };
344
+ const composedClosest = (element, selectorValue) => {
345
+ let current = element;
346
+
347
+ while (isHTMLElementNode(current)) {
348
+ const direct = current.closest?.(selectorValue);
349
+ if (isHTMLElementNode(direct)) {
350
+ return direct;
351
+ }
352
+ current = composedParentElement(current);
353
+ }
354
+
355
+ return undefined;
356
+ };
357
+ const associatedLabelControlOf = (element) => {
358
+ if (!isHTMLLabelNode(element)) return undefined;
359
+
360
+ const directControl = element.control;
361
+ if (isHTMLElementNode(directControl)) {
362
+ return directControl;
363
+ }
364
+
365
+ const nestedControl = element.querySelector?.('input, select, textarea');
366
+ return isHTMLElementNode(nestedControl) ? nestedControl : undefined;
367
+ };
368
+ const labelBackedChoiceControlOf = (element) => {
369
+ const control = associatedLabelControlOf(element);
370
+ if (!isHTMLInputNode(control)) return undefined;
371
+
372
+ const type = (control.type || '').toLowerCase();
373
+ if (type === 'radio' || type === 'checkbox') {
374
+ return control;
375
+ }
376
+
377
+ return undefined;
378
+ };
379
+ const collectInteractiveElements = (
380
+ root,
381
+ limit = collectorElementLimit,
382
+ acc = [],
383
+ seen = new Set()
384
+ ) => {
385
+ if (!root?.querySelectorAll || acc.length >= limit) {
386
+ return acc;
387
+ }
388
+
389
+ const matches = Array.from(root.querySelectorAll(selector)).concat(
390
+ Array.from(root.querySelectorAll('label')).filter((candidate) =>
391
+ isHTMLElementNode(candidate) && Boolean(labelBackedChoiceControlOf(candidate))
392
+ )
393
+ );
394
+ for (const candidate of matches) {
395
+ if (acc.length >= limit) break;
396
+ if (!isHTMLElementNode(candidate) || seen.has(candidate)) continue;
397
+ seen.add(candidate);
398
+ acc.push(candidate);
399
+ }
400
+
401
+ const descendants = Array.from(root.querySelectorAll('*'));
402
+ for (const candidate of descendants) {
403
+ if (acc.length >= limit) break;
404
+ if (!isHTMLElementNode(candidate) || !candidate.shadowRoot) continue;
405
+ collectInteractiveElements(candidate.shadowRoot, limit, acc, seen);
406
+ }
407
+
408
+ return acc;
409
+ };
410
+
411
+ const isVisible = (element) => {
412
+ ${TRANSPARENT_ACTIONABLE_CONTROL_HELPER_SCRIPT}
413
+ const style = window.getComputedStyle(element);
414
+ if (
415
+ style.display === 'none' ||
416
+ style.visibility === 'hidden' ||
417
+ style.visibility === 'collapse'
418
+ ) {
419
+ return false;
420
+ }
421
+ const rect = element.getBoundingClientRect();
422
+ if (rect.width <= 0 || rect.height <= 0) {
423
+ return false;
424
+ }
425
+ if (style.opacity === '0') {
426
+ return isTransparentActionableControl(element);
427
+ }
428
+ return true;
429
+ };
430
+
431
+ const normalizeDescriptorText = (value) => (value || '').replace(/\s+/g, ' ').trim();
432
+
433
+ const imageAltTextOf = (element) => {
434
+ if (!isHTMLElementNode(element)) return undefined;
435
+
436
+ const seen = new Set();
437
+ const values = [];
438
+ const push = (value) => {
439
+ const normalized = normalizeDescriptorText(value);
440
+ if (!normalized || normalized.length < 2 || normalized.length > 80) {
441
+ return;
442
+ }
443
+ const key = normalized.toLowerCase();
444
+ if (seen.has(key)) return;
445
+ seen.add(key);
446
+ values.push(normalized);
447
+ };
448
+
449
+ if (element.tagName.toLowerCase() === 'img') {
450
+ push(element.getAttribute('alt'));
451
+ }
452
+
453
+ for (const candidate of Array.from(element.querySelectorAll('img[alt]')).slice(0, 6)) {
454
+ if (!isHTMLElementNode(candidate)) continue;
455
+ push(candidate.getAttribute('alt'));
456
+ }
457
+
458
+ return values.length > 0 ? values.join(' ') : undefined;
459
+ };
460
+
461
+ const textOf = (element) => {
462
+ const tag = element?.tagName?.toLowerCase?.();
463
+ if (tag && ['script', 'style', 'noscript', 'template'].includes(tag)) {
464
+ return undefined;
465
+ }
466
+ const buttonLikeText =
467
+ isHTMLInputNode(element) &&
468
+ ['button', 'submit', 'reset'].includes((element.getAttribute('type') || 'text').trim().toLowerCase())
469
+ ? normalizeDescriptorText(element.value || element.getAttribute('value') || '')
470
+ : undefined;
471
+ if (buttonLikeText) {
472
+ return buttonLikeText;
473
+ }
474
+ const textValue = normalizeDescriptorText(element?.innerText || element?.textContent || '');
475
+ const altValue = imageAltTextOf(element);
476
+ if (!textValue && !altValue) {
477
+ return undefined;
478
+ }
479
+ if (!textValue) {
480
+ return altValue;
481
+ }
482
+ if (!altValue || textValue.toLowerCase().includes(altValue.toLowerCase())) {
483
+ return textValue;
484
+ }
485
+ return (altValue + ' ' + textValue).trim();
486
+ };
487
+
488
+ const isMeaningfulLabel = (value) => {
489
+ const normalized = (value || '').replace(/\s+/g, ' ').trim();
490
+ return Boolean(normalized) && normalized !== '[object Object]';
491
+ };
492
+
493
+ const inputTypeOf = (element) => {
494
+ if (!isHTMLInputNode(element)) {
495
+ return '';
496
+ }
497
+ return (element.getAttribute('type') || 'text').trim().toLowerCase();
498
+ };
499
+
500
+ const isButtonLikeInput = (element) => {
501
+ return isHTMLInputNode(element) && ['button', 'submit', 'reset'].includes(inputTypeOf(element));
502
+ };
503
+
504
+ const LOOSE_ACTION_TEXT_RE =
505
+ /^(?:pay|buy|continue|submit|search|book|reserve|checkout|next|done|оплат|куп|продолж|дальше|поиск|заброни)/i;
506
+
507
+ const tokenizeSemanticText = (value) => {
508
+ const normalized = (value || '')
509
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
510
+ .replace(/[^a-zA-Z0-9\u0400-\u04FF]+/g, ' ')
511
+ .toLowerCase()
512
+ .trim();
513
+ if (!normalized) return [];
514
+ return normalized
515
+ .split(/\s+/)
516
+ .filter((token) => token.length >= 2 && token !== 'input' && token !== 'field');
517
+ };
518
+
519
+ const fieldSemanticTokensOf = (element) => {
520
+ const tokens = new Set();
521
+ const pushValue = (value) => {
522
+ for (const token of tokenizeSemanticText(value)) {
523
+ tokens.add(token);
524
+ }
525
+ };
526
+
527
+ const inputType = inputTypeOf(element);
528
+ const autocomplete = (element.getAttribute('autocomplete') || '').trim().toLowerCase();
529
+
530
+ if (isButtonLikeInput(element)) {
531
+ return tokens;
532
+ }
533
+
534
+ pushValue(element.getAttribute('placeholder'));
535
+ pushValue(element.getAttribute('name'));
536
+ pushValue(element.getAttribute('id'));
537
+ pushValue(autocomplete);
538
+ pushValue(inputType);
539
+
540
+ if (inputType === 'email' || autocomplete.includes('email')) {
541
+ pushValue('email mail e-mail');
542
+ }
543
+ if (
544
+ inputType === 'tel' ||
545
+ autocomplete.startsWith('tel') ||
546
+ autocomplete.includes('phone')
547
+ ) {
548
+ pushValue('phone telephone mobile tel номер телефон');
549
+ }
550
+ if (inputType === 'password' || autocomplete.includes('password')) {
551
+ pushValue('password pass пароль');
552
+ }
553
+ if (inputType === 'search' || autocomplete.includes('search')) {
554
+ pushValue('search find поиск найти');
555
+ }
556
+ if (
557
+ inputType === 'date' ||
558
+ autocomplete.includes('bday') ||
559
+ autocomplete.includes('birth') ||
560
+ autocomplete.includes('dob')
561
+ ) {
562
+ pushValue('date birth birthday dob дата рождения');
563
+ }
564
+
565
+ return tokens;
566
+ };
567
+
568
+ const isLooseFieldLabelCompatible = (element, candidateText) => {
569
+ const normalizedCandidate = normalizeDescriptorText(candidateText || '');
570
+ if (!normalizedCandidate) return false;
571
+ if (LOOSE_ACTION_TEXT_RE.test(normalizedCandidate)) {
572
+ return false;
573
+ }
574
+
575
+ const semanticTokens = fieldSemanticTokensOf(element);
576
+ if (semanticTokens.size === 0) {
577
+ return true;
578
+ }
579
+
580
+ const candidateTokens = tokenizeSemanticText(normalizedCandidate);
581
+ if (candidateTokens.length === 0) {
582
+ return false;
583
+ }
584
+
585
+ return candidateTokens.some((token) => semanticTokens.has(token));
586
+ };
587
+
588
+ const isFieldLikeControl = (element) => {
589
+ const tag = element.tagName.toLowerCase();
590
+ const explicitRole = (element.getAttribute('role') || '').trim().toLowerCase();
591
+ if (tag === 'input') {
592
+ return !isButtonLikeInput(element);
593
+ }
594
+ return (
595
+ tag === 'textarea' ||
596
+ tag === 'select' ||
597
+ explicitRole === 'textbox' ||
598
+ explicitRole === 'combobox' ||
599
+ explicitRole === 'searchbox' ||
600
+ explicitRole === 'spinbutton'
601
+ );
602
+ };
603
+
604
+ const precedesElementInDocument = (candidate, element) => {
605
+ return Boolean(candidate.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING);
606
+ };
607
+
608
+ const ariaLabelledbyTextOf = (element) => {
609
+ const labelledBy = element.getAttribute('aria-labelledby')?.trim();
610
+ if (!labelledBy) return undefined;
611
+
612
+ const text = labelledBy
613
+ .split(/\s+/)
614
+ .map((id) => textOf(document.getElementById(id)))
615
+ .filter(Boolean)
616
+ .join(' ')
617
+ .trim();
618
+
619
+ return isMeaningfulLabel(text) ? text : undefined;
620
+ };
621
+
622
+ const describedByTextOf = (element) => {
623
+ const describedBy = element.getAttribute('aria-describedby')?.trim();
624
+ if (!describedBy) return undefined;
625
+
626
+ const text = describedBy
627
+ .split(/\s+/)
628
+ .map((id) => textOf(document.getElementById(id)))
629
+ .filter(Boolean)
630
+ .join(' ')
631
+ .trim();
632
+
633
+ return isMeaningfulLabel(text) ? text : undefined;
634
+ };
635
+
636
+ const nearestFieldLabelOf = (element) => {
637
+ const restrictLooseCandidatesToPreceding = isFieldLikeControl(element);
638
+ const anchors = [
639
+ composedParentElement(element),
640
+ composedParentElement(composedParentElement(element)),
641
+ composedParentElement(composedParentElement(composedParentElement(element))),
642
+ ].filter(Boolean);
643
+
644
+ for (const anchor of anchors) {
645
+ if (!isHTMLElementNode(anchor)) continue;
646
+
647
+ const explicitLabels = Array.from(
648
+ anchor.querySelectorAll('label, legend, [class*="label"], [data-testid*="label"]')
649
+ ).filter((candidate) => {
650
+ return (
651
+ isHTMLElementNode(candidate) &&
652
+ candidate !== element &&
653
+ !candidate.contains(element) &&
654
+ !element.contains(candidate)
655
+ );
656
+ });
657
+
658
+ for (const candidate of explicitLabels) {
659
+ if (
660
+ restrictLooseCandidatesToPreceding &&
661
+ !candidate.matches('label, legend') &&
662
+ !precedesElementInDocument(candidate, element)
663
+ ) {
664
+ continue;
665
+ }
666
+ const candidateText = textOf(candidate);
667
+ if (isMeaningfulLabel(candidateText)) {
668
+ return candidateText;
669
+ }
670
+ }
671
+
672
+ for (const child of Array.from(anchor.children).slice(0, 8)) {
673
+ if (!isHTMLElementNode(child)) continue;
674
+ if (child === element || child.contains(element) || element.contains(child)) continue;
675
+ if (restrictLooseCandidatesToPreceding && !precedesElementInDocument(child, element)) {
676
+ continue;
677
+ }
678
+
679
+ const candidateText = textOf(child);
680
+ if (isMeaningfulLabel(candidateText)) {
681
+ return candidateText;
682
+ }
683
+ }
684
+ }
685
+
686
+ return undefined;
687
+ };
688
+
689
+ const labelOf = (element) => {
690
+ const ariaLabel = element.getAttribute('aria-label')?.trim();
691
+ if (isMeaningfulLabel(ariaLabel)) return ariaLabel;
692
+
693
+ const customLabel = element.getAttribute('label')?.trim();
694
+ if (isMeaningfulLabel(customLabel)) return customLabel;
695
+
696
+ const ariaLabelledbyText = ariaLabelledbyTextOf(element);
697
+ if (isMeaningfulLabel(ariaLabelledbyText)) return ariaLabelledbyText;
698
+
699
+ const labels = element.labels;
700
+ if (labels && labels.length > 0) {
701
+ const labelText = textOf(labels[0]);
702
+ if (isMeaningfulLabel(labelText)) return labelText;
703
+ }
704
+
705
+ const title = element.getAttribute('title')?.trim();
706
+ if (isMeaningfulLabel(title)) return title;
707
+
708
+ const skipNearestFieldLabel = isButtonLikeInput(element);
709
+ const preferFieldLabelBeforeVisibleText = isFieldLikeControl(element);
710
+ const text = textOf(element);
711
+ if (!preferFieldLabelBeforeVisibleText && isMeaningfulLabel(text)) return text;
712
+
713
+ const placeholder = element.getAttribute('placeholder')?.trim();
714
+ const nearestFieldLabel = nearestFieldLabelOf(element);
715
+ if (
716
+ !skipNearestFieldLabel &&
717
+ isMeaningfulLabel(nearestFieldLabel) &&
718
+ (!preferFieldLabelBeforeVisibleText ||
719
+ isLooseFieldLabelCompatible(element, nearestFieldLabel))
720
+ ) {
721
+ return nearestFieldLabel;
722
+ }
723
+
724
+ if (isMeaningfulLabel(placeholder)) return placeholder;
725
+
726
+ if (!skipNearestFieldLabel && isMeaningfulLabel(nearestFieldLabel)) return nearestFieldLabel;
727
+
728
+ if (isMeaningfulLabel(text)) return text;
729
+
730
+ const syntheticLabel = stableLabelOf(element);
731
+ if (isMeaningfulLabel(syntheticLabel)) return syntheticLabel;
732
+
733
+ return undefined;
734
+ };
735
+
736
+ const VALIDATION_TEXT_RE =
737
+ /(?:required|invalid|incorrect|too\s+(?:short|long)|must|error|format|please\s+(?:enter|select|choose|fill)|невер|ошиб|обязател|заполн|введите|укажите|выберите|долж|нужно|формат|цифр|символ)/i;
738
+
739
+ const syntheticLabelOf = (element) => {
740
+ const tag = element.tagName.toLowerCase();
741
+ const explicitRole = element.getAttribute('role')?.trim();
742
+ const hasStructuredAffordance =
743
+ element.hasAttribute('aria-haspopup') ||
744
+ element.hasAttribute('aria-controls') ||
745
+ element.hasAttribute('aria-expanded') ||
746
+ element.hasAttribute('aria-pressed') ||
747
+ element.hasAttribute('aria-selected') ||
748
+ element.hasAttribute('aria-describedby');
749
+ if (tag === 'input') {
750
+ const inputType = inputTypeOf(element);
751
+ if (isButtonLikeInput(element)) return 'Button';
752
+ if (inputType === 'tel') return 'Phone input';
753
+ if (inputType === 'email') return 'Email input';
754
+ if (inputType === 'password') return 'Password input';
755
+ if (inputType === 'search') return 'Search input';
756
+ if (inputType === 'date') return 'Date input';
757
+ return 'Text input';
758
+ }
759
+ if (tag === 'textarea') return 'Text area';
760
+ if (tag === 'select' || explicitRole === 'combobox') return 'Combobox';
761
+ if (explicitRole === 'textbox') return 'Text input';
762
+ if ((tag === 'button' || explicitRole === 'button') && hasStructuredAffordance) {
763
+ return 'Button';
764
+ }
765
+ if ((tag === 'a' || explicitRole === 'link') && hasStructuredAffordance) {
766
+ return 'Link';
767
+ }
768
+ if (explicitRole === 'option') return 'Option';
769
+ if (explicitRole === 'menuitem') return 'Menu item';
770
+ if (explicitRole === 'gridcell') return 'Grid cell';
771
+ return undefined;
772
+ };
773
+
774
+ const stableLabelOf = (element) => {
775
+ const ariaLabel = element.getAttribute('aria-label')?.trim();
776
+ if (isMeaningfulLabel(ariaLabel)) return ariaLabel;
777
+
778
+ const customLabel = element.getAttribute('label')?.trim();
779
+ if (isMeaningfulLabel(customLabel)) return customLabel;
780
+
781
+ const title = element.getAttribute('title')?.trim();
782
+ if (isMeaningfulLabel(title)) return title;
783
+
784
+ const ariaLabelledbyText = ariaLabelledbyTextOf(element);
785
+ if (isMeaningfulLabel(ariaLabelledbyText)) return ariaLabelledbyText;
786
+
787
+ const labels = element.labels;
788
+ if (labels && labels.length > 0) {
789
+ const labelText = textOf(labels[0]);
790
+ if (isMeaningfulLabel(labelText)) return labelText;
791
+ }
792
+
793
+ const text = textOf(element);
794
+ if (isMeaningfulLabel(text)) return text;
795
+
796
+ const skipNearestFieldLabel = isButtonLikeInput(element);
797
+ const nearestFieldLabel = nearestFieldLabelOf(element);
798
+ if (!skipNearestFieldLabel && isMeaningfulLabel(nearestFieldLabel)) return nearestFieldLabel;
799
+
800
+ return syntheticLabelOf(element);
801
+ };
802
+
803
+ const inferRole = (element) => {
804
+ const explicitRole = element.getAttribute('role')?.trim();
805
+ if (explicitRole) return explicitRole;
806
+
807
+ const labelBackedChoice = labelBackedChoiceControlOf(element);
808
+ if (isHTMLInputNode(labelBackedChoice)) {
809
+ const inputType = (labelBackedChoice.type || '').toLowerCase();
810
+ if (inputType === 'radio') return 'radio';
811
+ if (inputType === 'checkbox') return 'checkbox';
812
+ }
813
+
814
+ const tag = element.tagName.toLowerCase();
815
+ if (tag === 'button') return 'button';
816
+ if (tag === 'a' && element.getAttribute('href')) return 'link';
817
+ if (tag === 'select') return 'combobox';
818
+ if (tag === 'textarea') return 'textbox';
819
+ if (tag === 'input') {
820
+ const inputType = (element.getAttribute('type') || 'text').toLowerCase();
821
+ if (['button', 'submit', 'reset'].includes(inputType)) return 'button';
822
+ return 'textbox';
823
+ }
824
+ return undefined;
825
+ };
826
+
827
+ const kindOf = (element) => {
828
+ const tag = element.tagName.toLowerCase();
829
+ if (tag === 'input') return 'input';
830
+ if (tag === 'textarea') return 'textarea';
831
+ if (tag === 'select') return 'select';
832
+ if (tag === 'a') return 'link';
833
+ return inferRole(element) || tag;
834
+ };
835
+
836
+ const buildSelector = (element) => {
837
+ const testIdAttributeOf = (candidate) => {
838
+ if (candidate.hasAttribute('data-testid')) return 'data-testid';
839
+ if (candidate.hasAttribute('data-test-id')) return 'data-test-id';
840
+ return undefined;
841
+ };
842
+ const testIdSelectorOf = (candidate, value) => {
843
+ const attribute = testIdAttributeOf(candidate);
844
+ if (!attribute || !value) return undefined;
845
+ return '[' + attribute + '="' + cssEscape(value) + '"]';
846
+ };
847
+ const queryRootOf = (candidate) => {
848
+ const root = candidate.getRootNode?.();
849
+ return root && typeof root.querySelectorAll === 'function' ? root : document;
850
+ };
851
+
852
+ const isSelectorUniqueFor = (candidate, selectorValue) => {
853
+ const queryRoot = queryRootOf(candidate);
854
+ try {
855
+ const matches = Array.from(queryRoot.querySelectorAll(selectorValue));
856
+ return matches.length === 1 && matches[0] === candidate;
857
+ } catch {
858
+ return false;
859
+ }
860
+ };
861
+
862
+ if (element.id && isSelectorUniqueFor(element, '#' + cssEscape(element.id))) {
863
+ return '#' + cssEscape(element.id);
864
+ }
865
+
866
+ const testId =
867
+ element.getAttribute('data-testid')?.trim() || element.getAttribute('data-test-id')?.trim();
868
+ if (testId) {
869
+ const selectorValue = testIdSelectorOf(element, testId);
870
+ if (isSelectorUniqueFor(element, selectorValue)) {
871
+ return selectorValue;
872
+ }
873
+ }
874
+
875
+ const name = element.getAttribute('name')?.trim();
876
+ const tag = element.tagName.toLowerCase();
877
+ if (name) {
878
+ const selectorValue = tag + '[name="' + cssEscape(name) + '"]';
879
+ if (isSelectorUniqueFor(element, selectorValue)) {
880
+ return selectorValue;
881
+ }
882
+ }
883
+
884
+ const segmentOf = (current) => {
885
+ if (current.id && isSelectorUniqueFor(current, '#' + cssEscape(current.id))) {
886
+ return '#' + cssEscape(current.id);
887
+ }
888
+
889
+ const currentTestId =
890
+ current.getAttribute('data-testid')?.trim() ||
891
+ current.getAttribute('data-test-id')?.trim();
892
+ if (currentTestId) {
893
+ const selectorValue = testIdSelectorOf(current, currentTestId);
894
+ if (isSelectorUniqueFor(current, selectorValue)) {
895
+ return selectorValue;
896
+ }
897
+ }
898
+
899
+ const currentName = current.getAttribute('name')?.trim();
900
+ const currentTag = current.tagName.toLowerCase();
901
+ if (currentName) {
902
+ const selectorValue = currentTag + '[name="' + cssEscape(currentName) + '"]';
903
+ if (isSelectorUniqueFor(current, selectorValue)) {
904
+ return selectorValue;
905
+ }
906
+ }
907
+
908
+ const parent = current.parentElement;
909
+ const root = current.getRootNode?.();
910
+ const siblingPool = parent
911
+ ? Array.from(parent.children)
912
+ : isShadowRootNode(root)
913
+ ? Array.from(root.children)
914
+ : [];
915
+ const siblings = siblingPool.filter((child) => child.tagName.toLowerCase() === currentTag);
916
+ const index = siblings.indexOf(current) + 1;
917
+ return currentTag + ':nth-of-type(' + Math.max(index, 1) + ')';
918
+ };
919
+
920
+ const path = [];
921
+ let current = element;
922
+ while (current && current.nodeType === Node.ELEMENT_NODE && path.length < 8) {
923
+ path.unshift(segmentOf(current));
924
+ if (current.id) {
925
+ break;
926
+ }
927
+ current = current.parentElement;
928
+ }
929
+ if (path.length === 0) return undefined;
930
+
931
+ const structuralSelector = path.join(' > ');
932
+ return isSelectorUniqueFor(element, structuralSelector) ? structuralSelector : undefined;
933
+ };
934
+
935
+ const selectorFromRelation = (element, attribute) => {
936
+ const relation = element.getAttribute(attribute)?.trim();
937
+ if (!relation) return undefined;
938
+
939
+ for (const id of relation.split(/\s+/)) {
940
+ const related = document.getElementById(id);
941
+ if (!isHTMLElementNode(related)) continue;
942
+ const selector = buildSelector(related);
943
+ if (selector) return selector;
944
+ }
945
+
946
+ return undefined;
947
+ };
948
+
949
+ const laneOf = (rect) => {
950
+ const width = Math.max(window.innerWidth || 0, 1);
951
+ const center = rect.left + rect.width / 2;
952
+ if (center < width / 3) return 'left';
953
+ if (center < (width * 2) / 3) return 'center';
954
+ return 'right';
955
+ };
956
+
957
+ const bandOf = (rect) => {
958
+ const height = Math.max(window.innerHeight || 0, 1);
959
+ const center = rect.top + rect.height / 2;
960
+ if (center < height / 3) return 'top';
961
+ if (center < (height * 2) / 3) return 'middle';
962
+ return 'bottom';
963
+ };
964
+
965
+ const readBooleanState = (element, attribute) => {
966
+ const raw = element.getAttribute(attribute)?.trim();
967
+ if (raw === 'true') return true;
968
+ if (raw === 'false') return false;
969
+ return undefined;
970
+ };
971
+
972
+ const parseColor = (value) => {
973
+ if (!value || value === 'transparent') return null;
974
+
975
+ const rgbaMatch = value.match(/rgba?\(([^)]+)\)/i);
976
+ if (rgbaMatch) {
977
+ const [r = '0', g = '0', b = '0', a = '1'] = rgbaMatch[1].split(',').map((part) => part.trim());
978
+ return {
979
+ r: Number(r),
980
+ g: Number(g),
981
+ b: Number(b),
982
+ a: Number(a),
983
+ };
984
+ }
985
+
986
+ const hexMatch = value.match(/^#([0-9a-f]{3,8})$/i);
987
+ if (hexMatch) {
988
+ const hex = hexMatch[1];
989
+ if (hex.length === 3 || hex.length === 4) {
990
+ const [r, g, b, a = 'f'] = hex.split('');
991
+ return {
992
+ r: Number.parseInt(r + r, 16),
993
+ g: Number.parseInt(g + g, 16),
994
+ b: Number.parseInt(b + b, 16),
995
+ a: Number.parseInt(a + a, 16) / 255,
996
+ };
997
+ }
998
+ if (hex.length === 6 || hex.length === 8) {
999
+ return {
1000
+ r: Number.parseInt(hex.slice(0, 2), 16),
1001
+ g: Number.parseInt(hex.slice(2, 4), 16),
1002
+ b: Number.parseInt(hex.slice(4, 6), 16),
1003
+ a: hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1,
1004
+ };
1005
+ }
1006
+ }
1007
+
1008
+ return null;
1009
+ };
1010
+
1011
+ const luminanceOf = (color) => {
1012
+ if (!color || color.a <= 0) return null;
1013
+ return (0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b) / 255;
1014
+ };
1015
+
1016
+ const visualOf = (element) => {
1017
+ const style = window.getComputedStyle(element);
1018
+ const background = parseColor(style.backgroundColor);
1019
+ const border = parseColor(style.borderColor);
1020
+ const backgroundLuminance = luminanceOf(background);
1021
+ const borderVisible = Boolean(border && border.a > 0.2 && style.borderStyle !== 'none' && Number.parseFloat(style.borderWidth || '0') > 0);
1022
+
1023
+ let emphasis = 'normal';
1024
+ const opacity = Number.parseFloat(style.opacity || '1');
1025
+ const fontWeight = Number.parseInt(style.fontWeight || '400', 10);
1026
+ if (opacity < 0.7) {
1027
+ emphasis = 'muted';
1028
+ } else if (fontWeight >= 600) {
1029
+ emphasis = 'strong';
1030
+ }
1031
+
1032
+ let fill = 'none';
1033
+ if (background && background.a > 0.05 && backgroundLuminance !== null) {
1034
+ if (backgroundLuminance <= 0.35) {
1035
+ fill = 'dark';
1036
+ } else if (backgroundLuminance <= 0.75) {
1037
+ fill = 'mid';
1038
+ } else {
1039
+ fill = 'light';
1040
+ }
1041
+ }
1042
+
1043
+ return {
1044
+ emphasis,
1045
+ fill,
1046
+ outlined: borderVisible,
1047
+ };
1048
+ };
1049
+
1050
+ const landmarkLabelOf = (container) => {
1051
+ if (!container) return undefined;
1052
+
1053
+ const ariaLabel = container.getAttribute?.('aria-label')?.trim();
1054
+ if (ariaLabel) return ariaLabel;
1055
+
1056
+ const heading = container.querySelector?.(headingSelector);
1057
+ const headingText = textOf(heading);
1058
+ if (headingText) return headingText;
1059
+
1060
+ const text = textOf(container);
1061
+ if (text && text.length <= 140) return text;
1062
+ if (text) return text.slice(0, 140);
1063
+ return undefined;
1064
+ };
1065
+
1066
+ const isStructuredContainer = (element) => {
1067
+ if (!isHTMLElementNode(element)) return false;
1068
+
1069
+ const tag = element.tagName.toLowerCase();
1070
+ const role = element.getAttribute('role')?.trim() || '';
1071
+ if (
1072
+ ['label', 'li', 'article', 'tr', 'fieldset', 'section', 'aside', 'form'].includes(tag) ||
1073
+ ['option', 'listitem', 'row', 'group', 'dialog', 'region', 'tabpanel', 'listbox', 'menu', 'grid'].includes(role)
1074
+ ) {
1075
+ return true;
1076
+ }
1077
+
1078
+ const text = textOf(element);
1079
+ if (!text) return false;
1080
+
1081
+ const interactiveCount = element.querySelectorAll(selector).length;
1082
+ const hasHeading = Boolean(element.querySelector(headingSelector));
1083
+ return interactiveCount >= 2 && (hasHeading || text.length >= 40);
1084
+ };
1085
+
1086
+ const hasSemanticInteractiveAncestor = (element) => {
1087
+ let current = composedParentElement(element);
1088
+ while (current) {
1089
+ if (current.matches?.(selector)) {
1090
+ return true;
1091
+ }
1092
+ current = composedParentElement(current);
1093
+ }
1094
+ return false;
1095
+ };
1096
+
1097
+ const visibleInteractiveDescendantCountOf = (element) => {
1098
+ return Array.from(element.querySelectorAll(selector)).filter((candidate) => {
1099
+ return isHTMLElementNode(candidate) && isVisible(candidate);
1100
+ }).length;
1101
+ };
1102
+
1103
+ const clickableSemanticBlobOf = (element) => {
1104
+ return [
1105
+ element.getAttribute('class') || '',
1106
+ Object.values(element.dataset || {}).join(' '),
1107
+ element.getAttribute('data-testid') || '',
1108
+ element.getAttribute('data-test-id') || '',
1109
+ ]
1110
+ .join(' ')
1111
+ .toLowerCase();
1112
+ };
1113
+
1114
+ const viewportArea = () => {
1115
+ return Math.max(window.innerWidth || 0, 1) * Math.max(window.innerHeight || 0, 1);
1116
+ };
1117
+
1118
+ const isGenericClickableElement = (element) => {
1119
+ if (!isHTMLElementNode(element)) return false;
1120
+ if (element.matches?.(selector)) return false;
1121
+ if (hasSemanticInteractiveAncestor(element)) return false;
1122
+
1123
+ const tag = element.tagName.toLowerCase();
1124
+ const labelBackedChoice = labelBackedChoiceControlOf(element);
1125
+ if (['body', 'main', 'form'].includes(tag)) return false;
1126
+ if (tag === 'label' && !labelBackedChoice) return false;
1127
+
1128
+ const style = window.getComputedStyle(element);
1129
+ if (style.pointerEvents === 'none') return false;
1130
+
1131
+ const rect = element.getBoundingClientRect();
1132
+ const area = rect.width * rect.height;
1133
+ if (rect.width < 40 || rect.height < 20 || area < 1200) return false;
1134
+
1135
+ const explicitClick =
1136
+ element.hasAttribute('onclick') || typeof element.onclick === 'function';
1137
+ const cursorClick = style.cursor === 'pointer';
1138
+ if (!explicitClick && !cursorClick) return false;
1139
+
1140
+ const descriptorText = stableLabelOf(element) || textOf(element);
1141
+ if (!descriptorText) return false;
1142
+
1143
+ const interactiveDescendants = visibleInteractiveDescendantCountOf(element);
1144
+ if (interactiveDescendants > 8) return false;
1145
+ if (!explicitClick && area >= viewportArea() * 0.75 && interactiveDescendants >= 2) {
1146
+ return false;
1147
+ }
1148
+
1149
+ const structuredLike =
1150
+ isStructuredContainer(element) ||
1151
+ ['article', 'li'].includes(tag) ||
1152
+ /\b(card|item|result|fare|flight|ticket|offer|row)\b/.test(
1153
+ clickableSemanticBlobOf(element)
1154
+ );
1155
+
1156
+ if (!structuredLike && !explicitClick && !labelBackedChoice && descriptorText.length < 12) {
1157
+ return false;
1158
+ }
1159
+
1160
+ return true;
1161
+ };
1162
+
1163
+ const hasAcceptedGenericClickableAncestor = (element, accepted) => {
1164
+ return accepted.some((ancestor) => ancestor !== element && ancestor.contains(element));
1165
+ };
1166
+
1167
+ const collectGenericClickableElements = (
1168
+ root,
1169
+ limit = collectorElementLimit,
1170
+ acc = [],
1171
+ seen = new Set()
1172
+ ) => {
1173
+ if (!root?.querySelectorAll || acc.length >= limit) {
1174
+ return acc;
1175
+ }
1176
+
1177
+ const descendants = Array.from(root.querySelectorAll('*'));
1178
+ for (const candidate of descendants) {
1179
+ if (acc.length >= limit) break;
1180
+ if (!isHTMLElementNode(candidate) || seen.has(candidate)) continue;
1181
+ if (!isGenericClickableElement(candidate)) continue;
1182
+ if (hasAcceptedGenericClickableAncestor(candidate, acc)) continue;
1183
+ seen.add(candidate);
1184
+ acc.push(candidate);
1185
+ }
1186
+
1187
+ for (const candidate of descendants) {
1188
+ if (acc.length >= limit) break;
1189
+ if (!isHTMLElementNode(candidate) || !candidate.shadowRoot) continue;
1190
+ collectGenericClickableElements(candidate.shadowRoot, limit, acc, seen);
1191
+ }
1192
+
1193
+ return acc;
1194
+ };
1195
+
1196
+ const containerOf = (element) => {
1197
+ let current = element.parentElement;
1198
+ let depth = 0;
1199
+ while (current && depth < 6) {
1200
+ if (isStructuredContainer(current)) {
1201
+ return current;
1202
+ }
1203
+ current = current.parentElement;
1204
+ depth += 1;
1205
+ }
1206
+ return undefined;
1207
+ };
1208
+
1209
+ const containerTextOf = (container) => {
1210
+ const text = textOf(container);
1211
+ if (!text) return undefined;
1212
+ return text.length <= 240 ? text : text.slice(0, 240);
1213
+ };
1214
+
1215
+ const groupOf = (element) => {
1216
+ if (element.matches?.(collectionSelector)) {
1217
+ return element;
1218
+ }
1219
+
1220
+ return composedClosest(element, collectionSelector) || undefined;
1221
+ };
1222
+
1223
+ const descriptiveItemOf = (element) => {
1224
+ const selfText = textOf(element);
1225
+ let current = composedParentElement(element);
1226
+ let depth = 0;
1227
+
1228
+ while (current && depth < 5) {
1229
+ const currentText = textOf(current);
1230
+ if (currentText && currentText !== selfText) {
1231
+ const interactiveCount = current.querySelectorAll(selector).length;
1232
+ const hasSiblingText = Array.from(current.children).some((child) => {
1233
+ if (!isHTMLElementNode(child)) return false;
1234
+ if (child === element || child.contains(element) || element.contains(child)) return false;
1235
+ return Boolean(textOf(child));
1236
+ });
1237
+
1238
+ if (
1239
+ interactiveCount > 0 &&
1240
+ interactiveCount <= 4 &&
1241
+ currentText.length <= 180 &&
1242
+ (hasSiblingText || current.matches?.(itemSelector) || isStructuredContainer(current))
1243
+ ) {
1244
+ return current;
1245
+ }
1246
+ }
1247
+
1248
+ current = composedParentElement(current);
1249
+ depth += 1;
1250
+ }
1251
+
1252
+ return undefined;
1253
+ };
1254
+
1255
+ const itemOf = (element) => {
1256
+ const descriptiveItem = descriptiveItemOf(element);
1257
+ if (descriptiveItem) {
1258
+ return descriptiveItem;
1259
+ }
1260
+
1261
+ if (element.matches?.(itemSelector)) {
1262
+ const ancestorItem = composedClosest(composedParentElement(element), itemSelector);
1263
+ if (ancestorItem) {
1264
+ const selfText = textOf(element);
1265
+ const ancestorText = textOf(ancestorItem);
1266
+ if (
1267
+ ancestorText &&
1268
+ ancestorText !== selfText &&
1269
+ ancestorText.length > (selfText?.length || 0)
1270
+ ) {
1271
+ return ancestorItem;
1272
+ }
1273
+ }
1274
+
1275
+ return element;
1276
+ }
1277
+
1278
+ return composedClosest(element, itemSelector) || undefined;
1279
+ };
1280
+
1281
+ const normalizeText = (value) => (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
1282
+
1283
+ const contextualHintOf = (element) => {
1284
+ const excluded = new Set(
1285
+ [labelOf(element), textOf(element)]
1286
+ .map((value) => normalizeText(value))
1287
+ .filter(Boolean)
1288
+ );
1289
+
1290
+ const seen = new Set();
1291
+ const pushCandidate = (candidates, value) => {
1292
+ const text = (value || '').replace(/\s+/g, ' ').trim();
1293
+ const normalized = normalizeText(text);
1294
+ if (!normalized || excluded.has(normalized) || seen.has(normalized)) {
1295
+ return;
1296
+ }
1297
+ if (text.length < 4 || text.length > 120) {
1298
+ return;
1299
+ }
1300
+ seen.add(normalized);
1301
+ candidates.push(text);
1302
+ };
1303
+
1304
+ const describeNode = (node, candidates) => {
1305
+ if (!isHTMLElementNode(node)) return;
1306
+
1307
+ const heading = node.querySelector?.(headingSelector);
1308
+ if (isHTMLElementNode(heading) && heading !== element && !heading.contains(element)) {
1309
+ pushCandidate(candidates, textOf(heading));
1310
+ }
1311
+
1312
+ for (const child of Array.from(node.children).slice(0, 8)) {
1313
+ if (!isHTMLElementNode(child)) continue;
1314
+ if (child === element || child.contains(element) || element.contains(child)) continue;
1315
+ pushCandidate(candidates, textOf(child));
1316
+ }
1317
+
1318
+ pushCandidate(candidates, landmarkLabelOf(node));
1319
+ pushCandidate(candidates, textOf(node));
1320
+ };
1321
+
1322
+ const anchors = [
1323
+ itemOf(element),
1324
+ groupOf(element),
1325
+ containerOf(element),
1326
+ composedParentElement(element),
1327
+ composedParentElement(composedParentElement(element)),
1328
+ ].filter(Boolean);
1329
+
1330
+ for (const anchor of anchors) {
1331
+ const candidates = [];
1332
+ pushCandidate(candidates, describedByTextOf(element));
1333
+ describeNode(anchor, candidates);
1334
+ if (candidates.length > 0) {
1335
+ return candidates[0];
1336
+ }
1337
+ }
1338
+
1339
+ const describedBy = describedByTextOf(element);
1340
+ if (describedBy) {
1341
+ return describedBy;
1342
+ }
1343
+
1344
+ return undefined;
1345
+ };
1346
+
1347
+ const VALIDATION_CLASS_RE = /\b(?:error|invalid|warning|danger|alert|failed)\b/i;
1348
+ const validationFieldSelectors =
1349
+ 'input, textarea, select, [role="textbox"], [contenteditable="true"], [aria-invalid="true"]';
1350
+
1351
+ const relatedValidationMessagesOf = (element) => {
1352
+ if (!isHTMLElementNode(element)) {
1353
+ return [];
1354
+ }
1355
+
1356
+ const values = [];
1357
+ const pushMessage = (value) => {
1358
+ const text = normalizeDescriptorText(value || '');
1359
+ if (!text || !VALIDATION_TEXT_RE.test(text)) {
1360
+ return;
1361
+ }
1362
+ if (values.includes(text)) {
1363
+ return;
1364
+ }
1365
+ values.push(text.slice(0, 240));
1366
+ if (values.length > 4) {
1367
+ values.length = 4;
1368
+ }
1369
+ };
1370
+
1371
+ const describedBy = describedByTextOf(element);
1372
+ if (describedBy) {
1373
+ pushMessage(describedBy);
1374
+ }
1375
+
1376
+ let anchor = element.parentElement;
1377
+ for (let depth = 0; anchor && depth < 4 && values.length < 4; depth += 1, anchor = anchor.parentElement) {
1378
+ const anchorFieldCount = anchor.querySelectorAll(validationFieldSelectors).length;
1379
+ if (anchorFieldCount === 0 || anchorFieldCount > 3) {
1380
+ continue;
1381
+ }
1382
+
1383
+ for (const candidate of Array.from(anchor.children)) {
1384
+ if (!isHTMLElementNode(candidate)) {
1385
+ continue;
1386
+ }
1387
+ if (candidate === element || candidate.contains(element) || element.contains(candidate)) {
1388
+ continue;
1389
+ }
1390
+ if (!isVisible(candidate) || candidate.matches('label, legend')) {
1391
+ continue;
1392
+ }
1393
+ if (candidate.querySelector(validationFieldSelectors)) {
1394
+ continue;
1395
+ }
1396
+ pushMessage(candidate.textContent);
1397
+ }
1398
+ }
1399
+
1400
+ return values;
1401
+ };
1402
+
1403
+ const hasValidationStyling = (element) => {
1404
+ if (!isHTMLElementNode(element)) {
1405
+ return false;
1406
+ }
1407
+
1408
+ let node = element;
1409
+ for (let depth = 0; node && depth < 4; depth += 1, node = node.parentElement) {
1410
+ const classBlob =
1411
+ (node.getAttribute('class') || '') +
1412
+ ' ' +
1413
+ Object.values(node.dataset || {}).join(' ') +
1414
+ ' ' +
1415
+ (node.getAttribute('data-state') || '') +
1416
+ ' ' +
1417
+ (node.getAttribute('data-status') || '');
1418
+ if (VALIDATION_CLASS_RE.test(classBlob.toLowerCase())) {
1419
+ return true;
1420
+ }
1421
+ }
1422
+
1423
+ return false;
1424
+ };
1425
+
1426
+ const validationEvidenceOf = (element) => {
1427
+ if (!isHTMLElementNode(element)) {
1428
+ return undefined;
1429
+ }
1430
+
1431
+ const required =
1432
+ element.getAttribute('aria-required') === 'true' ||
1433
+ (isHTMLInputNode(element) || isHTMLTextAreaNode(element) || isHTMLSelectNode(element)
1434
+ ? element.required
1435
+ : false);
1436
+ const invalid =
1437
+ element.getAttribute('aria-invalid') === 'true' ||
1438
+ ((isHTMLInputNode(element) || isHTMLTextAreaNode(element) || isHTMLSelectNode(element)) &&
1439
+ !element.checkValidity());
1440
+ const messages = relatedValidationMessagesOf(element);
1441
+ const errorStyling = hasValidationStyling(element);
1442
+ const message = messages[0];
1443
+
1444
+ if (!required && !invalid && !errorStyling && !message) {
1445
+ return undefined;
1446
+ }
1447
+
1448
+ return {
1449
+ invalid: invalid || undefined,
1450
+ required: required || undefined,
1451
+ message,
1452
+ errorStyling: errorStyling || undefined,
1453
+ };
1454
+ };
1455
+
1456
+ const stateOf = (element) => {
1457
+ const states = {};
1458
+ const labelBackedChoice = labelBackedChoiceControlOf(element);
1459
+
1460
+ const ariaDisabled = readBooleanState(element, 'aria-disabled');
1461
+ if (ariaDisabled !== undefined) states.disabled = ariaDisabled;
1462
+
1463
+ if (element.matches?.(':disabled')) {
1464
+ states.disabled = true;
1465
+ }
1466
+
1467
+ const style = window.getComputedStyle(element);
1468
+ if (style.pointerEvents === 'none') {
1469
+ states.disabled = true;
1470
+ }
1471
+
1472
+ const ariaSelected = readBooleanState(element, 'aria-selected');
1473
+ if (ariaSelected !== undefined) states.selected = ariaSelected;
1474
+
1475
+ const ariaExpanded = readBooleanState(element, 'aria-expanded');
1476
+ if (ariaExpanded !== undefined) states.expanded = ariaExpanded;
1477
+
1478
+ const ariaPressed = readBooleanState(element, 'aria-pressed');
1479
+ if (ariaPressed !== undefined) states.pressed = ariaPressed;
1480
+
1481
+ const ariaBusy = readBooleanState(element, 'aria-busy');
1482
+ if (ariaBusy !== undefined) states.busy = ariaBusy;
1483
+
1484
+ const ariaReadonly = readBooleanState(element, 'aria-readonly');
1485
+ if (ariaReadonly !== undefined) states.readonly = ariaReadonly;
1486
+
1487
+ const ariaChecked = element.getAttribute('aria-checked')?.trim();
1488
+ if (ariaChecked === 'true') states.checked = true;
1489
+ else if (ariaChecked === 'false') states.checked = false;
1490
+ else if (ariaChecked === 'mixed') states.checked = 'mixed';
1491
+
1492
+ const ariaCurrent = element.getAttribute('aria-current')?.trim();
1493
+ if (ariaCurrent) {
1494
+ states.current = ariaCurrent === 'true' ? true : ariaCurrent;
1495
+ }
1496
+
1497
+ if (isHTMLSelectNode(element) && typeof element.selectedIndex === 'number' && element.selectedIndex > 0) {
1498
+ states.hasSelection = true;
1499
+ }
1500
+
1501
+ const className = (element.getAttribute('class') || '').toLowerCase();
1502
+ const dataset = Object.values(element.dataset || {})
1503
+ .join(' ')
1504
+ .toLowerCase();
1505
+ const semanticBlob =
1506
+ className +
1507
+ ' ' +
1508
+ dataset +
1509
+ ' ' +
1510
+ (element.getAttribute('data-state') || '').toLowerCase() +
1511
+ ' ' +
1512
+ (element.getAttribute('data-status') || '').toLowerCase() +
1513
+ ' ' +
1514
+ (element.getAttribute('aria-label') || '').toLowerCase();
1515
+
1516
+ if (/(?:selected|active|current)\b/.test(semanticBlob)) {
1517
+ if (states.selected === undefined) states.selected = true;
1518
+ if (states.current === undefined) states.current = true;
1519
+ }
1520
+ if (/(?:occupied|unavailable|sold|taken|reserved|booked)\b/.test(semanticBlob)) {
1521
+ states.occupied = true;
1522
+ states.selectable = false;
1523
+ states.disabled = true;
1524
+ }
1525
+ if (/(?:premium|extra-legroom|comfort|preferred)\b/.test(semanticBlob)) {
1526
+ states.premium = true;
1527
+ }
1528
+ if (states.selectable === undefined && states.disabled !== true && states.occupied !== true) {
1529
+ const role = inferRole(element);
1530
+ const tag = element.tagName.toLowerCase();
1531
+ if (role === 'gridcell' || tag === 'button') {
1532
+ states.selectable = true;
1533
+ }
1534
+ }
1535
+
1536
+ if (isHTMLInputNode(element)) {
1537
+ const type = (element.type || 'text').toLowerCase();
1538
+ if (type === 'checkbox' || type === 'radio') {
1539
+ states.checked = element.indeterminate ? 'mixed' : element.checked;
1540
+ }
1541
+ if (element.readOnly) {
1542
+ states.readonly = true;
1543
+ }
1544
+ }
1545
+
1546
+ if (isHTMLInputNode(labelBackedChoice)) {
1547
+ const type = (labelBackedChoice.type || 'text').toLowerCase();
1548
+ if (type === 'checkbox' || type === 'radio') {
1549
+ states.checked = labelBackedChoice.indeterminate ? 'mixed' : labelBackedChoice.checked;
1550
+ }
1551
+ if (labelBackedChoice.disabled) {
1552
+ states.disabled = true;
1553
+ }
1554
+ if (labelBackedChoice.readOnly) {
1555
+ states.readonly = true;
1556
+ }
1557
+ }
1558
+
1559
+ if (isHTMLTextAreaNode(element) && element.readOnly) {
1560
+ states.readonly = true;
1561
+ }
1562
+
1563
+ if (isHTMLSelectNode(element) && element.disabled) {
1564
+ states.disabled = true;
1565
+ }
1566
+
1567
+ return Object.keys(states).length > 0 ? states : undefined;
1568
+ };
1569
+
1570
+ const inferStructuredCell = (element, surface) => {
1571
+ if (!isHTMLElementNode(element)) return undefined;
1572
+
1573
+ const role = inferRole(element) || '';
1574
+ const className = (element.getAttribute('class') || '').toLowerCase();
1575
+ const surfaceKind = inferSurfaceKind(surface);
1576
+ const label = labelOf(element) || textOf(element) || '';
1577
+ const normalizedLabel = label.replace(/\s+/g, ' ').trim();
1578
+ const seatIdentityLike =
1579
+ /(?:\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(
1580
+ label
1581
+ );
1582
+ const seatLike =
1583
+ seatIdentityLike ||
1584
+ /seat|cabin|fare|row/.test(className) ||
1585
+ element.hasAttribute('data-row') ||
1586
+ element.hasAttribute('data-column') ||
1587
+ element.hasAttribute('data-seat');
1588
+ const structuredSurface = surfaceKind === 'grid' || surfaceKind === 'datepicker';
1589
+ const explicitDateCellMetadata =
1590
+ element.hasAttribute('data-day') ||
1591
+ element.hasAttribute('data-date') ||
1592
+ element.hasAttribute('aria-rowindex') ||
1593
+ element.hasAttribute('aria-colindex') ||
1594
+ Boolean(
1595
+ composedClosest(
1596
+ element,
1597
+ '[data-day], [data-date], [aria-rowindex], [aria-colindex]'
1598
+ )
1599
+ );
1600
+ const compactDateCellLabel =
1601
+ /^(?:\d{1,2}|january|february|march|april|may|june|july|august|september|october|november|december|январ|феврал|март|апрел|ма[йя]|июн|июл|август|сентябр|октябр|ноябр|декабр)$/i.test(
1602
+ normalizedLabel
1603
+ ) ||
1604
+ /^(?:\d{1,2}[./-]\d{1,2}(?:[./-]\d{2,4})?)$/.test(normalizedLabel);
1605
+ const dateLike =
1606
+ surfaceKind === 'datepicker' ||
1607
+ ((role === 'gridcell' || structuredSurface || explicitDateCellMetadata) &&
1608
+ compactDateCellLabel);
1609
+ const structuredCellRole = role === 'gridcell' || role === 'button';
1610
+
1611
+ if (!structuredCellRole || (!structuredSurface && !seatLike && !dateLike)) {
1612
+ return undefined;
1613
+ }
1614
+
1615
+ const row =
1616
+ element.getAttribute('aria-rowindex')?.trim() ||
1617
+ element.getAttribute('data-row')?.trim() ||
1618
+ composedClosest(element, '[aria-rowindex]')?.getAttribute?.('aria-rowindex')?.trim() ||
1619
+ label.match(/\b(\d{1,3})[a-z]\b/i)?.[1] ||
1620
+ undefined;
1621
+ const column =
1622
+ element.getAttribute('aria-colindex')?.trim() ||
1623
+ element.getAttribute('data-column')?.trim() ||
1624
+ label.match(/\b\d{1,3}([a-z])\b/i)?.[1]?.toUpperCase() ||
1625
+ undefined;
1626
+ const zone =
1627
+ element.getAttribute('data-zone')?.trim() ||
1628
+ composedClosest(element, '[data-zone]')?.getAttribute?.('data-zone')?.trim() ||
1629
+ contextNodeOf(groupOf(element))?.label ||
1630
+ contextNodeOf(containerOf(element))?.label ||
1631
+ undefined;
1632
+
1633
+ return {
1634
+ family: 'structured-grid',
1635
+ variant: dateLike ? 'date-cell' : seatLike ? 'seat-cell' : 'grid-cell',
1636
+ row,
1637
+ column,
1638
+ zone,
1639
+ cellLabel: label || undefined,
1640
+ };
1641
+ };
1642
+
1643
+ const inferSurfaceKind = (surface) => {
1644
+ if (!isHTMLElementNode(surface)) return undefined;
1645
+ const role = surface.getAttribute('role')?.trim();
1646
+ if (role === 'dialog') return 'dialog';
1647
+ if (role === 'listbox') return 'listbox';
1648
+ if (role === 'menu') return 'menu';
1649
+ if (role === 'grid') return 'grid';
1650
+ if (role === 'tabpanel') return 'tabpanel';
1651
+ if (isGenericClickableElement(surface) && isStructuredContainer(surface)) return 'card';
1652
+ const className = (surface.getAttribute('class') || '').toLowerCase();
1653
+ if (className.includes('calendar') || className.includes('datepicker')) return 'datepicker';
1654
+ if (className.includes('popover')) return 'popover';
1655
+ if (className.includes('dropdown')) return 'dropdown';
1656
+ const tag = surface.tagName.toLowerCase();
1657
+ if (tag === 'article') return 'card';
1658
+ if (tag === 'fieldset') return 'group';
1659
+ if (tag === 'li') return 'listitem';
1660
+ if (tag === 'section' || tag === 'form' || tag === 'aside') return tag;
1661
+ return role || surface.tagName.toLowerCase();
1662
+ };
1663
+
1664
+ const contextNodeOf = (element) => {
1665
+ if (!element) return undefined;
1666
+
1667
+ const kind = element.getAttribute?.('role')?.trim() || element.tagName?.toLowerCase?.();
1668
+ const label = landmarkLabelOf(element);
1669
+ const text = containerTextOf(element);
1670
+
1671
+ if (!kind && !label && !text) return undefined;
1672
+ return { kind: kind || undefined, label, text };
1673
+ };
1674
+
1675
+ const pageSignature =
1676
+ inheritedPageSignature ||
1677
+ (() => {
1678
+ try {
1679
+ const url = new URL(window.location.href);
1680
+ return url.origin + url.pathname;
1681
+ } catch {
1682
+ return window.location.href;
1683
+ }
1684
+ })();
1685
+
1686
+ const collectTargets = (doc) => {
1687
+ const seenElements = new Set();
1688
+ const overlaySurfaceSelector =
1689
+ '[role="dialog"], [aria-modal="true"], [role="listbox"], [role="menu"], [role="grid"], [role="tabpanel"], [class*="popover"], [class*="dropdown"], [class*="listbox"], [class*="calendar"], [class*="datepicker"]';
1690
+
1691
+ const compactText = (value) => (value || '').replace(/\s+/g, ' ').trim();
1692
+
1693
+ const surfacePriorityOf = (surface) => {
1694
+ if (!isHTMLElementNode(surface)) return 0;
1695
+ const role = surface.getAttribute('role')?.trim() || '';
1696
+ const className = (surface.getAttribute('class') || '').toLowerCase();
1697
+ if (role === 'dialog' || surface.getAttribute('aria-modal') === 'true') return 100;
1698
+ if (role === 'listbox' || role === 'menu') return 95;
1699
+ if (className.includes('calendar') || className.includes('datepicker')) return 90;
1700
+ if (className.includes('popover') || className.includes('dropdown')) return 85;
1701
+ if (role === 'grid' || role === 'tabpanel') return 80;
1702
+ if (isGenericClickableElement(surface) && isStructuredContainer(surface)) {
1703
+ const interactiveCount = visibleInteractiveDescendantCountOf(surface);
1704
+ return interactiveCount >= 1 && interactiveCount <= 6 ? 58 : 54;
1705
+ }
1706
+ const tag = surface.tagName.toLowerCase();
1707
+ const interactiveCount = surface.querySelectorAll(selector).length;
1708
+ if (tag === 'article' || tag === 'fieldset') return interactiveCount >= 2 ? 65 : 55;
1709
+ if (role === 'group' || role === 'region') return interactiveCount >= 2 ? 60 : 50;
1710
+ if (tag === 'li' || role === 'listitem' || role === 'row') return interactiveCount >= 2 ? 58 : 48;
1711
+ if (tag === 'section' || tag === 'form' || tag === 'aside') {
1712
+ return interactiveCount >= 2 && interactiveCount <= 8 ? 52 : 0;
1713
+ }
1714
+ return 0;
1715
+ };
1716
+
1717
+ const surfaceKindOf = inferSurfaceKind;
1718
+
1719
+ const visualSeatGridMeta = new WeakMap();
1720
+
1721
+ const isPotentialVisualGridSurface = (element) => {
1722
+ if (!isHTMLElementNode(element) || !isVisible(element)) {
1723
+ return false;
1724
+ }
1725
+
1726
+ const rect = element.getBoundingClientRect();
1727
+ if (rect.width < 220 || rect.height < 220) {
1728
+ return false;
1729
+ }
1730
+
1731
+ const role = element.getAttribute('role')?.trim() || '';
1732
+ const tag = element.tagName.toLowerCase();
1733
+ return (
1734
+ role === 'grid' ||
1735
+ ['article', 'section', 'main', 'form', 'aside', 'div'].includes(tag)
1736
+ );
1737
+ };
1738
+
1739
+ const isVisualGridSquareElement = (element) => {
1740
+ if (!isHTMLElementNode(element) || !isVisible(element)) {
1741
+ return false;
1742
+ }
1743
+ if (element.matches?.(selector)) {
1744
+ return false;
1745
+ }
1746
+
1747
+ const rect = element.getBoundingClientRect();
1748
+ if (rect.width < 24 || rect.width > 52 || rect.height < 24 || rect.height > 52) {
1749
+ return false;
1750
+ }
1751
+ if (Math.abs(rect.width - rect.height) > Math.max(8, Math.min(rect.width, rect.height) * 0.35)) {
1752
+ return false;
1753
+ }
1754
+ if (rect.width * rect.height < 576) {
1755
+ return false;
1756
+ }
1757
+
1758
+ return true;
1759
+ };
1760
+
1761
+ const visualSeatGridTokenKindOf = (element) => {
1762
+ const text = compactText(textOf(element));
1763
+ if (!text) {
1764
+ return undefined;
1765
+ }
1766
+ if (/^\d{1,3}$/.test(text)) {
1767
+ return 'row';
1768
+ }
1769
+ if (/^[a-z]$/i.test(text)) {
1770
+ return 'column';
1771
+ }
1772
+ return undefined;
1773
+ };
1774
+
1775
+ const visualSeatCellStateOf = (element) => {
1776
+ const style = window.getComputedStyle(element);
1777
+ const background = parseColor(style.backgroundColor);
1778
+ const hasFilledBackground = Boolean(background && background.a > 0.15);
1779
+ const glyphCount = element.querySelectorAll(
1780
+ 'svg, path, use, circle, line, polyline, polygon'
1781
+ ).length;
1782
+ const states = {};
1783
+
1784
+ if (glyphCount > 0 && !hasFilledBackground) {
1785
+ states.occupied = true;
1786
+ states.selectable = false;
1787
+ states.disabled = true;
1788
+ return states;
1789
+ }
1790
+
1791
+ states.selectable = true;
1792
+ if (glyphCount > 0 && hasFilledBackground) {
1793
+ states.selected = true;
1794
+ }
1795
+
1796
+ if (background && background.a > 0.15) {
1797
+ const warm = background.r > background.b + 40 && background.g > 60;
1798
+ if (warm) {
1799
+ states.premium = true;
1800
+ }
1801
+ }
1802
+
1803
+ return states;
1804
+ };
1805
+
1806
+ const isVisualSeatCellElement = (element) => {
1807
+ if (!isVisualGridSquareElement(element)) {
1808
+ return false;
1809
+ }
1810
+ if (hasSemanticInteractiveAncestor(element)) {
1811
+ return false;
1812
+ }
1813
+
1814
+ const text = compactText(textOf(element));
1815
+ if (text) {
1816
+ return false;
1817
+ }
1818
+
1819
+ const style = window.getComputedStyle(element);
1820
+ if (style.pointerEvents === 'none') {
1821
+ return false;
1822
+ }
1823
+
1824
+ const background = parseColor(style.backgroundColor);
1825
+ const border = parseColor(style.borderColor);
1826
+ const glyphCount = element.querySelectorAll(
1827
+ 'svg, path, use, circle, line, polyline, polygon'
1828
+ ).length;
1829
+ const borderVisible =
1830
+ Boolean(border && border.a > 0.2) &&
1831
+ style.borderStyle !== 'none' &&
1832
+ Number.parseFloat(style.borderWidth || '0') > 0;
1833
+ const backgroundVisible = Boolean(background && background.a > 0.15);
1834
+
1835
+ if (!backgroundVisible && !borderVisible && glyphCount === 0) {
1836
+ return false;
1837
+ }
1838
+ if (element.children.length > 3) {
1839
+ return false;
1840
+ }
1841
+
1842
+ return true;
1843
+ };
1844
+
1845
+ const visualSeatGridOwnerSurfaceOf = (surface) => {
1846
+ let preferredArticle = undefined;
1847
+ let preferredSection = undefined;
1848
+ let preferredFallback = undefined;
1849
+ let current = surface;
1850
+ let depth = 0;
1851
+ while (current && depth < 8) {
1852
+ if (!isHTMLElementNode(current) || !isVisible(current)) {
1853
+ current = current?.parentElement ?? null;
1854
+ depth += 1;
1855
+ continue;
1856
+ }
1857
+
1858
+ const tag = current.tagName.toLowerCase();
1859
+ if (tag === 'article' && !preferredArticle) {
1860
+ preferredArticle = current;
1861
+ } else if (tag === 'section' && !preferredSection) {
1862
+ preferredSection = current;
1863
+ } else if (!preferredFallback && ['main', 'form', 'aside'].includes(tag)) {
1864
+ preferredFallback = current;
1865
+ }
1866
+
1867
+ current = current.parentElement;
1868
+ depth += 1;
1869
+ }
1870
+
1871
+ return preferredArticle || preferredSection || preferredFallback || surface;
1872
+ };
1873
+
1874
+ const chooseNearestHeader = (headers, rect, axis) => {
1875
+ if (headers.length === 0) {
1876
+ return undefined;
1877
+ }
1878
+
1879
+ const centerX = rect.left + rect.width / 2;
1880
+ const centerY = rect.top + rect.height / 2;
1881
+ const ranked = headers
1882
+ .map((header) => {
1883
+ const headerCenterX = header.rect.left + header.rect.width / 2;
1884
+ const headerCenterY = header.rect.top + header.rect.height / 2;
1885
+ const primaryDistance =
1886
+ axis === 'column'
1887
+ ? Math.abs(headerCenterX - centerX)
1888
+ : Math.abs(headerCenterY - centerY);
1889
+ const secondaryDistance =
1890
+ axis === 'column'
1891
+ ? Math.max(0, rect.top - header.rect.bottom)
1892
+ : header.rect.left <= rect.left
1893
+ ? Math.max(0, rect.left - header.rect.right)
1894
+ : Math.max(0, header.rect.left - rect.right);
1895
+ return {
1896
+ header,
1897
+ primaryDistance,
1898
+ secondaryDistance,
1899
+ };
1900
+ })
1901
+ .sort((left, right) => {
1902
+ if (left.primaryDistance !== right.primaryDistance) {
1903
+ return left.primaryDistance - right.primaryDistance;
1904
+ }
1905
+ return left.secondaryDistance - right.secondaryDistance;
1906
+ });
1907
+
1908
+ return ranked[0]?.header;
1909
+ };
1910
+
1911
+ const analyzeVisualSeatGridSurface = (surface) => {
1912
+ if (!isPotentialVisualGridSurface(surface)) {
1913
+ return null;
1914
+ }
1915
+
1916
+ const squareNodes = Array.from(surface.querySelectorAll('*')).filter((candidate) =>
1917
+ isVisualGridSquareElement(candidate)
1918
+ );
1919
+ if (squareNodes.length < 16) {
1920
+ return null;
1921
+ }
1922
+
1923
+ const rowHeaders = [];
1924
+ const columnHeaders = [];
1925
+ const seatCells = [];
1926
+
1927
+ for (const candidate of squareNodes) {
1928
+ const rect = candidate.getBoundingClientRect();
1929
+ const tokenKind = visualSeatGridTokenKindOf(candidate);
1930
+ if (tokenKind === 'row') {
1931
+ rowHeaders.push({
1932
+ element: candidate,
1933
+ text: compactText(textOf(candidate)),
1934
+ rect,
1935
+ });
1936
+ continue;
1937
+ }
1938
+ if (tokenKind === 'column') {
1939
+ columnHeaders.push({
1940
+ element: candidate,
1941
+ text: compactText(textOf(candidate)).toUpperCase(),
1942
+ rect,
1943
+ });
1944
+ continue;
1945
+ }
1946
+ if (isVisualSeatCellElement(candidate)) {
1947
+ seatCells.push({
1948
+ element: candidate,
1949
+ rect,
1950
+ });
1951
+ }
1952
+ }
1953
+
1954
+ if (seatCells.length < 8 || rowHeaders.length < 4 || columnHeaders.length < 4) {
1955
+ return null;
1956
+ }
1957
+
1958
+ const topMostColumn = Math.min(...columnHeaders.map((header) => header.rect.top));
1959
+ const topColumnHeaders = columnHeaders
1960
+ .filter((header) => Math.abs(header.rect.top - topMostColumn) <= Math.max(14, header.rect.height))
1961
+ .sort((left, right) => left.rect.left - right.rect.left);
1962
+ if (topColumnHeaders.length < 4) {
1963
+ return null;
1964
+ }
1965
+
1966
+ const ownerSurface = visualSeatGridOwnerSurfaceOf(surface);
1967
+ const descriptors = [];
1968
+
1969
+ for (const seatCell of seatCells) {
1970
+ const rowHeaderCandidates = rowHeaders.filter((header) => {
1971
+ const headerCenterY = header.rect.top + header.rect.height / 2;
1972
+ const seatCenterY = seatCell.rect.top + seatCell.rect.height / 2;
1973
+ return Math.abs(headerCenterY - seatCenterY) <= Math.max(12, seatCell.rect.height);
1974
+ });
1975
+ const preferredRowHeaders = rowHeaderCandidates.filter(
1976
+ (header) => header.rect.right <= seatCell.rect.left + 4
1977
+ );
1978
+ const rowHeader =
1979
+ chooseNearestHeader(
1980
+ preferredRowHeaders.length > 0 ? preferredRowHeaders : rowHeaderCandidates,
1981
+ seatCell.rect,
1982
+ 'row'
1983
+ ) || undefined;
1984
+ const columnHeader = chooseNearestHeader(
1985
+ topColumnHeaders.filter((header) => header.rect.bottom <= seatCell.rect.top + 12),
1986
+ seatCell.rect,
1987
+ 'column'
1988
+ );
1989
+
1990
+ const row = rowHeader?.text;
1991
+ const column = columnHeader?.text;
1992
+ if (!row || !column) {
1993
+ continue;
1994
+ }
1995
+
1996
+ const label = row + column;
1997
+ const states = visualSeatCellStateOf(seatCell.element);
1998
+ descriptors.push({
1999
+ element: seatCell.element,
2000
+ meta: {
2001
+ kind: 'div',
2002
+ label,
2003
+ interactionHint: 'click',
2004
+ states,
2005
+ structure: {
2006
+ family: 'structured-grid',
2007
+ variant: 'seat-cell',
2008
+ row,
2009
+ column,
2010
+ zone: landmarkLabelOf(ownerSurface) || landmarkLabelOf(surface) || undefined,
2011
+ cellLabel: label,
2012
+ },
2013
+ surface: ownerSurface,
2014
+ surfaceKind: 'grid',
2015
+ hintText: 'Seat map',
2016
+ },
2017
+ });
2018
+ }
2019
+
2020
+ return descriptors.length >= 6 ? descriptors : null;
2021
+ };
2022
+
2023
+ const collectVisualSeatGridElements = (
2024
+ root,
2025
+ limit = collectorElementLimit,
2026
+ acc = [],
2027
+ seen = new Set()
2028
+ ) => {
2029
+ if (!root?.querySelectorAll || acc.length >= limit) {
2030
+ return acc;
2031
+ }
2032
+
2033
+ const squareNodes = Array.from(root.querySelectorAll('*')).filter((candidate) =>
2034
+ isVisualGridSquareElement(candidate)
2035
+ );
2036
+ if (squareNodes.length < 16) {
2037
+ return acc;
2038
+ }
2039
+
2040
+ const candidateSurfaces = new Map();
2041
+ for (const squareNode of squareNodes) {
2042
+ let current = composedParentElement(squareNode);
2043
+ let depth = 0;
2044
+ while (current && depth < 8) {
2045
+ if (isPotentialVisualGridSurface(current)) {
2046
+ candidateSurfaces.set(
2047
+ current,
2048
+ (candidateSurfaces.get(current) || 0) + 1
2049
+ );
2050
+ }
2051
+ current = composedParentElement(current);
2052
+ depth += 1;
2053
+ }
2054
+ }
2055
+
2056
+ const rankedSurfaces = [...candidateSurfaces.entries()]
2057
+ .filter(([, count]) => count >= 16)
2058
+ .sort((left, right) => right[1] - left[1]);
2059
+
2060
+ for (const [surface] of rankedSurfaces) {
2061
+ if (acc.length >= limit) {
2062
+ break;
2063
+ }
2064
+
2065
+ const descriptors = analyzeVisualSeatGridSurface(surface);
2066
+ if (!descriptors) {
2067
+ continue;
2068
+ }
2069
+
2070
+ for (const descriptor of descriptors) {
2071
+ if (acc.length >= limit) {
2072
+ break;
2073
+ }
2074
+ if (seen.has(descriptor.element)) {
2075
+ continue;
2076
+ }
2077
+ seen.add(descriptor.element);
2078
+ visualSeatGridMeta.set(descriptor.element, descriptor.meta);
2079
+ acc.push(descriptor.element);
2080
+ }
2081
+ }
2082
+
2083
+ return acc;
2084
+ };
2085
+
2086
+ const elements = collectInteractiveElements(doc, collectorElementLimit, [], seenElements)
2087
+ .concat(
2088
+ includeActivationAffordances
2089
+ ? collectGenericClickableElements(doc, collectorElementLimit, [], seenElements)
2090
+ : []
2091
+ )
2092
+ .concat(collectVisualSeatGridElements(doc, collectorElementLimit, [], seenElements))
2093
+ .filter((element) => isHTMLElementNode(element))
2094
+ .filter((element) => isVisible(element));
2095
+
2096
+ const localSurfaceCandidateOf = (element) => {
2097
+ const candidates = [itemOf(element), containerOf(element)].filter((candidate) => {
2098
+ return (
2099
+ isHTMLElementNode(candidate) &&
2100
+ candidate !== element &&
2101
+ !candidate.matches?.(overlaySurfaceSelector) &&
2102
+ isVisible(candidate) &&
2103
+ surfacePriorityOf(candidate) > 0
2104
+ );
2105
+ });
2106
+
2107
+ return candidates[0];
2108
+ };
2109
+
2110
+ const surfaceSelectorsOf = (element, localSurface) => {
2111
+ const selectors = [];
2112
+ let current = element.parentElement;
2113
+
2114
+ while (current) {
2115
+ if (current.matches?.(overlaySurfaceSelector)) {
2116
+ const selector = buildSelector(current);
2117
+ if (selector && !selectors.includes(selector)) {
2118
+ selectors.push(selector);
2119
+ }
2120
+ }
2121
+ current = current.parentElement;
2122
+ }
2123
+
2124
+ if (localSurface) {
2125
+ const selector = buildSelector(localSurface);
2126
+ if (selector && !selectors.includes(selector)) {
2127
+ selectors.push(selector);
2128
+ }
2129
+ }
2130
+
2131
+ return selectors.length > 0 ? selectors : undefined;
2132
+ };
2133
+
2134
+ const targets = elements.map((element, ordinal) => {
2135
+ const visualSeatGrid = visualSeatGridMeta.get(element);
2136
+ const labelBackedChoice = labelBackedChoiceControlOf(element);
2137
+ const genericClickable = isGenericClickableElement(element);
2138
+ const effectiveElement = element;
2139
+ const domSignature = domSignatureOf(effectiveElement);
2140
+ const genericCardLike =
2141
+ !labelBackedChoice &&
2142
+ genericClickable &&
2143
+ (isStructuredContainer(element) ||
2144
+ /\b(card|item|result|fare|flight|ticket|offer|row)\b/.test(
2145
+ clickableSemanticBlobOf(element)
2146
+ ));
2147
+ const inferredKind =
2148
+ visualSeatGrid?.kind ||
2149
+ (labelBackedChoice
2150
+ ? (labelBackedChoice.type || '').toLowerCase() === 'checkbox'
2151
+ ? 'checkbox'
2152
+ : 'radio'
2153
+ : genericCardLike
2154
+ ? 'card'
2155
+ : kindOf(element));
2156
+ const rect = element.getBoundingClientRect();
2157
+ const container = containerOf(element);
2158
+ const landmark = composedClosest(element, contextSelector);
2159
+ const group = groupOf(element);
2160
+ const item = itemOf(element);
2161
+ const overlaySurface = composedClosest(element, overlaySurfaceSelector);
2162
+ const localSurface = localSurfaceCandidateOf(element);
2163
+ const selfSurface =
2164
+ genericClickable && !labelBackedChoice && isStructuredContainer(element) ? element : undefined;
2165
+ const surface =
2166
+ visualSeatGrid?.surface ||
2167
+ (isHTMLElementNode(overlaySurface) ? overlaySurface : localSurface || selfSurface);
2168
+ const surfaceSelectors = surfaceSelectorsOf(element, localSurface || selfSurface);
2169
+ const structure = visualSeatGrid?.structure || inferStructuredCell(element, surface);
2170
+ const stableLabel = visualSeatGrid?.label || stableLabelOf(element);
2171
+ const role = inferRole(element);
2172
+ const surfaceKind = visualSeatGrid?.surfaceKind || surfaceKindOf(surface);
2173
+ const form = composedClosest(element, 'form');
2174
+ const testIdAttribute = element.hasAttribute('data-testid')
2175
+ ? 'data-testid'
2176
+ : element.hasAttribute('data-test-id')
2177
+ ? 'data-test-id'
2178
+ : undefined;
2179
+ return {
2180
+ kind: inferredKind,
2181
+ label: visualSeatGrid?.label || labelOf(element) || stableLabel,
2182
+ interactionHint: visualSeatGrid?.interactionHint || (genericClickable ? 'click' : undefined),
2183
+ role,
2184
+ text: textOf(element),
2185
+ placeholder: element.getAttribute('placeholder')?.trim() || undefined,
2186
+ inputName: element.getAttribute('name')?.trim() || undefined,
2187
+ inputType: element.getAttribute('type')?.trim() || undefined,
2188
+ autocomplete: element.getAttribute('autocomplete')?.trim() || undefined,
2189
+ validation: validationEvidenceOf(element),
2190
+ title: element.getAttribute('title')?.trim() || undefined,
2191
+ testId:
2192
+ element.getAttribute('data-testid')?.trim() ||
2193
+ element.getAttribute('data-test-id')?.trim() ||
2194
+ undefined,
2195
+ testIdAttribute,
2196
+ selector: buildSelector(element),
2197
+ framePath: inheritedFramePath.length > 0 ? inheritedFramePath : undefined,
2198
+ frameUrl: inheritedFrameUrl || undefined,
2199
+ pageSignature,
2200
+ domSignature,
2201
+ states:
2202
+ Object.keys({ ...(stateOf(element) || {}), ...(visualSeatGrid?.states || {}) }).length > 0
2203
+ ? { ...(stateOf(element) || {}), ...(visualSeatGrid?.states || {}) }
2204
+ : undefined,
2205
+ surfaceKind,
2206
+ surfaceLabel: landmarkLabelOf(surface),
2207
+ surfaceSelector: surface ? buildSelector(surface) : undefined,
2208
+ surfaceSelectors,
2209
+ surfacePriority: surfacePriorityOf(surface),
2210
+ controlsSurfaceSelector:
2211
+ selectorFromRelation(element, 'aria-controls') || selectorFromRelation(element, 'aria-owns'),
2212
+ formSelector: form ? buildSelector(form) : undefined,
2213
+ descendantInteractiveCount: element.querySelectorAll(selector).length,
2214
+ descendantEditableCount: element.querySelectorAll(
2215
+ 'input:not([type="hidden"]), textarea, select, [contenteditable="true"]'
2216
+ ).length,
2217
+ structure,
2218
+ ordinal,
2219
+ context: {
2220
+ item: contextNodeOf(item),
2221
+ group: contextNodeOf(group),
2222
+ container: contextNodeOf(container),
2223
+ landmark: contextNodeOf(landmark),
2224
+ layout: {
2225
+ lane: laneOf(rect),
2226
+ band: bandOf(rect),
2227
+ },
2228
+ hintText: visualSeatGrid?.hintText || contextualHintOf(element),
2229
+ visual: visualOf(element),
2230
+ },
2231
+ };
2232
+ });
2233
+
2234
+ return targets;
2235
+ };
2236
+
2237
+ const enrichDisplayLabels = (targets) => {
2238
+ const repeatedLabels = new Map();
2239
+
2240
+ for (const target of targets) {
2241
+ const label = (target.label || '').trim().toLowerCase();
2242
+ if (!label) continue;
2243
+ repeatedLabels.set(label, (repeatedLabels.get(label) || 0) + 1);
2244
+ }
2245
+
2246
+ const describeContext = (candidate) => {
2247
+ const item = candidate.context?.item;
2248
+ const group = candidate.context?.group;
2249
+ const container = candidate.context?.container;
2250
+ return (
2251
+ candidate.context?.hintText ||
2252
+ item?.label ||
2253
+ item?.text ||
2254
+ group?.label ||
2255
+ group?.text ||
2256
+ container?.label ||
2257
+ candidate.context?.hintText
2258
+ );
2259
+ };
2260
+
2261
+ return targets.map((target) => {
2262
+ const label = (target.label || '').trim();
2263
+ if (!label) return target;
2264
+
2265
+ const repeated = (repeatedLabels.get(label.toLowerCase()) || 0) > 1;
2266
+ if (!repeated) return target;
2267
+
2268
+ const detail = describeContext(target);
2269
+ if (!detail || detail === label) return target;
2270
+
2271
+ const compactDetail = detail.length <= 80 ? detail : detail.slice(0, 80);
2272
+ return {
2273
+ ...target,
2274
+ displayLabel: label + ' — ' + compactDetail,
2275
+ };
2276
+ });
2277
+ };
2278
+
2279
+ const hasUsefulInventorySignal = (candidate) => {
2280
+ if (candidate.label || candidate.text || candidate.placeholder || candidate.title) {
2281
+ return true;
2282
+ }
2283
+
2284
+ if (candidate.structure?.family) {
2285
+ return true;
2286
+ }
2287
+
2288
+ if (candidate.states && Object.keys(candidate.states).length > 0) {
2289
+ return true;
2290
+ }
2291
+
2292
+ const kind = (candidate.kind || '').toLowerCase();
2293
+ const role = (candidate.role || '').toLowerCase();
2294
+ if (['input', 'textarea', 'select'].includes(kind)) {
2295
+ return true;
2296
+ }
2297
+ if (['textbox', 'combobox', 'option', 'menuitem', 'gridcell'].includes(role)) {
2298
+ return true;
2299
+ }
2300
+ if (candidate.controlsSurfaceSelector) {
2301
+ return true;
2302
+ }
2303
+
2304
+ return false;
2305
+ };
2306
+
2307
+ const seen = new Set();
2308
+ return enrichDisplayLabels(collectTargets(document))
2309
+ .filter((candidate) => {
2310
+ const frameKey = candidate.framePath ? candidate.framePath.join(' -> ') : 'top';
2311
+ const key = frameKey + '|' + (candidate.selector || candidate.domSignature || '');
2312
+ if (!candidate.selector && !candidate.domSignature) return false;
2313
+ if (seen.has(key)) return false;
2314
+ seen.add(key);
2315
+ return hasUsefulInventorySignal(candidate);
2316
+ })
2317
+ .slice(0, collectorOutputLimit);
2318
+ })()`);
2319
+ if (!Array.isArray(observedTargets)) {
2320
+ return [];
2321
+ }
2322
+ return observedTargets
2323
+ .filter((target) => Boolean(target && typeof target === 'object' && typeof target.kind === 'string'))
2324
+ .map((target) => enrichObservedTargetSemantics(applyInheritedDomTargetMetadata(target, {
2325
+ framePath: options?.framePath,
2326
+ frameUrl: options?.frameUrl,
2327
+ pageSignature: options?.pageSignature,
2328
+ })));
2329
+ }
2330
+ async function collectPageSignalsFromDocument(context, options) {
2331
+ const inheritedFramePath = JSON.stringify(options?.framePath ?? []);
2332
+ const inheritedFrameUrl = JSON.stringify(options?.frameUrl ?? '');
2333
+ const observedSignals = await context.evaluate(String.raw `(() => {
2334
+ const inheritedFramePath = ${inheritedFramePath};
2335
+ const inheritedFrameUrl = ${inheritedFrameUrl};
2336
+ const limit = ${DOM_SIGNAL_COLLECTION_LIMIT};
2337
+ const interactiveSelector =
2338
+ '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"])';
2339
+ const explicitSelector = [
2340
+ '[role="alert"]',
2341
+ '[role="status"]',
2342
+ '[aria-live="assertive"]',
2343
+ '[aria-live="polite"]',
2344
+ '[role="dialog"]',
2345
+ '[aria-modal="true"]',
2346
+ '[class*="toast"]',
2347
+ '[class*="snackbar"]',
2348
+ '[class*="banner"]',
2349
+ '[class*="notice"]',
2350
+ '[class*="success"]',
2351
+ '[class*="error"]',
2352
+ '[class*="warning"]',
2353
+ '[data-testid*="toast"]',
2354
+ '[data-testid*="banner"]',
2355
+ '[data-testid*="alert"]',
2356
+ '[data-testid*="success"]',
2357
+ '[data-testid*="error"]',
2358
+ '[aria-busy="true"]',
2359
+ '[role="progressbar"]',
2360
+ 'button[disabled]',
2361
+ ].join(', ');
2362
+ const candidateSelector = 'h1, h2, h3, p, section, article, div, [role="region"]';
2363
+ const outcomeTextRe =
2364
+ /(?: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;
2365
+
2366
+ const normalizeText = (value) => (value ?? '').replace(/\s+/g, ' ').trim();
2367
+ const sampleText = (value, maxLength) => {
2368
+ const normalized = normalizeText(value);
2369
+ if (!normalized) {
2370
+ return '';
2371
+ }
2372
+ if (normalized.length <= maxLength) {
2373
+ return normalized;
2374
+ }
2375
+ return normalized.slice(0, maxLength - 1).trimEnd() + '…';
2376
+ };
2377
+ const isVisible = (element) => {
2378
+ if (!(element instanceof HTMLElement)) {
2379
+ return false;
2380
+ }
2381
+ const style = element.ownerDocument?.defaultView?.getComputedStyle(element);
2382
+ if (!style || style.display === 'none' || style.visibility === 'hidden') {
2383
+ return false;
2384
+ }
2385
+ const rect = element.getBoundingClientRect();
2386
+ return rect.width > 0 && rect.height > 0;
2387
+ };
2388
+ const textOf = (element) => {
2389
+ if (!(element instanceof HTMLElement)) {
2390
+ return '';
2391
+ }
2392
+ return sampleText(element.innerText || element.textContent || '', 240);
2393
+ };
2394
+ const hasVisibleInteractiveDescendant = (element) => {
2395
+ if (!(element instanceof HTMLElement)) {
2396
+ return false;
2397
+ }
2398
+ return Array.from(element.querySelectorAll(interactiveSelector)).some(
2399
+ (candidate) => candidate !== element && candidate instanceof HTMLElement && isVisible(candidate)
2400
+ );
2401
+ };
2402
+ const hasNestedOutcomeCandidate = (element) => {
2403
+ if (!(element instanceof HTMLElement)) {
2404
+ return false;
2405
+ }
2406
+ return Array.from(
2407
+ element.querySelectorAll(
2408
+ 'h1, h2, h3, p, [role="alert"], [role="status"], [aria-live="assertive"], [aria-live="polite"]'
2409
+ )
2410
+ ).some((candidate) => {
2411
+ if (!(candidate instanceof HTMLElement) || candidate === element || !isVisible(candidate)) {
2412
+ return false;
2413
+ }
2414
+ return outcomeTextRe.test(normalizeText(candidate.innerText || candidate.textContent || ''));
2415
+ });
2416
+ };
2417
+ const signalKindOf = (element, text) => {
2418
+ const role = element.getAttribute('role') || '';
2419
+ const ariaLive = element.getAttribute('aria-live') || '';
2420
+ const classBlob =
2421
+ ((element.getAttribute('class') || '') + ' ' + Object.values(element.dataset || {}).join(' ')).toLowerCase();
2422
+
2423
+ if (role === 'dialog' || element.getAttribute('aria-modal') === 'true') {
2424
+ return 'dialog';
2425
+ }
2426
+ if (role === 'alert' || ariaLive === 'assertive') {
2427
+ return 'alert';
2428
+ }
2429
+ if (
2430
+ role === 'status' ||
2431
+ ariaLive === 'polite' ||
2432
+ element.hasAttribute('aria-busy') ||
2433
+ element.matches('button[disabled], [role="progressbar"]')
2434
+ ) {
2435
+ return 'status';
2436
+ }
2437
+ if (/toast|snackbar|banner|notice|warning|error|success/.test(classBlob)) {
2438
+ return outcomeTextRe.test(text) ? 'outcome' : 'notice';
2439
+ }
2440
+ return outcomeTextRe.test(text) ? 'outcome' : 'notice';
2441
+ };
2442
+
2443
+ const seen = new Set();
2444
+ const signals = [];
2445
+ const pushSignal = (kind, text) => {
2446
+ const normalized = sampleText(text, 240);
2447
+ if (!normalized) {
2448
+ return;
2449
+ }
2450
+ const key = kind + '|' + normalized.toLowerCase();
2451
+ if (seen.has(key)) {
2452
+ return;
2453
+ }
2454
+ seen.add(key);
2455
+ signals.push({
2456
+ kind,
2457
+ text: normalized,
2458
+ framePath: inheritedFramePath.length > 0 ? inheritedFramePath : undefined,
2459
+ frameUrl: inheritedFrameUrl || undefined,
2460
+ source: 'dom',
2461
+ });
2462
+ if (signals.length > limit) {
2463
+ signals.length = limit;
2464
+ }
2465
+ };
2466
+
2467
+ for (const element of Array.from(document.querySelectorAll(explicitSelector))) {
2468
+ if (!(element instanceof HTMLElement) || !isVisible(element)) {
2469
+ continue;
2470
+ }
2471
+ const text = textOf(element);
2472
+ if (!text) {
2473
+ continue;
2474
+ }
2475
+ pushSignal(signalKindOf(element, text), text);
2476
+ if (signals.length >= limit) {
2477
+ return signals;
2478
+ }
2479
+ }
2480
+
2481
+ for (const element of Array.from(document.querySelectorAll(candidateSelector))) {
2482
+ if (!(element instanceof HTMLElement) || !isVisible(element)) {
2483
+ continue;
2484
+ }
2485
+ const text = textOf(element);
2486
+ if (text.length < 12 || text.length > 240 || !outcomeTextRe.test(text)) {
2487
+ continue;
2488
+ }
2489
+ if (hasVisibleInteractiveDescendant(element) || hasNestedOutcomeCandidate(element)) {
2490
+ continue;
2491
+ }
2492
+ pushSignal(signalKindOf(element, text), text);
2493
+ if (signals.length >= limit) {
2494
+ break;
2495
+ }
2496
+ }
2497
+
2498
+ return signals;
2499
+ })()`);
2500
+ if (!Array.isArray(observedSignals)) {
2501
+ return [];
2502
+ }
2503
+ return observedSignals.filter((signal) => Boolean(signal &&
2504
+ typeof signal === 'object' &&
2505
+ typeof signal.kind === 'string' &&
2506
+ typeof signal.text === 'string' &&
2507
+ signal.text.length > 0)).map((signal) => applyInheritedSignalMetadata(signal, {
2508
+ framePath: options?.framePath,
2509
+ frameUrl: options?.frameUrl,
2510
+ }));
2511
+ }
2512
+ const FRAME_HOST_DESCRIPTOR_SCRIPT = String.raw `
2513
+ const ownerWindowOf = (node) => node?.ownerDocument?.defaultView || window;
2514
+ const isHTMLElementNode = (value) => {
2515
+ const view = ownerWindowOf(value);
2516
+ return Boolean(view && value instanceof view.HTMLElement);
2517
+ };
2518
+ const isShadowRootNode = (value) => {
2519
+ const view = ownerWindowOf(value);
2520
+ return Boolean(view && value instanceof view.ShadowRoot);
2521
+ };
2522
+ const composedParentElement = (element) => {
2523
+ if (!isHTMLElementNode(element)) return undefined;
2524
+ if (element.parentElement) return element.parentElement;
2525
+ const root = element.getRootNode?.();
2526
+ if (isShadowRootNode(root) && isHTMLElementNode(root.host)) {
2527
+ return root.host;
2528
+ }
2529
+ return undefined;
2530
+ };
2531
+ const cssEscape = (value) =>
2532
+ typeof CSS !== 'undefined' && typeof CSS.escape === 'function'
2533
+ ? CSS.escape(value)
2534
+ : String(value || '').replace(/["\\]/g, '\\$&');
2535
+ const queryRootOf = (candidate) => {
2536
+ const root = candidate.getRootNode?.();
2537
+ return root && typeof root.querySelectorAll === 'function' ? root : document;
2538
+ };
2539
+ const isUserVisible = (candidate) => {
2540
+ if (!isHTMLElementNode(candidate)) return false;
2541
+
2542
+ const view = ownerWindowOf(candidate);
2543
+ const style = view?.getComputedStyle?.(candidate);
2544
+ if (!style) {
2545
+ return false;
2546
+ }
2547
+ if (style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse') {
2548
+ return false;
2549
+ }
2550
+
2551
+ const opacity = Number(style.opacity || '1');
2552
+ if (Number.isFinite(opacity) && opacity <= 0.01) {
2553
+ return false;
2554
+ }
2555
+
2556
+ if (candidate.getAttribute('aria-hidden') === 'true' || candidate.inert) {
2557
+ return false;
2558
+ }
2559
+
2560
+ const rect = candidate.getBoundingClientRect();
2561
+ if (!rect || rect.width < 4 || rect.height < 4) {
2562
+ return false;
2563
+ }
2564
+
2565
+ const viewportWidth =
2566
+ view?.innerWidth ||
2567
+ candidate.ownerDocument?.documentElement?.clientWidth ||
2568
+ 0;
2569
+ const viewportHeight =
2570
+ view?.innerHeight ||
2571
+ candidate.ownerDocument?.documentElement?.clientHeight ||
2572
+ 0;
2573
+ if (viewportWidth > 0 && viewportHeight > 0) {
2574
+ if (rect.bottom <= 0 || rect.right <= 0 || rect.top >= viewportHeight || rect.left >= viewportWidth) {
2575
+ return false;
2576
+ }
2577
+ }
2578
+
2579
+ return true;
2580
+ };
2581
+ const isSelectorUniqueFor = (candidate, selectorValue) => {
2582
+ const queryRoot = queryRootOf(candidate);
2583
+ try {
2584
+ const matches = Array.from(queryRoot.querySelectorAll(selectorValue));
2585
+ return matches.length === 1 && matches[0] === candidate;
2586
+ } catch {
2587
+ return false;
2588
+ }
2589
+ };
2590
+
2591
+ if (!(element instanceof HTMLIFrameElement || element instanceof HTMLFrameElement)) {
2592
+ return null;
2593
+ }
2594
+
2595
+ const descriptorOf = (selector) =>
2596
+ selector
2597
+ ? {
2598
+ selector,
2599
+ userVisible: isUserVisible(element),
2600
+ }
2601
+ : null;
2602
+
2603
+ const tag = element.tagName.toLowerCase();
2604
+ const testId =
2605
+ element.getAttribute('data-testid')?.trim() ||
2606
+ element.getAttribute('data-test-id')?.trim();
2607
+ const testIdAttribute = element.hasAttribute('data-testid')
2608
+ ? 'data-testid'
2609
+ : element.hasAttribute('data-test-id')
2610
+ ? 'data-test-id'
2611
+ : undefined;
2612
+ const name = element.getAttribute('name')?.trim();
2613
+ const title = element.getAttribute('title')?.trim();
2614
+ const src = element.getAttribute('src')?.trim();
2615
+
2616
+ if (element.id && isSelectorUniqueFor(element, '#' + cssEscape(element.id))) {
2617
+ return descriptorOf('#' + cssEscape(element.id));
2618
+ }
2619
+ if (testId) {
2620
+ const selectorValue = testIdAttribute
2621
+ ? '[' + testIdAttribute + '="' + cssEscape(testId) + '"]'
2622
+ : undefined;
2623
+ if (isSelectorUniqueFor(element, selectorValue)) {
2624
+ return descriptorOf(selectorValue);
2625
+ }
2626
+ }
2627
+ if (name) {
2628
+ const selectorValue = tag + '[name="' + cssEscape(name) + '"]';
2629
+ if (isSelectorUniqueFor(element, selectorValue)) {
2630
+ return descriptorOf(selectorValue);
2631
+ }
2632
+ }
2633
+ if (title) {
2634
+ const selectorValue = tag + '[title="' + cssEscape(title) + '"]';
2635
+ if (isSelectorUniqueFor(element, selectorValue)) {
2636
+ return descriptorOf(selectorValue);
2637
+ }
2638
+ }
2639
+ if (src) {
2640
+ const selectorValue = tag + '[src="' + cssEscape(src) + '"]';
2641
+ if (isSelectorUniqueFor(element, selectorValue)) {
2642
+ return descriptorOf(selectorValue);
2643
+ }
2644
+ }
2645
+
2646
+ const segmentOf = (current) => {
2647
+ if (current.id && isSelectorUniqueFor(current, '#' + cssEscape(current.id))) {
2648
+ return '#' + cssEscape(current.id);
2649
+ }
2650
+
2651
+ const currentTestId =
2652
+ current.getAttribute('data-testid')?.trim() ||
2653
+ current.getAttribute('data-test-id')?.trim();
2654
+ const currentTestIdAttribute = current.hasAttribute('data-testid')
2655
+ ? 'data-testid'
2656
+ : current.hasAttribute('data-test-id')
2657
+ ? 'data-test-id'
2658
+ : undefined;
2659
+ if (currentTestId) {
2660
+ const selectorValue = currentTestIdAttribute
2661
+ ? '[' + currentTestIdAttribute + '="' + cssEscape(currentTestId) + '"]'
2662
+ : undefined;
2663
+ if (isSelectorUniqueFor(current, selectorValue)) {
2664
+ return selectorValue;
2665
+ }
2666
+ }
2667
+
2668
+ const currentName = current.getAttribute('name')?.trim();
2669
+ const currentTitle = current.getAttribute('title')?.trim();
2670
+ const currentTag = current.tagName.toLowerCase();
2671
+
2672
+ if (currentName) {
2673
+ const selectorValue = currentTag + '[name="' + cssEscape(currentName) + '"]';
2674
+ if (isSelectorUniqueFor(current, selectorValue)) {
2675
+ return selectorValue;
2676
+ }
2677
+ }
2678
+
2679
+ if (currentTitle) {
2680
+ const selectorValue = currentTag + '[title="' + cssEscape(currentTitle) + '"]';
2681
+ if (isSelectorUniqueFor(current, selectorValue)) {
2682
+ return selectorValue;
2683
+ }
2684
+ }
2685
+
2686
+ const parent = current.parentElement;
2687
+ const root = current.getRootNode?.();
2688
+ const siblingPool = parent
2689
+ ? Array.from(parent.children)
2690
+ : isShadowRootNode(root)
2691
+ ? Array.from(root.children)
2692
+ : [];
2693
+ const siblings = siblingPool.filter((child) => child.tagName.toLowerCase() === currentTag);
2694
+ const index = siblings.indexOf(current) + 1;
2695
+ return currentTag + ':nth-of-type(' + Math.max(index, 1) + ')';
2696
+ };
2697
+
2698
+ const path = [];
2699
+ let current = element;
2700
+ while (current && current.nodeType === Node.ELEMENT_NODE && path.length < 8) {
2701
+ path.unshift(segmentOf(current));
2702
+ if (current.id) {
2703
+ break;
2704
+ }
2705
+ current = current.parentElement;
2706
+ }
2707
+
2708
+ if (path.length === 0) return null;
2709
+
2710
+ const structuralSelector = path.join(' > ');
2711
+ const selector = isSelectorUniqueFor(element, structuralSelector) ? structuralSelector : null;
2712
+ return descriptorOf(selector);
2713
+ `;
2714
+ async function readFrameHostDescriptor(frame) {
2715
+ const frameElement = await frame.frameElement().catch(() => null);
2716
+ if (!frameElement) {
2717
+ return null;
2718
+ }
2719
+ try {
2720
+ const descriptor = await frameElement.evaluate((element, source) => Function('element', source)(element), FRAME_HOST_DESCRIPTOR_SCRIPT);
2721
+ if (!descriptor ||
2722
+ typeof descriptor !== 'object' ||
2723
+ typeof descriptor.selector !== 'string' ||
2724
+ descriptor.selector.length === 0 ||
2725
+ typeof descriptor.userVisible !== 'boolean') {
2726
+ return null;
2727
+ }
2728
+ return descriptor;
2729
+ }
2730
+ catch {
2731
+ return null;
2732
+ }
2733
+ finally {
2734
+ await frameElement.dispose().catch(() => undefined);
2735
+ }
2736
+ }
2737
+ export async function collectDomTargets(page, options) {
2738
+ const topLevelPageSignature = typeof page.url === 'function' ? normalizePageSignature(page.url()) : options?.pageSignature;
2739
+ if (typeof page.mainFrame !== 'function') {
2740
+ return collectDomTargetsFromDocument(page, {
2741
+ ...options,
2742
+ pageSignature: topLevelPageSignature,
2743
+ });
2744
+ }
2745
+ const collected = [];
2746
+ const includeActivationAffordances = options?.includeActivationAffordances === true;
2747
+ const walk = async (frame, framePath) => {
2748
+ if (collected.length >= DOM_TARGET_COLLECTION_LIMIT) {
2749
+ return;
2750
+ }
2751
+ const frameUrl = frame.url().trim() || undefined;
2752
+ const targets = await collectDomTargetsFromDocument(frame, {
2753
+ includeActivationAffordances,
2754
+ pageSignature: topLevelPageSignature,
2755
+ framePath,
2756
+ frameUrl,
2757
+ }).catch(() => []);
2758
+ for (const target of targets) {
2759
+ if (collected.length >= DOM_TARGET_COLLECTION_LIMIT) {
2760
+ break;
2761
+ }
2762
+ collected.push(target);
2763
+ }
2764
+ if (collected.length >= DOM_TARGET_COLLECTION_LIMIT) {
2765
+ return;
2766
+ }
2767
+ for (const childFrame of frame.childFrames().slice(0, 20)) {
2768
+ if (collected.length >= DOM_TARGET_COLLECTION_LIMIT) {
2769
+ break;
2770
+ }
2771
+ const frameHost = await readFrameHostDescriptor(childFrame);
2772
+ if (!frameHost?.selector || !frameHost.userVisible) {
2773
+ continue;
2774
+ }
2775
+ await walk(childFrame, [...(framePath ?? []), frameHost.selector]);
2776
+ }
2777
+ };
2778
+ await walk(page.mainFrame());
2779
+ return collected;
2780
+ }
2781
+ export async function collectPageSignals(page) {
2782
+ if (typeof page.mainFrame !== 'function') {
2783
+ return collectPageSignalsFromDocument(page);
2784
+ }
2785
+ const collected = [];
2786
+ const seen = new Set();
2787
+ const pushSignals = (signals) => {
2788
+ for (const signal of signals) {
2789
+ const key = [signal.kind, signal.text.toLowerCase(), signal.framePath?.join('>') ?? 'top'].join('|');
2790
+ if (seen.has(key)) {
2791
+ continue;
2792
+ }
2793
+ seen.add(key);
2794
+ collected.push(signal);
2795
+ if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
2796
+ break;
2797
+ }
2798
+ }
2799
+ };
2800
+ const walk = async (frame, framePath) => {
2801
+ if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
2802
+ return;
2803
+ }
2804
+ const frameUrl = frame.url().trim() || undefined;
2805
+ const signals = await collectPageSignalsFromDocument(frame, {
2806
+ framePath,
2807
+ frameUrl,
2808
+ }).catch(() => []);
2809
+ pushSignals(signals);
2810
+ if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
2811
+ return;
2812
+ }
2813
+ for (const childFrame of frame.childFrames().slice(0, 20)) {
2814
+ if (collected.length >= DOM_SIGNAL_COLLECTION_LIMIT) {
2815
+ break;
2816
+ }
2817
+ const frameHost = await readFrameHostDescriptor(childFrame);
2818
+ if (!frameHost?.selector || !frameHost.userVisible) {
2819
+ continue;
2820
+ }
2821
+ await walk(childFrame, [...(framePath ?? []), frameHost.selector]);
2822
+ }
2823
+ };
2824
+ await walk(page.mainFrame());
2825
+ return collected;
2826
+ }
2827
+ export const __testDomTargetCollection = {
2828
+ collectDomTargetsFromDocument,
2829
+ collectPageSignalsFromDocument,
2830
+ locatorDomSignatureScript: LOCATOR_DOM_SIGNATURE_SCRIPT,
2831
+ };
2832
+ export const __testStagehandDescriptor = {
2833
+ normalizeStagehandSelector,
2834
+ readStagehandDomFacts,
2835
+ readStagehandDomFactsInBrowser,
2836
+ stagehandDomFactsScript: STAGEHAND_DOM_FACTS_SCRIPT,
2837
+ };