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
@@ -18,6 +18,55 @@ export const FORM_DISCOVERY_SCRIPT = `
18
18
  (function() {
19
19
  const result = { forms: [] };
20
20
 
21
+ // Rendered and not visibility-hidden, ancestors included (opacity is left
22
+ // out: styled checkboxes and radios are often transparent)
23
+ function isShown(element) {
24
+ if (typeof element.checkVisibility === 'function') {
25
+ return element.checkVisibility({ visibilityProperty: true });
26
+ }
27
+ const style = window.getComputedStyle(element);
28
+ return style.display !== 'none' && style.visibility !== 'hidden';
29
+ }
30
+
31
+ // Inside an open dialog: <dialog open>, aria-modal or a dialog role, or a
32
+ // modal without them (DocSearch): an overlay ancestor below <body>
33
+ // (position fixed or absolute) that is raised (z-index > 0) or has a
34
+ // backdrop and whose class names a modal or dialog, or a fixed overlay over
35
+ // half the viewport or more that is raised or lies over other page content
36
+ // at the viewport centre. Static wrappers (Drupal's
37
+ // dialog-off-canvas-main-canvas) and fixed app shells holding the whole
38
+ // page are not dialogs.
39
+ function inDialog(element) {
40
+ if (element.closest('dialog[open], [aria-modal="true"], [role="dialog"], [role="alertdialog"]')) return true;
41
+ const viewportArea = window.innerWidth * window.innerHeight;
42
+ for (let node = element.parentElement; node && node !== document.body; node = node.parentElement) {
43
+ const style = window.getComputedStyle(node);
44
+ if (style.position !== 'fixed' && style.position !== 'absolute') continue;
45
+ const raised = parseInt(style.zIndex, 10) > 0;
46
+ const className = typeof node.className === 'string' ? node.className : '';
47
+ if (/(^|[\\s_-])(modal|dialog)($|[\\s_-])/i.test(className) && (raised || hasBackdrop(node))) return true;
48
+ if (style.position !== 'fixed') continue;
49
+ const box = node.getBoundingClientRect();
50
+ if (box.width * box.height >= viewportArea / 2 && (raised || coversContent(node))) return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ // A backdrop element next to or inside an overlay
56
+ function hasBackdrop(node) {
57
+ const backdrop = /backdrop|overlay/i;
58
+ const near = [node.previousElementSibling, node.nextElementSibling, ...Array.from(node.children)];
59
+ return near.some((n) => n && typeof n.className === 'string' && backdrop.test(n.className));
60
+ }
61
+
62
+ // The overlay is on top at the viewport centre with other page content
63
+ // (not its ancestors) below it
64
+ function coversContent(node) {
65
+ const stack = document.elementsFromPoint(window.innerWidth / 2, window.innerHeight / 2);
66
+ if (!stack.length || !node.contains(stack[0])) return false;
67
+ return stack.some((n) => !n.contains(node) && !node.contains(n));
68
+ }
69
+
21
70
  function isUnique(selector) {
22
71
  try {
23
72
  return document.querySelectorAll(selector).length === 1;
@@ -66,9 +115,50 @@ export const FORM_DISCOVERY_SCRIPT = `
66
115
  cleaned = cleaned.replace(pattern, '');
67
116
  }
68
117
  cleaned = cleaned.replace(/\\s{2,}/g, ' ').trim();
118
+ cleaned = cleaned.replace(/^\\*\\s+/, '').replace(/\\s+\\*(\\s*:?)$/, '$1');
69
119
  return cleaned || text.trim();
70
120
  }
71
121
 
