browser-debugger-cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -9,25 +9,97 @@
9
9
  * {@link BOUND_TARGET_SELECTOR}.
10
10
  */
11
11
  import { CommandError } from '../../errors/index.js';
12
- import { staleNodeError } from '../../errors/messages.js';
12
+ import { emptySelectorError, indexOutOfRangeError, noNodesFoundError, staleNodeError, } from '../../errors/messages.js';
13
+ import { frameScopedConnection } from './frameScopedConnection.js';
14
+ import { createLogger } from '../../ui/logging/index.js';
15
+ import { getErrorMessage } from '../../utils/errors.js';
13
16
  import { EXIT_CODES } from '../../utils/exitCodes.js';
14
- import { parseSelectorFilters } from '../../utils/selectorFilters.js';
17
+ import { parseSelectorFilters, } from '../../utils/selectorFilters.js';
15
18
  /** Selector placeholder that makes page scripts use the bound node. */
16
19
  export const BOUND_TARGET_SELECTOR = '__bdg_bound_target__';
20
+ /** Removes the node bound for index-based commands from the window it was stored on */
21
+ export const UNBIND_TARGET_SCRIPT = 'delete window.__bdgTarget';
22
+ const log = createLogger('dom');
17
23
  /**
18
- * Page-side test of one text or visibility filter ({@link SelectorFilter}).
19
- * Visible means a non-empty box and `visibility: visible`, as in Playwright
20
- * (`opacity: 0` still counts as visible). Text is the rendered text (`innerText`, `textContent` for elements without
21
- * it) with whitespace collapsed; filter texts arrive normalized (`has-text`
22
- * lowercased).
24
+ * Page-side matching of filters ({@link SelectorFilter}) and scoped steps
25
+ * ({@link ScopedStep}): called with the open shadow roots of the page, returns
26
+ * `{ passesAll, descend }`.
27
+ *
28
+ * Visible means rendered (`checkVisibility()`: not inside a closed
29
+ * `<details>` or under `content-visibility: hidden`), a non-empty box and
30
+ * `visibility: visible`, as in Playwright (`opacity: 0` still counts as
31
+ * visible). Text is the rendered text (`innerText`) of visible elements and
32
+ * the text nodes of hidden ones (display or visibility; not those of
33
+ * `<script>`, `<style>` or `<noscript>`), so text filters match hidden
34
+ * elements like Playwright's and `:visible` decides visibility; button inputs
35
+ * use their value. Whitespace is collapsed; filter texts arrive normalized
36
+ * (`has-text` lowercased). `:visible` is checked before the text filters,
37
+ * which read the text.
38
+ *
39
+ * Steps and `:has()` match CSS relative to an element (`:scope > css`). A
40
+ * descendant step also searches the open shadow roots under the element (the
41
+ * whole step CSS inside one shadow tree); a child step stays in the
42
+ * element's own tree.
23
43
  */
