@enricai/barnacle 1.6.12 → 1.6.13

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 (96) hide show
  1. package/README.md +113 -9
  2. package/dist/api/schemas/submissions.d.ts +14 -0
  3. package/dist/api/schemas/submissions.d.ts.map +1 -1
  4. package/dist/api/schemas/submissions.js +1 -0
  5. package/dist/api/schemas/submissions.js.map +1 -1
  6. package/dist/config.d.ts +33 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +4 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/lib/telemetry/beacon-capture.d.ts +45 -2
  11. package/dist/lib/telemetry/beacon-capture.d.ts.map +1 -1
  12. package/dist/lib/telemetry/beacon-capture.js +36 -0
  13. package/dist/lib/telemetry/beacon-capture.js.map +1 -1
  14. package/dist/lib/telemetry/reconciliation-record.d.ts +41 -1
  15. package/dist/lib/telemetry/reconciliation-record.d.ts.map +1 -1
  16. package/dist/lib/telemetry/reconciliation-record.js +29 -2
  17. package/dist/lib/telemetry/reconciliation-record.js.map +1 -1
  18. package/dist/lib/telemetry/reconciliation-source.d.ts +7 -1
  19. package/dist/lib/telemetry/reconciliation-source.d.ts.map +1 -1
  20. package/dist/lib/telemetry/reconciliation-source.js +21 -5
  21. package/dist/lib/telemetry/reconciliation-source.js.map +1 -1
  22. package/dist/lib/telemetry/run-telemetry.d.ts +67 -0
  23. package/dist/lib/telemetry/run-telemetry.d.ts.map +1 -0
  24. package/dist/lib/telemetry/run-telemetry.js +55 -0
  25. package/dist/lib/telemetry/run-telemetry.js.map +1 -0
  26. package/dist/lib/telemetry/submission-capture.d.ts +11 -4
  27. package/dist/lib/telemetry/submission-capture.d.ts.map +1 -1
  28. package/dist/lib/telemetry/submission-capture.js +1 -0
  29. package/dist/lib/telemetry/submission-capture.js.map +1 -1
  30. package/dist/lib/telemetry/submission-reader.d.ts +11 -3
  31. package/dist/lib/telemetry/submission-reader.d.ts.map +1 -1
  32. package/dist/lib/telemetry/submission-reader.js +32 -8
  33. package/dist/lib/telemetry/submission-reader.js.map +1 -1
  34. package/dist/lib/tracking-click.d.ts.map +1 -1
  35. package/dist/lib/tracking-click.js +19 -0
  36. package/dist/lib/tracking-click.js.map +1 -1
  37. package/dist/plugins/loader.d.ts +20 -0
  38. package/dist/plugins/loader.d.ts.map +1 -1
  39. package/dist/plugins/loader.js +94 -17
  40. package/dist/plugins/loader.js.map +1 -1
  41. package/dist/scraper/deep-locator-actuate.d.ts +94 -0
  42. package/dist/scraper/deep-locator-actuate.d.ts.map +1 -0
  43. package/dist/scraper/deep-locator-actuate.js +216 -0
  44. package/dist/scraper/deep-locator-actuate.js.map +1 -0
  45. package/dist/scraper/deep-locator-candidates.d.ts +105 -18
  46. package/dist/scraper/deep-locator-candidates.d.ts.map +1 -1
  47. package/dist/scraper/deep-locator-candidates.js +238 -46
  48. package/dist/scraper/deep-locator-candidates.js.map +1 -1
  49. package/dist/scraper/deep-locator-click.d.ts +71 -0
  50. package/dist/scraper/deep-locator-click.d.ts.map +1 -0
  51. package/dist/scraper/deep-locator-click.js +71 -0
  52. package/dist/scraper/deep-locator-click.js.map +1 -0
  53. package/dist/scraper/deep-locator-fake.d.ts +344 -14
  54. package/dist/scraper/deep-locator-fake.d.ts.map +1 -1
  55. package/dist/scraper/deep-locator-fake.js +509 -7
  56. package/dist/scraper/deep-locator-fake.js.map +1 -1
  57. package/dist/scraper/deep-locator-scan.d.ts +220 -0
  58. package/dist/scraper/deep-locator-scan.d.ts.map +1 -0
  59. package/dist/scraper/deep-locator-scan.js +386 -0
  60. package/dist/scraper/deep-locator-scan.js.map +1 -0
  61. package/dist/scraper/fake-timer-advance.d.ts +47 -0
  62. package/dist/scraper/fake-timer-advance.d.ts.map +1 -0
  63. package/dist/scraper/fake-timer-advance.js +44 -0
  64. package/dist/scraper/fake-timer-advance.js.map +1 -0
  65. package/dist/scraper/flow-runner.d.ts +36 -1
  66. package/dist/scraper/flow-runner.d.ts.map +1 -1
  67. package/dist/scraper/flow-runner.js +354 -56
  68. package/dist/scraper/flow-runner.js.map +1 -1
  69. package/dist/scraper/frame-target.d.ts +28 -0
  70. package/dist/scraper/frame-target.d.ts.map +1 -1
  71. package/dist/scraper/frame-target.js +30 -0
  72. package/dist/scraper/frame-target.js.map +1 -1
  73. package/dist/scraper/session-browserbase.d.ts +13 -0
  74. package/dist/scraper/session-browserbase.d.ts.map +1 -1
  75. package/dist/scraper/session-browserbase.js +29 -0
  76. package/dist/scraper/session-browserbase.js.map +1 -1
  77. package/dist/scraper/session-ip.d.ts +69 -0
  78. package/dist/scraper/session-ip.d.ts.map +1 -0
  79. package/dist/scraper/session-ip.js +88 -0
  80. package/dist/scraper/session-ip.js.map +1 -0
  81. package/dist/scraper/session-shared.d.ts +9 -0
  82. package/dist/scraper/session-shared.d.ts.map +1 -1
  83. package/dist/scraper/session-shared.js.map +1 -1
  84. package/dist/scraper/submit-control.d.ts +14 -2
  85. package/dist/scraper/submit-control.d.ts.map +1 -1
  86. package/dist/scraper/submit-control.js +33 -2
  87. package/dist/scraper/submit-control.js.map +1 -1
  88. package/dist/scripts/recon-browser.d.ts.map +1 -1
  89. package/dist/scripts/recon-browser.js +32 -0
  90. package/dist/scripts/recon-browser.js.map +1 -1
  91. package/dist/site-plugin.d.ts +20 -0
  92. package/dist/site-plugin.d.ts.map +1 -1
  93. package/dist/testing/integration-runner.d.ts.map +1 -1
  94. package/dist/testing/integration-runner.js +6 -7
  95. package/dist/testing/integration-runner.js.map +1 -1
  96. package/package.json +1 -1
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Frame-scoped candidate-scan seam: `resolveDeepLocatorCandidates`
3
+ * (`deep-locator-candidates.ts`) pays one CDP round-trip per candidate via
4
+ * `delegate.nth(index).textContent()`, because Stagehand 3.7.0's
5
+ * `FrameSelectorResolver.resolveAll` re-resolves the selector from index 0
6
+ * on every call (understudy/selectorResolver.js) — enumerating n candidates
7
+ * costs n(n+1)/2 round-trips, not n. Through Browserbase's proxied CDP into
8
+ * a cross-origin OOPIF, that measured out to ~4.6s per candidate — 371
9
+ * candidates never finish inside any reasonable budget.
10
+ *
11
+ * {@link buildScanFrameCandidatesExpr} is the fix: one `Frame.evaluate` call
12
+ * that reads accessible-name + layout/visibility for EVERY match of an inner
13
+ * selector inside the frame's own document, in a single CDP round-trip. The
14
+ * accessible name (not bare `textContent`, which the DOM spec always leaves
15
+ * empty for `input`/`select`/`textarea`) is what lets an `INTERACTIVE_CANDIDATE_SELECTOR`
16
+ * match like a labelled `<input>` surface something the rephrase LLM and
17
+ * `scoreCandidate` can actually compare against a step instruction.
18
+ * {@link buildClickFrameCandidateExpr} is the actuation half of Issue #2
19
+ * (clicking an unrendered node): a second one-round-trip evaluate call that
20
+ * re-runs the same `querySelectorAll(innerSelector)` resolution, clicks the
21
+ * element at a scan-derived `index`, and reports out-of-range /
22
+ * not-actionable outcomes as data instead of throwing a CDP `-32000` error.
23
+ * {@link isNodeNotActionableError} is the companion predicate for the case
24
+ * where a click is attempted through a different path (e.g. Stagehand's own
25
+ * `deepLocator().click()`) and throws anyway — both live here because a
26
+ * caller enumerates with the first and needs the second or third to
27
+ * interpret a click outcome against whatever the scan already reported as
28
+ * `visible`.
29
+ *
30
+ * {@link buildFillFrameCandidateExpr} and {@link buildSelectFrameCandidateExpr}
31
+ * extend the same one-round-trip actuation seam to the two write primitives
32
+ * `clickDeepLocatorCandidate` never needed: today
33
+ * `fillDeepLocatorCandidate`/`selectDeepLocatorCandidateOption`
34
+ * (`deep-locator-actuate.ts`) still pay a `deepLocator().nth(index).fill()`
35
+ * (or `.selectOption()`) round-trip PLUS a separate `.inputValue()`
36
+ * round-trip to confirm the write — each individually as expensive as
37
+ * `clickDeepLocatorCandidate`'s pre-fix per-index resolve. These two
38
+ * builders collapse write + read-back into the same evaluate call, out-of-
39
+ * range/not-actionable reported as data exactly like the click builder.
40
+ */
41
+ /**
42
+ * CSS selector for the interactive-element universe the deepLocator cascade
43
+ * should enumerate instead of `"*"`, so a candidate set that would otherwise
44
+ * be hundreds of structural nodes (`html`, `body`, every wrapping `div`) is
45
+ * instead a handful of clickable controls. Exported as the single definition
46
+ * both the cascade and its tests share. Safe as the final `buildHopSelector`
47
+ * segment — `resolveLocatorTarget` only ever splits hop notation on `">>"`
48
+ * (understudy/deepLocator.js), so this selector's commas pass through intact.
49
+ */
50
+ export declare const INTERACTIVE_CANDIDATE_SELECTOR = "button, a, input, select, textarea, [role=button], [tabindex]";
51
+ /**
52
+ * Additional per-CDP-round-trip watchdog budget charged per candidate
53
+ * `index`, on top of a call's own `callTimeoutMs`, for every legacy
54
+ * `deepLocator(hop).nth(index)` delegate fallback — `clickDeepLocatorCandidate`
55
+ * (`deep-locator-candidates.ts`) and `fillDeepLocatorCandidate`/
56
+ * `selectDeepLocatorCandidateOption` (`deep-locator-actuate.ts`) alike.
57
+ * Stagehand's `FrameSelectorResolver.resolveAtIndex(query, i)` resolves
58
+ * `Locator.nth(i)` via `resolveAll(query, {limit: i + 1})`, whose
59
+ * `resolveCss` loops one serial `Runtime.evaluate` round-trip per index up
60
+ * to and including `i` (understudy/selectorResolver.js:70,79-115) before
61
+ * ANY `.nth(i)`-chained method (`click()`, `fill()`, `selectOption()`,
62
+ * `inputValue()`) ever dispatches — so acting at index `i` costs `i + 1`
63
+ * round-trips, not one, and a fixed `callTimeoutMs` (which only ever
64
+ * budgeted a single round-trip) starves any candidate past the index where
65
+ * `(i + 1) * measuredRoundTripMs` exceeds it (measured ~0.66s/round-trip
66
+ * through Browserbase's proxied CDP into a live cross-origin OOPIF — run-7:
67
+ * candidate 13 enumerated within a 60s budget, i.e. 91 cumulative
68
+ * round-trips). `callTimeoutMs` already covers the first round-trip; this
69
+ * constant is the budget added per each of the remaining `index` round-trips,
70
+ * rounded up from the measured cost to leave headroom for CDP jitter. Hoisted
71
+ * here (rather than left module-private to `deep-locator-candidates.ts`) so
72
+ * `deep-locator-actuate.ts` can reuse it without importing
73
+ * `deep-locator-candidates.ts` or `flow-runner.ts` (import-cycle risk).
74
+ */
75
+ export declare const DEEP_LOCATOR_CLICK_INDEX_ROUND_TRIP_MS = 1000;
76
+ /**
77
+ * Builds a self-contained evaluate expression that queries `root` for every
78
+ * match of `innerSelector` via `document.querySelectorAll` — the SAME
79
+ * resolution Stagehand's own primary CSS resolver uses
80
+ * (`resolveCssSelector(sel, i) = querySelectorAll(sel)[i]`, in the frame's
81
+ * own document) — and returns one entry per match, in document order:
82
+ * `{ index, text, visible }`. `index` therefore lines up 1:1 with the index
83
+ * `deepLocator(hop).nth(index)` will later resolve to, so a caller can scan
84
+ * once here and hand an index to the existing per-index click path without
85
+ * re-deriving the candidate set.
86
+ *
87
+ * That alignment diverges only when the light-DOM query matches fewer than
88
+ * `i + 1` elements, which is what triggers Stagehand's shadow-piercing
89
+ * `resolveCssSelectorPierce` fallback instead of the plain CSS resolver —
90
+ * `innerSelector` should stay a plain, non-shadow-crossing selector (e.g.
91
+ * {@link INTERACTIVE_CANDIDATE_SELECTOR}) whenever a caller depends on index
92
+ * alignment with `deepLocator`.
93
+ *
94
+ * `root` overrides the traversal root expression (default `"document"`),
95
+ * interpolated verbatim into the generated code so a caller evaluating this
96
+ * expression via `Frame.evaluate` still resolves that frame's own document —
97
+ * the expression never captures an outer `document` reference (mirrors
98
+ * `buildRankSubmitCandidatesExpr`'s contract in `submit-control.ts`).
99
+ */
100
+ export declare function buildScanFrameCandidatesExpr(innerSelector: string, root?: string): string;
101
+ /** One candidate {@link buildScanFrameCandidatesExpr}'s evaluate call returns. */
102
+ export interface FrameCandidateScanResult {
103
+ /** Position in `querySelectorAll(innerSelector)`'s match order — aligns with `deepLocator(hop).nth(index)` and with {@link buildClickFrameCandidateExpr}'s `index` argument, subject to the shadow-piercing divergence documented on {@link buildScanFrameCandidatesExpr}. */
104
+ index: number;
105
+ /** The element's derived accessible name (see {@link buildAccessibleNameExpr}'s precedence), untrimmed at the edges the precedence chain doesn't already trim — `deep-locator-candidates.ts`'s `scanFrameCandidatesBatched` `.trim()`s it before use. */
106
+ text: string;
107
+ /** `false` when the element has a 0x0 layout box or a computed `display:none`/`visibility:hidden` style — never actionable via a real click. */
108
+ visible: boolean;
109
+ }
110
+ /**
111
+ * Builds a self-contained evaluate expression that re-runs the SAME
112
+ * `root.querySelectorAll(innerSelector)` resolution {@link buildScanFrameCandidatesExpr}
113
+ * uses and clicks the element at `index` (dispatching focus + bubbling
114
+ * mousedown/mouseup/click, matching `buildClickByDeepIndexExpr`'s
115
+ * (`submit-control.ts`) controlled-state click convention). This is the
116
+ * one-round-trip actuation half of the batched-scan fix: a caller that
117
+ * scanned via {@link buildScanFrameCandidatesExpr} can hand the chosen
118
+ * `index` straight to this builder without re-deriving the candidate set,
119
+ * because both builders resolve `querySelectorAll(innerSelector)` against
120
+ * the same frame document in the same match order.
121
+ *
122
+ * Reports out-of-range / not-actionable outcomes as data rather than by
123
+ * throwing, mirroring {@link isNodeNotActionableError}'s CDP-error-side
124
+ * contract from the DOM-observable side:
125
+ * - `index` no longer matches (e.g. the DOM changed between scan and click)
126
+ * returns `{ clicked: false, reason: "out-of-range" }`.
127
+ * - the matched element fails {@link IS_VISIBLE_EXPR} — the same
128
+ * visibility check {@link buildScanFrameCandidatesExpr} reports as
129
+ * `visible: false` — returns `{ clicked: false, reason: "not-actionable" }`
130
+ * instead of dispatching a click a real browser would reject with a
131
+ * `-32000 Node does not have a layout object` CDP error.
132
+ *
133
+ * `root` overrides the traversal root expression (default `"document"`) and
134
+ * must match the `root` passed to the {@link buildScanFrameCandidatesExpr}
135
+ * call that produced `index`, or the re-run query will not resolve against
136
+ * the same document. Interpolated verbatim so a caller evaluating this
137
+ * expression via `Frame.evaluate` still resolves that frame's own document —
138
+ * the expression never captures an outer `document` reference.
139
+ */
140
+ export declare function buildClickFrameCandidateExpr(innerSelector: string, index: number, root?: string): string;
141
+ /** Reason {@link buildClickFrameCandidateExpr} reports when it returns `{ clicked: false }` instead of throwing. */
142
+ export type FrameCandidateClickSkipReason = "out-of-range" | "not-actionable";
143
+ /** Result of {@link buildClickFrameCandidateExpr}'s evaluate call. */
144
+ export interface FrameCandidateClickResult {
145
+ clicked: boolean;
146
+ /** Present only when `clicked` is `false` — distinguishes a stale index from an unrendered element. */
147
+ reason?: FrameCandidateClickSkipReason;
148
+ }
149
+ /** Reason {@link buildFillFrameCandidateExpr}/{@link buildSelectFrameCandidateExpr} report when they return `{ written: false }` instead of throwing — same two reasons {@link FrameCandidateClickSkipReason} reports for a click, since both re-run the identical resolve-and-check-visibility steps. */
150
+ export type FrameCandidateWriteSkipReason = "out-of-range" | "not-actionable";
151
+ /** Result of {@link buildFillFrameCandidateExpr}'s or {@link buildSelectFrameCandidateExpr}'s evaluate call. */
152
+ export interface FrameCandidateWriteResult {
153
+ written: boolean;
154
+ /** Present only when `written` is `true` — the value read back from the element immediately after the write, so a caller can compare it against what it asked for without a second round-trip. For the select expression this is the MATCHED option's `value`, not necessarily the (possibly label) string the caller passed in. */
155
+ readBack?: string;
156
+ /** Present only when `written` is `false` — distinguishes a stale index from an element the write could not land on. */
157
+ reason?: FrameCandidateWriteSkipReason;
158
+ }
159
+ /**
160
+ * Builds a self-contained evaluate expression that fills the `<input>`/
161
+ * `<textarea>` at `root.querySelectorAll(innerSelector)[index]` with
162
+ * `value` — the one-round-trip write half of
163
+ * {@link fillDeepLocatorCandidate}'s (`deep-locator-actuate.ts`) contract.
164
+ * See {@link buildWriteFrameCandidateExpr} for the shared write/dispatch/
165
+ * read-back mechanism and out-of-range/not-actionable reporting. Resolves
166
+ * the native value-setter descriptor off `HTMLTextAreaElement.prototype`
167
+ * when the matched element's own `tagName` is `"textarea"`, else
168
+ * `HTMLInputElement.prototype` — determined at evaluate-time (inside the
169
+ * generated code), since `innerSelector` may match a mix of input and
170
+ * textarea nodes (e.g. {@link INTERACTIVE_CANDIDATE_SELECTOR}).
171
+ *
172
+ * `root` overrides the traversal root expression (default `"document"`) and
173
+ * must match the `root` a prior {@link buildScanFrameCandidatesExpr} call
174
+ * used to derive `index`, mirroring
175
+ * {@link buildClickFrameCandidateExpr}'s `root` contract.
176
+ */
177
+ export declare function buildFillFrameCandidateExpr(innerSelector: string, index: number, value: string, root?: string): string;
178
+ /**
179
+ * Builds a self-contained evaluate expression that re-runs the SAME
180
+ * `root.querySelectorAll(innerSelector)` resolution and selects, on the
181
+ * `<select>`-shaped candidate at `index`, the option whose `value` — or,
182
+ * failing that, whose trimmed visible label — matches `value`. Matching by
183
+ * value first mirrors what a `<select>`'s own `value` property actually is
184
+ * (the matched option's `value` attribute); falling back to the trimmed
185
+ * label covers a flow instruction that quotes the option's VISIBLE label
186
+ * instead (`parseSelectStep`), which a `<select>`'s DOM `value` never is —
187
+ * the same value-then-label tolerance `trySelectPrimitive`'s deterministic
188
+ * match already applies across every `<select>` on the page
189
+ * (`flow-runner.ts:3576`), reproduced here inline because this expression
190
+ * matches options within one already-chosen candidate rather than
191
+ * enumerating the whole page. See {@link buildWriteFrameCandidateExpr} for
192
+ * the write/dispatch/read-back mechanism this reimplements to accommodate
193
+ * the option lookup — the shared helper alone can't express "write the
194
+ * MATCHED option's value, not the caller's raw string."
195
+ *
196
+ * `root` overrides the traversal root expression (default `"document"`) and
197
+ * must match the `root` a prior {@link buildScanFrameCandidatesExpr} call
198
+ * used to derive `index`, mirroring
199
+ * {@link buildClickFrameCandidateExpr}'s `root` contract. A candidate with
200
+ * no option matching `value` by either value or label reports
201
+ * `{ written: false, reason: "not-actionable" }` — nothing on the page can
202
+ * satisfy the write.
203
+ */
204
+ export declare function buildSelectFrameCandidateExpr(innerSelector: string, index: number, value: string, root?: string): string;
205
+ /**
206
+ * Detects the CDP "node is not actionable" failure shape so a caller
207
+ * cascading through candidates can treat it as "skip this candidate, try the
208
+ * next" instead of scoring the attempt as a failed click. Covers both
209
+ * surfaces a click against an unrendered node can throw through: a raw CDP
210
+ * protocol error rejects as `new Error(`${code} ${message}`)`
211
+ * (understudy/cdp.js), so a `DOM.getBoxModel`/`DOM.scrollIntoViewIfNeeded`
212
+ * failure over "no layout object" arrives as a plain `Error` whose message is
213
+ * exactly `-32000 Node does not have a layout object`; separately,
214
+ * `Locator.click()`/`Locator.hover()` (understudy/locator.js) throw
215
+ * `ElementNotVisibleError` directly when `DOM.getBoxModel` returns no model.
216
+ * A name/substring check on both shapes is sufficient — no new error class
217
+ * is needed.
218
+ */
219
+ export declare function isNodeNotActionableError(error: unknown): boolean;
220
+ //# sourceMappingURL=deep-locator-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-locator-scan.d.ts","sourceRoot":"","sources":["../../src/scraper/deep-locator-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,kEACsB,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,sCAAsC,OAAQ,CAAC;AA4F5D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,SAAa,GAAG,MAAM,CAW7F;AAED,kFAAkF;AAClF,MAAM,WAAW,wBAAwB;IACvC,8QAA8Q;IAC9Q,KAAK,EAAE,MAAM,CAAC;IACd,yPAAyP;IACzP,IAAI,EAAE,MAAM,CAAC;IACb,gJAAgJ;IAChJ,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,4BAA4B,CAC1C,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,IAAI,SAAa,GAChB,MAAM,CAaR;AAED,oHAAoH;AACpH,MAAM,MAAM,6BAA6B,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAE9E,sEAAsE;AACtE,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,uGAAuG;IACvG,MAAM,CAAC,EAAE,6BAA6B,CAAC;CACxC;AAED,0SAA0S;AAC1S,MAAM,MAAM,6BAA6B,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAE9E,gHAAgH;AAChH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,oUAAoU;IACpU,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wHAAwH;IACxH,MAAM,CAAC,EAAE,6BAA6B,CAAC;CACxC;AAoDD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,SAAa,GAChB,MAAM,CAIR;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,SAAa,GAChB,MAAM,CAqBR;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIhE"}
@@ -0,0 +1,386 @@
1
+ "use strict";
2
+ /**
3
+ * Frame-scoped candidate-scan seam: `resolveDeepLocatorCandidates`
4
+ * (`deep-locator-candidates.ts`) pays one CDP round-trip per candidate via
5
+ * `delegate.nth(index).textContent()`, because Stagehand 3.7.0's
6
+ * `FrameSelectorResolver.resolveAll` re-resolves the selector from index 0
7
+ * on every call (understudy/selectorResolver.js) — enumerating n candidates
8
+ * costs n(n+1)/2 round-trips, not n. Through Browserbase's proxied CDP into
9
+ * a cross-origin OOPIF, that measured out to ~4.6s per candidate — 371
10
+ * candidates never finish inside any reasonable budget.
11
+ *
12
+ * {@link buildScanFrameCandidatesExpr} is the fix: one `Frame.evaluate` call
13
+ * that reads accessible-name + layout/visibility for EVERY match of an inner
14
+ * selector inside the frame's own document, in a single CDP round-trip. The
15
+ * accessible name (not bare `textContent`, which the DOM spec always leaves
16
+ * empty for `input`/`select`/`textarea`) is what lets an `INTERACTIVE_CANDIDATE_SELECTOR`
17
+ * match like a labelled `<input>` surface something the rephrase LLM and
18
+ * `scoreCandidate` can actually compare against a step instruction.
19
+ * {@link buildClickFrameCandidateExpr} is the actuation half of Issue #2
20
+ * (clicking an unrendered node): a second one-round-trip evaluate call that
21
+ * re-runs the same `querySelectorAll(innerSelector)` resolution, clicks the
22
+ * element at a scan-derived `index`, and reports out-of-range /
23
+ * not-actionable outcomes as data instead of throwing a CDP `-32000` error.
24
+ * {@link isNodeNotActionableError} is the companion predicate for the case
25
+ * where a click is attempted through a different path (e.g. Stagehand's own
26
+ * `deepLocator().click()`) and throws anyway — both live here because a
27
+ * caller enumerates with the first and needs the second or third to
28
+ * interpret a click outcome against whatever the scan already reported as
29
+ * `visible`.
30
+ *
31
+ * {@link buildFillFrameCandidateExpr} and {@link buildSelectFrameCandidateExpr}
32
+ * extend the same one-round-trip actuation seam to the two write primitives
33
+ * `clickDeepLocatorCandidate` never needed: today
34
+ * `fillDeepLocatorCandidate`/`selectDeepLocatorCandidateOption`
35
+ * (`deep-locator-actuate.ts`) still pay a `deepLocator().nth(index).fill()`
36
+ * (or `.selectOption()`) round-trip PLUS a separate `.inputValue()`
37
+ * round-trip to confirm the write — each individually as expensive as
38
+ * `clickDeepLocatorCandidate`'s pre-fix per-index resolve. These two
39
+ * builders collapse write + read-back into the same evaluate call, out-of-
40
+ * range/not-actionable reported as data exactly like the click builder.
41
+ */
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.DEEP_LOCATOR_CLICK_INDEX_ROUND_TRIP_MS = exports.INTERACTIVE_CANDIDATE_SELECTOR = void 0;
44
+ exports.buildScanFrameCandidatesExpr = buildScanFrameCandidatesExpr;
45
+ exports.buildClickFrameCandidateExpr = buildClickFrameCandidateExpr;
46
+ exports.buildFillFrameCandidateExpr = buildFillFrameCandidateExpr;
47
+ exports.buildSelectFrameCandidateExpr = buildSelectFrameCandidateExpr;
48
+ exports.isNodeNotActionableError = isNodeNotActionableError;
49
+ /**
50
+ * CSS selector for the interactive-element universe the deepLocator cascade
51
+ * should enumerate instead of `"*"`, so a candidate set that would otherwise
52
+ * be hundreds of structural nodes (`html`, `body`, every wrapping `div`) is
53
+ * instead a handful of clickable controls. Exported as the single definition
54
+ * both the cascade and its tests share. Safe as the final `buildHopSelector`
55
+ * segment — `resolveLocatorTarget` only ever splits hop notation on `">>"`
56
+ * (understudy/deepLocator.js), so this selector's commas pass through intact.
57
+ */
58
+ exports.INTERACTIVE_CANDIDATE_SELECTOR = "button, a, input, select, textarea, [role=button], [tabindex]";
59
+ /**
60
+ * Additional per-CDP-round-trip watchdog budget charged per candidate
61
+ * `index`, on top of a call's own `callTimeoutMs`, for every legacy
62
+ * `deepLocator(hop).nth(index)` delegate fallback — `clickDeepLocatorCandidate`
63
+ * (`deep-locator-candidates.ts`) and `fillDeepLocatorCandidate`/
64
+ * `selectDeepLocatorCandidateOption` (`deep-locator-actuate.ts`) alike.
65
+ * Stagehand's `FrameSelectorResolver.resolveAtIndex(query, i)` resolves
66
+ * `Locator.nth(i)` via `resolveAll(query, {limit: i + 1})`, whose
67
+ * `resolveCss` loops one serial `Runtime.evaluate` round-trip per index up
68
+ * to and including `i` (understudy/selectorResolver.js:70,79-115) before
69
+ * ANY `.nth(i)`-chained method (`click()`, `fill()`, `selectOption()`,
70
+ * `inputValue()`) ever dispatches — so acting at index `i` costs `i + 1`
71
+ * round-trips, not one, and a fixed `callTimeoutMs` (which only ever
72
+ * budgeted a single round-trip) starves any candidate past the index where
73
+ * `(i + 1) * measuredRoundTripMs` exceeds it (measured ~0.66s/round-trip
74
+ * through Browserbase's proxied CDP into a live cross-origin OOPIF — run-7:
75
+ * candidate 13 enumerated within a 60s budget, i.e. 91 cumulative
76
+ * round-trips). `callTimeoutMs` already covers the first round-trip; this
77
+ * constant is the budget added per each of the remaining `index` round-trips,
78
+ * rounded up from the measured cost to leave headroom for CDP jitter. Hoisted
79
+ * here (rather than left module-private to `deep-locator-candidates.ts`) so
80
+ * `deep-locator-actuate.ts` can reuse it without importing
81
+ * `deep-locator-candidates.ts` or `flow-runner.ts` (import-cycle risk).
82
+ */
83
+ exports.DEEP_LOCATOR_CLICK_INDEX_ROUND_TRIP_MS = 1_000;
84
+ /**
85
+ * Visibility check shared by every candidate the scan expression builds: a
86
+ * node with a 0x0 layout box, or a computed `display:none`/
87
+ * `visibility:hidden`, can never be the target of a real click — this is the
88
+ * DOM-observable symptom that {@link isNodeNotActionableError} catches from
89
+ * the CDP-error side when a click is attempted anyway.
90
+ */
91
+ const IS_VISIBLE_EXPR = `((el) => {
92
+ const rect = el.getBoundingClientRect();
93
+ if (rect.width === 0 && rect.height === 0) return false;
94
+ const style = getComputedStyle(el);
95
+ return style.display !== "none" && style.visibility !== "hidden";
96
+ })`;
97
+ /**
98
+ * Computes an element's accessible name so an interactive control with no
99
+ * visible text — an `input`/`select`/`textarea`, which the DOM spec defines
100
+ * as always having empty `textContent` — still yields something
101
+ * `scoreCandidate` (`deep-locator-candidates.ts`) and the rephrase LLM can
102
+ * tell apart from every other candidate. Precedence mirrors the a11y-name
103
+ * convention `buildRankSubmitCandidatesExpr` (`submit-control.ts`) applies at
104
+ * its `aria-label`-over-`textContent` tier, extended with the sources a form
105
+ * control actually carries: `aria-label` → `aria-labelledby` (ids resolved
106
+ * against `root`) → an associated `label` (`label[for=id]`, or the nearest
107
+ * ancestor `label`) → `placeholder` → `title` → `alt` → non-empty
108
+ * `textContent` → `value` (only for `input[type=button|submit]`, where
109
+ * `value` IS the rendered label). `<select>` skips the `textContent` tier
110
+ * entirely — a `<select>`'s `textContent` is every `<option>`'s text
111
+ * concatenated ("United StatesCanadaMexico…"), which can accidentally
112
+ * substring-match an unrelated step instruction and outrank the
113
+ * correctly-labelled field; an unlabelled `<select>` falls through to `""`
114
+ * (the caller's `|| ""` contract) instead. `root`-relative so the generated
115
+ * code never captures an outer `document` — the same contract
116
+ * {@link buildScanFrameCandidatesExpr} itself honors.
117
+ */
118
+ function buildAccessibleNameExpr(root) {
119
+ return `((el) => {
120
+ const clean = (s) => {
121
+ const t = (s || "").trim();
122
+ return t.length > 0 ? t : null;
123
+ };
124
+ const attr = (name) => (el.getAttribute ? el.getAttribute(name) : null);
125
+ const ariaLabel = clean(attr("aria-label"));
126
+ if (ariaLabel) return ariaLabel;
127
+ const labelledBy = attr("aria-labelledby");
128
+ if (labelledBy) {
129
+ const getById = (elId) => (${root}.getElementById ? ${root}.getElementById(elId) : null);
130
+ const labelledText = clean(
131
+ labelledBy
132
+ .split(/\\s+/)
133
+ .map((elId) => {
134
+ const target = getById(elId);
135
+ return target ? target.textContent : "";
136
+ })
137
+ .join(" ")
138
+ );
139
+ if (labelledText) return labelledText;
140
+ }
141
+ const findLabelFor = (targetId) => {
142
+ if (!targetId || !${root}.querySelectorAll) return null;
143
+ const labels = Array.from(${root}.querySelectorAll("label"));
144
+ return (
145
+ labels.find((label) => label.getAttribute && label.getAttribute("for") === targetId) ||
146
+ null
147
+ );
148
+ };
149
+ const id = attr("id");
150
+ const associatedLabel = findLabelFor(id) || (el.closest ? el.closest("label") : null);
151
+ const labelText = associatedLabel ? clean(associatedLabel.textContent) : null;
152
+ if (labelText) return labelText;
153
+ const placeholder = clean(attr("placeholder"));
154
+ if (placeholder) return placeholder;
155
+ const title = clean(attr("title"));
156
+ if (title) return title;
157
+ const alt = clean(attr("alt"));
158
+ if (alt) return alt;
159
+ const tag = (el.tagName || "").toLowerCase();
160
+ if (tag !== "select") {
161
+ const text = clean(el.textContent);
162
+ if (text) return text;
163
+ }
164
+ const type = (attr("type") || "").toLowerCase();
165
+ if (tag === "input" && (type === "button" || type === "submit")) {
166
+ const value = clean(attr("value"));
167
+ if (value) return value;
168
+ }
169
+ return "";
170
+ })`;
171
+ }
172
+ /**
173
+ * Builds a self-contained evaluate expression that queries `root` for every
174
+ * match of `innerSelector` via `document.querySelectorAll` — the SAME
175
+ * resolution Stagehand's own primary CSS resolver uses
176
+ * (`resolveCssSelector(sel, i) = querySelectorAll(sel)[i]`, in the frame's
177
+ * own document) — and returns one entry per match, in document order:
178
+ * `{ index, text, visible }`. `index` therefore lines up 1:1 with the index
179
+ * `deepLocator(hop).nth(index)` will later resolve to, so a caller can scan
180
+ * once here and hand an index to the existing per-index click path without
181
+ * re-deriving the candidate set.
182
+ *
183
+ * That alignment diverges only when the light-DOM query matches fewer than
184
+ * `i + 1` elements, which is what triggers Stagehand's shadow-piercing
185
+ * `resolveCssSelectorPierce` fallback instead of the plain CSS resolver —
186
+ * `innerSelector` should stay a plain, non-shadow-crossing selector (e.g.
187
+ * {@link INTERACTIVE_CANDIDATE_SELECTOR}) whenever a caller depends on index
188
+ * alignment with `deepLocator`.
189
+ *
190
+ * `root` overrides the traversal root expression (default `"document"`),
191
+ * interpolated verbatim into the generated code so a caller evaluating this
192
+ * expression via `Frame.evaluate` still resolves that frame's own document —
193
+ * the expression never captures an outer `document` reference (mirrors
194
+ * `buildRankSubmitCandidatesExpr`'s contract in `submit-control.ts`).
195
+ */
196
+ function buildScanFrameCandidatesExpr(innerSelector, root = "document") {
197
+ return `(() => {
198
+ const isVisible = ${IS_VISIBLE_EXPR};
199
+ const accessibleName = ${buildAccessibleNameExpr(root)};
200
+ const matches = Array.from(${root}.querySelectorAll(${JSON.stringify(innerSelector)}));
201
+ return matches.map((el, index) => ({
202
+ index,
203
+ text: accessibleName(el),
204
+ visible: isVisible(el),
205
+ }));
206
+ })()`;
207
+ }
208
+ /**
209
+ * Builds a self-contained evaluate expression that re-runs the SAME
210
+ * `root.querySelectorAll(innerSelector)` resolution {@link buildScanFrameCandidatesExpr}
211
+ * uses and clicks the element at `index` (dispatching focus + bubbling
212
+ * mousedown/mouseup/click, matching `buildClickByDeepIndexExpr`'s
213
+ * (`submit-control.ts`) controlled-state click convention). This is the
214
+ * one-round-trip actuation half of the batched-scan fix: a caller that
215
+ * scanned via {@link buildScanFrameCandidatesExpr} can hand the chosen
216
+ * `index` straight to this builder without re-deriving the candidate set,
217
+ * because both builders resolve `querySelectorAll(innerSelector)` against
218
+ * the same frame document in the same match order.
219
+ *
220
+ * Reports out-of-range / not-actionable outcomes as data rather than by
221
+ * throwing, mirroring {@link isNodeNotActionableError}'s CDP-error-side
222
+ * contract from the DOM-observable side:
223
+ * - `index` no longer matches (e.g. the DOM changed between scan and click)
224
+ * returns `{ clicked: false, reason: "out-of-range" }`.
225
+ * - the matched element fails {@link IS_VISIBLE_EXPR} — the same
226
+ * visibility check {@link buildScanFrameCandidatesExpr} reports as
227
+ * `visible: false` — returns `{ clicked: false, reason: "not-actionable" }`
228
+ * instead of dispatching a click a real browser would reject with a
229
+ * `-32000 Node does not have a layout object` CDP error.
230
+ *
231
+ * `root` overrides the traversal root expression (default `"document"`) and
232
+ * must match the `root` passed to the {@link buildScanFrameCandidatesExpr}
233
+ * call that produced `index`, or the re-run query will not resolve against
234
+ * the same document. Interpolated verbatim so a caller evaluating this
235
+ * expression via `Frame.evaluate` still resolves that frame's own document —
236
+ * the expression never captures an outer `document` reference.
237
+ */
238
+ function buildClickFrameCandidateExpr(innerSelector, index, root = "document") {
239
+ return `(() => {
240
+ const isVisible = ${IS_VISIBLE_EXPR};
241
+ const matches = Array.from(${root}.querySelectorAll(${JSON.stringify(innerSelector)}));
242
+ const el = matches[${JSON.stringify(index)}];
243
+ if (!el) return { clicked: false, reason: "out-of-range" };
244
+ if (!isVisible(el)) return { clicked: false, reason: "not-actionable" };
245
+ if (typeof el.focus === "function") { try { el.focus(); } catch (e) {} }
246
+ el.dispatchEvent(new Event("mousedown", { bubbles: true }));
247
+ el.dispatchEvent(new Event("mouseup", { bubbles: true }));
248
+ el.dispatchEvent(new Event("click", { bubbles: true }));
249
+ return { clicked: true };
250
+ })()`;
251
+ }
252
+ /**
253
+ * Builds the write body {@link buildFillFrameCandidateExpr} and
254
+ * {@link buildSelectFrameCandidateExpr} both interpolate: re-runs the SAME
255
+ * `root.querySelectorAll(innerSelector)` resolution
256
+ * {@link buildScanFrameCandidatesExpr}/{@link buildClickFrameCandidateExpr}
257
+ * use, guards out-of-range/not-actionable the same way as
258
+ * {@link buildClickFrameCandidateExpr}, then writes `value` through
259
+ * `nativePrototypeExpr`'s `value` setter descriptor rather than a bare
260
+ * `el.value = value` assignment — a React/Angular/Vue controlled component
261
+ * shadows the setter at the instance level, so a bare assignment can be
262
+ * silently absorbed by the framework's own value tracking while the DOM
263
+ * read-back still looks correct (a silent false positive); calling the
264
+ * descriptor's setter explicitly restores native behavior, mirroring
265
+ * `fillHtml5DateTimeInput`'s and `applySelectValue`'s identical workaround in
266
+ * `flow-runner.ts`. Falls back to a bare assignment when no such descriptor
267
+ * exists (a plain, unmanaged form control). Dispatches bubbling `input` then
268
+ * `change` then `blur` — the sequence a controlled component's `onChange`/
269
+ * `onBlur` listen for — before reading `el.value` back into the returned
270
+ * payload, so the caller gets write + verify in one round-trip instead of a
271
+ * second `inputValue()` call.
272
+ *
273
+ * `nativePrototypeExpr` is interpolated verbatim as a JS expression
274
+ * evaluated inside the generated code (with `el` in scope) rather than
275
+ * passed as a resolved value, so {@link buildFillFrameCandidateExpr} can
276
+ * choose between `HTMLInputElement`/`HTMLTextAreaElement` based on the
277
+ * resolved element's own tag at evaluate-time.
278
+ */
279
+ function buildWriteFrameCandidateExpr(innerSelector, index, value, nativePrototypeExpr, root) {
280
+ return `(() => {
281
+ const isVisible = ${IS_VISIBLE_EXPR};
282
+ const matches = Array.from(${root}.querySelectorAll(${JSON.stringify(innerSelector)}));
283
+ const el = matches[${JSON.stringify(index)}];
284
+ if (!el) return { written: false, reason: "out-of-range" };
285
+ if (!isVisible(el)) return { written: false, reason: "not-actionable" };
286
+ const value = ${JSON.stringify(value)};
287
+ const descriptor = Object.getOwnPropertyDescriptor(${nativePrototypeExpr}, "value");
288
+ if (descriptor && descriptor.set) { descriptor.set.call(el, value); } else { el.value = value; }
289
+ el.dispatchEvent(new Event("input", { bubbles: true }));
290
+ el.dispatchEvent(new Event("change", { bubbles: true }));
291
+ el.dispatchEvent(new Event("blur", { bubbles: true }));
292
+ return { written: true, readBack: el.value };
293
+ })()`;
294
+ }
295
+ /**
296
+ * Builds a self-contained evaluate expression that fills the `<input>`/
297
+ * `<textarea>` at `root.querySelectorAll(innerSelector)[index]` with
298
+ * `value` — the one-round-trip write half of
299
+ * {@link fillDeepLocatorCandidate}'s (`deep-locator-actuate.ts`) contract.
300
+ * See {@link buildWriteFrameCandidateExpr} for the shared write/dispatch/
301
+ * read-back mechanism and out-of-range/not-actionable reporting. Resolves
302
+ * the native value-setter descriptor off `HTMLTextAreaElement.prototype`
303
+ * when the matched element's own `tagName` is `"textarea"`, else
304
+ * `HTMLInputElement.prototype` — determined at evaluate-time (inside the
305
+ * generated code), since `innerSelector` may match a mix of input and
306
+ * textarea nodes (e.g. {@link INTERACTIVE_CANDIDATE_SELECTOR}).
307
+ *
308
+ * `root` overrides the traversal root expression (default `"document"`) and
309
+ * must match the `root` a prior {@link buildScanFrameCandidatesExpr} call
310
+ * used to derive `index`, mirroring
311
+ * {@link buildClickFrameCandidateExpr}'s `root` contract.
312
+ */
313
+ function buildFillFrameCandidateExpr(innerSelector, index, value, root = "document") {
314
+ const nativePrototypeExpr = '(el.tagName && el.tagName.toLowerCase() === "textarea" ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype)';
315
+ return buildWriteFrameCandidateExpr(innerSelector, index, value, nativePrototypeExpr, root);
316
+ }
317
+ /**
318
+ * Builds a self-contained evaluate expression that re-runs the SAME
319
+ * `root.querySelectorAll(innerSelector)` resolution and selects, on the
320
+ * `<select>`-shaped candidate at `index`, the option whose `value` — or,
321
+ * failing that, whose trimmed visible label — matches `value`. Matching by
322
+ * value first mirrors what a `<select>`'s own `value` property actually is
323
+ * (the matched option's `value` attribute); falling back to the trimmed
324
+ * label covers a flow instruction that quotes the option's VISIBLE label
325
+ * instead (`parseSelectStep`), which a `<select>`'s DOM `value` never is —
326
+ * the same value-then-label tolerance `trySelectPrimitive`'s deterministic
327
+ * match already applies across every `<select>` on the page
328
+ * (`flow-runner.ts:3576`), reproduced here inline because this expression
329
+ * matches options within one already-chosen candidate rather than
330
+ * enumerating the whole page. See {@link buildWriteFrameCandidateExpr} for
331
+ * the write/dispatch/read-back mechanism this reimplements to accommodate
332
+ * the option lookup — the shared helper alone can't express "write the
333
+ * MATCHED option's value, not the caller's raw string."
334
+ *
335
+ * `root` overrides the traversal root expression (default `"document"`) and
336
+ * must match the `root` a prior {@link buildScanFrameCandidatesExpr} call
337
+ * used to derive `index`, mirroring
338
+ * {@link buildClickFrameCandidateExpr}'s `root` contract. A candidate with
339
+ * no option matching `value` by either value or label reports
340
+ * `{ written: false, reason: "not-actionable" }` — nothing on the page can
341
+ * satisfy the write.
342
+ */
343
+ function buildSelectFrameCandidateExpr(innerSelector, index, value, root = "document") {
344
+ return `(() => {
345
+ const isVisible = ${IS_VISIBLE_EXPR};
346
+ const matches = Array.from(${root}.querySelectorAll(${JSON.stringify(innerSelector)}));
347
+ const el = matches[${JSON.stringify(index)}];
348
+ if (!el) return { written: false, reason: "out-of-range" };
349
+ if (!isVisible(el)) return { written: false, reason: "not-actionable" };
350
+ const wanted = ${JSON.stringify(value)};
351
+ const wantedTrimmed = wanted.trim();
352
+ const options = Array.from(el.options || []);
353
+ const target =
354
+ options.find((o) => o.value === wanted) ||
355
+ options.find((o) => (o.textContent || "").trim() === wantedTrimmed);
356
+ if (!target) return { written: false, reason: "not-actionable" };
357
+ const descriptor = Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype, "value");
358
+ if (descriptor && descriptor.set) { descriptor.set.call(el, target.value); } else { el.value = target.value; }
359
+ el.dispatchEvent(new Event("input", { bubbles: true }));
360
+ el.dispatchEvent(new Event("change", { bubbles: true }));
361
+ el.dispatchEvent(new Event("blur", { bubbles: true }));
362
+ return { written: true, readBack: el.value };
363
+ })()`;
364
+ }
365
+ /**
366
+ * Detects the CDP "node is not actionable" failure shape so a caller
367
+ * cascading through candidates can treat it as "skip this candidate, try the
368
+ * next" instead of scoring the attempt as a failed click. Covers both
369
+ * surfaces a click against an unrendered node can throw through: a raw CDP
370
+ * protocol error rejects as `new Error(`${code} ${message}`)`
371
+ * (understudy/cdp.js), so a `DOM.getBoxModel`/`DOM.scrollIntoViewIfNeeded`
372
+ * failure over "no layout object" arrives as a plain `Error` whose message is
373
+ * exactly `-32000 Node does not have a layout object`; separately,
374
+ * `Locator.click()`/`Locator.hover()` (understudy/locator.js) throw
375
+ * `ElementNotVisibleError` directly when `DOM.getBoxModel` returns no model.
376
+ * A name/substring check on both shapes is sufficient — no new error class
377
+ * is needed.
378
+ */
379
+ function isNodeNotActionableError(error) {
380
+ if (!(error instanceof Error))
381
+ return false;
382
+ if (error.name === "ElementNotVisibleError")
383
+ return true;
384
+ return /node does not have a layout object/i.test(error.message);
385
+ }
386
+ //# sourceMappingURL=deep-locator-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-locator-scan.js","sourceRoot":"","sources":["../../src/scraper/deep-locator-scan.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;;;;;;;;AAEH;;;;;;;;GAQG;AACU,QAAA,8BAA8B,GACzC,+DAA+D,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACU,QAAA,sCAAsC,GAAG,KAAK,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,eAAe,GAAG;;;;;GAKrB,CAAC;AAEJ;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,uBAAuB,CAAC,IAAY;IAC3C,OAAO;;;;;;;;;;mCAU0B,IAAI,qBAAqB,IAAI;;;;;;;;;;;;;0BAatC,IAAI;kCACI,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2BjC,CAAC;AACN,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,sCAA6C,aAAqB,EAAE,IAAI,GAAG,UAAU;IACnF,OAAO;wBACe,eAAe;6BACV,uBAAuB,CAAC,IAAI,CAAC;iCACzB,IAAI,qBAAqB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;;;;;;OAMhF,CAAC;AACR,CAAC;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,sCACE,aAAqB,EACrB,KAAa,EACb,IAAI,GAAG,UAAU;IAEjB,OAAO;wBACe,eAAe;iCACN,IAAI,qBAAqB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;yBAC9D,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;;;;;;;;OAQvC,CAAC;AACR,CAAC;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,4BAA4B,CACnC,aAAqB,EACrB,KAAa,EACb,KAAa,EACb,mBAA2B,EAC3B,IAAY;IAEZ,OAAO;wBACe,eAAe;iCACN,IAAI,qBAAqB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;yBAC9D,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;;;oBAG1B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;yDACgB,mBAAmB;;;;;;OAMrE,CAAC;AACR,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qCACE,aAAqB,EACrB,KAAa,EACb,KAAa,EACb,IAAI,GAAG,UAAU;IAEjB,MAAM,mBAAmB,GACvB,sHAAsH,CAAC;IACzH,OAAO,4BAA4B,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC;AAC9F,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uCACE,aAAqB,EACrB,KAAa,EACb,KAAa,EACb,IAAI,GAAG,UAAU;IAEjB,OAAO;wBACe,eAAe;iCACN,IAAI,qBAAqB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;yBAC9D,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;;;qBAGzB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;;;;;;;;;;;;;OAanC,CAAC;AACR,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,kCAAyC,KAAc;IACrD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,CAAC,IAAI,KAAK,wBAAwB;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,qCAAqC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACnE,CAAC"}