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
@@ -10,18 +10,21 @@
10
10
  * roots and same-origin iframes, like a user sees the page.
11
11
  */
12
12
  import { CommandError } from '../../../errors/index.js';
13
- import { noNodesFoundError, indexOutOfRangeError, eitherArgumentRequiredError, invalidSelectorError, nodeIdNotFoundError, staleNodeError, } from '../../../errors/messages.js';
13
+ import { noNodesFoundError, indexOutOfRangeError, eitherArgumentRequiredError, invalidSelectorError, nodeIdNotFoundError, operationFailedError, similarSelectorsLine, staleNodeError, } from '../../../errors/messages.js';
14
14
  import { callCDP } from '../../../ipc/client.js';
15
- import { DEEP_QUERY_JS, selectorArgsJS } from '../../../runtime/dom/targetNode.js';
16
- import { resolveA11yNode } from '../../../telemetry/a11y.js';
15
+ import { ELEMENT_GEOMETRY_JS, VIEWPORT_SIZE_JS, classifyViewportPosition, } from '../../../runtime/dom/elementGeometry.js';
16
+ import { ELEMENT_CONTEXT_JS, ELEMENT_TEXT_JS, ELEMENT_TEXT_LENGTH, textPreview, } from '../../../runtime/dom/elementInfo.js';
17
+ import { DEEP_QUERY_JS, UNSEARCHED_CONTENT_JS, pageNamesJS, selectorArgsJS, } from '../../../runtime/dom/targetNode.js';
17
18
  import { createLogger } from '../../../ui/logging/index.js';
19
+ import { sessionCommand } from '../../../ui/messages/sessionCommand.js';
18
20
  import { ConcurrencyLimiter } from '../../../utils/concurrency.js';
21
+ import { getErrorMessage } from '../../../utils/errors.js';
19
22
  import { EXIT_CODES } from '../../../utils/exitCodes.js';
23
+ import { parseSelectorFilters, withoutVisibleFilters } from '../../../utils/selectorFilters.js';
24
+ import { findSimilarNames, parseSingleNameSelector } from '../../../utils/suggestions.js';
20
25
  const log = createLogger('dom');
21
26
  /** Maximum concurrent CDP calls to avoid overwhelming the connection. */
22
27
  const CDP_CONCURRENCY_LIMIT = 10;
23
- /** Length of the text preview shown for queried elements. */
24
- const PREVIEW_LENGTH = 80;
25
28
  /**
26
29
  * Convert CDP's flat attribute list to a record.
27
30
  *
@@ -63,33 +66,6 @@ async function getOuterHTML(ref) {
63
66
  const response = await callCDP('DOM.getOuterHTML', ref);
64
67
  return response.data?.result?.outerHTML;
65
68
  }
66
- /**
67
- * Page-side text of an element as a user sees it: `innerText` for a rendered
68
- * element (CSS-hidden parts left out, inline elements not split apart), none
69
- * for an element that is not rendered, `textContent` for SVG and other
70
- * elements without `innerText` and for `display: contents` wrappers (no box
71
- * of their own, but their children are shown).
72
- */
73
- const ELEMENT_TEXT_JS = `(el) => {
74
- if (typeof el.innerText !== 'string') return el.textContent || '';
75
- if (!el.checkVisibility || el.checkVisibility()) return el.innerText;
76
- const boxless = el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';
77
- return boxless ? el.textContent || '' : '';
78
- }`;
79
- /**
80
- * Short text preview of an element's text: whitespace collapsed, cut on a
81
- * whole character (an emoji is never split, which would make JSON invalid).
82
- *
83
- * @param text - Element text as the page renders it
84
- * @returns Collapsed text, truncated to {@link PREVIEW_LENGTH} characters
85
- */
86
- export function textPreview(text) {
87
- const collapsed = text.replace(/\s+/g, ' ').trim();
88
- const characters = Array.from(collapsed);
89
- return characters.length > PREVIEW_LENGTH
90
- ? characters.slice(0, PREVIEW_LENGTH).join('') + '...'
91
- : collapsed;
92
- }
93
69
  /** Counter giving each query its own object group (queries may run concurrently) */
