browser-debugger-cli 0.8.0 → 0.10.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 (304) hide show
  1. package/README.md +7 -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 +29 -6
  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 +189 -119
  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/keyAttributes.d.ts +20 -0
  27. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  28. package/dist/commands/dom/helpers/query.d.ts +44 -17
  29. package/dist/commands/dom/helpers/query.js +300 -106
  30. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  31. package/dist/commands/dom/helpers/runElementCommand.js +98 -30
  32. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  33. package/dist/commands/dom/helpers/screenshot.js +239 -51
  34. package/dist/commands/dom/index.d.ts +4 -1
  35. package/dist/commands/dom/index.js +22 -7
  36. package/dist/commands/dom/inspect.d.ts +15 -0
  37. package/dist/commands/dom/inspect.js +82 -0
  38. package/dist/commands/dom/layout.d.ts +14 -0
  39. package/dist/commands/dom/layout.js +54 -0
  40. package/dist/commands/dom/listeners.d.ts +5 -1
  41. package/dist/commands/dom/listeners.js +15 -5
  42. package/dist/commands/dom/query.js +2 -3
  43. package/dist/commands/dom/screenshot.d.ts +12 -2
  44. package/dist/commands/dom/screenshot.js +27 -3
  45. package/dist/commands/dom/semanticUtils.d.ts +16 -10
  46. package/dist/commands/dom/semanticUtils.js +53 -16
  47. package/dist/commands/dom/wait.d.ts +13 -0
  48. package/dist/commands/dom/wait.js +83 -0
  49. package/dist/commands/helpJson.js +2 -2
  50. package/dist/commands/network/list.js +17 -13
  51. package/dist/commands/optionBehaviors.js +154 -21
  52. package/dist/commands/page.d.ts +3 -2
  53. package/dist/commands/page.js +100 -5
  54. package/dist/commands/peek.js +4 -11
  55. package/dist/commands/sessions.d.ts +8 -0
  56. package/dist/commands/sessions.js +19 -0
  57. package/dist/commands/shared/CommandRunner.js +4 -4
  58. package/dist/commands/shared/commonOptions.d.ts +4 -0
  59. package/dist/commands/shared/commonOptions.js +9 -0
  60. package/dist/commands/shared/dataFetcher.js +2 -2
  61. package/dist/commands/shared/followMode.d.ts +21 -1
  62. package/dist/commands/shared/followMode.js +29 -2
  63. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  64. package/dist/commands/shared/handleValidationError.js +12 -3
  65. package/dist/commands/shared/optionTypes.d.ts +61 -5
  66. package/dist/commands/shared/startHelpers.d.ts +66 -0
  67. package/dist/commands/shared/startHelpers.js +103 -13
  68. package/dist/commands/shared/validation.d.ts +14 -2
  69. package/dist/commands/shared/validation.js +20 -3
  70. package/dist/commands/start.d.ts +63 -0
  71. package/dist/commands/start.js +115 -15
  72. package/dist/commands/status.js +29 -7
  73. package/dist/commands/stop.js +7 -6
  74. package/dist/commands/tail.js +4 -11
  75. package/dist/commands/types.d.ts +2 -0
  76. package/dist/commands.js +2 -0
  77. package/dist/connection/chromeIdentity.d.ts +65 -0
  78. package/dist/connection/chromeIdentity.js +143 -0
  79. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  80. package/dist/connection/launcher/profilePreferences.js +151 -0
  81. package/dist/connection/launcher.d.ts +21 -2
  82. package/dist/connection/launcher.js +42 -16
  83. package/dist/connection/portReservation.d.ts +14 -4
  84. package/dist/connection/portReservation.js +21 -6
  85. package/dist/connection/startupExit.d.ts +8 -0
  86. package/dist/connection/startupExit.js +15 -6
  87. package/dist/constants.d.ts +6 -2
  88. package/dist/constants.js +9 -2
  89. package/dist/daemon/SessionController.js +23 -7
  90. package/dist/daemon/errors.d.ts +1 -1
  91. package/dist/daemon/errors.js +1 -1
  92. package/dist/daemon/launcher.d.ts +10 -2
  93. package/dist/daemon/launcher.js +8 -7
  94. package/dist/daemon/server/SocketServer.js +1 -2
  95. package/dist/daemon/session/Session.d.ts +20 -0
  96. package/dist/daemon/session/Session.js +80 -9
  97. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  98. package/dist/daemon/session/chromeConnection.js +45 -8
  99. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  100. package/dist/daemon/session/commandRegistry.js +113 -67
  101. package/dist/daemon/session/interactions.d.ts +48 -9
  102. package/dist/daemon/session/interactions.js +46 -9
  103. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  104. package/dist/daemon/session/triggeredRequests.js +157 -0
  105. package/dist/daemon/session/types.d.ts +5 -1
  106. package/dist/daemon.js +10630 -3601
  107. package/dist/errors/messages.d.ts +456 -24
  108. package/dist/errors/messages.js +862 -67
  109. package/dist/index.js +6915 -3401
  110. package/dist/ipc/client.d.ts +21 -1
  111. package/dist/ipc/client.js +35 -3
  112. package/dist/ipc/protocol/commands.d.ts +145 -5
  113. package/dist/ipc/protocol/commands.js +4 -0
  114. package/dist/ipc/protocol/domTypes.d.ts +291 -7
  115. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  116. package/dist/ipc/protocol/inspectTypes.js +10 -0
  117. package/dist/ipc/session/lifecycle.d.ts +8 -1
  118. package/dist/ipc/session/queries.d.ts +5 -1
  119. package/dist/ipc/session/types.d.ts +5 -0
  120. package/dist/ipc/transport/index.d.ts +2 -1
  121. package/dist/ipc/transport/index.js +2 -2
  122. package/dist/runtime/dom/actionEffects.d.ts +185 -0
  123. package/dist/runtime/dom/actionEffects.js +402 -0
  124. package/dist/runtime/dom/actionEffectsScripts.d.ts +90 -0
  125. package/dist/runtime/dom/actionEffectsScripts.js +426 -0
  126. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  127. package/dist/runtime/dom/elementGeometry.js +553 -0
  128. package/dist/runtime/dom/elementInfo.d.ts +103 -0
  129. package/dist/runtime/dom/elementInfo.js +256 -0
  130. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  131. package/dist/runtime/dom/evalHelpers.js +136 -26
  132. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  133. package/dist/runtime/dom/eventListeners.js +184 -47
  134. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  135. package/dist/runtime/dom/formDiscovery.js +116 -16
  136. package/dist/runtime/dom/formFillHelpers/fill.d.ts +9 -0
  137. package/dist/runtime/dom/formFillHelpers/fill.js +178 -14
  138. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  139. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  140. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  141. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  142. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  143. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  144. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  145. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  146. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  147. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  148. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  149. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  150. package/dist/runtime/dom/frameLayout.js +140 -0
  151. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  152. package/dist/runtime/dom/frameOrigin.js +62 -0
  153. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  154. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  155. package/dist/runtime/dom/frameSelection.d.ts +12 -1
  156. package/dist/runtime/dom/frameSelection.js +22 -3
  157. package/dist/runtime/dom/frames.d.ts +61 -5
  158. package/dist/runtime/dom/frames.js +329 -75
  159. package/dist/runtime/dom/inspect.d.ts +28 -0
  160. package/dist/runtime/dom/inspect.js +557 -0
  161. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  162. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  163. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  164. package/dist/runtime/dom/inspectCascade.js +371 -0
  165. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  166. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  167. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  168. package/dist/runtime/dom/inspectHints.js +305 -0
  169. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  170. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  171. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  172. package/dist/runtime/dom/inspectModel.js +184 -0
  173. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  174. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  175. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  176. package/dist/runtime/dom/inspectRules.js +101 -0
  177. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  178. package/dist/runtime/dom/inspectScripts.js +263 -0
  179. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  180. package/dist/runtime/dom/inspectTree.js +134 -0
  181. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  182. package/dist/runtime/dom/inspectVariables.js +94 -0
  183. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  184. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  185. package/dist/runtime/dom/layout.d.ts +71 -0
  186. package/dist/runtime/dom/layout.js +340 -0
  187. package/dist/runtime/dom/listenerPageScripts.d.ts +72 -0
  188. package/dist/runtime/dom/listenerPageScripts.js +365 -0
  189. package/dist/runtime/dom/listenerSummary.d.ts +136 -11
  190. package/dist/runtime/dom/listenerSummary.js +361 -22
  191. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  192. package/dist/runtime/dom/pageActivity.js +123 -0
  193. package/dist/runtime/dom/reactEventHelpers.d.ts +63 -2
  194. package/dist/runtime/dom/reactEventHelpers.js +220 -41
  195. package/dist/runtime/dom/targetNode.d.ts +80 -27
  196. package/dist/runtime/dom/targetNode.js +249 -33
  197. package/dist/runtime/dom/wait.d.ts +25 -0
  198. package/dist/runtime/dom/wait.js +199 -0
  199. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  200. package/dist/runtime/dom/waitCondition.js +75 -0
  201. package/dist/runtime/page/emulation.d.ts +71 -0
  202. package/dist/runtime/page/emulation.js +117 -0
  203. package/dist/runtime/page/loadingState.d.ts +36 -0
  204. package/dist/runtime/page/loadingState.js +86 -0
  205. package/dist/runtime/page/navigation.d.ts +46 -2
  206. package/dist/runtime/page/navigation.js +69 -33
  207. package/dist/session/QueryCacheManager.d.ts +11 -1
  208. package/dist/session/QueryCacheManager.js +25 -3
  209. package/dist/session/chromeOwners.d.ts +34 -0
  210. package/dist/session/chromeOwners.js +51 -0
  211. package/dist/session/cleanup/staleSession.d.ts +11 -1
  212. package/dist/session/cleanup/staleSession.js +17 -6
  213. package/dist/session/cleanup/userCommands.js +2 -4
  214. package/dist/session/metadata.d.ts +5 -1
  215. package/dist/session/metadata.js +2 -1
  216. package/dist/session/paths.d.ts +77 -3
  217. package/dist/session/paths.js +111 -5
  218. package/dist/session/port.d.ts +31 -7
  219. package/dist/session/port.js +50 -43
  220. package/dist/session/portClaims.d.ts +66 -0
  221. package/dist/session/portClaims.js +284 -0
  222. package/dist/session/sessionList.d.ts +58 -0
  223. package/dist/session/sessionList.js +199 -0
  224. package/dist/session/sessionName.d.ts +46 -0
  225. package/dist/session/sessionName.js +97 -0
  226. package/dist/telemetry/a11y.d.ts +18 -3
  227. package/dist/telemetry/a11y.js +170 -29
  228. package/dist/telemetry/console.d.ts +1 -0
  229. package/dist/telemetry/console.js +100 -5
  230. package/dist/telemetry/network.js +3 -1
  231. package/dist/telemetry/requestKinds.d.ts +32 -0
  232. package/dist/telemetry/requestKinds.js +61 -0
  233. package/dist/telemetry/requestState.d.ts +31 -0
  234. package/dist/telemetry/requestState.js +38 -0
  235. package/dist/types.d.ts +112 -3
  236. package/dist/ui/formatters/a11y.js +3 -0
  237. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  238. package/dist/ui/formatters/console/chronological.js +17 -4
  239. package/dist/ui/formatters/console/json.js +3 -4
  240. package/dist/ui/formatters/console/shared.d.ts +12 -0
  241. package/dist/ui/formatters/console.d.ts +2 -2
  242. package/dist/ui/formatters/console.js +1 -1
  243. package/dist/ui/formatters/details.d.ts +8 -0
  244. package/dist/ui/formatters/details.js +61 -4
  245. package/dist/ui/formatters/dom.d.ts +27 -14
  246. package/dist/ui/formatters/dom.js +88 -59
  247. package/dist/ui/formatters/form.js +29 -18
  248. package/dist/ui/formatters/inspect.d.ts +39 -0
  249. package/dist/ui/formatters/inspect.js +596 -0
  250. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  251. package/dist/ui/formatters/keyAttributes.js +84 -0
  252. package/dist/ui/formatters/layout.d.ts +31 -0
  253. package/dist/ui/formatters/layout.js +53 -0
  254. package/dist/ui/formatters/listeners.d.ts +3 -2
  255. package/dist/ui/formatters/listeners.js +73 -9
  256. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  257. package/dist/ui/formatters/networkHeaders.js +36 -3
  258. package/dist/ui/formatters/networkList.d.ts +29 -1
  259. package/dist/ui/formatters/networkList.js +86 -20
  260. package/dist/ui/formatters/preview.js +2 -1
  261. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  262. package/dist/ui/formatters/requestStatus.js +2 -30
  263. package/dist/ui/formatters/sessions.d.ts +12 -0
  264. package/dist/ui/formatters/sessions.js +40 -0
  265. package/dist/ui/formatters/status.d.ts +21 -2
  266. package/dist/ui/formatters/status.js +47 -10
  267. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  268. package/dist/ui/formatters/triggeredRequests.js +65 -0
  269. package/dist/ui/formatting.d.ts +19 -0
  270. package/dist/ui/formatting.js +31 -36
  271. package/dist/ui/messages/chrome.d.ts +9 -0
  272. package/dist/ui/messages/chrome.js +17 -5
  273. package/dist/ui/messages/commands.d.ts +504 -14
  274. package/dist/ui/messages/commands.js +835 -21
  275. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  276. package/dist/ui/messages/consoleMessages.js +17 -0
  277. package/dist/ui/messages/hints.js +2 -1
  278. package/dist/ui/messages/networkMessages.d.ts +14 -0
  279. package/dist/ui/messages/networkMessages.js +18 -0
  280. package/dist/ui/messages/preview.js +5 -4
  281. package/dist/ui/messages/session.d.ts +30 -21
  282. package/dist/ui/messages/session.js +48 -26
  283. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  284. package/dist/ui/messages/sessionCommand.js +52 -0
  285. package/dist/utils/async.d.ts +17 -0
  286. package/dist/utils/async.js +36 -0
  287. package/dist/utils/color.d.ts +84 -0
  288. package/dist/utils/color.js +376 -0
  289. package/dist/utils/cssValues.d.ts +109 -0
  290. package/dist/utils/cssValues.js +236 -0
  291. package/dist/utils/http.d.ts +22 -1
  292. package/dist/utils/http.js +28 -9
  293. package/dist/utils/selectorFilters.d.ts +48 -8
  294. package/dist/utils/selectorFilters.js +296 -53
  295. package/dist/utils/shellDetection.d.ts +8 -2
  296. package/dist/utils/shellDetection.js +120 -33
  297. package/dist/utils/suggestions.d.ts +26 -0
  298. package/dist/utils/suggestions.js +73 -0
  299. package/dist/utils/taskMappings.js +10 -0
  300. package/dist/utils/url.d.ts +12 -2
  301. package/dist/utils/url.js +69 -7
  302. package/package.json +1 -1
  303. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  304. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -9,19 +9,23 @@