24
- const FILTER_MATCHES_JS = `(el, filter) => {
25
- if (filter.kind === 'visible') {
26
- const rect = el.getBoundingClientRect();
27
- return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';
28
- }
29
- const text = (typeof el.innerText === 'string' ? el.innerText : el.textContent || '').replace(/\\s+/g, ' ').trim();
30
- return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);
44
+ export const FILTER_MATCHING_JS = `(shadowRoots) => {
45
+ const skipped = /^(script|style|noscript|template)$/;
46
+ const hiddenText = (el) => {
47
+ const walker = el.ownerDocument.createTreeWalker(el, 5, {
48
+ acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)
49
+ });
50
+ let text = '';
51
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
52
+ if (node.nodeType === 3) text += node.data;
53
+ }
54
+ return text;
55
+ };
56
+ const textOf = (el) => {
57
+ if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;
58
+ const rendered = typeof el.innerText === 'string' &&
59
+ (typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));
60
+ return (rendered ? el.innerText : hiddenText(el)).replace(/\\s+/g, ' ').trim();
61
+ };
62
+ const passes = (el, filter) => {
63
+ if (filter.kind === 'visible') {
64
+ if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) return false;
65
+ const rect = el.getBoundingClientRect();
66
+ return rect.width > 0 && rect.height > 0 && el.ownerDocument.defaultView.getComputedStyle(el).visibility === 'visible';
67
+ }
68
+ if (filter.kind === 'has') return filter.selectors.some((chain) => descend(el, chain).length > 0);
69
+ const text = textOf(el);
70
+ return filter.kind === 'text-is' ? text === filter.text : text.toLowerCase().includes(filter.text);
71
+ };
72
+ const passesAll = (el, filters) =>
73
+ filters.every((filter) => filter.kind !== 'visible' || passes(el, filter)) &&
74
+ filters.every((filter) => filter.kind === 'visible' || passes(el, filter));
75
+ const isUnder = (scope, node) => {
76
+ for (let current = node; current; current = current.getRootNode().host) {
77
+ if (scope === current || scope.contains(current)) return true;
78
+ }
79
+ return false;
80
+ };
81
+ const stepMatches = (scope, step) => {
82
+ const matches = [...scope.querySelectorAll(':scope ' + step.combinator + ' ' + step.css)];
83
+ if (step.combinator !== ' ') return matches;
84
+ for (const root of shadowRoots) {
85
+ if (isUnder(scope, root.host)) matches.push(...root.querySelectorAll(step.css));
86
+ }
87
+ return matches;
88
+ };
89
+ const descend = (scope, steps) => {
90
+ let current = [scope];
91
+ for (const step of steps) {
92
+ const next = new Set();
93
+ for (const el of current) {
94
+ for (const match of stepMatches(el, step)) {
95
+ if (passesAll(match, step.filters)) next.add(match);
96
+ }
97
+ }
98
+ current = [...next];
99
+ }
100
+ return current;
101
+ };
102
+ return { passesAll: passesAll, descend: descend };
31
103
  }`;
32
104
  /**
33
105
  * Page-side selector search that also reaches into open shadow roots and
@@ -35,15 +107,19 @@ const FILTER_MATCHES_JS = `(el, filter) => {
35
107
  *
36
108
  * Takes the selector and, when it has text or visibility filters, its parts
37
109
  * from {@link parseSelectorFilters}: their CSS runs as one selector list and
38
- * a match is kept when it passes the filters of a part it matches.
39
- * Matches of the document come first, then those of each shadow root and
40
- * frame in document order. Cross-origin iframes are separate processes and
41
- * cannot be searched. Throws a `SyntaxError` for an invalid selector.
110
+ * a match is kept when it passes the filters of a part it matches; a part
111
+ * with scoped steps contributes the steps' matches under it instead (each
112
+ * element once). Matches come in document order: those of the document
113
+ * first, then those of each shadow root and frame in the order they are
114
+ * reached. Cross-origin iframes are separate processes and cannot be
115
+ * searched. Throws a `SyntaxError` for an invalid selector.
42
116
  */
43
117
  export const DEEP_QUERY_JS = `function (selector, parts) {
44
118
  const css = parts ? parts.map((part) => part.css).join(', ') : selector;
45
119
  const found = [];
120
+ const roots = [];
46
121
  const visit = (root) => {
122
+ roots.push(root);
47
123
  for (const match of root.querySelectorAll(css)) found.push(match);
48
124
  for (const el of root.querySelectorAll('*')) {
49
125
  if (el.shadowRoot) visit(el.shadowRoot);
@@ -56,8 +132,20 @@ export const DEEP_QUERY_JS = `function (selector, parts) {
56
132
  };
57
133
  visit(document);
58
134
  if (!parts) return found;
59
- const passes = ${FILTER_MATCHES_JS};
60
- return found.filter((el) => parts.some((part) => el.matches(part.css) && part.filters.every((filter) => passes(el, filter))));
135
+ const shadowRoots = roots.filter((root) => root.host);
136
+ const { passesAll, descend } = (${FILTER_MATCHING_JS})(shadowRoots);
137
+ const result = new Set();
138
+ for (const el of found) {
139
+ for (const part of parts) {
140
+ if (!el.matches(part.css) || !passesAll(el, part.filters)) continue;
141
+ for (const match of descend(el, part.steps || [])) result.add(match);
142
+ }
143
+ }
144
+ const rootOrder = new Map(roots.map((root, i) => [root, i]));
145
+ const rootIndex = (el) => rootOrder.get(el.getRootNode()) ?? roots.length;
146
+ return [...result].sort((a, b) =>
147
+ rootIndex(a) - rootIndex(b) || (a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1)
148
+ );
61
149
  }`;
62
150
  /**
63
151
  * Page-side element lookup shared by the interaction scripts.
@@ -72,6 +160,45 @@ export const FIND_ELEMENTS_JS = `function (selector, parts) {
72
160
  }