94
70
  let queryCount = 0;
95
71
  /**
@@ -126,53 +102,168 @@ async function withSelection(selector, use) {
126
102
  await callCDP('Runtime.releaseObjectGroup', { objectGroup });
127
103
  }
128
104
  }
129
- /** Where each element of a page-side array lives (an iframe and/or a shadow root), and its text */
105
+ /**
106
+ * The "no nodes" error for a selector that matched nothing. One more page
107
+ * evaluation, on this failure path only, tells what the page says about it
108
+ * ({@link noMatchContext}).
109
+ *
110
+ * @param selector - Selector as given
111
+ * @returns Message and suggestion
112
+ */
113
+ export async function noMatchesError(selector) {
114
+ return noNodesFoundError(selector, await noMatchContext(selector));
115
+ }
116
+ /**
117
+ * What the page says about a selector that matched nothing, in one
118
+ * evaluation: whether it is still loading, how many elements match with the
119
+ * selector's `:visible` filters removed, whether it has cross-origin iframes
120
+ * or embeds (which selectors do not search), and for a selector that is a
121
+ * single id or class, the similar ids or classes on the page.
122
+ *
123
+ * @param selector - Selector as given
124
+ * @returns Context for {@link noNodesFoundError} (empty when the page did not answer)
125
+ */
126
+ export async function noMatchContext(selector) {
127
+ const parts = parseSelectorFilters(selector);
128
+ const unfiltered = parts && withoutVisibleFilters(parts);
129
+ const single = parseSingleNameSelector(selector);
130
+ const hidden = unfiltered
131
+ ? `(() => { try { return (${DEEP_QUERY_JS})(${JSON.stringify(selector)}, ${JSON.stringify(unfiltered)}).length; } catch (e) { return 0; } })()`
132
+ : '0';
133
+ const names = single ? pageNamesJS(single.kind) : '[]';
134
+ try {
135
+ const evaluated = await callCDP('Runtime.evaluate', {
136
+ expression: `({ hidden: ${hidden}, readyState: document.readyState, unsearched: ${UNSEARCHED_CONTENT_JS}, names: ${names} })`,
137
+ returnByValue: true,
138
+ });
139
+ const { result } = (evaluated.data?.result ?? {});
140
+ const value = (result?.value ?? {});
141
+ const similar = single && Array.isArray(value.names)
142
+ ? similarSelectorsLine(single.kind, findSimilarNames(single.name, value.names.filter((n) => typeof n === 'string')))
143
+ : '';
144
+ return {
145
+ hidden: typeof value.hidden === 'number' ? value.hidden : 0,
146
+ ...(typeof value.readyState === 'string' && { readyState: value.readyState }),
147
+ ...(value.unsearched && {
148
+ unsearched: {
149
+ crossOriginFrames: value.unsearched.crossOriginFrames === true,
150
+ embeds: value.unsearched.embeds === true,
151
+ },
152
+ }),
153
+ ...(similar && { similar }),
154
+ };
155
+ }
156
+ catch (error) {
157
+ log.debug(`Could not read the page after no match: ${getErrorMessage(error)}`);
158
+ return {};
159
+ }
160
+ }
161
+ /**
162
+ * The page's `document.readyState`, read for a failure that may come from a
163
+ * page still loading.
164
+ *
165
+ * @returns The state, or undefined when the page did not answer
166
+ */
167
+ export async function documentReadyState() {
168
+ try {
169
+ const evaluated = await callCDP('Runtime.evaluate', {
170
+ expression: 'document.readyState',
171
+ returnByValue: true,
172
+ });
173
+ const { result } = (evaluated.data?.result ?? {});
174
+ return typeof result?.value === 'string' ? result.value : undefined;
175
+ }
176
+ catch (error) {
177
+ log.debug(`Could not read document.readyState: ${getErrorMessage(error)}`);
178
+ return undefined;
179
+ }
180
+ }
181
+ /** Matches whose viewport position `dom query` reports (measuring is not free) */
182
+ const VIEWPORT_HINT_LIMIT = 100;
183
+ /**
184
+ * Where each element of a page-side array lives (an iframe and/or a shadow
185
+ * root), its text and, for the first {@link VIEWPORT_HINT_LIMIT}, its position
186
+ * relative to the viewport, plus the viewport size. An element that cannot be
187
+ * read gets empty details instead of failing the whole query.
188
+ */
130
189
  const ELEMENT_DETAILS_FUNCTION = `function () {
131
- const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '');
190
+ const contextOf = ${ELEMENT_CONTEXT_JS};
132
191
  const textOf = ${ELEMENT_TEXT_JS};
133
- return Array.from(this, (el) => {
134
- const parts = [];
135
- for (let doc = el.ownerDocument; doc && doc.defaultView && doc.defaultView.frameElement; ) {
136
- const frame = doc.defaultView.frameElement;
137
- parts.unshift(describe(frame));
138
- doc = frame.ownerDocument;
192
+ const geometryOf = ${ELEMENT_GEOMETRY_JS};
193
+ const read = (el, index) => {
194
+ try {
195
+ return { context: contextOf(el), text: textOf(el), geometry: index < ${VIEWPORT_HINT_LIMIT} ? geometryOf(el) : null };
196
+ } catch (e) {
197
+ return {};
139
198
  }
140
- const root = el.getRootNode();
141
- if (root.host) parts.push('shadow root of <' + describe(root.host) + '>');
142
- return { context: parts.join(' > '), text: textOf(el) };
143
- });
199
+ };
200
+ return { viewport: (${VIEWPORT_SIZE_JS})(window), elements: Array.from(this, read) };
144
201
  }`;