9
9
  * bdg invocations. Selectors are matched in the page, including open shadow
10
10
  * roots and same-origin iframes, like a user sees the page.
11
11
  */
12
+ import { keyAttributes } from './keyAttributes.js';
12
13
  import { CommandError } from '../../../errors/index.js';
13
- import { noNodesFoundError, indexOutOfRangeError, eitherArgumentRequiredError, invalidSelectorError, nodeIdNotFoundError, staleNodeError, } from '../../../errors/messages.js';
14
+ import { noNodesFoundError, indexOutOfRangeError, eitherArgumentRequiredError, invalidSelectorError, nodeIdNotFoundError, operationFailedError, similarSelectorsLine, staleNodeError, } from '../../../errors/messages.js';
14
15
  import { callCDP } from '../../../ipc/client.js';
15
- import { DEEP_QUERY_JS, selectorArgsJS } from '../../../runtime/dom/targetNode.js';
16
- import { resolveA11yNode } from '../../../telemetry/a11y.js';
16
+ import { ELEMENT_GEOMETRY_JS, VIEWPORT_SIZE_JS, classifyViewportPosition, } from '../../../runtime/dom/elementGeometry.js';
17
+ import { ELEMENT_CONTEXT_JS, ELEMENT_STATE_JS, ELEMENT_TEXT_JS, ELEMENT_TEXT_LENGTH, textPreview, } from '../../../runtime/dom/elementInfo.js';
18
+ import { DEEP_QUERY_JS, UNSEARCHED_CONTENT_JS, pageNamesJS, selectorArgsJS, } from '../../../runtime/dom/targetNode.js';
17
19
  import { createLogger } from '../../../ui/logging/index.js';