73
161
  return (${DEEP_QUERY_JS})(selector, parts);
74
162
  }`;
163
+ /**
164
+ * Page-side check of what selectors cannot search in the top document:
165
+ * iframes whose document cannot be read (cross-origin) and `<object>`/`<embed>`
166
+ * elements. Cheap: no frame is entered.
167
+ */
168
+ export const UNSEARCHED_CONTENT_JS = `(() => {
169
+ const frames = Array.from(document.querySelectorAll('iframe, frame'));
170
+ const unreadable = (frame) => {
171
+ try { return !frame.contentDocument; } catch (e) { return true; }
172
+ };
173
+ return { crossOriginFrames: frames.some(unreadable), embeds: !!document.querySelector('object, embed') };
174
+ })()`;
175
+ /** Elements whose ids or classes are read for "did you mean" (bounds the work on huge pages) */
176
+ const NAME_SCAN_LIMIT = 5000;
177
+ /**
178
+ * Page-side ids (`kind` "id") or classes (`kind` "class") in the top
179
+ * document, from the first {@link NAME_SCAN_LIMIT} elements that have one.
180
+ *
181
+ * @param kind - Which names to read
182
+ * @returns Expression evaluating to the distinct names
183
+ */
184
+ export function pageNamesJS(kind) {
185
+ return `(() => {
186
+ const names = new Set();
187
+ const elements = document.querySelectorAll('[${kind}]');
188
+ for (let i = 0; i < elements.length && i < ${NAME_SCAN_LIMIT}; i++) {
189
+ const el = elements[i];
190
+ ${kind === 'id' ? 'if (el.id) names.add(el.id);' : 'for (const name of el.classList) names.add(name);'}
191
+ }
192
+ return Array.from(names);
193
+ })()`;
194
+ }
195
+ /**
196
+ * Page-side: the form control a `<label>` stands for (`label.control`: its
197
+ * `for` target or the control inside it), or null for other elements and
198
+ * labels without one. Fill, click and key presses act on that control, like
199
+ * Playwright.
200
+ */
201
+ export const LABEL_CONTROL_JS = `(el) => (el && el.localName === 'label' ? el.control : null)`;
75
202
  /**
76
203
  * Arguments for {@link DEEP_QUERY_JS} / {@link FIND_ELEMENTS_JS} and the page
77
204
  * scripts built on them: the selector as a JS string literal and its parts
@@ -79,29 +206,36 @@ export const FIND_ELEMENTS_JS = `function (selector, parts) {
79
206
  *
80
207
  * @param selector - Selector as the user gave it (or the bound-node placeholder)
81
208
  * @returns JS source of the two arguments, e.g. `"li:visible", [{...}]`
82
- * @throws CommandError (81) for a misplaced or malformed filter
209
+ * @throws CommandError (81) for an empty selector or a misplaced or malformed filter
83
210
  */
84
211
  export function selectorArgsJS(selector) {
212
+ if (selector.trim() === '') {
213
+ const err = emptySelectorError();
214
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
215
+ }
85
216
  return `${JSON.stringify(selector)}, ${JSON.stringify(parseSelectorFilters(selector))}`;
86
217
  }
87
218
  /**
88
219
  * Store the node for the page scripts. It runs in the node's own frame, while
89
- * the scripts run in the top page, so the node goes on the top window (always
90
- * reachable for the same-origin frames bdg can target).
220
+ * the scripts run in the top page, so the node goes on the top window, which
221
+ * same-origin frames can reach. A cross-origin frame cannot write to the top
222
+ * window: the node then goes on its own window (`"frame"`) and the scripts
223
+ * run in that frame ({@link frameScopedConnection}).
91
224
  */
92
225
  const BIND_FUNCTION = `function () {
93
- let host = window;
94
- try { host = window.top; host.__bdgTarget = this; } catch (e) { host = window; host.__bdgTarget = this; }
95
- return this.isConnected;
226
+ if (!this.isConnected) return false;
227
+ try { window.top.__bdgTarget = this; return 'top'; } catch (e) { window.__bdgTarget = this; return 'frame'; }
96
228
  }`;
97
229
  /**
98
230
  * Make the node with `backendNodeId` the target of the next page script.
99
231
  *
100
232
  * @param cdp - CDP connection
101
233
  * @param backendNodeId - Backend node id from the query cache
234
+ * @returns Connection for the page scripts: the session's, or one running them
235
+ * in the node's frame when that frame is cross-origin
102
236
  * @throws CommandError (exit 87) when the node no longer exists in the page
103
237
  */
104
- export async function bindTargetNode(cdp, backendNodeId) {
238
+ async function bindTargetNode(cdp, backendNodeId) {
105
239
  const err = staleNodeError();
106
240
  const stale = new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
107
241
  let objectId;
@@ -119,25 +253,88 @@ export async function bindTargetNode(cdp, backendNodeId) {
119
253
  functionDeclaration: BIND_FUNCTION,
120
254
  returnByValue: true,
121
255
  }));
122
- if (bound.result?.value !== true)
256
+ const scope = bound.result?.value;
257
+ if (scope === 'frame')
258
+ return frameScopedConnection(cdp, objectId);
259
+ if (scope !== 'top')
123
260
  throw stale;
261
+ return cdp;
124
262
  }