145
202
  /**
146
203
  * Backend node ids of the elements in a page-side array, each with where it
147
- * lives (empty for the main document) and its text.
204
+ * lives (empty for the main document), its text and its viewport position.
148
205
  *
149
206
  * @param arrayObjectId - Remote object id of the array
150
207
  * @returns Elements in array order (ones that cannot be described are left out)
208
+ * @throws CommandError (91) when the page could not describe the matches
151
209
  */
152
210
  async function elementsWithDetails(arrayObjectId) {
211
+ const { viewport, elements = [] } = await readPageDetails(arrayObjectId);
212
+ const ids = await elementBackendNodeIds(arrayObjectId);
213
+ return ids.flatMap((backendNodeId, index) => {
214
+ if (backendNodeId === undefined)
215
+ return [];
216
+ const details = elements[index];
217
+ return [
218
+ {
219
+ backendNodeId,
220
+ context: details?.context ?? '',
221
+ text: details?.text ?? '',
222
+ ...viewportHint(details?.geometry, viewport),
223
+ },
224
+ ];
225
+ });
226
+ }
227
+ /**
228
+ * Run {@link ELEMENT_DETAILS_FUNCTION} on a page-side array.
229
+ *
230
+ * @param arrayObjectId - Remote object id of the array
231
+ * @returns Details of each element and the viewport size
232
+ * @throws CommandError (91) when the script failed
233
+ */
234
+ async function readPageDetails(arrayObjectId) {
153
235
  const response = await callCDP('Runtime.callFunctionOn', {
154
236
  objectId: arrayObjectId,
155
237
  functionDeclaration: ELEMENT_DETAILS_FUNCTION,
156
238
  returnByValue: true,
157
239
  });
158
- const value = response.data?.result?.result
159
- ?.value;
160
- const details = (Array.isArray(value) ? value : []);
161
- const ids = await elementBackendNodeIds(arrayObjectId);
162
- return ids.flatMap((backendNodeId, index) => backendNodeId === undefined
163
- ? []
164
- : [
165
- {
166
- backendNodeId,
167
- context: details[index]?.context ?? '',
168
- text: details[index]?.text ?? '',
169
- },
170
- ]);
240
+ const result = response.data?.result;
241
+ if (response.status === 'error' || result?.exceptionDetails) {
242
+ const detail = result?.exceptionDetails?.exception?.description ?? response.error ?? 'no result';
243
+ const err = operationFailedError('describe the matches', detail.split('\n')[0] ?? detail);
244
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
245
+ }
246
+ return (result?.result?.value ?? {});
247
+ }
248
+ /**
249
+ * Viewport position of a measured element for `dom query`.
250
+ *
251
+ * @param geometry - Page-side measurements (none beyond the hint limit)
252
+ * @param viewport - Viewport size
253
+ * @returns `inViewport` (and `clippedBy`), or nothing when not measured
254
+ */
255
+ function viewportHint(geometry, viewport) {
256
+ if (!geometry || !viewport)
257
+ return {};
258
+ const { inViewport, clippedBy } = classifyViewportPosition(geometry, viewport);
259
+ return { inViewport, ...(clippedBy && { clippedBy }) };
171
260
  }