122
+ // The element labelling a field: label[for], aria-labelledby, wrapping label
123
+ function labelElement(element) {
124
+ const labelFor = element.id ? document.querySelector('label[for="' + CSS.escape(element.id) + '"]') : null;
125
+ const labelledBy = element.getAttribute('aria-labelledby');
126
+ return labelFor || (labelledBy && document.getElementById(labelledBy)) || element.closest('label');
127
+ }
128
+
129
+ // A standalone required star in label text: "* Name", "Name *", "Name *:"
130
+ // (not a footnote mark such as "Terms*")
131
+ function labelMarksRequired(text) {
132
+ return /^\\*\\s|\\s\\*\\s*:?$/.test(String(text).trim());
133
+ }
134
+
135
+ // Required by attribute, ARIA, or a label marked with a standalone star,
136
+ // or with an element of its own holding just "*" (<span aria-hidden>*</span>)
137
+ function isRequired(element) {
138
+ if (element.required || element.getAttribute('aria-required') === 'true') return true;
139
+ const label = labelElement(element);
140
+ if (!label) return false;
141
+ const clone = label.cloneNode(true);
142
+ clone.querySelectorAll('input, select, textarea, button').forEach(i => i.remove());
143
+ return labelMarksRequired(clone.textContent || '') ||
144
+ Array.from(clone.querySelectorAll('*')).some(n => n.textContent.trim() === '*');
145
+ }
146
+
147
+ // Name of the radio or checkbox group a field belongs to: its radiogroup's
148
+ // label, its fieldset's legend, or its name
149
+ function groupLabel(element) {
150
+ const type = element.type?.toLowerCase();
151
+ if ((type !== 'radio' && type !== 'checkbox') || !element.name) return undefined;
152
+ const group = element.closest('[role="radiogroup"], fieldset');
153
+ const named = group && (group.getAttribute('aria-label') ||
154
+ (group.querySelector(':scope > legend') || {}).textContent);
155
+ if (named && named.trim()) return cleanLabelText(named);
156
+ return element.name
157
+ .replace(/[_-]/g, ' ')
158
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
159
+ .replace(/^./, s => s.toUpperCase());
160
+ }
161
+
72
162
  function extractLabel(element) {
73
163
  if (element.id) {
74
164
  const labelFor = document.querySelector('label[for="' + CSS.escape(element.id) + '"]');
@@ -237,7 +327,7 @@ export const FORM_DISCOVERY_SCRIPT = `
237
327
  score += Math.min(distinctFields * 3, 30);
238
328
  const textTypes = ['text', 'search', 'email', 'password', 'textarea', 'tel', 'url', 'number', 'textbox'];
239
329
  if (fields.some((f) => textTypes.includes(f.type))) score += 10;
240
- const hasSubmit = buttons.some(b => b.type === 'submit' || b.isPrimary);
330
+ const hasSubmit = buttons.some(b => b.type === 'submit' || b.primaryClass);
241
331
  if (hasSubmit) score += 10;
242
332
  const style = window.getComputedStyle(formEl);
243
333
  if (style.display === 'none' || style.visibility === 'hidden') score -= 100;
@@ -377,8 +467,7 @@ export const FORM_DISCOVERY_SCRIPT = `
377
467
  const allInputs = new Set([...nativeInputs, ...customInputs]);
378
468
  let idx = startIndex;
379
469
  for (const el of allInputs) {
380
- const style = window.getComputedStyle(el);
381
- const isHidden = style.display === 'none' || style.visibility === 'hidden' || el.type === 'hidden';
470
+ const isHidden = el.type === 'hidden' || !isShown(el);
382
471
  const value = getFieldValue(el);
383
472
  fields.push({
384
473
  index: idx,
@@ -389,7 +478,8 @@ export const FORM_DISCOVERY_SCRIPT = `
389
478
  label: extractLabel(el),
390
479
  name: el.name || null,
391
480
  placeholder: el.placeholder || undefined,
392
- required: el.required || el.getAttribute('aria-required') === 'true',
481
+ required: isRequired(el),
482
+ groupLabel: groupLabel(el),
393
483
  disabled: el.disabled || el.getAttribute('aria-disabled') === 'true',
394
484
  readOnly: el.readOnly || false,
395
485
  hidden: isHidden,
@@ -420,21 +510,22 @@ export const FORM_DISCOVERY_SCRIPT = `
420
510
  );
421
511
  let idx = startIndex;
422
512
  for (const el of buttonEls) {
423
- const style = window.getComputedStyle(el);
424
- if (style.display === 'none' || style.visibility === 'hidden') continue;
513
+ if (!isShown(el)) continue;
425
514
  const type = el.type?.toLowerCase() || 'button';
426
515
  const btnType = type === 'submit' ? 'submit' : type === 'reset' ? 'reset' : 'button';
427
- const isPrimary = btnType === 'submit' ||
428
- el.classList.contains('primary') ||
429
- el.classList.contains('btn-primary') ||
430
- el.classList.contains('submit');
516
+ const explicitSubmit = btnType === 'submit' &&
517
+ (el.tagName === 'INPUT' || (el.getAttribute('type') || '').toLowerCase() === 'submit');
518
+ const formDefault = btnType === 'submit' && !explicitSubmit && Boolean(el.form);
519
+ const primaryClass = Array.from(el.classList).some(c => /^(btn[-_])?(primary|submit)$/i.test(c));
431
520
  buttons.push({
432
521
  index: idx,
433
522
  selector: generateSelector(el),
434
523
  label: extractButtonLabel(el),
435
524
  type: btnType,
436
525
  disabled: el.disabled || el.getAttribute('aria-disabled') === 'true',
437
- isPrimary: isPrimary
526
+ explicitSubmit: explicitSubmit,
527
+ formDefault: formDefault,
528
+ primaryClass: primaryClass
438
529
  });
439
530
  idx++;
440
531
  }
@@ -444,9 +535,11 @@ export const FORM_DISCOVERY_SCRIPT = `
444
535
  const forms = document.querySelectorAll('form');
445
536
  let globalIndex = 0;
446
537
 
447
- if (forms.length === 0) {
448
- const bodyFields = discoverFields(document.body, 0, 0);
449
- const bodyButtons = discoverButtons(document.body, bodyFields.length);
538
+ // A page still loading may not have a body yet
539
+ const pageRoot = document.body || document.documentElement;
540
+ if (forms.length === 0 && pageRoot) {
541
+ const bodyFields = discoverFields(pageRoot, 0, 0);
542
+ const bodyButtons = discoverButtons(pageRoot, bodyFields.length);
450
543
  if (bodyFields.length > 0) {
451
544
  result.forms.push({
452
545
  index: 0,
@@ -455,6 +548,8 @@ export const FORM_DISCOVERY_SCRIPT = `
455
548
  method: 'GET',
456
549
  step: null,
457
550
  relevanceScore: bodyFields.length * 3,
551
+ hidden: false,
552
+ inDialog: false,
458
553
  inIframe: false,
459
554
  fields: bodyFields,
460
555
  buttons: bodyButtons
@@ -468,6 +563,10 @@ export const FORM_DISCOVERY_SCRIPT = `
468
563
  const buttons = discoverButtons(formEl, globalIndex);
469
564
  globalIndex += buttons.length;
470
565
  const inIframe = formEl.ownerDocument !== document;
566
+ // Shown when a field or a button is (buttons list shown ones only), or
567
+ // it has neither (a form with only type=hidden inputs and no button)
568
+ const shown = isShown(formEl) &&
569
+ (fields.some((f) => !f.hidden) || buttons.length > 0 || fields.length === 0);
471
570
  result.forms.push({
472
571
  index: i,
473
572
  name: extractFormName(formEl),
@@ -475,6 +574,8 @@ export const FORM_DISCOVERY_SCRIPT = `
475
574
  method: (formEl.method || 'GET').toUpperCase(),
476
575
  step: detectFormStep(formEl),
477
576
  relevanceScore: calculateRelevance(formEl, fields, buttons),
577
+ hidden: !shown,
578
+ inDialog: shown && inDialog(formEl),
478
579
  inIframe: inIframe,
479
580
  fields: fields,
480
581
  buttons: buttons
@@ -482,8 +583,6 @@ export const FORM_DISCOVERY_SCRIPT = `
482
583
  }
483
584
  }
484
585
 
485
- result.forms.sort((a, b) => b.relevanceScore - a.relevanceScore);
486
-
487
586
  // Forms the main document does not contain may be in its same-origin iframes
488
587
  if (result.forms.length === 0) {
489
588
  result.frameForms = [];
@@ -496,6 +595,7 @@ export const FORM_DISCOVERY_SCRIPT = `
496
595
  }
497
596
  }
498
597
 
598
+ result.readyState = document.readyState;
499
599
  return result;
500
600
  })()