125
263
  /**
126
264
  * Resolve what a page script should target for an interaction request.
127
265
  *
128
266
  * @param cdp - CDP connection
129
267
  * @param params - Request with a selector (and optional index) or a backend node id
130
- * @returns Selector/index for the page script
268
+ * @returns Selector/index for the page script and the connection to run it on
131
269
  */
132
- export async function resolveScriptTarget(cdp, params) {
270
+ async function resolveScriptTarget(cdp, params) {
133
271
  if (params.backendNodeId === undefined) {
134
272
  return {
135
273
  selector: params.selector ?? '',
136
274
  ...(params.index !== undefined && { index: params.index }),
275
+ cdp,
137
276
  };
138
277
  }
139
- await bindTargetNode(cdp, params.backendNodeId);
140
- return { selector: BOUND_TARGET_SELECTOR };
278
+ return { selector: BOUND_TARGET_SELECTOR, cdp: await bindTargetNode(cdp, params.backendNodeId) };
279
+ }
280
+ /**
281
+ * Whether an error is about the bound node: the placeholder only appears in
282
+ * "not found" messages, which for a bound node mean it left the page.
283
+ *
284
+ * @param text - Error message
285
+ * @returns True when the bound node was not found
286
+ */
287
+ function boundNodeMissing(text) {
288
+ return text?.includes(BOUND_TARGET_SELECTOR) === true;
289
+ }
290
+ /**
291
+ * Run an interaction on the element a request targets. Results and errors
292
+ * never show the internal placeholder: a bound node the page scripts could
293
+ * not find is reported as stale (87), and results carry the user's selector.
294
+ *
295
+ * @param cdp - CDP connection
296
+ * @param params - Request with a selector (and optional index) or a backend node id
297
+ * @param work - The interaction, given the script target
298
+ * @returns The interaction's result
299
+ * @throws CommandError (87) when the bound node left the page during the action
300
+ */
301
+ export async function onScriptTarget(cdp, params, work) {
302
+ const err = staleNodeError();
303
+ let target;
304
+ try {
305
+ target = await resolveScriptTarget(cdp, params);
306
+ const result = withUserSelector(await work(target), params.selector);
307
+ if (!boundNodeMissing(result.error))
308
+ return result;
309
+ return {
310
+ ...result,
311
+ error: err.message,
312
+ suggestion: err.suggestion,
313
+ exitCode: EXIT_CODES.STALE_CACHE,
314
+ };
315
+ }
316
+ catch (error) {
317
+ if (!(error instanceof CommandError) || !boundNodeMissing(error.message))
318
+ throw error;
319
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
320
+ }
321
+ finally {
322
+ if (target && target.cdp !== cdp)
323
+ unbindInFrame(target.cdp);
324
+ }
325
+ }
326
+ /**
327
+ * Remove a node bound in a cross-origin frame from that frame's window
328
+ * (through the scoped connection, so the script runs where the bind ran; the
329
+ * session removes the one bound on the top window after every interaction).
330
+ * Not waited for.
331
+ *
332
+ * @param frameConnection - Connection scoped to the node's frame
333
+ */
334
+ function unbindInFrame(frameConnection) {
335
+ void frameConnection
336
+ .send('Runtime.evaluate', { expression: UNBIND_TARGET_SCRIPT })
337
+ .catch((error) => log.debug(`Frame target not unbound: ${getErrorMessage(error)}`));
141
338
  }
142
339
  /**
143
340
  * Report the user's selector instead of the internal placeholder.
@@ -146,9 +343,28 @@ export async function resolveScriptTarget(cdp, params) {
146
343
  * @param selector - Selector the user gave (or the cached query's selector)
147
344
  * @returns Result with the placeholder replaced
148
345
  */
149
- export function withUserSelector(result, selector) {
346
+ function withUserSelector(result, selector) {
150
347
  if (result.selector !== BOUND_TARGET_SELECTOR)
151
348
  return result;
152
349
  return { ...result, ...(selector !== undefined && { selector }) };
153
350
  }
351
+ /**
352
+ * Error for an element a command could not find.
353
+ *
354
+ * @param target - Selector (and index) or backend node id the command was given
355
+ * @param matchCount - Elements the selector matched
356
+ * @returns Stale (87), out of range (81) or not found (83) error
357
+ */
358
+ export function missingElementError(target, matchCount) {
359
+ if (target.backendNodeId !== undefined) {
360
+ const err = staleNodeError();
361
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
362
+ }
363
+ if (matchCount > 0) {
364
+ const err = indexOutOfRangeError(target.index ?? 0, matchCount - 1);
365
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
366
+ }
367
+ const err = noNodesFoundError(target.selector);
368
+ return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
369
+ }
154
370
  //# sourceMappingURL=targetNode.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `bdg dom wait`: wait in the page until elements appear, become visible,
3
+ * contain a text or are gone, and/or the page has loaded.
4
+ *
5
+ * The page watches itself (a MutationObserver plus a short poll for style
6
+ * and frame changes the observer cannot see) and answers as soon as what it
7
+ * shows changes; the condition is decided here ({@link isWaitConditionMet}).
8
+ * Each page call lasts at most {@link WAIT_SLICE_MS}, so a navigation during
9
+ * the wait (the page's context is destroyed) only costs a retry on the new
10
+ * document. `--gone` is confirmed by a second snapshot of the same settled
11
+ * document, so the empty document right after a navigation does not count.
12
+ */
13
+ import type { DomWaitCommand, DomWaitData } from '../../ipc/protocol/commands.js';
14
+ import type { CDPSender } from '../../telemetry/objectExpander.js';
15
+ /**
16
+ * Wait until the page meets the condition.
17
+ *
18
+ * @param cdp - CDP connection
19
+ * @param params - Condition and timeout
20
+ * @returns What the page showed when it was met, and how long it took
21
+ * @throws CommandError (81) for an invalid selector, (102) when the timeout passed;
22
+ * CDP errors other than a lost page context (a closed connection) as they are
23
+ */
24
+ export declare function waitForCondition(cdp: CDPSender, params: DomWaitCommand): Promise<DomWaitData>;
25
+ //# sourceMappingURL=wait.d.ts.map
@@ -0,0 +1,199 @@
1
+ /**
2
+ * `bdg dom wait`: wait in the page until elements appear, become visible,
3
+ * contain a text or are gone, and/or the page has loaded.
4
+ *
5
+ * The page watches itself (a MutationObserver plus a short poll for style
6
+ * and frame changes the observer cannot see) and answers as soon as what it
7
+ * shows changes; the condition is decided here ({@link isWaitConditionMet}).
8
+ * Each page call lasts at most {@link WAIT_SLICE_MS}, so a navigation during
9
+ * the wait (the page's context is destroyed) only costs a retry on the new
10
+ * document. `--gone` is confirmed by a second snapshot of the same settled
11
+ * document, so the empty document right after a navigation does not count.
12
+ */
13
+ import { CommandError } from '../../errors/index.js';
14
+ import { invalidSelectorError, waitTimeoutError } from '../../errors/messages.js';
15
+ import { isContextLostError } from './evalHelpers.js';
16
+ import { DEEP_QUERY_JS, FILTER_MATCHING_JS, selectorArgsJS } from './targetNode.js';
17
+ import { isWaitConditionMet, needsGoneConfirmation, normalizeWaitText, } from './waitCondition.js';
18
+ import { createLogger } from '../../ui/logging/index.js';
19
+ import { delay, raceTimeout } from '../../utils/async.js';
20
+ import { getErrorMessage } from '../../utils/errors.js';
21
+ import { EXIT_CODES } from '../../utils/exitCodes.js';
22
+ import { filterDefined } from '../../utils/objects.js';
23
+ import { parseSelectorFilters } from '../../utils/selectorFilters.js';
24
+ const log = createLogger('dom');
25
+ /** Longest a single page call waits for a change */
26
+ const WAIT_SLICE_MS = 2000;
27
+ /** Pause before asking a page that could not answer (navigating) again */
28
+ const RETRY_DELAY_MS = 100;
29
+ /** Pause before the snapshot that confirms elements are gone */
30
+ const GONE_CONFIRM_DELAY_MS = 50;
31
+ /**
32
+ * Page-side: report what the page shows (matches, text matches, visible
33
+ * ones, readyState) as soon as it differs from `previous` (at once when
34
+ * `previous` is null), or after `sliceMs` without a change.
35
+ */
36
+ const WAIT_SNAPSHOT_JS = `async function (selector, parts, text, previous, sliceMs) {
37
+ const { passesAll } = (${FILTER_MATCHING_JS})([]);
38
+ const textFilter = text === null ? [] : [{ kind: 'has-text', text: text }];
39
+ const visibleFilter = [{ kind: 'visible' }];
40
+ const snapshot = () => {
41
+ const matches = selector === null ? [] : (${DEEP_QUERY_JS})(selector, parts);
42
+ const withText = matches.filter((el) => passesAll(el, textFilter));
43
+ return {
44
+ count: matches.length,
45
+ textCount: withText.length,
46
+ visibleCount: withText.filter((el) => passesAll(el, visibleFilter)).length,
47
+ readyState: document.readyState,
48
+ documentId: performance.timeOrigin
49
+ };
50
+ };
51
+ const changed = (s) => previous === null || s.count !== previous.count ||
52
+ s.textCount !== previous.textCount || s.visibleCount !== previous.visibleCount ||
53
+ s.readyState !== previous.readyState || s.documentId !== previous.documentId;
54
+ let current = snapshot();
55
+ if (changed(current)) return current;
56
+ return await new Promise((resolve) => {
57
+ let scheduled = null;
58
+ const observer = new MutationObserver(() => { if (scheduled === null) scheduled = setTimeout(check, 16); });
59
+ const poll = setInterval(check, 100);
60
+ const slice = setTimeout(finish, sliceMs);
61
+ function check() {
62
+ scheduled = null;
63
+ current = snapshot();
64
+ if (changed(current)) finish();
65
+ }
66
+ function finish() {
67
+ observer.disconnect();
68
+ clearInterval(poll);
69
+ clearTimeout(slice);
70
+ clearTimeout(scheduled);
71
+ document.removeEventListener('readystatechange', check);
72
+ resolve(current);
73
+ }
74
+ observer.observe(document, { childList: true, subtree: true, attributes: true, characterData: true });
75
+ document.addEventListener('readystatechange', check);
76
+ });
77
+ }`;
78
+ /**
79
+ * Wait until the page meets the condition.
80
+ *
81
+ * @param cdp - CDP connection
82
+ * @param params - Condition and timeout
83
+ * @returns What the page showed when it was met, and how long it took
84
+ * @throws CommandError (81) for an invalid selector, (102) when the timeout passed;
85
+ * CDP errors other than a lost page context (a closed connection) as they are
86
+ */
87
+ export async function waitForCondition(cdp, params) {
88
+ const condition = waitConditionOf(params);
89
+ const started = Date.now();
90
+ const deadline = started + params.timeout;
91
+ let last;
92
+ let confirming = false;
93
+ while (Date.now() < deadline) {
94
+ const snapshot = await nextSnapshot(cdp, condition, confirming ? null : (last ?? null), deadline);
95
+ if (!snapshot) {
96
+ confirming = false;
97
+ await delay(RETRY_DELAY_MS);
98
+ continue;
99
+ }
100
+ if (isWaitConditionMet(snapshot, condition, last)) {
101
+ return {
102
+ ...condition,
103
+ elapsedMs: Date.now() - started,
104
+ ...snapshotData(snapshot, condition),
105
+ };
106
+ }
107
+ last = snapshot;
108
+ confirming = needsGoneConfirmation(snapshot, condition);
109
+ if (confirming)
110
+ await delay(GONE_CONFIRM_DELAY_MS);
111
+ }
112
+ const err = waitTimeoutError(condition, last, params.timeout);
113
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.CDP_TIMEOUT);
114
+ }
115
+ /**
116
+ * The condition of a request (options that are off left out).
117
+ *
118
+ * @param params - Request
119
+ * @returns Condition
120
+ */
121
+ function waitConditionOf(params) {
122
+ return {
123
+ ...filterDefined({ selector: params.selector, text: params.text }),
124
+ ...(params.gone && { gone: true }),
125
+ ...(params.visible && { visible: true }),
126
+ ...(params.load && { load: true }),
127
+ };
128
+ }
129
+ /**
130
+ * The snapshot as reported: `textCount` only when a text was given.
131
+ *
132
+ * @param snapshot - What the page showed
133
+ * @param condition - What was waited for
134
+ * @returns Snapshot fields of the result
135
+ */
136
+ function snapshotData(snapshot, condition) {
137
+ return {
138
+ count: snapshot.count,
139
+ ...(condition.text !== undefined && { textCount: snapshot.textCount }),
140
+ visibleCount: snapshot.visibleCount,
141
+ readyState: snapshot.readyState,
142
+ };
143
+ }
144
+ /**
145
+ * Ask the page for its next snapshot.
146
+ *
147
+ * @param cdp - CDP connection
148
+ * @param condition - What is waited for
149
+ * @param previous - Last snapshot (null for the first call)
150
+ * @param deadline - When the wait ends (epoch ms)
151
+ * @returns The snapshot, or undefined when the page could not answer (its context was lost
152
+ * to a navigation, or the time ran out)
153
+ * @throws CommandError (81) for an invalid selector; other CDP errors (a closed connection) as they are
154
+ */
155
+ async function nextSnapshot(cdp, condition, previous, deadline) {
156
+ const remaining = deadline - Date.now();
157
+ const selectorArgs = condition.selector === undefined ? 'null, null' : selectorArgsJS(condition.selector);
158
+ const text = condition.text === undefined ? null : normalizeWaitText(condition.text);
159
+ const args = `${selectorArgs}, ${JSON.stringify(text)}, ${JSON.stringify(previous)}, ${Math.min(remaining, WAIT_SLICE_MS)}`;
160
+ const evaluated = cdp.send('Runtime.evaluate', {
161
+ expression: `(${WAIT_SNAPSHOT_JS})(${args})`,
162
+ awaitPromise: true,
163
+ returnByValue: true,
164
+ });
165
+ evaluated.catch(() => undefined);
166
+ const response = await raceTimeout(evaluated, remaining).catch((error) => {
167
+ if (!isContextLostError(error))
168
+ throw error;
169
+ log.debug(`dom wait: page context lost (${getErrorMessage(error)}), retrying`);
170
+ return undefined;
171
+ });
172
+ if (!response)
173
+ return undefined;
174
+ if (response.exceptionDetails)
175
+ return pageException(response.exceptionDetails, condition);
176
+ return response.result.value;
177
+ }
178
+ /**
179
+ * Handle an exception of the page script: an invalid selector is the
180
+ * user's; anything else (a document being replaced) is retried.
181
+ *
182
+ * @param details - Exception details
183
+ * @param condition - What is waited for
184
+ * @returns undefined (retry)
185
+ * @throws CommandError (81) for an invalid selector
186
+ */
187
+ function pageException(details, condition) {
188
+ const description = details.exception?.description ?? details.text;
189
+ if (condition.selector !== undefined && description.startsWith('SyntaxError')) {
190
+ const detail = parseSelectorFilters(condition.selector)
191
+ ? undefined
192
+ : description.split('\n')[0];
193
+ const err = invalidSelectorError(condition.selector, detail);
194
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
195
+ }
196
+ log.debug(`dom wait: page script failed (${description.split('\n')[0]}), retrying`);
197
+ return undefined;
198
+ }
199
+ //# sourceMappingURL=wait.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * What `bdg dom wait` waits for, and whether what the page shows meets it.
3
+ *
4
+ * The page reports a {@link WaitSnapshot} whenever it changes; the condition
5
+ * is decided here, in one place.
6
+ */
7
+ /** What to wait for */
8
+ export interface WaitCondition {
9
+ /** Selector (filters like :has-text and :visible allowed); absent when only waiting for the load */
10
+ selector?: string;
11
+ /** Text one of the matches must contain (case-insensitive, whitespace collapsed) */
12
+ text?: string;
13
+ /** Wait for the (visible, with `visible`) matches to be gone */
14
+ gone?: boolean;
15
+ /** Only count visible matches */
16
+ visible?: boolean;
17
+ /** Also wait for `document.readyState` to be `complete` */
18
+ load?: boolean;
19
+ }
20
+ /** What the page shows at one moment */
21
+ export interface WaitSnapshot {
22
+ /** Elements matching the selector */
23
+ count: number;
24
+ /** Of those, the ones containing the text (all of them without a text) */
25
+ textCount: number;
26
+ /** Of the text matches, the visible ones */
27
+ visibleCount: number;
28
+ /** `document.readyState` */
29
+ readyState: string;
30
+ /** The document's identity (`performance.timeOrigin`): a new document after a navigation has another */
31
+ documentId: number;
32
+ }
33
+ /**
34
+ * The text as the page compares it: whitespace collapsed, lower case (like `:has-text`).
35
+ *
36
+ * @param text - Text as given
37
+ * @returns Normalized text
38
+ */
39
+ export declare function normalizeWaitText(text: string): string;
40
+ /**
41
+ * The matches the condition counts: visible ones with `visible`, else those
42
+ * containing the text (all matches without a text).
43
+ *
44
+ * @param snapshot - What the page shows
45
+ * @param condition - What is waited for
46
+ * @returns Number of counted matches
47
+ */
48
+ export declare function countedMatches(snapshot: WaitSnapshot, condition: WaitCondition): number;
49
+ /**
50
+ * Whether the page shows what is waited for.
51
+ *
52
+ * Elements are gone only when two consecutive snapshots of the same
53
+ * document, neither of it still `loading`, show none: right after a
54
+ * navigation commits, the new document is empty for a moment.
55
+ *
56
+ * @param snapshot - What the page shows
57
+ * @param condition - What is waited for
58
+ * @param previous - The snapshot before it, if any
59
+ * @returns True when the elements are there (or gone with `gone`) and, with `load`, the page loaded
60
+ */
61
+ export declare function isWaitConditionMet(snapshot: WaitSnapshot, condition: WaitCondition, previous?: WaitSnapshot): boolean;
62
+ /**
63
+ * Whether a `--gone` wait should confirm this snapshot with another one: it
64
+ * shows no (counted) matches in a document past `loading`.
65
+ *
66
+ * @param snapshot - What the page shows
67
+ * @param condition - What is waited for
68
+ * @returns True when one more snapshot of the same document may meet the condition
69
+ */
70
+ export declare function needsGoneConfirmation(snapshot: WaitSnapshot, condition: WaitCondition): boolean;
71
+ //# sourceMappingURL=waitCondition.d.ts.map