@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,1411 @@
1
+ import { getSurface, getTarget } from '../runtime-state.js';
2
+ import { inferComparableValueTypeFromFacts, } from '../control-semantics.js';
3
+ import { buildLocator, resolveLocatorRoot } from './action-fallbacks.js';
4
+ import { resolveSurfaceScopeRoot } from './target-resolution.js';
5
+ import { isLocatorUserActionable } from './user-actionable.js';
6
+ const ACTION_CANDIDATE_PRIORITY = {
7
+ click: {
8
+ role: 0,
9
+ testId: 1,
10
+ label: 2,
11
+ text: 3,
12
+ title: 4,
13
+ css: 5,
14
+ xpath: 6,
15
+ },
16
+ fill: {
17
+ css: 0,
18
+ xpath: 1,
19
+ testId: 2,
20
+ label: 3,
21
+ placeholder: 4,
22
+ role: 5,
23
+ text: 6,
24
+ title: 7,
25
+ },
26
+ type: {
27
+ css: 0,
28
+ xpath: 1,
29
+ testId: 2,
30
+ label: 3,
31
+ placeholder: 4,
32
+ role: 5,
33
+ text: 6,
34
+ title: 7,
35
+ },
36
+ select: {
37
+ css: 0,
38
+ xpath: 1,
39
+ testId: 2,
40
+ label: 3,
41
+ role: 4,
42
+ text: 5,
43
+ title: 6,
44
+ placeholder: 7,
45
+ },
46
+ press: {
47
+ css: 0,
48
+ xpath: 1,
49
+ testId: 2,
50
+ label: 3,
51
+ placeholder: 4,
52
+ role: 5,
53
+ text: 6,
54
+ title: 7,
55
+ },
56
+ };
57
+ const ACCEPTANCE_POLL_INTERVAL_MS = 100;
58
+ const ACCEPTANCE_POLL_TIMEOUT_MS = 2_500;
59
+ const PAGE_OBSERVATION_SCRIPT = String.raw `(() => {
60
+ const normalizeText = (value) => (value ?? '').replace(/\s+/g, ' ').trim();
61
+ const sampleText = (value, limit) => {
62
+ if (value.length <= limit) {
63
+ return value;
64
+ }
65
+
66
+ const edge = Math.floor(limit / 3);
67
+ const middle = Math.max(1, limit - edge * 2);
68
+ const middleStart = Math.max(0, Math.floor((value.length - middle) / 2));
69
+ return [
70
+ value.slice(0, edge),
71
+ value.slice(middleStart, middleStart + middle),
72
+ value.slice(-edge),
73
+ ].join('\n');
74
+ };
75
+ const isVisible = (element) => {
76
+ if (!(element instanceof HTMLElement)) {
77
+ return false;
78
+ }
79
+
80
+ const style = element.ownerDocument?.defaultView?.getComputedStyle(element);
81
+ if (!style || style.display === 'none' || style.visibility === 'hidden') {
82
+ return false;
83
+ }
84
+
85
+ const rect = element.getBoundingClientRect();
86
+ return rect.width > 0 && rect.height > 0;
87
+ };
88
+ const interactiveSummary = () => {
89
+ const selectors = [
90
+ 'button',
91
+ 'a',
92
+ 'input',
93
+ 'textarea',
94
+ 'select',
95
+ '[role="button"]',
96
+ '[role="link"]',
97
+ '[role="option"]',
98
+ '[role="combobox"]',
99
+ ];
100
+ const items = [];
101
+
102
+ for (const element of Array.from(document.querySelectorAll(selectors.join(', ')))) {
103
+ if (!isVisible(element)) {
104
+ continue;
105
+ }
106
+
107
+ const role = element.getAttribute('role') || element.tagName.toLowerCase();
108
+ const label =
109
+ normalizeText(element.getAttribute('aria-label')) ||
110
+ normalizeText(
111
+ element instanceof HTMLInputElement ||
112
+ element instanceof HTMLTextAreaElement ||
113
+ element instanceof HTMLSelectElement
114
+ ? element.value
115
+ : element.textContent
116
+ ) ||
117
+ normalizeText(element.getAttribute('title')) ||
118
+ normalizeText(element.getAttribute('placeholder'));
119
+ if (!label) {
120
+ continue;
121
+ }
122
+
123
+ items.push(
124
+ [
125
+ role,
126
+ label.slice(0, 80),
127
+ element.getAttribute('aria-expanded') || '',
128
+ element.getAttribute('aria-selected') || '',
129
+ element.getAttribute('aria-pressed') || '',
130
+ ].join(':')
131
+ );
132
+ if (items.length >= 60) {
133
+ break;
134
+ }
135
+ }
136
+
137
+ return items.join('|');
138
+ };
139
+ const stableControlSummary = () => {
140
+ const selectors = [
141
+ 'button',
142
+ 'a[href]',
143
+ 'input',
144
+ 'textarea',
145
+ 'select',
146
+ '[role="button"]',
147
+ '[role="link"]',
148
+ '[role="option"]',
149
+ '[role="combobox"]',
150
+ '[role="textbox"]',
151
+ ];
152
+ const items = [];
153
+
154
+ for (const element of Array.from(document.querySelectorAll(selectors.join(', ')))) {
155
+ if (!isVisible(element)) {
156
+ continue;
157
+ }
158
+
159
+ const role = element.getAttribute('role') || element.tagName.toLowerCase();
160
+ const descriptor =
161
+ normalizeText(element.getAttribute('aria-label')) ||
162
+ normalizeText(element.getAttribute('name')) ||
163
+ normalizeText(element.getAttribute('placeholder')) ||
164
+ normalizeText(element.getAttribute('title')) ||
165
+ normalizeText(element.id) ||
166
+ normalizeText(element.getAttribute('data-testid')) ||
167
+ normalizeText(element.getAttribute('data-test-id')) ||
168
+ '';
169
+ const inputType =
170
+ element instanceof HTMLInputElement
171
+ ? normalizeText(element.type || 'text')
172
+ : element instanceof HTMLSelectElement
173
+ ? 'select'
174
+ : element instanceof HTMLTextAreaElement
175
+ ? 'textarea'
176
+ : '';
177
+ const disabled =
178
+ element instanceof HTMLButtonElement ||
179
+ element instanceof HTMLInputElement ||
180
+ element instanceof HTMLSelectElement ||
181
+ element instanceof HTMLTextAreaElement
182
+ ? element.disabled
183
+ : element.getAttribute('aria-disabled') === 'true';
184
+ const valueState =
185
+ element instanceof HTMLInputElement ||
186
+ element instanceof HTMLTextAreaElement ||
187
+ element instanceof HTMLSelectElement
188
+ ? normalizeText(element.value)
189
+ ? 'value'
190
+ : 'empty'
191
+ : '';
192
+
193
+ items.push(
194
+ [
195
+ role,
196
+ inputType,
197
+ descriptor || 'unlabeled',
198
+ disabled ? 'disabled' : '',
199
+ valueState,
200
+ element.getAttribute('aria-expanded') || '',
201
+ element.getAttribute('aria-selected') || '',
202
+ element.getAttribute('aria-pressed') || '',
203
+ ].join(':')
204
+ );
205
+ if (items.length >= 80) {
206
+ break;
207
+ }
208
+ }
209
+
210
+ return items.join('|');
211
+ };
212
+ const dialogSummary = () => {
213
+ const items = [];
214
+
215
+ for (const element of Array.from(
216
+ document.querySelectorAll('dialog, [role="dialog"], [aria-modal="true"]')
217
+ )) {
218
+ if (!isVisible(element)) {
219
+ continue;
220
+ }
221
+
222
+ const label =
223
+ normalizeText(element.getAttribute('aria-label')) ||
224
+ normalizeText(element.getAttribute('data-testid')) ||
225
+ normalizeText(element.id) ||
226
+ sampleText(normalizeText(element.textContent || ''), 120);
227
+ items.push('dialog:' + (label || 'visible'));
228
+ if (items.length >= 20) {
229
+ break;
230
+ }
231
+ }
232
+
233
+ return items.join('|');
234
+ };
235
+ const frameSummary = () => {
236
+ const items = [];
237
+
238
+ for (const element of Array.from(document.querySelectorAll('iframe, frame'))) {
239
+ if (!isVisible(element)) {
240
+ continue;
241
+ }
242
+
243
+ const title =
244
+ normalizeText(element.getAttribute('title')) ||
245
+ normalizeText(element.getAttribute('name'));
246
+ const rawSrc = element.getAttribute('src') || '';
247
+ let normalizedSrc = normalizeText(rawSrc);
248
+ if (rawSrc) {
249
+ try {
250
+ const url = new URL(rawSrc, document.baseURI);
251
+ normalizedSrc = (url.origin + url.pathname).toLowerCase();
252
+ } catch {
253
+ normalizedSrc = normalizeText(rawSrc);
254
+ }
255
+ }
256
+
257
+ items.push(
258
+ [
259
+ 'frame',
260
+ title || 'untitled',
261
+ normalizedSrc.slice(0, 160),
262
+ element.getAttribute('aria-hidden') || '',
263
+ ].join(':')
264
+ );
265
+ if (items.length >= 20) {
266
+ break;
267
+ }
268
+ }
269
+
270
+ return items.join('|');
271
+ };
272
+ const headingSummary = () => {
273
+ const items = [];
274
+
275
+ for (const element of Array.from(
276
+ document.querySelectorAll('h1, h2, h3, legend, [role="heading"]')
277
+ )) {
278
+ if (!isVisible(element)) {
279
+ continue;
280
+ }
281
+
282
+ const label = sampleText(normalizeText(element.textContent || ''), 120);
283
+ if (!label) {
284
+ continue;
285
+ }
286
+
287
+ items.push(label);
288
+ if (items.length >= 20) {
289
+ break;
290
+ }
291
+ }
292
+
293
+ return items.join('|');
294
+ };
295
+ const processingSummary = () => {
296
+ const selectors = [
297
+ '[aria-busy="true"]',
298
+ '[role="progressbar"]',
299
+ '[role="status"]',
300
+ '[data-loading]',
301
+ '[data-busy]',
302
+ 'button[disabled]',
303
+ 'input[disabled]',
304
+ 'select[disabled]',
305
+ 'textarea[disabled]',
306
+ ];
307
+ const items = [];
308
+
309
+ for (const element of Array.from(document.querySelectorAll(selectors.join(', ')))) {
310
+ if (!isVisible(element)) {
311
+ continue;
312
+ }
313
+
314
+ const role = element.getAttribute('role') || element.tagName.toLowerCase();
315
+ const label =
316
+ normalizeText(element.getAttribute('aria-label')) ||
317
+ normalizeText(element.getAttribute('name')) ||
318
+ normalizeText(element.getAttribute('title')) ||
319
+ normalizeText(element.id) ||
320
+ sampleText(normalizeText(element.textContent || ''), 80);
321
+ items.push(
322
+ [
323
+ role,
324
+ label || 'processing',
325
+ element.getAttribute('aria-busy') || '',
326
+ element.getAttribute('data-loading') || '',
327
+ element.getAttribute('data-busy') || '',
328
+ ].join(':')
329
+ );
330
+ if (items.length >= 20) {
331
+ break;
332
+ }
333
+ }
334
+
335
+ return items.join('|');
336
+ };
337
+
338
+ const body = document.body;
339
+ if (!body) {
340
+ return {
341
+ content: '',
342
+ structure: '',
343
+ submitSignals: '',
344
+ };
345
+ }
346
+
347
+ const attrs = body
348
+ .getAttributeNames()
349
+ .sort()
350
+ .map((name) => name + '=' + (body.getAttribute(name) ?? ''))
351
+ .join('|');
352
+ const text = sampleText(normalizeText(body.innerText || ''), 6000);
353
+ const controls = interactiveSummary();
354
+ const stableControls = stableControlSummary();
355
+ const dialogs = dialogSummary();
356
+ const frames = frameSummary();
357
+ const headings = headingSummary();
358
+ const processing = processingSummary();
359
+ return {
360
+ content: [attrs, text, controls, dialogs, frames].join('\n'),
361
+ structure: [attrs, stableControls, dialogs, frames, headings].join('\n'),
362
+ submitSignals: [processing, dialogs, frames, headings, stableControls].join('\n'),
363
+ };
364
+ })()`;
365
+ const NO_PROGRESS_PAGE_SIGNALS_SCRIPT = String.raw `(() => {
366
+ const normalizeText = (value) => (value ?? '').replace(/\s+/g, ' ').trim();
367
+ const isVisible = (element) => {
368
+ if (!(element instanceof HTMLElement)) {
369
+ return false;
370
+ }
371
+
372
+ const style = element.ownerDocument?.defaultView?.getComputedStyle(element);
373
+ if (!style || style.display === 'none' || style.visibility === 'hidden') {
374
+ return false;
375
+ }
376
+
377
+ const rect = element.getBoundingClientRect();
378
+ return rect.width > 0 && rect.height > 0;
379
+ };
380
+ const uniqueTexts = (elements, limit) => {
381
+ const values = [];
382
+ for (const element of elements) {
383
+ if (!isVisible(element)) {
384
+ continue;
385
+ }
386
+ const text = normalizeText(element.textContent);
387
+ if (!text || values.includes(text)) {
388
+ continue;
389
+ }
390
+ values.push(text.slice(0, 240));
391
+ if (values.length >= limit) {
392
+ break;
393
+ }
394
+ }
395
+ return values;
396
+ };
397
+ const labelForField = (element) => {
398
+ if (!(element instanceof HTMLElement)) {
399
+ return null;
400
+ }
401
+
402
+ const ariaLabel = normalizeText(element.getAttribute('aria-label'));
403
+ if (ariaLabel) {
404
+ return ariaLabel;
405
+ }
406
+
407
+ if (
408
+ element instanceof HTMLInputElement ||
409
+ element instanceof HTMLTextAreaElement ||
410
+ element instanceof HTMLSelectElement
411
+ ) {
412
+ if (element.labels?.length) {
413
+ const labels = Array.from(element.labels)
414
+ .map((label) => normalizeText(label.textContent))
415
+ .filter(Boolean);
416
+ if (labels.length > 0) {
417
+ return labels.join(' / ');
418
+ }
419
+ }
420
+ }
421
+
422
+ const placeholder = normalizeText(element.getAttribute('placeholder'));
423
+ if (placeholder) {
424
+ return placeholder;
425
+ }
426
+
427
+ const name = normalizeText(element.getAttribute('name'));
428
+ if (name) {
429
+ return name;
430
+ }
431
+
432
+ const id = normalizeText(element.id);
433
+ if (id) {
434
+ return id;
435
+ }
436
+
437
+ return normalizeText(element.textContent) || null;
438
+ };
439
+ const pushUniqueText = (values, nextValue, limit) => {
440
+ const text = normalizeText(nextValue);
441
+ if (!text || values.includes(text)) {
442
+ return;
443
+ }
444
+
445
+ values.push(text.slice(0, 240));
446
+ if (values.length > limit) {
447
+ values.length = limit;
448
+ }
449
+ };
450
+ const VALIDATION_TEXT_RE =
451
+ /(?:required|invalid|incorrect|too\s+(?:short|long)|must|error|format|please\s+(?:enter|select|choose|fill)|невер|ошиб|обязател|заполн|введите|укажите|выберите|долж|нужно|формат|цифр|символ)/i;
452
+
453
+ const fieldSelectors =
454
+ 'input, textarea, select, [role="textbox"], [contenteditable="true"], [aria-invalid="true"]';
455
+ const relatedHelperTexts = (element) => {
456
+ if (!(element instanceof HTMLElement)) {
457
+ return [];
458
+ }
459
+
460
+ const values = [];
461
+ const describedBy = normalizeText(element.getAttribute('aria-describedby'));
462
+ if (describedBy) {
463
+ for (const id of describedBy.split(/\s+/)) {
464
+ const helper = document.getElementById(id);
465
+ if (helper && isVisible(helper)) {
466
+ const helperText = normalizeText(helper.textContent);
467
+ if (VALIDATION_TEXT_RE.test(helperText)) {
468
+ pushUniqueText(values, helperText, 4);
469
+ }
470
+ }
471
+ }
472
+ }
473
+
474
+ let anchor = element.parentElement;
475
+ for (let depth = 0; anchor && depth < 4 && values.length < 4; depth += 1, anchor = anchor.parentElement) {
476
+ const anchorFieldCount = anchor.querySelectorAll(fieldSelectors).length;
477
+ if (anchorFieldCount === 0 || anchorFieldCount > 3) {
478
+ continue;
479
+ }
480
+
481
+ for (const candidate of Array.from(anchor.children)) {
482
+ if (!(candidate instanceof HTMLElement)) {
483
+ continue;
484
+ }
485
+ if (candidate === element || candidate.contains(element) || element.contains(candidate)) {
486
+ continue;
487
+ }
488
+ if (!isVisible(candidate) || candidate.matches('label, legend')) {
489
+ continue;
490
+ }
491
+ if (candidate.querySelector(fieldSelectors)) {
492
+ continue;
493
+ }
494
+
495
+ const helperText = normalizeText(candidate.textContent);
496
+ if (!VALIDATION_TEXT_RE.test(helperText)) {
497
+ continue;
498
+ }
499
+ pushUniqueText(values, helperText, 4);
500
+ }
501
+ }
502
+
503
+ return values;
504
+ };
505
+
506
+ const invalidFields = [];
507
+ const messages = [];
508
+ for (const element of Array.from(document.querySelectorAll(fieldSelectors))) {
509
+ if (!isVisible(element)) {
510
+ continue;
511
+ }
512
+
513
+ const ariaInvalid = element.getAttribute('aria-invalid') === 'true';
514
+ const htmlInvalid =
515
+ element instanceof HTMLInputElement ||
516
+ element instanceof HTMLTextAreaElement ||
517
+ element instanceof HTMLSelectElement
518
+ ? !element.checkValidity()
519
+ : false;
520
+ const helperMessages = relatedHelperTexts(element);
521
+
522
+ if (!ariaInvalid && !htmlInvalid && helperMessages.length === 0) {
523
+ continue;
524
+ }
525
+
526
+ const label = labelForField(element);
527
+ if (!label || invalidFields.includes(label)) {
528
+ continue;
529
+ }
530
+ invalidFields.push(label.slice(0, 140));
531
+ for (const helperMessage of helperMessages) {
532
+ pushUniqueText(messages, helperMessage, 6);
533
+ }
534
+ if (invalidFields.length >= 6) {
535
+ break;
536
+ }
537
+ }
538
+
539
+ const messageSelectors = [
540
+ '[role="alert"]',
541
+ '[aria-live="assertive"]',
542
+ '[aria-live="polite"]',
543
+ '.error',
544
+ '.errors',
545
+ '.invalid-feedback',
546
+ '.form-error',
547
+ '.warning',
548
+ '[data-testid*="error"]',
549
+ '[data-testid*="warning"]',
550
+ ];
551
+ for (const message of uniqueTexts(Array.from(document.querySelectorAll(messageSelectors.join(', '))), 6)) {
552
+ pushUniqueText(messages, message, 6);
553
+ }
554
+
555
+ const overlaySelectors = [
556
+ '[role="alertdialog"]',
557
+ '[role="dialog"][aria-modal="true"]',
558
+ '[role="dialog"][data-state="open"]',
559
+ '[data-state="open"][role="dialog"]',
560
+ ];
561
+ const blockingOverlays = uniqueTexts(
562
+ Array.from(document.querySelectorAll(overlaySelectors.join(', '))),
563
+ 4
564
+ );
565
+
566
+ return {
567
+ messages,
568
+ invalidFields,
569
+ blockingOverlays,
570
+ };
571
+ })()`;
572
+ function hashText(value) {
573
+ let hash = 0;
574
+ for (let index = 0; index < value.length; index += 1) {
575
+ hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
576
+ }
577
+ return String(hash);
578
+ }
579
+ function sleep(ms) {
580
+ return new Promise((resolve) => setTimeout(resolve, ms));
581
+ }
582
+ export function rankLocatorCandidates(candidates, action) {
583
+ const priority = ACTION_CANDIDATE_PRIORITY[action];
584
+ if (!priority) {
585
+ return [...candidates];
586
+ }
587
+ return [...candidates].sort((left, right) => {
588
+ const leftPriority = priority[left.strategy] ?? 99;
589
+ const rightPriority = priority[right.strategy] ?? 99;
590
+ return leftPriority - rightPriority;
591
+ });
592
+ }
593
+ export function shouldVerifyObservableProgress(target, action) {
594
+ if (action === 'fill' || action === 'type') {
595
+ return false;
596
+ }
597
+ return action === 'click' || action === 'press' || action === 'select';
598
+ }
599
+ function isEditableLikeTarget(target) {
600
+ if (target.controlFamily === 'text-input' ||
601
+ target.controlFamily === 'select' ||
602
+ target.controlFamily === 'datepicker') {
603
+ return true;
604
+ }
605
+ const kind = (target.kind ?? '').toLowerCase();
606
+ const role = (target.semantics?.role ?? '').toLowerCase();
607
+ return (['input', 'textarea', 'select', 'combobox'].includes(kind) ||
608
+ ['textbox', 'combobox'].includes(role) ||
609
+ target.allowedActions.includes('fill') ||
610
+ target.allowedActions.includes('type') ||
611
+ target.allowedActions.includes('select'));
612
+ }
613
+ function isSelectableChoiceTarget(target) {
614
+ const kind = (target.kind ?? '').toLowerCase();
615
+ const role = (target.semantics?.role ?? '').toLowerCase();
616
+ return (kind === 'option' ||
617
+ role === 'option' ||
618
+ role === 'menuitem' ||
619
+ role === 'gridcell' ||
620
+ target.structure?.family === 'structured-grid');
621
+ }
622
+ function acceptancePolicyForAction(target, action) {
623
+ if (action === 'select') {
624
+ if (target.controlFamily === 'datepicker') {
625
+ return 'date-selection';
626
+ }
627
+ if (target.controlFamily === 'select') {
628
+ return 'selection';
629
+ }
630
+ }
631
+ if (action === 'click' || action === 'press') {
632
+ if (target.controlFamily === 'datepicker') {
633
+ return isSelectableChoiceTarget(target) ? 'date-selection' : 'disclosure';
634
+ }
635
+ if (target.controlFamily === 'select') {
636
+ return isSelectableChoiceTarget(target) ? 'selection' : 'disclosure';
637
+ }
638
+ }
639
+ const policy = target.acceptancePolicy;
640
+ if (!policy) {
641
+ return undefined;
642
+ }
643
+ if ((action === 'click' || action === 'press') &&
644
+ policy === 'value-change' &&
645
+ isEditableLikeTarget(target)) {
646
+ return 'generic-click';
647
+ }
648
+ return policy;
649
+ }
650
+ function trackedStateKeys(target, action, policy) {
651
+ const states = target.semantics?.states;
652
+ const keys = new Set();
653
+ if (states) {
654
+ for (const key of ['selected', 'checked', 'expanded', 'pressed', 'current']) {
655
+ if (Object.prototype.hasOwnProperty.call(states, key)) {
656
+ keys.add(key);
657
+ }
658
+ }
659
+ }
660
+ if (policy === 'disclosure' &&
661
+ (action === 'click' || action === 'press') &&
662
+ (target.controlFamily === 'select' || target.controlFamily === 'datepicker')) {
663
+ keys.add('expanded');
664
+ }
665
+ return [...keys];
666
+ }
667
+ export async function capturePageObservation(page) {
668
+ const snapshot = typeof page.evaluate === 'function'
669
+ ? await page
670
+ .evaluate(PAGE_OBSERVATION_SCRIPT)
671
+ .catch(() => '')
672
+ : '';
673
+ const contentSnapshot = typeof snapshot === 'string' ? snapshot : (snapshot?.content ?? '');
674
+ const structureSnapshot = typeof snapshot === 'string' ? '' : (snapshot?.structure ?? '');
675
+ const submitSignalSnapshot = typeof snapshot === 'string' ? '' : (snapshot?.submitSignals ?? '');
676
+ return {
677
+ url: page.url(),
678
+ title: await page.title().catch(() => ''),
679
+ contentHash: contentSnapshot ? hashText(contentSnapshot) : null,
680
+ structureHash: structureSnapshot ? hashText(structureSnapshot) : null,
681
+ submitSignalHash: submitSignalSnapshot ? hashText(submitSignalSnapshot) : null,
682
+ };
683
+ }
684
+ async function readLocatorText(locator) {
685
+ const count = await locator.count().catch(() => 0);
686
+ if (count === 0) {
687
+ return null;
688
+ }
689
+ const normalizedInnerText = typeof locator.innerText === 'function'
690
+ ? await locator
691
+ .innerText()
692
+ .then((value) => value.replace(/\s+/g, ' ').trim())
693
+ .catch(() => '')
694
+ : '';
695
+ if (normalizedInnerText) {
696
+ return normalizedInnerText;
697
+ }
698
+ const normalizedTextContent = typeof locator.textContent === 'function'
699
+ ? await locator
700
+ .textContent()
701
+ .then((value) => (value ?? '').replace(/\s+/g, ' ').trim())
702
+ .catch(() => '')
703
+ : '';
704
+ return normalizedTextContent || null;
705
+ }
706
+ export async function captureLocatorContextHash(locator) {
707
+ const candidates = [];
708
+ if (typeof locator.locator === 'function') {
709
+ const ancestorContext = locator.locator('xpath=ancestor-or-self::*[' +
710
+ '@role="option" or @role="row" or @role="gridcell" or @role="listitem" or ' +
711
+ '@role="tabpanel" or @role="dialog" or @role="listbox" or @role="menu" or @role="grid" or ' +
712
+ 'self::article or self::li or self::tr or self::td or self::section or self::form' +
713
+ '][1]');
714
+ candidates.push(typeof ancestorContext.first === 'function' ? ancestorContext.first() : ancestorContext);
715
+ }
716
+ candidates.push(typeof locator.first === 'function' ? locator.first() : locator);
717
+ for (const candidate of candidates) {
718
+ const text = await readLocatorText(candidate);
719
+ if (text) {
720
+ return hashText(text);
721
+ }
722
+ }
723
+ return null;
724
+ }
725
+ async function captureLocatorValue(locator) {
726
+ const count = await locator.count().catch(() => 0);
727
+ if (count === 0) {
728
+ return null;
729
+ }
730
+ const inputValue = typeof locator.inputValue === 'function'
731
+ ? await locator.inputValue().catch(() => '')
732
+ : '';
733
+ if (inputValue) {
734
+ return inputValue;
735
+ }
736
+ return readLocatorText(locator);
737
+ }
738
+ async function captureLocatorValueFromCandidates(locators) {
739
+ let fallbackValue = null;
740
+ for (const locator of locators) {
741
+ const value = await captureLocatorValue(locator).catch(() => null);
742
+ if (value) {
743
+ return value;
744
+ }
745
+ if (value !== null && fallbackValue === null) {
746
+ fallbackValue = value;
747
+ }
748
+ }
749
+ return fallbackValue;
750
+ }
751
+ async function captureSelectedOptionText(locator) {
752
+ return locator
753
+ .evaluate((element) => {
754
+ if (!(element instanceof HTMLSelectElement)) {
755
+ return null;
756
+ }
757
+ const selected = element.options[element.selectedIndex] ?? null;
758
+ const text = (selected?.textContent ?? '').replace(/\s+/g, ' ').trim();
759
+ return text || null;
760
+ })
761
+ .catch(() => null);
762
+ }
763
+ function pushComparableValue(values, nextValue) {
764
+ const raw = (nextValue ?? '').replace(/\s+/g, ' ').trim();
765
+ const normalized = normalizeComparableValue(raw);
766
+ if (!normalized) {
767
+ return;
768
+ }
769
+ if (values.some((existing) => normalizeComparableValue(existing) === normalized)) {
770
+ return;
771
+ }
772
+ values.push(raw);
773
+ }
774
+ async function captureLocatorComparableValues(locator) {
775
+ const count = await locator.count().catch(() => 0);
776
+ if (count === 0) {
777
+ return [];
778
+ }
779
+ const values = [];
780
+ pushComparableValue(values, await captureLocatorValue(locator));
781
+ pushComparableValue(values, await captureSelectedOptionText(locator));
782
+ return values;
783
+ }
784
+ async function captureLocatorComparableValuesFromCandidates(locators) {
785
+ const values = [];
786
+ for (const locator of locators) {
787
+ const nextValues = await captureLocatorComparableValues(locator).catch(() => []);
788
+ for (const nextValue of nextValues) {
789
+ pushComparableValue(values, nextValue);
790
+ }
791
+ }
792
+ return values;
793
+ }
794
+ async function captureLocatorStateFromCandidates(locators, keys) {
795
+ for (const locator of locators) {
796
+ const state = await captureLocatorState(locator, keys).catch(() => null);
797
+ if (state && Object.keys(state).length > 0) {
798
+ return state;
799
+ }
800
+ }
801
+ return null;
802
+ }
803
+ async function captureLocatorContextHashFromCandidates(locators) {
804
+ for (const locator of locators) {
805
+ const hash = await captureLocatorContextHash(locator).catch(() => null);
806
+ if (hash) {
807
+ return hash;
808
+ }
809
+ }
810
+ return null;
811
+ }
812
+ function comparableValueTypeForTarget(target) {
813
+ return inferComparableValueTypeFromFacts({
814
+ kind: target.kind,
815
+ role: target.semantics?.role,
816
+ label: target.label,
817
+ displayLabel: target.displayLabel,
818
+ placeholder: target.placeholder,
819
+ inputName: target.inputName,
820
+ inputType: target.inputType,
821
+ autocomplete: target.autocomplete,
822
+ states: target.semantics?.states,
823
+ structure: target.structure,
824
+ });
825
+ }
826
+ function normalizeComparableValue(value, comparableValueType) {
827
+ const raw = (value ?? '').replace(/\s+/g, ' ').trim();
828
+ if (!raw) {
829
+ return '';
830
+ }
831
+ if (comparableValueType === 'card-number' ||
832
+ comparableValueType === 'expiry' ||
833
+ comparableValueType === 'cvc') {
834
+ return raw.replace(/\D/g, '');
835
+ }
836
+ if (comparableValueType === 'date') {
837
+ const isoMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
838
+ if (isoMatch) {
839
+ return `${isoMatch[1]}${isoMatch[2]}${isoMatch[3]}`;
840
+ }
841
+ const localizedMatch = raw.match(/^(\d{1,2})[./-](\d{1,2})[./-](\d{2,4})$/);
842
+ if (localizedMatch) {
843
+ const [, day = '', month = '', year = ''] = localizedMatch;
844
+ if (!day || !month || !year) {
845
+ return raw.replace(/\D/g, '');
846
+ }
847
+ const normalizedYear = year.length === 2 ? `20${year}` : year.padStart(4, '0');
848
+ return `${normalizedYear}${month.padStart(2, '0')}${day.padStart(2, '0')}`;
849
+ }
850
+ return raw.replace(/\D/g, '');
851
+ }
852
+ if (comparableValueType === 'phone' ||
853
+ (!comparableValueType && /^\+?[\d\s().-]{5,}$/.test(raw))) {
854
+ const normalized = raw.replace(/[^\d+]/g, '');
855
+ return normalized.startsWith('+') ? `+${normalized.slice(1).replace(/\+/g, '')}` : normalized;
856
+ }
857
+ return raw.toLowerCase();
858
+ }
859
+ function valuesMatchExpected(expected, actual, comparableValueType) {
860
+ const normalizedExpected = normalizeComparableValue(expected, comparableValueType);
861
+ const normalizedActual = normalizeComparableValue(actual, comparableValueType);
862
+ if (!normalizedExpected || !normalizedActual) {
863
+ return false;
864
+ }
865
+ return normalizedActual === normalizedExpected || normalizedActual.includes(normalizedExpected);
866
+ }
867
+ function valuesMatchAnyExpected(expected, actualValues, comparableValueType) {
868
+ return actualValues.some((actualValue) => valuesMatchExpected(expected, actualValue, comparableValueType));
869
+ }
870
+ function expectedValueForAcceptance(action, actionValue) {
871
+ if (action === 'fill' || action === 'type' || action === 'select') {
872
+ return actionValue ?? null;
873
+ }
874
+ return null;
875
+ }
876
+ function recoveryDescendantSelector(target, action) {
877
+ if (action === 'fill' || action === 'type') {
878
+ return 'input:not([type="hidden"]), textarea, select, [contenteditable="true"]';
879
+ }
880
+ if ((action === 'click' || action === 'press') &&
881
+ (target.controlFamily === 'text-input' ||
882
+ target.controlFamily === 'select' ||
883
+ target.controlFamily === 'datepicker')) {
884
+ return 'input:not([type="hidden"]), textarea, select, [contenteditable="true"], [role="textbox"], [role="combobox"]';
885
+ }
886
+ return null;
887
+ }
888
+ async function prepareReadLocator(locator, target, action) {
889
+ const visible = await isLocatorUserActionable(locator);
890
+ if (!visible) {
891
+ return null;
892
+ }
893
+ const descendantSelector = recoveryDescendantSelector(target, action);
894
+ if (!descendantSelector) {
895
+ return locator;
896
+ }
897
+ const requiresEditableDescendant = action === 'fill' || action === 'type';
898
+ if (requiresEditableDescendant) {
899
+ const editable = await locator.isEditable().catch(() => false);
900
+ if (editable) {
901
+ return locator;
902
+ }
903
+ }
904
+ const descendants = locator.locator(descendantSelector);
905
+ const count = await descendants.count().catch(() => 0);
906
+ const visibleDescendants = [];
907
+ for (let index = 0; index < count; index += 1) {
908
+ const descendant = descendants.nth(index);
909
+ const descendantVisible = await isLocatorUserActionable(descendant);
910
+ if (!descendantVisible) {
911
+ continue;
912
+ }
913
+ if (requiresEditableDescendant) {
914
+ const editable = await descendant.isEditable().catch(() => false);
915
+ if (!editable) {
916
+ continue;
917
+ }
918
+ }
919
+ visibleDescendants.push(descendant);
920
+ }
921
+ if (visibleDescendants.length === 1) {
922
+ return visibleDescendants[0] ?? null;
923
+ }
924
+ return requiresEditableDescendant ? null : locator;
925
+ }
926
+ function valueMeaningfullyChanged(before, after) {
927
+ const normalizedBefore = normalizeComparableValue(before);
928
+ const normalizedAfter = normalizeComparableValue(after);
929
+ if (!normalizedAfter) {
930
+ return false;
931
+ }
932
+ return normalizedBefore !== normalizedAfter;
933
+ }
934
+ export const __testComparableValues = {
935
+ comparableValueTypeForTarget,
936
+ normalizeComparableValue,
937
+ valuesMatchExpected,
938
+ };
939
+ export async function captureLocatorState(locator, keys) {
940
+ if (keys.length === 0) {
941
+ return null;
942
+ }
943
+ const count = await locator.count().catch(() => 0);
944
+ if (count === 0) {
945
+ return { current: 'missing' };
946
+ }
947
+ const state = {};
948
+ const heuristicFlags = await locator
949
+ .evaluate((element) => {
950
+ if (!(element instanceof HTMLElement)) {
951
+ return { selected: null, current: null, pressed: null };
952
+ }
953
+ const blob = (element.getAttribute('class') || '').toLowerCase() +
954
+ ' ' +
955
+ (element.getAttribute('data-state') || '').toLowerCase() +
956
+ ' ' +
957
+ (element.getAttribute('data-status') || '').toLowerCase() +
958
+ ' ' +
959
+ Object.values(element.dataset || {})
960
+ .join(' ')
961
+ .toLowerCase();
962
+ const active = /(?:selected|active|current)\b/.test(blob);
963
+ const pressed = /(?:pressed|active)\b/.test(blob);
964
+ return {
965
+ selected: active,
966
+ current: active,
967
+ pressed,
968
+ };
969
+ })
970
+ .catch(() => ({ selected: null, current: null, pressed: null }));
971
+ for (const key of keys) {
972
+ switch (key) {
973
+ case 'checked': {
974
+ if (typeof locator.isChecked === 'function') {
975
+ const checked = await locator.isChecked().catch(() => undefined);
976
+ if (typeof checked === 'boolean') {
977
+ state.checked = checked;
978
+ break;
979
+ }
980
+ }
981
+ const ariaChecked = await locator.getAttribute?.('aria-checked').catch(() => null);
982
+ if (ariaChecked === 'true')
983
+ state.checked = true;
984
+ else if (ariaChecked === 'false')
985
+ state.checked = false;
986
+ else if (ariaChecked === 'mixed')
987
+ state.checked = 'mixed';
988
+ break;
989
+ }
990
+ case 'selected': {
991
+ const value = await locator.getAttribute?.('aria-selected').catch(() => null);
992
+ if (value === 'true')
993
+ state.selected = true;
994
+ else if (value === 'false')
995
+ state.selected = false;
996
+ else if (typeof heuristicFlags.selected === 'boolean')
997
+ state.selected = heuristicFlags.selected;
998
+ break;
999
+ }
1000
+ case 'expanded': {
1001
+ const value = await locator.getAttribute?.('aria-expanded').catch(() => null);
1002
+ if (value === 'true')
1003
+ state.expanded = true;
1004
+ else if (value === 'false')
1005
+ state.expanded = false;
1006
+ break;
1007
+ }
1008
+ case 'pressed': {
1009
+ const value = await locator.getAttribute?.('aria-pressed').catch(() => null);
1010
+ if (value === 'true')
1011
+ state.pressed = true;
1012
+ else if (value === 'false')
1013
+ state.pressed = false;
1014
+ else if (typeof heuristicFlags.pressed === 'boolean')
1015
+ state.pressed = heuristicFlags.pressed;
1016
+ break;
1017
+ }
1018
+ case 'current': {
1019
+ const value = await locator.getAttribute?.('aria-current').catch(() => null);
1020
+ if (value === 'true')
1021
+ state.current = true;
1022
+ else if (typeof value === 'string' && value.length > 0)
1023
+ state.current = value;
1024
+ else if (typeof heuristicFlags.current === 'boolean')
1025
+ state.current = heuristicFlags.current;
1026
+ break;
1027
+ }
1028
+ case 'focused': {
1029
+ const focused = await locator
1030
+ .evaluate((element) => {
1031
+ if (!(element instanceof HTMLElement)) {
1032
+ return false;
1033
+ }
1034
+ return (element.matches?.(':focus') === true ||
1035
+ element.ownerDocument?.activeElement === element);
1036
+ })
1037
+ .catch(() => false);
1038
+ state.focused = focused;
1039
+ break;
1040
+ }
1041
+ }
1042
+ }
1043
+ return Object.keys(state).length > 0 ? state : null;
1044
+ }
1045
+ export function locatorStateChanged(before, after) {
1046
+ if (!before && !after) {
1047
+ return false;
1048
+ }
1049
+ if (!before) {
1050
+ return Boolean(after && Object.keys(after).length > 0);
1051
+ }
1052
+ if (!after) {
1053
+ return Object.keys(before).length > 0;
1054
+ }
1055
+ const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
1056
+ for (const key of keys) {
1057
+ if (before[key] !== after[key]) {
1058
+ return true;
1059
+ }
1060
+ }
1061
+ return false;
1062
+ }
1063
+ export function pageObservationChanged(before, after) {
1064
+ if (!before || !after) {
1065
+ return false;
1066
+ }
1067
+ if (before.url !== after.url || before.title !== after.title) {
1068
+ return true;
1069
+ }
1070
+ if (before.contentHash && after.contentHash && before.contentHash !== after.contentHash) {
1071
+ return true;
1072
+ }
1073
+ return false;
1074
+ }
1075
+ export function submitObservationChanged(before, after) {
1076
+ if (!before || !after) {
1077
+ return false;
1078
+ }
1079
+ if (before.url !== after.url || before.title !== after.title) {
1080
+ return true;
1081
+ }
1082
+ if (before.structureHash &&
1083
+ after.structureHash &&
1084
+ before.structureHash !== after.structureHash) {
1085
+ return true;
1086
+ }
1087
+ return false;
1088
+ }
1089
+ export async function diagnoseNoObservableProgress(page, locator) {
1090
+ const targetState = await locator
1091
+ .evaluate((element) => {
1092
+ if (!(element instanceof HTMLElement)) {
1093
+ return {};
1094
+ }
1095
+ const readonly = element.hasAttribute('readonly') ||
1096
+ element.getAttribute('aria-readonly') === 'true' ||
1097
+ ('readOnly' in element &&
1098
+ typeof element.readOnly === 'boolean' &&
1099
+ element.readOnly);
1100
+ return {
1101
+ disabled: Boolean(element.disabled),
1102
+ ariaDisabled: element.getAttribute('aria-disabled') === 'true',
1103
+ readonly,
1104
+ centerHitSelf: (() => {
1105
+ const rect = element.getBoundingClientRect();
1106
+ if (rect.width <= 0 || rect.height <= 0) {
1107
+ return false;
1108
+ }
1109
+ const centerX = rect.left + rect.width / 2;
1110
+ const centerY = rect.top + rect.height / 2;
1111
+ const hit = element.ownerDocument?.elementFromPoint(centerX, centerY);
1112
+ return Boolean(hit &&
1113
+ (hit === element ||
1114
+ element.contains(hit) ||
1115
+ (hit instanceof HTMLElement && hit.shadowRoot?.contains(element))));
1116
+ })(),
1117
+ };
1118
+ })
1119
+ .catch(() => ({}));
1120
+ const pageSignals = await page
1121
+ .evaluate(NO_PROGRESS_PAGE_SIGNALS_SCRIPT)
1122
+ .catch(() => null);
1123
+ if (!pageSignals || typeof pageSignals !== 'object' || Array.isArray(pageSignals)) {
1124
+ return null;
1125
+ }
1126
+ const messages = Array.isArray(pageSignals.messages)
1127
+ ? pageSignals.messages.filter((value) => typeof value === 'string')
1128
+ : [];
1129
+ const invalidFields = Array.isArray(pageSignals.invalidFields)
1130
+ ? pageSignals.invalidFields.filter((value) => typeof value === 'string')
1131
+ : [];
1132
+ const blockingOverlays = Array.isArray(pageSignals.blockingOverlays)
1133
+ ? pageSignals.blockingOverlays.filter((value) => typeof value === 'string')
1134
+ : [];
1135
+ const rawTargetState = targetState &&
1136
+ typeof targetState === 'object' &&
1137
+ !Array.isArray(targetState)
1138
+ ? targetState
1139
+ : undefined;
1140
+ const normalizedTargetState = rawTargetState
1141
+ ? {
1142
+ disabled: Boolean(rawTargetState.disabled),
1143
+ ariaDisabled: Boolean(rawTargetState.ariaDisabled),
1144
+ readonly: Boolean(rawTargetState.readonly),
1145
+ centerHitSelf: Boolean(rawTargetState.centerHitSelf),
1146
+ }
1147
+ : undefined;
1148
+ const effectiveBlockingOverlays = normalizedTargetState?.centerHitSelf ? [] : blockingOverlays;
1149
+ let kind = 'site-noop';
1150
+ if (messages.length > 0 || invalidFields.length > 0) {
1151
+ kind = 'validation-blocked';
1152
+ }
1153
+ else if (normalizedTargetState?.disabled ||
1154
+ normalizedTargetState?.ariaDisabled ||
1155
+ normalizedTargetState?.readonly) {
1156
+ kind = 'target-blocked';
1157
+ }
1158
+ else if (effectiveBlockingOverlays.length > 0) {
1159
+ kind = 'overlay-blocked';
1160
+ }
1161
+ return {
1162
+ kind,
1163
+ messages,
1164
+ invalidFields,
1165
+ blockingOverlays: effectiveBlockingOverlays,
1166
+ targetState: normalizedTargetState,
1167
+ };
1168
+ }
1169
+ async function resolveTargetLocatorForRead(page, target, surface, action = 'click') {
1170
+ const surfaceRoot = await resolveSurfaceScopeRoot(page, surface);
1171
+ const baseRoot = resolveLocatorRoot(page, target.framePath ?? surface?.framePath);
1172
+ const defaultRoot = surfaceRoot ?? baseRoot;
1173
+ for (const candidate of rankLocatorCandidates(target.locatorCandidates, action)) {
1174
+ const locatorRoot = candidate.scope === 'root'
1175
+ ? baseRoot
1176
+ : candidate.scope === 'surface'
1177
+ ? surfaceRoot
1178
+ : defaultRoot;
1179
+ if (!locatorRoot) {
1180
+ continue;
1181
+ }
1182
+ const locator = buildLocator(locatorRoot, candidate);
1183
+ if (!locator)
1184
+ continue;
1185
+ const count = await locator.count().catch(() => 0);
1186
+ if (count === 0)
1187
+ continue;
1188
+ const first = locator.first();
1189
+ const prepared = await prepareReadLocator(first, target, action);
1190
+ if (!prepared)
1191
+ continue;
1192
+ return prepared;
1193
+ }
1194
+ const descendantSelector = recoveryDescendantSelector(target, action);
1195
+ if (!surfaceRoot || !descendantSelector) {
1196
+ return null;
1197
+ }
1198
+ const descendants = surfaceRoot.locator(descendantSelector);
1199
+ const descendantCount = await descendants.count().catch(() => 0);
1200
+ const visibleDescendants = [];
1201
+ for (let index = 0; index < descendantCount; index += 1) {
1202
+ const descendant = descendants.nth(index);
1203
+ const visible = await isLocatorUserActionable(descendant);
1204
+ if (!visible) {
1205
+ continue;
1206
+ }
1207
+ if (action === 'fill' || action === 'type') {
1208
+ const editable = await descendant.isEditable().catch(() => false);
1209
+ if (!editable) {
1210
+ continue;
1211
+ }
1212
+ }
1213
+ visibleDescendants.push(descendant);
1214
+ }
1215
+ return visibleDescendants.length === 1 ? (visibleDescendants[0] ?? null) : null;
1216
+ }
1217
+ export async function createAcceptanceProbe(args) {
1218
+ const { session, page, target, action, actionValue, locator, beforePageObservation } = args;
1219
+ const policy = acceptancePolicyForAction(target, action);
1220
+ if (!policy) {
1221
+ return null;
1222
+ }
1223
+ const trackedStates = shouldVerifyObservableProgress(target, action)
1224
+ ? trackedStateKeys(target, action, policy)
1225
+ : [];
1226
+ if ((action === 'click' || action === 'press') &&
1227
+ policy === 'generic-click' &&
1228
+ isEditableLikeTarget(target) &&
1229
+ !trackedStates.includes('focused')) {
1230
+ trackedStates.push('focused');
1231
+ }
1232
+ const beforeLocatorObservation = trackedStates.length > 0 ? await captureLocatorState(locator, trackedStates) : null;
1233
+ const beforeContextHash = policy === 'value-change' || policy === 'submit' ? null : await captureLocatorContextHash(locator);
1234
+ const surface = target.surfaceRef ? getSurface(session, target.surfaceRef) : null;
1235
+ const surfaceLocator = surface ? await resolveSurfaceScopeRoot(page, surface) : null;
1236
+ const pageReadLocator = await resolveTargetLocatorForRead(page, target, surface, action);
1237
+ const readLocator = pageReadLocator ?? locator;
1238
+ const readLocators = pageReadLocator && pageReadLocator !== locator ? [locator, pageReadLocator] : [locator];
1239
+ const beforeReadLocatorObservation = trackedStates.length > 0
1240
+ ? await captureLocatorStateFromCandidates([readLocator], trackedStates)
1241
+ : null;
1242
+ const beforeReadContextHash = policy === 'value-change' || policy === 'submit'
1243
+ ? null
1244
+ : await captureLocatorContextHashFromCandidates([readLocator]);
1245
+ const beforeValue = await captureLocatorValueFromCandidates(readLocators);
1246
+ const ownerTarget = target.ownerRef ? getTarget(session, target.ownerRef) : null;
1247
+ const ownerSurface = ownerTarget?.surfaceRef ? getSurface(session, ownerTarget.surfaceRef) : null;
1248
+ const ownerLocator = ownerTarget
1249
+ ? await resolveTargetLocatorForRead(page, ownerTarget, ownerSurface, action)
1250
+ : null;
1251
+ const beforeOwnerValue = ownerLocator && (policy === 'selection' || policy === 'date-selection')
1252
+ ? await captureLocatorValue(ownerLocator)
1253
+ : null;
1254
+ const beforeSurfaceContextHash = surfaceLocator &&
1255
+ target.structure?.family === 'structured-grid' &&
1256
+ (policy === 'selection' || policy === 'date-selection')
1257
+ ? await captureLocatorContextHash(surfaceLocator)
1258
+ : null;
1259
+ const comparableValueType = comparableValueTypeForTarget(target);
1260
+ return {
1261
+ policy,
1262
+ page,
1263
+ target,
1264
+ action,
1265
+ surface,
1266
+ ownerTarget,
1267
+ ownerSurface,
1268
+ beforePage: beforePageObservation,
1269
+ beforeLocator: beforeLocatorObservation,
1270
+ beforeContextHash,
1271
+ beforeReadLocator: beforeReadLocatorObservation,
1272
+ beforeReadContextHash,
1273
+ trackedStateKeys: trackedStates,
1274
+ locator,
1275
+ readLocator,
1276
+ readLocators,
1277
+ surfaceLocator,
1278
+ expectedValue: expectedValueForAcceptance(action, actionValue),
1279
+ beforeValue,
1280
+ comparableValueType,
1281
+ ownerLocator,
1282
+ beforeOwnerValue,
1283
+ beforeSurfaceContextHash,
1284
+ };
1285
+ }
1286
+ export async function evaluateAcceptanceProbe(probe, afterPageObservation) {
1287
+ const liveReadLocator = (await resolveTargetLocatorForRead(probe.page, probe.target, probe.surface, probe.action).catch(() => null)) ?? probe.readLocator;
1288
+ const liveReadLocators = liveReadLocator && liveReadLocator !== probe.locator
1289
+ ? [probe.locator, liveReadLocator]
1290
+ : [probe.locator];
1291
+ const liveOwnerLocator = probe.ownerTarget
1292
+ ? (await resolveTargetLocatorForRead(probe.page, probe.ownerTarget, probe.ownerSurface, probe.action).catch(() => null)) ?? probe.ownerLocator
1293
+ : null;
1294
+ const liveSurfaceLocator = probe.target.structure?.family === 'structured-grid' && probe.surface
1295
+ ? (await resolveSurfaceScopeRoot(probe.page, probe.surface).catch(() => null)) ??
1296
+ probe.surfaceLocator
1297
+ : probe.surfaceLocator;
1298
+ const afterLocatorObservation = probe.trackedStateKeys.length > 0
1299
+ ? await captureLocatorState(probe.locator, probe.trackedStateKeys)
1300
+ : null;
1301
+ const afterContextHash = probe.policy === 'value-change' || probe.policy === 'submit'
1302
+ ? probe.beforeContextHash
1303
+ : await captureLocatorContextHash(probe.locator);
1304
+ const afterReadLocatorObservation = probe.trackedStateKeys.length > 0
1305
+ ? await captureLocatorStateFromCandidates([liveReadLocator], probe.trackedStateKeys)
1306
+ : null;
1307
+ const afterReadContextHash = probe.policy === 'value-change' || probe.policy === 'submit'
1308
+ ? probe.beforeReadContextHash
1309
+ : await captureLocatorContextHashFromCandidates([liveReadLocator]);
1310
+ const afterValue = await captureLocatorValueFromCandidates(liveReadLocators);
1311
+ const afterOwnerValue = liveOwnerLocator ? await captureLocatorValue(liveOwnerLocator) : null;
1312
+ const afterSurfaceContextHash = liveSurfaceLocator &&
1313
+ probe.target.structure?.family === 'structured-grid' &&
1314
+ (probe.policy === 'selection' || probe.policy === 'date-selection')
1315
+ ? await captureLocatorContextHash(liveSurfaceLocator)
1316
+ : probe.beforeSurfaceContextHash;
1317
+ const targetValueChanged = probe.expectedValue === null && valueMeaningfullyChanged(probe.beforeValue, afterValue);
1318
+ const ownerValueChanged = probe.expectedValue === null && valueMeaningfullyChanged(probe.beforeOwnerValue, afterOwnerValue);
1319
+ const surfaceContextChanged = probe.expectedValue === null &&
1320
+ probe.beforeSurfaceContextHash !== null &&
1321
+ afterSurfaceContextHash !== null &&
1322
+ probe.beforeSurfaceContextHash !== afterSurfaceContextHash;
1323
+ switch (probe.policy) {
1324
+ case 'value-change':
1325
+ return valuesMatchExpected(probe.expectedValue, afterValue, probe.comparableValueType);
1326
+ case 'selection':
1327
+ const afterComparableValues = await captureLocatorComparableValuesFromCandidates(liveReadLocators);
1328
+ const afterOwnerComparableValues = liveOwnerLocator
1329
+ ? await captureLocatorComparableValuesFromCandidates([liveOwnerLocator])
1330
+ : [];
1331
+ if (probe.expectedValue !== null) {
1332
+ return (valuesMatchAnyExpected(probe.expectedValue, afterComparableValues, probe.comparableValueType) ||
1333
+ valuesMatchAnyExpected(probe.expectedValue, afterOwnerComparableValues, probe.comparableValueType));
1334
+ }
1335
+ return (targetValueChanged ||
1336
+ ownerValueChanged ||
1337
+ surfaceContextChanged ||
1338
+ locatorStateChanged(probe.beforeLocator, afterLocatorObservation) ||
1339
+ locatorStateChanged(probe.beforeReadLocator, afterReadLocatorObservation) ||
1340
+ probe.beforeContextHash !== afterContextHash ||
1341
+ probe.beforeReadContextHash !== afterReadContextHash);
1342
+ case 'toggle':
1343
+ return (locatorStateChanged(probe.beforeLocator, afterLocatorObservation) ||
1344
+ locatorStateChanged(probe.beforeReadLocator, afterReadLocatorObservation));
1345
+ case 'disclosure':
1346
+ return (targetValueChanged ||
1347
+ locatorStateChanged(probe.beforeLocator, afterLocatorObservation) ||
1348
+ locatorStateChanged(probe.beforeReadLocator, afterReadLocatorObservation) ||
1349
+ probe.beforeContextHash !== afterContextHash ||
1350
+ probe.beforeReadContextHash !== afterReadContextHash);
1351
+ case 'date-selection':
1352
+ const afterDateComparableValues = await captureLocatorComparableValuesFromCandidates(liveReadLocators);
1353
+ const afterDateOwnerComparableValues = liveOwnerLocator
1354
+ ? await captureLocatorComparableValuesFromCandidates([liveOwnerLocator])
1355
+ : [];
1356
+ const explicitDateMatched = probe.expectedValue !== null &&
1357
+ (valuesMatchAnyExpected(probe.expectedValue, afterDateComparableValues, probe.comparableValueType) ||
1358
+ valuesMatchAnyExpected(probe.expectedValue, afterDateOwnerComparableValues, probe.comparableValueType));
1359
+ return (explicitDateMatched ||
1360
+ locatorStateChanged(probe.beforeLocator, afterLocatorObservation) ||
1361
+ locatorStateChanged(probe.beforeReadLocator, afterReadLocatorObservation) ||
1362
+ targetValueChanged ||
1363
+ ownerValueChanged ||
1364
+ surfaceContextChanged ||
1365
+ (probe.expectedValue === null &&
1366
+ (probe.beforeContextHash !== afterContextHash ||
1367
+ probe.beforeReadContextHash !== afterReadContextHash)));
1368
+ case 'submit':
1369
+ return submitObservationChanged(probe.beforePage, afterPageObservation);
1370
+ case 'navigation':
1371
+ return (targetValueChanged ||
1372
+ pageObservationChanged(probe.beforePage, afterPageObservation) ||
1373
+ probe.beforeContextHash !== afterContextHash ||
1374
+ probe.beforeReadContextHash !== afterReadContextHash);
1375
+ case 'generic-click':
1376
+ return (targetValueChanged ||
1377
+ locatorStateChanged(probe.beforeLocator, afterLocatorObservation) ||
1378
+ locatorStateChanged(probe.beforeReadLocator, afterReadLocatorObservation) ||
1379
+ probe.beforeContextHash !== afterContextHash ||
1380
+ probe.beforeReadContextHash !== afterReadContextHash ||
1381
+ pageObservationChanged(probe.beforePage, afterPageObservation));
1382
+ }
1383
+ return false;
1384
+ }
1385
+ export async function waitForAcceptanceProbe(probe, options) {
1386
+ const timeoutMs = options?.timeoutMs ?? ACCEPTANCE_POLL_TIMEOUT_MS;
1387
+ const intervalMs = options?.intervalMs ?? ACCEPTANCE_POLL_INTERVAL_MS;
1388
+ const startedAt = Date.now();
1389
+ let polls = 0;
1390
+ let afterPageObservation = probe.beforePage ? await capturePageObservation(probe.page) : null;
1391
+ while (true) {
1392
+ polls += 1;
1393
+ const accepted = await evaluateAcceptanceProbe(probe, afterPageObservation);
1394
+ if (accepted) {
1395
+ return {
1396
+ accepted: true,
1397
+ afterPageObservation,
1398
+ polls,
1399
+ };
1400
+ }
1401
+ if (Date.now() - startedAt >= timeoutMs) {
1402
+ return {
1403
+ accepted: false,
1404
+ afterPageObservation,
1405
+ polls,
1406
+ };
1407
+ }
1408
+ await sleep(intervalMs);
1409
+ afterPageObservation = probe.beforePage ? await capturePageObservation(probe.page) : null;
1410
+ }
1411
+ }