172
261
  /**
173
262
  * The page-side array of a selector query, or the error explaining why there
174
263
  * is none: a selector the browser rejects (a `SyntaxError` DOMException) is the
175
- * user's (81); anything else (no session, a page navigating away) is not.
264
+ * user's (81; the browser's message is left out for selectors with filters,
265
+ * as it quotes the CSS bdg rewrote them to); anything else (no session, a
266
+ * page navigating away) is not.
176
267
  *
177
268
  * @param selector - CSS selector
178
269
  * @param evaluated - `Runtime.evaluate` response
@@ -184,7 +275,7 @@ function selectionObjectId(selector, evaluated) {
184
275
  {});
185
276
  const description = exceptionDetails?.exception?.description;
186
277
  if (description?.startsWith('SyntaxError')) {
187
- const detail = description.split('\n')[0];
278
+ const detail = parseSelectorFilters(selector) ? undefined : description.split('\n')[0];
188
279
  const err = invalidSelectorError(selector, detail);
189
280
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
190
281
  }
@@ -242,7 +333,8 @@ export async function queryDOMElements(selector) {
242
333
  if (elements.length > 20) {
243
334
  log.debug(`Querying ${elements.length} elements with selector: ${selector}`);
244
335
  }
245
- const nodes = await mapConcurrently(elements, async ({ backendNodeId, context, text }, index) => {
336
+ const nodes = await mapConcurrently(elements, async (element, index) => {
337
+ const { backendNodeId, context, text, inViewport, clippedBy } = element;
246
338
  const desc = await describeNode({ backendNodeId });
247
339
  if (!desc)
248
340
  return { index, nodeId: 0 };
@@ -253,10 +345,12 @@ export async function queryDOMElements(selector) {
253
345
  index,
254
346
  nodeId: desc.backendNodeId,
255
347
  tag: desc.nodeName.toLowerCase(),
256
- ...identifyingAttributes(attributes),
348
+ ...identifyingAttributes(attributes, desc.nodeName),
257
349
  ...(classes && { classes }),
258
350
  ...(preview && { preview }),
259
351
  ...(context && { context }),
352
+ ...(inViewport && { inViewport }),
353
+ ...(clippedBy && { clippedBy }),
260
354
  };
261
355
  });
262
356
  return { selector, count: nodes.length, nodes };
@@ -265,22 +359,26 @@ export async function queryDOMElements(selector) {
265
359
  * The attributes that tell similar elements apart (form fields especially).
266
360
  *
267
361
  * @param attributes - Element attributes
268
- * @returns id, name and type when present
362
+ * @param nodeName - Element name (`OPTION`s also report their `value`)
363
+ * @returns id, name and type (and an option's value) when present
269
364
  */