20
+ import { sessionCommand } from '../../../ui/messages/sessionCommand.js';
18
21
  import { ConcurrencyLimiter } from '../../../utils/concurrency.js';
22
+ import { getErrorMessage } from '../../../utils/errors.js';
19
23
  import { EXIT_CODES } from '../../../utils/exitCodes.js';
24
+ import { leadingCompounds, parseSelectorFilters, withoutVisibleFilters, } from '../../../utils/selectorFilters.js';
25
+ import { findSimilarNames, parseSingleNameSelector } from '../../../utils/suggestions.js';
20
26
  const log = createLogger('dom');
21
27
  /** Maximum concurrent CDP calls to avoid overwhelming the connection. */
22
28
  const CDP_CONCURRENCY_LIMIT = 10;
23
- /** Length of the text preview shown for queried elements. */
24
- const PREVIEW_LENGTH = 80;
25
29
  /**
26
30
  * Convert CDP's flat attribute list to a record.
27
31
  *
@@ -63,33 +67,6 @@ async function getOuterHTML(ref) {
63
67
  const response = await callCDP('DOM.getOuterHTML', ref);
64
68
  return response.data?.result?.outerHTML;
65
69
  }
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
70
  /** Counter giving each query its own object group (queries may run concurrently) */
94
71
  let queryCount = 0;