501
601
  `;
@@ -19,11 +19,20 @@ export type PointerAction = 'click' | 'double' | 'right' | 'hover';
19
19
  * @returns `Input.dispatchMouseEvent` parameters, in order
20
20
  */
21
21
  export declare function mouseEvents(action: PointerAction, x: number, y: number): Array<Record<string, unknown>>;
22
+ /**
23
+ * Whether the mouse press just dispatched reached the target element
24
+ * (see {@link readPressProbe}).
25
+ *
26
+ * @param cdp - CDP connection
27
+ * @returns False only when the target received no pointerdown/mousedown
28
+ */
29
+ export declare function pressReachedTarget(cdp: CDPConnection): Promise<boolean>;
22
30
  /**
23
31
  * Click an element.
24
32
  */
25
33
  export declare function clickElement(cdp: CDPConnection, selector: string, options?: {
26
34
  index?: number;
27
35
  action?: PointerAction;
36
+ strict?: boolean;
28
37
  }): Promise<ClickResult>;
29
38
  //# sourceMappingURL=fill.d.ts.map
@@ -5,12 +5,12 @@
5
5
  import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  import { CommandError } from '../../../errors/index.js';
8
- import { fileNotFoundError, uploadDirectoryError, singleFileInputError, fillableElementNotFoundError, clickableElementNotFoundError, clickTargetDetachedError, unexpectedResponseFormatError, operationFailedError, } from '../../../errors/messages.js';
9
- import { escapeValueForJS, formatScriptExecutionError, throwIfInvalidSelector, withMultipleMatchesWarning, } from './shared.js';
10
- import { REACT_FILL_SCRIPT, CLICK_ELEMENT_SCRIPT, isFillResult, isClickResult, } from '../reactEventHelpers.js';
11
- import { FIND_ELEMENTS_JS, selectorArgsJS } from '../targetNode.js';
8
+ import { fileNotFoundError, uploadDirectoryError, singleFileInputError, fillableElementNotFoundError, clickableElementNotFoundError, clickTargetDetachedError, unexpectedResponseFormatError, operationFailedError, pressNotReceivedError, unreachableElementError, } from '../../../errors/messages.js';
9
+ import { escapeValueForJS, formatScriptExecutionError, throwIfInvalidSelector, withMultipleMatchesWarning, withValueMismatchWarning, } from './shared.js';
10
+ import { FILL_READ_BACK_SCRIPT, REACT_FILL_SCRIPT, CLICK_ELEMENT_SCRIPT, isFillResult, isClickResult, } from '../reactEventHelpers.js';
11
+ import { FIND_ELEMENTS_JS, LABEL_CONTROL_JS, selectorArgsJS } from '../targetNode.js';
12
12
  import { createLogger } from '../../../ui/logging/index.js';
13
- import { POINTER_ACTION_DONE, domClickFallbackWarning } from '../../../ui/messages/commands.js';
13
+ import { CLICK_NOT_RECEIVED_WARNING, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, domClickFallbackWarning, } from '../../../ui/messages/commands.js';
14
14
  import { getErrorMessage } from '../../../utils/errors.js';
15
15
  import { EXIT_CODES } from '../../../utils/exitCodes.js';
16
16
  const log = createLogger('dom');
@@ -38,7 +38,12 @@ export async function fillElement(cdp, selector, value, options = {}) {
38
38
  }
39
39
  if (cdpResponse.result?.value && isFillResult(cdpResponse.result.value)) {
40
40
  const result = cdpResponse.result.value;
41
- return result.fileInput ? await setFileInput(cdp, selector, value, options) : result;
41
+ if (!result.fileInput)
42
+ return withValueMismatchWarning(await withReadBack(cdp, result));
43
+ const uploaded = await setFileInput(cdp, selector, value, options);
44
+ return uploaded.success && result.elementType
45
+ ? { ...uploaded, elementType: result.elementType }
46
+ : uploaded;
42
47
  }
43
48
  const err = unexpectedResponseFormatError('FillResult');
44
49
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
@@ -52,6 +57,52 @@ export async function fillElement(cdp, selector, value, options = {}) {
52
57
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
53
58
  }
54
59
  }
60
+ /** How long reading a filled value back may take before it is skipped */
61
+ const READ_BACK_TIMEOUT_MS = 1000;
62
+ /**
63
+ * Add the read-back of a successful fill ({@link FILL_READ_BACK_SCRIPT}):
64
+ * `valueMismatch` when the field's value is not the one given. The fill
65
+ * stands without it when the read-back fails, takes longer than
66
+ * {@link READ_BACK_TIMEOUT_MS} or finds nothing (a change handler navigated
67
+ * or replaced the page).
68
+ *
69
+ * @param cdp - CDP connection
70
+ * @param result - Fill result
71
+ * @returns The result, with `valueMismatch` when the value differs
72
+ */
73
+ async function withReadBack(cdp, result) {
74
+ if (!result.success)
75
+ return result;
76
+ let timer;
77
+ const timedOut = new Promise((resolve) => {
78
+ timer = setTimeout(() => resolve(undefined), READ_BACK_TIMEOUT_MS);
79
+ });
80
+ const readBack = cdp
81
+ .send('Runtime.evaluate', {
82
+ expression: FILL_READ_BACK_SCRIPT,
83
+ returnByValue: true,
84
+ awaitPromise: true,
85
+ })
86
+ .then((response) => response.result?.value)
87
+ .catch((error) => {
88
+ log.debug(`Filled value not read back: ${getErrorMessage(error)}`);
89
+ return undefined;
90
+ });
91
+ const mismatch = await Promise.race([readBack, timedOut]).finally(() => clearTimeout(timer));
92
+ return isValueMismatch(mismatch) ? { ...result, valueMismatch: mismatch } : result;
93
+ }
94
+ /**
95
+ * Whether a read-back value is a mismatch.
96
+ *
97
+ * @param value - Value from {@link FILL_READ_BACK_SCRIPT}
98
+ * @returns True for `{ expected, actual }`
99
+ */
100
+ function isValueMismatch(value) {
101
+ if (typeof value !== 'object' || value === null)
102
+ return false;
103
+ const obj = value;
104
+ return typeof obj['expected'] === 'string' && typeof obj['actual'] === 'string';
105
+ }
55
106
  /** Empties a file input as a user removing the selection would (with events). */
56
107
  const CLEAR_FILE_INPUT_FUNCTION = `function () {
57
108
  this.value = '';
@@ -125,7 +176,7 @@ async function setFileInput(cdp, selector, value, options) {
125
176
  return problem;
126
177
  try {
127
178
  const located = (await cdp.send('Runtime.evaluate', {
128
- expression: `(${FIND_ELEMENTS_JS})(${selectorArgsJS(selector)})[${options.index ?? 0}]`,
179
+ expression: `((el) => (${LABEL_CONTROL_JS})(el) || el)((${FIND_ELEMENTS_JS})(${selectorArgsJS(selector)})[${options.index ?? 0}])`,
129
180
  objectGroup: UPLOAD_OBJECT_GROUP,
130
181
  }));
131
182
  if (located.exceptionDetails)
@@ -213,6 +264,87 @@ export function mouseEvents(action, x, y) {
213
264
  ];
214
265
  return action === 'double' ? [moved, ...press(1), ...press(2)] : [moved, ...press(1)];
215
266
  }
267
+ /**
268
+ * Page script that stops the press probe installed by CLICK_ELEMENT_SCRIPT
269
+ * and evaluates to whether the mouse press reached the target and, when it
270
+ * did not, the element it landed on (if the page saw it at all).
271
+ */
272
+ const PRESS_PROBE_READ_SCRIPT = `(() => {
273
+ const probe = window.__bdgPressProbe;
274
+ delete window.__bdgPressProbe;
275
+ if (!probe) return { reached: true };
276
+ probe.stop();
277
+ return { reached: probe.reached, landedOn: probe.landedOn };
278
+ })()`;
279
+ /**
280
+ * Read the press probe: whether the mouse press just dispatched reached the
281
+ * target element, and otherwise where it landed.
282
+ *
283
+ * Any doubt (no probe, evaluation failure) counts as reached, so only a
284
+ * press that the page provably never saw is reported.
285
+ *
286
+ * @param cdp - CDP connection
287
+ * @returns Outcome; not reached only when the target received no pointerdown/mousedown
288
+ */
289
+ async function readPressProbe(cdp) {
290
+ try {
291
+ const response = (await cdp.send('Runtime.evaluate', {
292
+ expression: PRESS_PROBE_READ_SCRIPT,
293
+ returnByValue: true,
294
+ }));
295
+ const value = response.result?.value;
296
+ if (value?.reached !== false)
297
+ return { reached: true };
298
+ return typeof value.landedOn === 'string'
299
+ ? { reached: false, landedOn: value.landedOn }
300
+ : { reached: false };
301
+ }
302
+ catch (error) {
303
+ log.debug(`Press probe not read: ${getErrorMessage(error)}`);
304
+ return { reached: true };
305
+ }
306
+ }
307
+ /**
308
+ * Whether the mouse press just dispatched reached the target element
309
+ * (see {@link readPressProbe}).
310
+ *
311
+ * @param cdp - CDP connection
312
+ * @returns False only when the target received no pointerdown/mousedown
313
+ */
314
+ export async function pressReachedTarget(cdp) {
315
+ return (await readPressProbe(cdp)).reached;
316
+ }
317
+ /**
318
+ * Dispatch real mouse events for an action, checking after the first press
319
+ * that the target received it. With `stopIfMissed`, a press that missed is
320
+ * only released (no further presses). If dispatching fails first, the press
321
+ * probe is still removed from the page.
322
+ *
323
+ * @param cdp - CDP connection
324
+ * @param action - What to do
325
+ * @param point - Page coordinates
326
+ * @param stopIfMissed - Stop after releasing a press that missed (--strict)
327
+ * @returns Whether the first press reached the target, and where it landed otherwise
328
+ */
329
+ async function dispatchMouseAction(cdp, action, point, stopIfMissed) {
330
+ let outcome;
331
+ try {
332
+ for (const event of mouseEvents(action, point.x, point.y)) {
333
+ await cdp.send('Input.dispatchMouseEvent', event);
334
+ if (event['type'] === 'mouseReleased' && outcome?.reached === false && stopIfMissed)
335
+ break;
336
+ if (event['type'] === 'mousePressed' && outcome === undefined) {
337
+ outcome = await readPressProbe(cdp);
338
+ }
339
+ }
340
+ }
341
+ catch (error) {
342
+ if (outcome === undefined)
343
+ await readPressProbe(cdp);
344
+ throw error;
345
+ }
346
+ return outcome ?? { reached: true };
347
+ }
216
348
  /**
217
349
  * Drop the located element from the page without waiting: right after a
218
350
  * click on a link, evaluation waits until the new page commits (seconds for a
@@ -225,30 +357,62 @@ function releaseClickTarget(cdp) {
225
357
  .send('Runtime.evaluate', { expression: 'delete window.__bdgClickTarget' })
226
358
  .catch((error) => log.debug(`Click target not released: ${getErrorMessage(error)}`));
227
359
  }
360
+ /**
361
+ * Refuse a pointer action under `--strict` (exit 90, the page's state
362
+ * conflicts with the request): the element is unreachable by the mouse, or
363
+ * the press never reached it.
364
+ *
365
+ * @param result - Located click (selector and element description)
366
+ * @param action - What was refused
367
+ * @param why - Why the mouse could not reach it (nothing was sent), or the
368
+ * press that was sent and missed
369
+ * @returns Never
370
+ * @throws CommandError always
371
+ */
372
+ function refuseUnreachable(result, action, why) {
373
+ const target = { selector: result.selector ?? '', element: result.element };
374
+ const verb = POINTER_ACTION_NOUN[action];
375
+ const err = 'missed' in why
376
+ ? pressNotReceivedError(target, verb, why.missed.landedOn)
377
+ : unreachableElementError(target, why.obstruction, verb);
378
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_CONFLICT);
379
+ }
228
380
  /**
229
381
  * Click a located element.
230
382
  *
231
383
  * Uses real mouse events (pointerdown/mousedown/pointerup/mouseup/click, all
232
384
  * trusted) at the element's center when it is the topmost element there, so
233
385
  * components that react to pointer or mouse events (menus, selects) respond.
234
- * Otherwise (covered or zero-size) falls back to `el.click()` and says so.
386
+ * Otherwise (covered or zero-size) falls back to `el.click()` and says so,
387
+ * or with `strict` refuses (as it does when the press never reached it).
235
388
  * Double and right clicks, and hovering, work the same way.
236
389
  *
237
390
  * @param cdp - CDP connection
238
391
  * @param located - Locate result with center point
239
392
  * @param action - Click, double click, right click or hover
393
+ * @param strict - Refuse instead of falling back to DOM events
240
394
  * @returns Click result
395
+ * @throws CommandError under `strict` when a user could not reach the element
241
396
  */
242
- async function performClick(cdp, located, action) {
397
+ async function performClick(cdp, located, action, strict) {
243
398
  const { x, y, hittable, obstruction, ...result } = located;
244
399
  if (!result.success)
245
400
  return result;
246
401
  if (hittable && x !== undefined && y !== undefined) {
247
- for (const event of mouseEvents(action, x, y)) {
248
- await cdp.send('Input.dispatchMouseEvent', event);
249
- }
402
+ const press = await dispatchMouseAction(cdp, action, { x, y }, strict);
403
+ releaseClickTarget(cdp);
404
+ if (!press.reached && strict)
405
+ refuseUnreachable(result, action, { missed: press });
406
+ return withMultipleMatchesWarning({
407
+ ...result,
408
+ action,
409
+ method: 'mouse',
410
+ ...(!press.reached && { warning: CLICK_NOT_RECEIVED_WARNING }),
411
+ }, result.selectedIndex, `${POINTER_ACTION_DONE[action].toLowerCase()} the first visible one`);
412
+ }
413
+ if (strict) {
250
414
  releaseClickTarget(cdp);
251
- return withMultipleMatchesWarning({ ...result, action, method: 'mouse' }, result.selectedIndex, `${POINTER_ACTION_DONE[action].toLowerCase()} the first visible one`);
415
+ refuseUnreachable(result, action, { obstruction: obstruction ?? null });
252
416
  }
253
417
  const response = (await cdp.send('Runtime.evaluate', {
254
418
  expression: domFallbackScript(action),
@@ -282,7 +446,7 @@ export async function clickElement(cdp, selector, options = {}) {
282
446
  throw new CommandError(errorMessage, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
283
447
  }
284
448
  if (cdpResponse.result?.value && isClickResult(cdpResponse.result.value)) {
285
- return await performClick(cdp, cdpResponse.result.value, action);
449
+ return await performClick(cdp, cdpResponse.result.value, action, options.strict === true);
286
450
  }
287
451
  const err = unexpectedResponseFormatError('ClickResult');
288
452
  throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
@@ -4,7 +4,7 @@
4
4
  * - `fill.ts` — fillElement, clickElement
5
5
  * - `pressKey.ts` — pressKeyElement + types
6
6
  * - `scroll.ts` — scrollPage + types
7
- * - `stability.ts` — waitForActionStability
7
+ * - `stability.ts` — withActionStability
8
8
  * - `shared.ts` — internal script/error helpers
9
9
  *
10
10
  * Callers keep importing from `@/commands/dom/formFillHelpers.js` which
@@ -13,5 +13,5 @@
13
13
  export { fillElement, clickElement } from './fill.js';
14
14
  export { pressKeyElement, type PressKeyOptions, type PressKeyResult, } from './pressKey.js';
15
15
  export { scrollPage, type ScrollOptions, type ScrollResult, } from './scroll.js';
16
- export { waitForActionStability } from './stability.js';
16
+ export { withActionStability } from './stability.js';
17
17
  //# sourceMappingURL=index.d.ts.map
@@ -4,7 +4,7 @@
4
4
  * - `fill.ts` — fillElement, clickElement
5
5
  * - `pressKey.ts` — pressKeyElement + types
6
6
  * - `scroll.ts` — scrollPage + types
7
- * - `stability.ts` — waitForActionStability
7
+ * - `stability.ts` — withActionStability
8
8
  * - `shared.ts` — internal script/error helpers
9
9
  *
10
10
  * Callers keep importing from `@/commands/dom/formFillHelpers.js` which
@@ -13,5 +13,5 @@
13
13
  export { fillElement, clickElement } from './fill.js';
14
14
  export { pressKeyElement, } from './pressKey.js';
15
15
  export { scrollPage, } from './scroll.js';
16
- export { waitForActionStability } from './stability.js';
16
+ export { withActionStability } from './stability.js';
17
17
  //# sourceMappingURL=index.js.map
@@ -4,10 +4,11 @@
4
4
  * that CDP skips (keypress / input / change / submit).
5
5
  */
6
6
  import { CommandError } from '../../../errors/index.js';
7
- import { keyPressFailedError, operationFailedError, unknownKeyError } from '../../../errors/messages.js';
7
+ import { VIA_LABEL_SUFFIX, keyPressFailedError, operationFailedError, unknownKeyError, } from '../../../errors/messages.js';
8
+ import { ELEMENT_IDENTITY_JS } from '../elementInfo.js';
8
9
  import { throwIfInvalidSelector, withMultipleMatchesWarning, } from './shared.js';
9
10
  import { describeModifiers, getKeyDefinition, impliesShift, MODIFIER_FLAGS, parseModifiers, similarKeyNames, shortcutCommands, } from '../keyMapping.js';
10
- import { FIND_ELEMENTS_JS, selectorArgsJS } from '../targetNode.js';
11
+ import { FIND_ELEMENTS_JS, LABEL_CONTROL_JS, selectorArgsJS } from '../targetNode.js';
11
12
  import { EXIT_CODES } from '../../../utils/exitCodes.js';
12
13
  const FOCUS_ELEMENT_SCRIPT = `
13
14
  (function(selector, parts, index) {
@@ -29,6 +30,9 @@ const FOCUS_ELEMENT_SCRIPT = `
29
30
  } else {
30
31
  el = allMatches[0];
31
32
  }
33
+ const labelControl = (${LABEL_CONTROL_JS})(el);
34
+ const viaLabel = labelControl ? ${JSON.stringify(VIA_LABEL_SUFFIX)} : '';
35
+ if (labelControl) el = labelControl;
32
36
 
33
37
  const pageLevel = el === document.body || el === document.documentElement;
34
38
  if (!pageLevel) {
@@ -61,7 +65,13 @@ const FOCUS_ELEMENT_SCRIPT = `
61
65
  }
62
66
  }
63
67
 
64
- return { success: true, selector: selector, elementType: el.tagName.toLowerCase(), matchCount: allMatches.length };
68
+ return {
69
+ success: true,
70
+ selector: selector,
71
+ element: (${ELEMENT_IDENTITY_JS})(el),
72
+ elementType: el.tagName.toLowerCase() + viaLabel,
73
+ matchCount: allMatches.length
74
+ };
65
75
  })`;
66
76
  /** Exit codes and suggestions for focus failures, by reason. */
67
77
  const FOCUS_FAILURES = {
@@ -135,6 +145,7 @@ export async function pressKeyElement(cdp, selector, keyName, options = {}) {
135
145
  key: keyName,
136
146
  times,
137
147
  ...(modifierFlags > 0 && { modifiers: describeModifiers(modifierFlags) }),
148
+ ...(focusResult.element !== undefined && { element: focusResult.element }),
138
149
  elementType: focusResult.elementType,
139
150
  ...(focusResult.matchCount !== undefined && { matchCount: focusResult.matchCount }),
140
151
  }, options.index, 'pressed the key on the first');
@@ -6,6 +6,9 @@ import type { CDPConnection } from '../../../connection/cdp.js';
6
6
  import type { ScrollResult } from '../../../ipc/protocol/domTypes.js';
7
7
  /**
8
8
  * Scroll options.
9
+ *
10
+ * The reported viewport is the layout viewport without scrollbars and the
11
+ * page size the scrolling element's, as in `bdg dom layout`.
9
12
  */
10
13
  export interface ScrollOptions {
11
14
  down?: number;