270
- function identifyingAttributes(attributes) {
365
+ function identifyingAttributes(attributes, nodeName) {
366
+ const value = nodeName === 'OPTION' ? attributes['value'] : undefined;
271
367
  return {
272
368
  ...(attributes['id'] && { id: attributes['id'] }),
273
369
  ...(attributes['name'] && { name: attributes['name'] }),
274
370
  ...(attributes['type'] && { type: attributes['type'] }),
371
+ ...(value !== undefined && { value }),
275
372
  };
276
373
  }
277
374
  /**
278
375
  * The text of one element as the page renders it ({@link ELEMENT_TEXT_JS}).
279
376
  *
280
377
  * @param ref - Node reference
378
+ * @param full - Read all of a large container's text, not just its start
281
379
  * @returns Element text, or empty when the node cannot be read
282
380
  */
283
- async function elementText(ref) {
381
+ async function elementText(ref, full) {
284
382
  const objectGroup = `bdg-text-${process.pid}-${++queryCount}`;
285
383
  const resolved = await callCDP('DOM.resolveNode', { ...ref, objectGroup });
286
384
  const objectId = resolved.data?.result?.object
@@ -290,7 +388,8 @@ async function elementText(ref) {
290
388
  try {
291
389
  const response = await callCDP('Runtime.callFunctionOn', {
292
390
  objectId,
293
- functionDeclaration: `function () { return (${ELEMENT_TEXT_JS})(this); }`,
391
+ functionDeclaration: `function (full) { return (${ELEMENT_TEXT_JS})(this, full); }`,
392
+ arguments: [{ value: full }],
294
393
  returnByValue: true,
295
394
  });
296
395
  const value = response.data?.result?.result
@@ -302,26 +401,94 @@ async function elementText(ref) {
302
401
  }
303
402
  }
304
403
  /**
305
- * Get DOM context (tag, classes, text preview) for a node.
404
+ * Get DOM context (tag, classes, text preview) for a node: a one-line
405
+ * preview, and up to {@link ELEMENT_TEXT_LENGTH} characters of text when it
406
+ * is longer (all of it with `full`).
306
407
  *
307
408
  * @param ref - Node reference
409
+ * @param options - `full`: the whole text instead of its first 500 characters
308
410
  * @returns DOM context, or null if the node does not exist
309
411
  */
310
- export async function getDomContext(ref) {
412
+ export async function getDomContext(ref, options = {}) {
311
413
  await callCDP('DOM.enable', {});
312
414
  const desc = await describeNode(ref);
313
415
  if (!desc) {
314
416
  log.debug(`No DOM context for ${JSON.stringify(ref)}`);
315
417
  return null;
316
418
  }
419
+ const full = options.full === true;
317
420
  const classes = unpackAttributes(desc.attributes)['class']?.split(/\s+/).filter(Boolean);
318
- const preview = textPreview(await elementText(ref));
421
+ const text = await elementText(ref, full);
422
+ const preview = textPreview(text);
423
+ const longer = textPreview(text, full ? Number.POSITIVE_INFINITY : ELEMENT_TEXT_LENGTH);
319
424
  return {
320
425
  tag: desc.nodeName.toLowerCase(),
321
426
  ...(classes && classes.length > 0 && { classes }),
322
427
  ...(preview && { preview }),
428
+ ...(longer !== preview && { text: longer }),
429
+ ...(!preview && (await childElements(ref))),
430
+ };
431
+ }
432
+ /** Child elements named for an element without text */
433
+ const CHILDREN_LISTED = 5;
434
+ /**
435
+ * The child elements of an element (for one without text: a body holding
436
+ * only an iframe, an empty app root).
437
+ *
438
+ * @param ref - Node reference
439
+ * @returns The first {@link CHILDREN_LISTED} as `tag#id.class` and how many there are
440
+ */
441
+ async function childElements(ref) {
442
+ const response = await callCDP('DOM.describeNode', { ...ref, depth: 1 });
443
+ const node = response.data?.result?.node;
444
+ const elements = (node?.children ?? []).filter((child) => child.nodeType === 1);
445
+ return {
446
+ children: elements.slice(0, CHILDREN_LISTED).map(childLabel),
447
+ childCount: elements.length,
323
448
  };
324
449
  }
450
+ /**
451
+ * A child element in a few characters.
452
+ *
453
+ * @param node - Child node
454
+ * @returns e.g. `iframe#app.full`
455
+ */
456
+ function childLabel(node) {
457
+ const attributes = unpackAttributes(node.attributes);
458
+ const id = attributes['id'] ? `#${attributes['id']}` : '';
459
+ const classes = (attributes['class'] ?? '').split(/\s+/).filter(Boolean).slice(0, 2);
460
+ return `${node.nodeName.toLowerCase()}${id}${classes.map((name) => `.${name}`).join('')}`;
461
+ }
462
+ /**
463
+ * All matches of a selector.
464
+ *
465
+ * @param selector - CSS selector
466
+ * @returns Backend node ids of the matches (at least one)
467
+ * @throws CommandError (83) when nothing matches
468
+ */
469
+ async function selectMatches(selector) {
470
+ const backendNodeIds = await selectAll(selector);
471
+ if (backendNodeIds.length > 0)
472
+ return backendNodeIds;
473
+ const err = await noMatchesError(selector);
474
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
475
+ }
476
+ /**
477
+ * One match of a selector.
478
+ *
479
+ * @param selector - CSS selector
480
+ * @param index - Which match (0-based)
481
+ * @returns Its backend node id
482
+ * @throws CommandError (83) when nothing matches, (81) for an index beyond the matches
483
+ */
484
+ export async function selectMatch(selector, index = 0) {
485
+ const backendNodeIds = await selectMatches(selector);
486
+ const backendNodeId = backendNodeIds[index];
487
+ if (backendNodeId !== undefined)
488
+ return backendNodeId;
489
+ const err = indexOutOfRangeError(index, backendNodeIds.length - 1);
490
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
491
+ }
325
492
  /**
326
493
  * Pick the nodes `dom get --raw` should report for a selector.
327
494
  *
@@ -330,20 +497,10 @@ export async function getDomContext(ref) {
330
497
  * @returns Node references to describe
331
498
  */
332
499
  async function selectForGet(selector, options) {
333
- const backendNodeIds = await selectAll(selector);
334
- if (backendNodeIds.length === 0) {
335
- const err = noNodesFoundError(selector);
336
- throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
337
- }
338
- if (options.all)
339
- return backendNodeIds.map((backendNodeId) => ({ backendNodeId }));
340
- const position = options.nth ?? 0;
341
- const backendNodeId = backendNodeIds[position];
342
- if (backendNodeId === undefined) {
343
- const err = indexOutOfRangeError(position, backendNodeIds.length - 1);
344
- throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
500
+ if (options.all) {
501
+ return (await selectMatches(selector)).map((backendNodeId) => ({ backendNodeId }));
345
502
  }
346
- return [{ backendNodeId }];
503
+ return [{ backendNodeId: await selectMatch(selector, options.nth ?? 0) }];
347
504
  }
348
505
  /**
349
506
  * Get full details (attributes, outer HTML) for `bdg dom get --raw`.
@@ -366,7 +523,7 @@ export async function getDOMElements(options) {
366
523
  refs = await selectForGet(options.selector, options);
367
524
  }
368
525
  else {
369
- const err = eitherArgumentRequiredError('selector', 'nodeId', 'bdg dom get <selector> or bdg dom get --node-id <id>');
526
+ const err = eitherArgumentRequiredError('selector', 'nodeId', `${sessionCommand('bdg dom get <selector>')} or ${sessionCommand('bdg dom get --node-id <id>')}`);
370
527
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
371
528
  }
372
529
  const nodes = await mapConcurrently(refs, async (ref) => {
@@ -398,7 +555,7 @@ export async function getDOMElements(options) {
398
555
  export async function resolveSelector(selector) {
399
556
  const backendNodeId = (await selectAll(selector))[0];
400
557
  if (backendNodeId === undefined) {
401
- const err = noNodesFoundError(selector);
558
+ const err = await noMatchesError(selector);
402
559
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
403
560
  }
404
561
  return { backendNodeId };
@@ -412,16 +569,6 @@ export async function resolveSelector(selector) {
412
569
  export async function resolveBackendNodeIds(selectors) {
413
570
  return mapConcurrently(selectors, async (selector) => (await selectAll(selector))[0]);
414
571
  }
415
- /**
416
- * Accessibility node of the first element matching a selector.
417
- *
418
- * @param selector - CSS selector
419
- * @returns A11y node, or null when nothing matches or the node is not exposed
420
- */
421
- export async function resolveA11yNodeForSelector(selector) {
422
- const [backendNodeId] = await resolveBackendNodeIds([selector]);
423
- return backendNodeId === undefined ? null : resolveA11yNode({ backendNodeId });
424
- }
425
572
  /**
426
573
  * Check that a cached element is still part of the current page.
427
574
  *
@@ -431,10 +578,10 @@ export async function resolveA11yNodeForSelector(selector) {
431
578
  * false for removed elements.
432
579
  *
433
580
  * @param backendNodeId - Backend node id from the query cache
434
- * @param index - Index the user gave, for the error message
581
+ * @param source - Index the user gave and the list it refers to, for the error message
435
582
  * @throws CommandError (87) when the element is gone
436
583
  */
437
- export async function assertNodeAttached(backendNodeId, index) {
584
+ export async function assertNodeAttached(backendNodeId, source) {
438
585
  const resolved = await callCDP('DOM.resolveNode', { backendNodeId, objectGroup: 'bdg-check' });
439
586
  const objectId = resolved.data?.result?.object
440
587
  .objectId;
@@ -450,7 +597,7 @@ export async function assertNodeAttached(backendNodeId, index) {
450
597
  await callCDP('Runtime.releaseObjectGroup', { objectGroup: 'bdg-check' });
451
598
  }
452
599
  if (!attached) {
453
- const err = staleNodeError(index);
600
+ const err = staleNodeError(source?.index, source);
454
601
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
455
602
  }
456
603
  }
@@ -4,6 +4,7 @@
4
4
  * Captures the common flow: resolve selector/index → call IPC → normalize errors.
5
5
  * Keeps individual command handlers focused on option wiring and output formatting.
6
6
  */
7
+ import type { IndexSource } from '../../../types.js';
7
8
  interface IpcResponse<T> {
8
9
  status: string;
9
10
  data?: T;
@@ -16,6 +17,8 @@ interface ResultPayload {
16
17
  error?: string | undefined;
17
18
  suggestion?: string | undefined;
18
19
  exitCode?: number | undefined;
20
+ /** The element is not one the command acts on (fill, submit) */
21
+ unsuitableElement?: boolean | undefined;
19
22
  }
20
23
  interface CommandResult<T> {
21
24
  success: boolean;
@@ -42,6 +45,8 @@ export interface ElementCommandOptions<Req, Res extends ResultPayload> {
42
45
  }) => Req;
43
46
  /** Invoke the IPC client function. */
44
47
  call: (req: Req) => Promise<IpcResponse<Res>>;
48
+ /** `bdg dom` subcommand being run, e.g. "fill" (for suggestions). */
49
+ command: string;
45
50
  /** Operation label used in fallback error messages (e.g. "fill element"). */
46
51
  action: string;
47
52
  /** Fallback suggestion when the result reports failure without one. */
@@ -49,8 +54,11 @@ export interface ElementCommandOptions<Req, Res extends ResultPayload> {
49
54
  }
50
55
  /**
51
56
  * Resolve an element target, invoke the IPC call, and normalize failures
52
- * into the structured `CommandRunner` result shape.
57
+ * into the structured `CommandRunner` result shape. A numeric index names the
58
+ * list it refers to (`indexSource` in the data, and in errors).
53
59
  */
54
- export declare function runElementCommand<Req, Res extends ResultPayload>(options: ElementCommandOptions<Req, Res>): Promise<CommandResult<Omit<Res, 'success'>>>;
60
+ export declare function runElementCommand<Req, Res extends ResultPayload>(options: ElementCommandOptions<Req, Res>): Promise<CommandResult<Omit<Res, 'success'> & {
61
+ indexSource?: IndexSource;
62
+ }>>;
55
63
  export {};
56
64
  //# sourceMappingURL=runElementCommand.d.ts.map