95
72
  /**
@@ -126,53 +103,203 @@ async function withSelection(selector, use) {
126
103
  await callCDP('Runtime.releaseObjectGroup', { objectGroup });
127
104
  }
128
105
  }
129
- /** Where each element of a page-side array lives (an iframe and/or a shadow root), and its text */
106
+ /**
107
+ * The "no nodes" error for a selector that matched nothing. One more page
108
+ * evaluation, on this failure path only, tells what the page says about it
109
+ * ({@link noMatchContext}).
110
+ *
111
+ * @param selector - Selector as given
112
+ * @returns Message and suggestion
113
+ */
114
+ export async function noMatchesError(selector) {
115
+ return noNodesFoundError(selector, await noMatchContext(selector));
116
+ }
117
+ /**
118
+ * Page-side index of the first leading compound that matches a shadow host
119
+ * (an element with an open shadow root, anywhere a selector reaches) while
120
+ * the selector after it finds something: then the selector only failed by
121
+ * crossing into the shadow root.
122
+ *
123
+ * @param compounds - Leading compounds of the selector
124
+ * @returns Expression evaluating to the index, or -1
125
+ */
126
+ function shadowHostJS(compounds) {
127
+ const plain = compounds
128
+ .slice(0, MAX_HOST_COMPOUNDS)
129
+ .map((entry) => [withoutFilters(entry.compound), withoutFilters(entry.rest)]);
130
+ return `${JSON.stringify(plain)}.findIndex(([host, rest]) => { try { const deep = ${DEEP_QUERY_JS}; return deep(host, null).some((el) => el.shadowRoot) && deep(rest, null).length > 0; } catch (e) { return false; } })`;
131
+ }
132
+ /** Leading compounds checked for a shadow host (each check walks the page) */
133
+ const MAX_HOST_COMPOUNDS = 4;
134
+ /**
135
+ * A selector without bdg's filters (`:visible`, `:has-text()`, `:text-is()`),
136
+ * which plain CSS does not know.
137
+ *
138
+ * @param selector - Selector
139
+ * @returns Plain CSS
140
+ */
141
+ function withoutFilters(selector) {
142
+ return (selector.replace(/:visible\b|:(has-text|text-is)\((?:"[^"]*"|'[^']*'|[^)"'])*\)/g, '').trim() ||
143
+ '*');
144
+ }
145
+ /**
146
+ * What the page says about a selector that matched nothing, in one
147
+ * evaluation: whether it is still loading, how many elements match with the
148
+ * selector's `:visible` filters removed, whether it has cross-origin iframes
149
+ * or embeds (which selectors do not search), and for a selector that is a
150
+ * single id or class, the similar ids or classes on the page.
151
+ *
152
+ * @param selector - Selector as given
153
+ * @returns Context for {@link noNodesFoundError} (empty when the page did not answer)
154
+ */
155
+ export async function noMatchContext(selector) {
156
+ const parts = parseSelectorFilters(selector);
157
+ const unfiltered = parts && withoutVisibleFilters(parts);
158
+ const single = parseSingleNameSelector(selector);
159
+ const hidden = unfiltered
160
+ ? `(() => { try { return (${DEEP_QUERY_JS})(${JSON.stringify(selector)}, ${JSON.stringify(unfiltered)}).length; } catch (e) { return 0; } })()`
161
+ : '0';
162
+ const names = single ? pageNamesJS(single.kind) : '[]';
163
+ const compounds = leadingCompounds(selector);
164
+ const shadowHost = compounds.length > 0 ? shadowHostJS(compounds) : '-1';
165
+ try {
166
+ const evaluated = await callCDP('Runtime.evaluate', {
167
+ expression: `({ hidden: ${hidden}, readyState: document.readyState, unsearched: ${UNSEARCHED_CONTENT_JS}, names: ${names}, shadowHost: ${shadowHost} })`,
168
+ returnByValue: true,
169
+ });
170
+ const { result } = (evaluated.data?.result ?? {});
171
+ const value = (result?.value ?? {});
172
+ const similar = single && Array.isArray(value.names)
173
+ ? similarSelectorsLine(single.kind, findSimilarNames(single.name, value.names.filter((n) => typeof n === 'string')))
174
+ : '';
175
+ return {
176
+ hidden: typeof value.hidden === 'number' ? value.hidden : 0,
177
+ ...(typeof value.readyState === 'string' && { readyState: value.readyState }),
178
+ ...(value.unsearched && {
179
+ unsearched: {
180
+ crossOriginFrames: value.unsearched.crossOriginFrames === true,
181
+ embeds: value.unsearched.embeds === true,
182
+ },
183
+ }),
184
+ ...(similar && { similar }),
185
+ ...(typeof value.shadowHost === 'number' &&
186
+ compounds[value.shadowHost] && { shadowHost: compounds[value.shadowHost] }),
187
+ };
188
+ }
189
+ catch (error) {
190
+ log.debug(`Could not read the page after no match: ${getErrorMessage(error)}`);
191
+ return {};
192
+ }
193
+ }
194
+ /**
195
+ * The page's `document.readyState`, read for a failure that may come from a
196
+ * page still loading.
197
+ *
198
+ * @returns The state, or undefined when the page did not answer
199
+ */
200
+ export async function documentReadyState() {
201
+ try {
202
+ const evaluated = await callCDP('Runtime.evaluate', {
203
+ expression: 'document.readyState',
204
+ returnByValue: true,
205
+ });
206
+ const { result } = (evaluated.data?.result ?? {});
207
+ return typeof result?.value === 'string' ? result.value : undefined;
208
+ }
209
+ catch (error) {
210
+ log.debug(`Could not read document.readyState: ${getErrorMessage(error)}`);
211
+ return undefined;
212
+ }
213
+ }
214
+ /** Matches whose viewport position `dom query` reports (measuring is not free) */
215
+ const VIEWPORT_HINT_LIMIT = 100;
216
+ /**
217
+ * Where each element of a page-side array lives (an iframe and/or a shadow
218
+ * root), its text, its form control state ({@link ELEMENT_STATE_JS}) and, for
219
+ * the first {@link VIEWPORT_HINT_LIMIT}, its position
220
+ * relative to the viewport, plus the viewport size. An element that cannot be
221
+ * read gets empty details instead of failing the whole query.
222
+ */
130
223
  const ELEMENT_DETAILS_FUNCTION = `function () {
131
- const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '');
224
+ const contextOf = ${ELEMENT_CONTEXT_JS};
132
225
  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;
226
+ const geometryOf = ${ELEMENT_GEOMETRY_JS};
227
+ const stateOf = ${ELEMENT_STATE_JS};
228
+ const read = (el, index) => {
229
+ try {
230
+ return { context: contextOf(el), text: textOf(el), state: stateOf(el), geometry: index < ${VIEWPORT_HINT_LIMIT} ? geometryOf(el) : null };
231
+ } catch (e) {
232
+ return {};
139
233
  }
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
- });
234
+ };
235
+ return { viewport: (${VIEWPORT_SIZE_JS})(window), elements: Array.from(this, read) };
144
236
  }`;
145
237
  /**
146
238
  * 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.
239
+ * lives (empty for the main document), its text and its viewport position.
148
240
  *
149
241
  * @param arrayObjectId - Remote object id of the array
150
242
  * @returns Elements in array order (ones that cannot be described are left out)
243
+ * @throws CommandError (91) when the page could not describe the matches
151
244
  */
152
245
  async function elementsWithDetails(arrayObjectId) {
246
+ const { viewport, elements = [] } = await readPageDetails(arrayObjectId);
247
+ const ids = await elementBackendNodeIds(arrayObjectId);
248
+ return ids.flatMap((backendNodeId, index) => {
249
+ if (backendNodeId === undefined)
250
+ return [];
251
+ const details = elements[index];
252
+ return [
253
+ {
254
+ backendNodeId,
255
+ context: details?.context ?? '',
256
+ text: details?.text ?? '',
257
+ state: details?.state ?? {},
258
+ ...viewportHint(details?.geometry, viewport),
259
+ },
260
+ ];
261
+ });
262
+ }
263
+ /**
264
+ * Run {@link ELEMENT_DETAILS_FUNCTION} on a page-side array.
265
+ *
266
+ * @param arrayObjectId - Remote object id of the array
267
+ * @returns Details of each element and the viewport size
268
+ * @throws CommandError (91) when the script failed
269
+ */
270
+ async function readPageDetails(arrayObjectId) {
153
271
  const response = await callCDP('Runtime.callFunctionOn', {
154
272
  objectId: arrayObjectId,
155
273
  functionDeclaration: ELEMENT_DETAILS_FUNCTION,
156
274
  returnByValue: true,
157
275
  });
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
- ]);
276
+ const result = response.data?.result;
277
+ if (response.status === 'error' || result?.exceptionDetails) {
278
+ const detail = result?.exceptionDetails?.exception?.description ?? response.error ?? 'no result';
279
+ const err = operationFailedError('describe the matches', detail.split('\n')[0] ?? detail);
280
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SCRIPT_ERROR);
281
+ }
282
+ return (result?.result?.value ?? {});
283
+ }
284
+ /**
285
+ * Viewport position of a measured element for `dom query`.
286
+ *
287
+ * @param geometry - Page-side measurements (none beyond the hint limit)
288
+ * @param viewport - Viewport size
289
+ * @returns `inViewport` (and `clippedBy`), or nothing when not measured
290
+ */
291
+ function viewportHint(geometry, viewport) {
292
+ if (!geometry || !viewport)
293
+ return {};
294
+ const { inViewport, clippedBy } = classifyViewportPosition(geometry, viewport);
295
+ return { inViewport, ...(clippedBy && { clippedBy }) };
171
296
  }
172
297
  /**
173
298
  * The page-side array of a selector query, or the error explaining why there
174
299
  * 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.
300
+ * user's (81; the browser's message is left out for selectors with filters,
301
+ * as it quotes the CSS bdg rewrote them to); anything else (no session, a
302
+ * page navigating away) is not.
176
303
  *
177
304
  * @param selector - CSS selector
178
305
  * @param evaluated - `Runtime.evaluate` response
@@ -184,7 +311,7 @@ function selectionObjectId(selector, evaluated) {
184
311
  {});
185
312
  const description = exceptionDetails?.exception?.description;
186
313
  if (description?.startsWith('SyntaxError')) {
187
- const detail = description.split('\n')[0];
314
+ const detail = parseSelectorFilters(selector) ? undefined : description.split('\n')[0];
188
315
  const err = invalidSelectorError(selector, detail);
189
316
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
190
317
  }
@@ -242,21 +369,27 @@ export async function queryDOMElements(selector) {
242
369
  if (elements.length > 20) {
243
370
  log.debug(`Querying ${elements.length} elements with selector: ${selector}`);
244
371
  }
245
- const nodes = await mapConcurrently(elements, async ({ backendNodeId, context, text }, index) => {
372
+ const nodes = await mapConcurrently(elements, async (element, index) => {
373
+ const { backendNodeId, context, text, state, inViewport, clippedBy } = element;
246
374
  const desc = await describeNode({ backendNodeId });
247
375
  if (!desc)
248
376
  return { index, nodeId: 0 };
249
377
  const attributes = unpackAttributes(desc.attributes);
250
378
  const classes = attributes['class']?.split(/\s+/).filter(Boolean);
251
379
  const preview = textPreview(text);
380
+ const tag = desc.nodeName.toLowerCase();
381
+ const keys = keyAttributes(tag, attributes, state);
252
382
  return {
253
383
  index,
254
384
  nodeId: desc.backendNodeId,
255
- tag: desc.nodeName.toLowerCase(),
256
- ...identifyingAttributes(attributes),
385
+ tag,
386
+ ...identifyingAttributes(attributes, desc.nodeName),
387
+ ...(keys && { attributes: keys }),
257
388
  ...(classes && { classes }),
258
389
  ...(preview && { preview }),
259
390
  ...(context && { context }),
391
+ ...(inViewport && { inViewport }),
392
+ ...(clippedBy && { clippedBy }),
260
393
  };
261
394
  });
262
395
  return { selector, count: nodes.length, nodes };
@@ -265,63 +398,144 @@ export async function queryDOMElements(selector) {
265
398
  * The attributes that tell similar elements apart (form fields especially).
266
399
  *
267
400
  * @param attributes - Element attributes
268
- * @returns id, name and type when present
401
+ * @param nodeName - Element name (`OPTION`s also report their `value`)
402
+ * @returns id, name and type (and an option's value) when present
269
403
  */
270
- function identifyingAttributes(attributes) {
404
+ function identifyingAttributes(attributes, nodeName) {
405
+ const value = nodeName === 'OPTION' ? attributes['value'] : undefined;
271
406
  return {
272
407
  ...(attributes['id'] && { id: attributes['id'] }),
273
408
  ...(attributes['name'] && { name: attributes['name'] }),
274
409
  ...(attributes['type'] && { type: attributes['type'] }),
410
+ ...(value !== undefined && { value }),
275
411
  };
276
412
  }
277
413
  /**
278
- * The text of one element as the page renders it ({@link ELEMENT_TEXT_JS}).
414
+ * The text of one element as the page renders it ({@link ELEMENT_TEXT_JS})
415
+ * and its form control state ({@link ELEMENT_STATE_JS}).
279
416
  *
280
417
  * @param ref - Node reference
281
- * @returns Element text, or empty when the node cannot be read
418
+ * @param full - Read all of a large container's text, not just its start
419
+ * @returns Element text and state, empty when the node cannot be read
282
420
  */
283
- async function elementText(ref) {
421
+ async function elementTextAndState(ref, full) {
284
422
  const objectGroup = `bdg-text-${process.pid}-${++queryCount}`;
285
423
  const resolved = await callCDP('DOM.resolveNode', { ...ref, objectGroup });
286
424
  const objectId = resolved.data?.result?.object
287
425
  .objectId;
288
426
  if (!objectId)
289
- return '';
427
+ return { text: '', state: {} };
290
428
  try {
291
429
  const response = await callCDP('Runtime.callFunctionOn', {
292
430
  objectId,
293
- functionDeclaration: `function () { return (${ELEMENT_TEXT_JS})(this); }`,
431
+ functionDeclaration: `function (full) { return { text: (${ELEMENT_TEXT_JS})(this, full), state: (${ELEMENT_STATE_JS})(this) }; }`,
432
+ arguments: [{ value: full }],
294
433
  returnByValue: true,
295
434
  });
296
- const value = response.data?.result?.result
297
- ?.value;
298
- return typeof value === 'string' ? value : '';
435
+ const value = response.data?.result?.result?.value;
436
+ return {
437
+ text: typeof value?.text === 'string' ? value.text : '',
438
+ state: value?.state ?? {},
439
+ };
299
440
  }
300
441
  finally {
301
442
  await callCDP('Runtime.releaseObjectGroup', { objectGroup });
302
443
  }
303
444
  }
304
445
  /**
305
- * Get DOM context (tag, classes, text preview) for a node.
446
+ * Get DOM context (tag, classes, key attributes, text preview) for a node: a one-line
447
+ * preview, and up to {@link ELEMENT_TEXT_LENGTH} characters of text when it
448
+ * is longer (all of it with `full`).
306
449
  *
307
450
  * @param ref - Node reference
451
+ * @param options - `full`: the whole text instead of its first 500 characters
308
452
  * @returns DOM context, or null if the node does not exist
309
453
  */
310
- export async function getDomContext(ref) {
454
+ export async function getDomContext(ref, options = {}) {
311
455
  await callCDP('DOM.enable', {});
312
456
  const desc = await describeNode(ref);
313
457
  if (!desc) {
314
458
  log.debug(`No DOM context for ${JSON.stringify(ref)}`);
315
459
  return null;
316
460
  }
317
- const classes = unpackAttributes(desc.attributes)['class']?.split(/\s+/).filter(Boolean);
318
- const preview = textPreview(await elementText(ref));
461
+ const full = options.full === true;
462
+ const attributes = unpackAttributes(desc.attributes);
463
+ const classes = attributes['class']?.split(/\s+/).filter(Boolean);
464
+ const { text, state } = await elementTextAndState(ref, full);
465
+ const preview = textPreview(text);
466
+ const longer = textPreview(text, full ? Number.POSITIVE_INFINITY : ELEMENT_TEXT_LENGTH);
467
+ const tag = desc.nodeName.toLowerCase();
468
+ const keys = keyAttributes(tag, attributes, state);
319
469
  return {
320
- tag: desc.nodeName.toLowerCase(),
470
+ tag,
321
471
  ...(classes && classes.length > 0 && { classes }),
472
+ ...(keys && { attributes: keys }),
473
+ ...(state.sensitive && { sensitive: true }),
322
474
  ...(preview && { preview }),
475
+ ...(longer !== preview && { text: longer }),
476
+ ...(!preview && (await childElements(ref))),
477
+ };
478
+ }
479
+ /** Child elements named for an element without text */
480
+ const CHILDREN_LISTED = 5;
481
+ /**
482
+ * The child elements of an element (for one without text: a body holding
483
+ * only an iframe, an empty app root).
484
+ *
485
+ * @param ref - Node reference
486
+ * @returns The first {@link CHILDREN_LISTED} as `tag#id.class` and how many there are
487
+ */
488
+ async function childElements(ref) {
489
+ const response = await callCDP('DOM.describeNode', { ...ref, depth: 1 });
490
+ const node = response.data?.result?.node;
491
+ const elements = (node?.children ?? []).filter((child) => child.nodeType === 1);
492
+ return {
493
+ children: elements.slice(0, CHILDREN_LISTED).map(childLabel),
494
+ childCount: elements.length,
323
495
  };
324
496
  }
497
+ /**
498
+ * A child element in a few characters.
499
+ *
500
+ * @param node - Child node
501
+ * @returns e.g. `iframe#app.full`
502
+ */
503
+ function childLabel(node) {
504
+ const attributes = unpackAttributes(node.attributes);
505
+ const id = attributes['id'] ? `#${attributes['id']}` : '';
506
+ const classes = (attributes['class'] ?? '').split(/\s+/).filter(Boolean).slice(0, 2);
507
+ return `${node.nodeName.toLowerCase()}${id}${classes.map((name) => `.${name}`).join('')}`;
508
+ }
509
+ /**
510
+ * All matches of a selector.
511
+ *
512
+ * @param selector - CSS selector
513
+ * @returns Backend node ids of the matches (at least one)
514
+ * @throws CommandError (83) when nothing matches
515
+ */
516
+ async function selectMatches(selector) {
517
+ const backendNodeIds = await selectAll(selector);
518
+ if (backendNodeIds.length > 0)
519
+ return backendNodeIds;
520
+ const err = await noMatchesError(selector);
521
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
522
+ }
523
+ /**
524
+ * One match of a selector.
525
+ *
526
+ * @param selector - CSS selector
527
+ * @param index - Which match (0-based)
528
+ * @returns Its backend node id
529
+ * @throws CommandError (83) when nothing matches, (81) for an index beyond the matches
530
+ */
531
+ export async function selectMatch(selector, index = 0) {
532
+ const backendNodeIds = await selectMatches(selector);
533
+ const backendNodeId = backendNodeIds[index];
534
+ if (backendNodeId !== undefined)
535
+ return backendNodeId;
536
+ const err = indexOutOfRangeError(index, backendNodeIds.length - 1);
537
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
538
+ }
325
539
  /**
326
540
  * Pick the nodes `dom get --raw` should report for a selector.
327
541
  *
@@ -330,20 +544,10 @@ export async function getDomContext(ref) {
330
544
  * @returns Node references to describe
331
545
  */
332
546
  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);
547
+ if (options.all) {
548
+ return (await selectMatches(selector)).map((backendNodeId) => ({ backendNodeId }));
345
549
  }
346
- return [{ backendNodeId }];
550
+ return [{ backendNodeId: await selectMatch(selector, options.nth ?? 0) }];
347
551
  }
348
552
  /**
349
553
  * Get full details (attributes, outer HTML) for `bdg dom get --raw`.
@@ -366,7 +570,7 @@ export async function getDOMElements(options) {
366
570
  refs = await selectForGet(options.selector, options);
367
571
  }
368
572
  else {
369
- const err = eitherArgumentRequiredError('selector', 'nodeId', 'bdg dom get <selector> or bdg dom get --node-id <id>');
573
+ const err = eitherArgumentRequiredError('selector', 'nodeId', `${sessionCommand('bdg dom get <selector>')} or ${sessionCommand('bdg dom get --node-id <id>')}`);
370
574
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
371
575
  }
372
576
  const nodes = await mapConcurrently(refs, async (ref) => {
@@ -398,7 +602,7 @@ export async function getDOMElements(options) {
398
602
  export async function resolveSelector(selector) {
399
603
  const backendNodeId = (await selectAll(selector))[0];
400
604
  if (backendNodeId === undefined) {
401
- const err = noNodesFoundError(selector);
605
+ const err = await noMatchesError(selector);
402
606
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
403
607
  }
404
608
  return { backendNodeId };
@@ -412,16 +616,6 @@ export async function resolveSelector(selector) {
412
616
  export async function resolveBackendNodeIds(selectors) {
413
617
  return mapConcurrently(selectors, async (selector) => (await selectAll(selector))[0]);
414
618
  }
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
619
  /**
426
620
  * Check that a cached element is still part of the current page.
427
621
  *
@@ -431,10 +625,10 @@ export async function resolveA11yNodeForSelector(selector) {
431
625
  * false for removed elements.
432
626
  *
433
627
  * @param backendNodeId - Backend node id from the query cache
434
- * @param index - Index the user gave, for the error message
628
+ * @param source - Index the user gave and the list it refers to, for the error message
435
629
  * @throws CommandError (87) when the element is gone
436
630
  */
437
- export async function assertNodeAttached(backendNodeId, index) {
631
+ export async function assertNodeAttached(backendNodeId, source) {
438
632
  const resolved = await callCDP('DOM.resolveNode', { backendNodeId, objectGroup: 'bdg-check' });
439
633
  const objectId = resolved.data?.result?.object
440
634
  .objectId;
@@ -450,7 +644,7 @@ export async function assertNodeAttached(backendNodeId, index) {
450
644
  await callCDP('Runtime.releaseObjectGroup', { objectGroup: 'bdg-check' });
451
645
  }
452
646
  if (!attached) {
453
- const err = staleNodeError(index);
647
+ const err = staleNodeError(source?.index, source);
454
648
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
455
649
  }
456
650
  }
@@ -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