browser-debugger-cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/README.md +4 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +27 -5
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +152 -113
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/query.d.ts +44 -17
  27. package/dist/commands/dom/helpers/query.js +244 -97
  28. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  29. package/dist/commands/dom/helpers/runElementCommand.js +97 -30
  30. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  31. package/dist/commands/dom/helpers/screenshot.js +164 -49
  32. package/dist/commands/dom/index.d.ts +3 -1
  33. package/dist/commands/dom/index.js +16 -6
  34. package/dist/commands/dom/layout.d.ts +14 -0
  35. package/dist/commands/dom/layout.js +54 -0
  36. package/dist/commands/dom/listeners.d.ts +5 -1
  37. package/dist/commands/dom/listeners.js +13 -3
  38. package/dist/commands/dom/query.js +2 -3
  39. package/dist/commands/dom/screenshot.d.ts +12 -2
  40. package/dist/commands/dom/screenshot.js +27 -3
  41. package/dist/commands/dom/semanticUtils.d.ts +6 -13
  42. package/dist/commands/dom/semanticUtils.js +15 -19
  43. package/dist/commands/dom/wait.d.ts +13 -0
  44. package/dist/commands/dom/wait.js +83 -0
  45. package/dist/commands/helpJson.js +2 -2
  46. package/dist/commands/network/list.js +4 -11
  47. package/dist/commands/optionBehaviors.js +112 -21
  48. package/dist/commands/page.d.ts +2 -1
  49. package/dist/commands/page.js +41 -5
  50. package/dist/commands/peek.js +4 -11
  51. package/dist/commands/sessions.d.ts +8 -0
  52. package/dist/commands/sessions.js +19 -0
  53. package/dist/commands/shared/CommandRunner.js +4 -4
  54. package/dist/commands/shared/dataFetcher.js +2 -2
  55. package/dist/commands/shared/followMode.d.ts +21 -1
  56. package/dist/commands/shared/followMode.js +29 -2
  57. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  58. package/dist/commands/shared/handleValidationError.js +12 -3
  59. package/dist/commands/shared/optionTypes.d.ts +40 -5
  60. package/dist/commands/shared/startHelpers.js +12 -3
  61. package/dist/commands/shared/validation.d.ts +3 -2
  62. package/dist/commands/shared/validation.js +4 -3
  63. package/dist/commands/start.d.ts +63 -0
  64. package/dist/commands/start.js +115 -15
  65. package/dist/commands/status.js +29 -7
  66. package/dist/commands/stop.js +7 -6
  67. package/dist/commands/tail.js +4 -11
  68. package/dist/commands/types.d.ts +2 -0
  69. package/dist/commands.js +2 -0
  70. package/dist/connection/chromeIdentity.d.ts +65 -0
  71. package/dist/connection/chromeIdentity.js +143 -0
  72. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  73. package/dist/connection/launcher/profilePreferences.js +151 -0
  74. package/dist/connection/launcher.d.ts +21 -2
  75. package/dist/connection/launcher.js +42 -16
  76. package/dist/connection/portReservation.d.ts +14 -4
  77. package/dist/connection/portReservation.js +21 -6
  78. package/dist/connection/startupExit.d.ts +8 -0
  79. package/dist/connection/startupExit.js +15 -6
  80. package/dist/constants.d.ts +6 -2
  81. package/dist/constants.js +9 -2
  82. package/dist/daemon/SessionController.js +23 -7
  83. package/dist/daemon/errors.d.ts +1 -1
  84. package/dist/daemon/errors.js +1 -1
  85. package/dist/daemon/launcher.d.ts +2 -1
  86. package/dist/daemon/launcher.js +5 -6
  87. package/dist/daemon/server/SocketServer.js +1 -2
  88. package/dist/daemon/session/Session.d.ts +13 -0
  89. package/dist/daemon/session/Session.js +57 -8
  90. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  91. package/dist/daemon/session/chromeConnection.js +45 -8
  92. package/dist/daemon/session/commandRegistry.js +52 -62
  93. package/dist/daemon/session/interactions.d.ts +35 -9
  94. package/dist/daemon/session/interactions.js +36 -9
  95. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  96. package/dist/daemon/session/triggeredRequests.js +157 -0
  97. package/dist/daemon/session/types.d.ts +5 -1
  98. package/dist/daemon.js +5393 -1600
  99. package/dist/errors/messages.d.ts +387 -24
  100. package/dist/errors/messages.js +761 -67
  101. package/dist/index.js +3976 -1558
  102. package/dist/ipc/client.d.ts +12 -1
  103. package/dist/ipc/client.js +22 -3
  104. package/dist/ipc/protocol/commands.d.ts +89 -4
  105. package/dist/ipc/protocol/commands.js +2 -0
  106. package/dist/ipc/protocol/domTypes.d.ts +258 -7
  107. package/dist/ipc/session/lifecycle.d.ts +8 -1
  108. package/dist/ipc/session/queries.d.ts +5 -1
  109. package/dist/ipc/session/types.d.ts +5 -0
  110. package/dist/ipc/transport/index.d.ts +2 -1
  111. package/dist/ipc/transport/index.js +2 -2
  112. package/dist/runtime/dom/actionEffects.d.ts +106 -0
  113. package/dist/runtime/dom/actionEffects.js +256 -0
  114. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -0
  115. package/dist/runtime/dom/actionEffectsScripts.js +234 -0
  116. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  117. package/dist/runtime/dom/elementGeometry.js +553 -0
  118. package/dist/runtime/dom/elementInfo.d.ts +77 -0
  119. package/dist/runtime/dom/elementInfo.js +191 -0
  120. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  121. package/dist/runtime/dom/evalHelpers.js +136 -26
  122. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  123. package/dist/runtime/dom/eventListeners.js +174 -47
  124. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  125. package/dist/runtime/dom/formDiscovery.js +116 -16
  126. package/dist/runtime/dom/formFillHelpers/fill.d.ts +10 -0
  127. package/dist/runtime/dom/formFillHelpers/fill.js +125 -10
  128. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  129. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  130. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  131. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  132. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  133. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  134. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  135. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  136. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  137. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  138. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  139. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  140. package/dist/runtime/dom/frameLayout.js +140 -0
  141. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  142. package/dist/runtime/dom/frameOrigin.js +62 -0
  143. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  144. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  145. package/dist/runtime/dom/frameSelection.d.ts +1 -1
  146. package/dist/runtime/dom/frameSelection.js +2 -2
  147. package/dist/runtime/dom/frames.d.ts +25 -2
  148. package/dist/runtime/dom/frames.js +202 -63
  149. package/dist/runtime/dom/layout.d.ts +67 -0
  150. package/dist/runtime/dom/layout.js +333 -0
  151. package/dist/runtime/dom/listenerPageScripts.d.ts +66 -0
  152. package/dist/runtime/dom/listenerPageScripts.js +279 -0
  153. package/dist/runtime/dom/listenerSummary.d.ts +132 -11
  154. package/dist/runtime/dom/listenerSummary.js +344 -22
  155. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  156. package/dist/runtime/dom/pageActivity.js +123 -0
  157. package/dist/runtime/dom/reactEventHelpers.d.ts +58 -2
  158. package/dist/runtime/dom/reactEventHelpers.js +212 -41
  159. package/dist/runtime/dom/targetNode.d.ts +80 -27
  160. package/dist/runtime/dom/targetNode.js +249 -33
  161. package/dist/runtime/dom/wait.d.ts +25 -0
  162. package/dist/runtime/dom/wait.js +199 -0
  163. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  164. package/dist/runtime/dom/waitCondition.js +75 -0
  165. package/dist/runtime/page/emulation.d.ts +51 -0
  166. package/dist/runtime/page/emulation.js +80 -0
  167. package/dist/runtime/page/loadingState.d.ts +36 -0
  168. package/dist/runtime/page/loadingState.js +86 -0
  169. package/dist/runtime/page/navigation.d.ts +46 -2
  170. package/dist/runtime/page/navigation.js +69 -33
  171. package/dist/session/QueryCacheManager.d.ts +11 -1
  172. package/dist/session/QueryCacheManager.js +25 -3
  173. package/dist/session/chromeOwners.d.ts +34 -0
  174. package/dist/session/chromeOwners.js +51 -0
  175. package/dist/session/cleanup/staleSession.d.ts +11 -1
  176. package/dist/session/cleanup/staleSession.js +17 -6
  177. package/dist/session/cleanup/userCommands.js +2 -4
  178. package/dist/session/metadata.d.ts +5 -1
  179. package/dist/session/metadata.js +2 -1
  180. package/dist/session/paths.d.ts +77 -3
  181. package/dist/session/paths.js +111 -5
  182. package/dist/session/port.d.ts +31 -7
  183. package/dist/session/port.js +50 -43
  184. package/dist/session/portClaims.d.ts +66 -0
  185. package/dist/session/portClaims.js +284 -0
  186. package/dist/session/sessionList.d.ts +58 -0
  187. package/dist/session/sessionList.js +199 -0
  188. package/dist/session/sessionName.d.ts +46 -0
  189. package/dist/session/sessionName.js +97 -0
  190. package/dist/telemetry/a11y.d.ts +8 -3
  191. package/dist/telemetry/a11y.js +92 -28
  192. package/dist/telemetry/requestKinds.d.ts +32 -0
  193. package/dist/telemetry/requestKinds.js +61 -0
  194. package/dist/telemetry/requestState.d.ts +31 -0
  195. package/dist/telemetry/requestState.js +38 -0
  196. package/dist/types.d.ts +80 -3
  197. package/dist/ui/formatters/a11y.js +3 -0
  198. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  199. package/dist/ui/formatters/console/chronological.js +17 -4
  200. package/dist/ui/formatters/console/json.js +3 -4
  201. package/dist/ui/formatters/console/shared.d.ts +12 -0
  202. package/dist/ui/formatters/console.d.ts +2 -2
  203. package/dist/ui/formatters/console.js +1 -1
  204. package/dist/ui/formatters/details.js +2 -1
  205. package/dist/ui/formatters/dom.d.ts +26 -14
  206. package/dist/ui/formatters/dom.js +65 -52
  207. package/dist/ui/formatters/form.js +29 -18
  208. package/dist/ui/formatters/layout.d.ts +31 -0
  209. package/dist/ui/formatters/layout.js +53 -0
  210. package/dist/ui/formatters/listeners.d.ts +3 -2
  211. package/dist/ui/formatters/listeners.js +73 -9
  212. package/dist/ui/formatters/networkHeaders.js +13 -0
  213. package/dist/ui/formatters/preview.js +2 -1
  214. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  215. package/dist/ui/formatters/requestStatus.js +2 -30
  216. package/dist/ui/formatters/sessions.d.ts +12 -0
  217. package/dist/ui/formatters/sessions.js +40 -0
  218. package/dist/ui/formatters/status.d.ts +21 -2
  219. package/dist/ui/formatters/status.js +47 -10
  220. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  221. package/dist/ui/formatters/triggeredRequests.js +65 -0
  222. package/dist/ui/formatting.d.ts +10 -0
  223. package/dist/ui/formatting.js +28 -36
  224. package/dist/ui/messages/chrome.d.ts +9 -0
  225. package/dist/ui/messages/chrome.js +17 -5
  226. package/dist/ui/messages/commands.d.ts +388 -14
  227. package/dist/ui/messages/commands.js +664 -21
  228. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  229. package/dist/ui/messages/consoleMessages.js +17 -0
  230. package/dist/ui/messages/hints.js +2 -1
  231. package/dist/ui/messages/preview.js +5 -4
  232. package/dist/ui/messages/session.d.ts +16 -21
  233. package/dist/ui/messages/session.js +28 -26
  234. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  235. package/dist/ui/messages/sessionCommand.js +52 -0
  236. package/dist/utils/async.d.ts +8 -0
  237. package/dist/utils/async.js +19 -0
  238. package/dist/utils/http.d.ts +22 -1
  239. package/dist/utils/http.js +28 -9
  240. package/dist/utils/selectorFilters.d.ts +36 -8
  241. package/dist/utils/selectorFilters.js +267 -53
  242. package/dist/utils/shellDetection.d.ts +8 -2
  243. package/dist/utils/shellDetection.js +120 -33
  244. package/dist/utils/suggestions.d.ts +26 -0
  245. package/dist/utils/suggestions.js +73 -0
  246. package/dist/utils/taskMappings.js +10 -0
  247. package/dist/utils/url.d.ts +12 -2
  248. package/dist/utils/url.js +69 -7
  249. package/package.json +1 -1
  250. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  251. package/dist/ui/formatters/sessionFormatters.js +0 -121
@@ -7,17 +7,18 @@
7
7
  * subscriptions for network-stability waits) on behalf of the CLI.
8
8
  */
9
9
  import { InvalidArgumentError } from 'commander';
10
- import { DomElementResolver } from './DomElementResolver.js';
11
10
  import { runElementCommand } from './helpers/runElementCommand.js';
12
11
  import { runCommand } from '../shared/CommandRunner.js';
13
12
  import { jsonOption } from '../shared/commonOptions.js';
14
13
  import { integerOption } from '../shared/validation.js';
15
14
  import { CommandError } from '../../errors/index.js';
16
- import { conflictingOptionsMessage, internalError, scrollOptionsError } from '../../errors/messages.js';
15
+ import { VIA_LABEL_SUFFIX, conflictingOptionsMessage, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
17
16
  import { domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
18
17
  import { findUnknownModifiers } from '../../runtime/dom/keyMapping.js';
18
+ import { formatTriggeredRequestLines, formatTriggeredRequestsTitle, } from '../../ui/formatters/triggeredRequests.js';
19
19
  import { OutputFormatter } from '../../ui/formatting.js';
20
- import { POINTER_ACTION_DONE, dialogConsoleText } from '../../ui/messages/commands.js';
20
+ import { CLICK_RESULT_WAIT_HELP, POINTER_ACTION_DONE, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, } from '../../ui/messages/commands.js';
21
+ import { sessionCommand } from '../../ui/messages/sessionCommand.js';
21
22
  import { EXIT_CODES } from '../../utils/exitCodes.js';
22
23
  /**
23
24
  * Commander parser for `--modifiers`: rejects unknown names instead of
@@ -71,6 +72,7 @@ export function registerFormInteractionCommands(program) {
71
72
  wait: options.wait !== false,
72
73
  }),
73
74
  call: domFill,
75
+ command: 'fill',
74
76
  action: 'fill element',
75
77
  failureSuggestion: 'Verify the selector matches a fillable element (input, textarea, select)',
76
78
  }), options, formatFillOutput);
@@ -84,6 +86,7 @@ export function registerFormInteractionCommands(program) {
84
86
  .option('--right', 'Right-click (opens the context menu)')
85
87
  .option('--no-wait', 'Skip waiting for network stability after click')
86
88
  .addOption(jsonOption())
89
+ .addHelpText('after', CLICK_RESULT_WAIT_HELP)
87
90
  .action(async (selectorOrIndex, options) => {
88
91
  const action = options.double ? 'double' : options.right ? 'right' : 'click';
89
92
  await runPointerCommand(selectorOrIndex, options, action);
@@ -107,6 +110,7 @@ export function registerFormInteractionCommands(program) {
107
110
  .option('--wait-network <ms>', 'Wait for network idle after submit (milliseconds)', integerOption(0), 1000)
108
111
  .option('--timeout <ms>', 'Maximum time to wait (milliseconds)', integerOption(1), 10000)
109
112
  .addOption(jsonOption())
113
+ .addHelpText('after', CLICK_RESULT_WAIT_HELP)
110
114
  .action(async (selectorOrIndex, options) => {
111
115
  await runCommand(() => runElementCommand({
112
116
  selectorOrIndex,
@@ -120,6 +124,7 @@ export function registerFormInteractionCommands(program) {
120
124
  timeout: options.timeout,
121
125
  }),
122
126
  call: domSubmit,
127
+ command: 'submit',
123
128
  action: 'submit form',
124
129
  failureSuggestion: 'Verify the selector matches a form or submit button',
125
130
  }), options, formatSubmitOutput);
@@ -146,6 +151,7 @@ export function registerFormInteractionCommands(program) {
146
151
  wait: options.wait !== false,
147
152
  }),
148
153
  call: domPressKey,
154
+ command: 'pressKey',
149
155
  action: 'press key',
150
156
  failureSuggestion: 'Verify the selector matches a focusable element',
151
157
  }), options, formatPressKeyOutput);
@@ -164,64 +170,62 @@ export function registerFormInteractionCommands(program) {
164
170
  .option('--no-wait', 'Skip waiting for lazy-loaded content after scroll')
165
171
  .addOption(jsonOption())
166
172
  .action(async (selector, options) => {
167
- await runCommand(async () => {
168
- const problem = scrollOptionsProblem(selector, options);
169
- if (problem) {
170
- const err = scrollOptionsError(problem);
171
- return {
172
- success: false,
173
- error: err.message,
174
- exitCode: EXIT_CODES.INVALID_ARGUMENTS,
175
- errorContext: { suggestion: err.suggestion },
176
- };
177
- }
178
- const target = selector
179
- ? await DomElementResolver.getInstance().resolve(selector, options.index)
180
- : undefined;
181
- if (target && !target.success) {
182
- return {
183
- success: false,
184
- error: target.error,
185
- exitCode: target.exitCode,
186
- ...(target.suggestion && { errorContext: { suggestion: target.suggestion } }),
187
- };
188
- }
189
- const response = await domScroll({
190
- ...(target && { selector: target.selector }),
191
- ...(target?.index !== undefined && { index: target.index }),
192
- ...(target?.backendNodeId !== undefined && { backendNodeId: target.backendNodeId }),
193
- ...(options.down !== undefined && { down: options.down }),
194
- ...(options.up !== undefined && { up: options.up }),
195
- ...(options.left !== undefined && { left: options.left }),
196
- ...(options.right !== undefined && { right: options.right }),
197
- ...(options.top !== undefined && { top: options.top }),
198
- ...(options.bottom !== undefined && { bottom: options.bottom }),
199
- wait: options.wait !== false,
200
- });
201
- if (response.status === 'error' || !response.data) {
202
- return {
203
- success: false,
204
- error: response.error ?? 'Failed to scroll',
205
- exitCode: response.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
206
- ...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
207
- };
208
- }
209
- const result = response.data;
210
- if (!result.success) {
211
- return {
212
- success: false,
213
- error: result.error ?? 'Failed to scroll',
214
- exitCode: result.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
215
- errorContext: {
216
- suggestion: result.suggestion ?? 'Verify the selector exists on the page',
217
- },
218
- };
219
- }
220
- const { success: _success, ...data } = result;
221
- return { success: true, data };
222
- }, options, formatScrollOutput);
173
+ await runCommand(() => runScroll(selector, options), options, formatScrollOutput);
223
174
  });
224
175
  }
176
+ /**
177
+ * Scroll to an element (selector or cached index) or by offsets/to an edge.
178
+ *
179
+ * @param selector - Selector or index argument, if any
180
+ * @param options - Scroll options
181
+ * @returns Command result
182
+ */
183
+ async function runScroll(selector, options) {
184
+ const problem = scrollOptionsProblem(selector, options);
185
+ if (problem) {
186
+ const err = scrollOptionsError(problem);
187
+ return {
188
+ success: false,
189
+ error: err.message,
190
+ exitCode: EXIT_CODES.INVALID_ARGUMENTS,
191
+ errorContext: { suggestion: err.suggestion },
192
+ };
193
+ }
194
+ const request = {
195
+ ...(options.down !== undefined && { down: options.down }),
196
+ ...(options.up !== undefined && { up: options.up }),
197
+ ...(options.left !== undefined && { left: options.left }),
198
+ ...(options.right !== undefined && { right: options.right }),
199
+ ...(options.top !== undefined && { top: options.top }),
200
+ ...(options.bottom !== undefined && { bottom: options.bottom }),
201
+ wait: options.wait !== false,
202
+ };
203
+ if (selector) {
204
+ return runElementCommand({
205
+ selectorOrIndex: selector,
206
+ index: options.index,
207
+ buildRequest: (target) => ({ ...target, ...request }),
208
+ call: domScroll,
209
+ command: 'scroll',
210
+ action: 'scroll',
211
+ failureSuggestion: 'Verify the selector exists on the page',
212
+ });
213
+ }
214
+ const response = await domScroll(request);
215
+ const result = response.data;
216
+ if (response.status === 'error' || !result?.success) {
217
+ return {
218
+ success: false,
219
+ error: result?.error ?? response.error ?? 'Failed to scroll',
220
+ exitCode: result?.exitCode ?? response.exitCode ?? EXIT_CODES.INVALID_ARGUMENTS,
221
+ ...((result?.suggestion ?? response.suggestion) && {
222
+ errorContext: { suggestion: result?.suggestion ?? response.suggestion ?? '' },
223
+ }),
224
+ };
225
+ }
226
+ const { success: _success, ...data } = result;
227
+ return { success: true, data };
228
+ }
225
229
  /**
226
230
  * The first rule `bdg dom scroll` options break, if any: one target (an
227
231
  * element, or offsets/edges), and no opposite directions.
@@ -268,87 +272,130 @@ async function runPointerCommand(selectorOrIndex, options, action) {
268
272
  ...(action !== 'click' && { action }),
269
273
  }),
270
274
  call: domClick,
275
+ command: action === 'hover' ? 'hover' : 'click',
271
276
  action: action === 'hover' ? 'hover element' : 'click element',
272
277
  failureSuggestion: 'Verify the selector matches a clickable element',
273
278
  }), options, formatClickOutput);
274
279
  }
275
280
  /**
276
- * Add an action's warning and the dialogs it caused to the output.
281
+ * Build an action's output: the status line ("✓ Element Clicked",
282
+ * "⚠ Element Clicked (with warnings)" with the warning right below it, or
283
+ * "⚠ Element Clicked (no visible effect: …)"), the details, what changed on
284
+ * the page (`Page:` navigation, `New text:` messages), then the network
285
+ * requests it triggered and the dialogs it caused. No request list is shown
286
+ * when there were none (JSON has an empty `triggeredRequests` then).
277
287
  *
278
- * @param fmt - Output being built
288
+ * @param done - What was done, e.g. "Element Clicked"
289
+ * @param details - Label/value rows
279
290
  * @param result - Action result
291
+ * @param keyWidth - Width of the labels
292
+ * @returns Output being built (more can be appended)
280
293
  */
281
- function appendNotices(fmt, result) {
282
- if (result.warning) {
283
- fmt.blank();
294
+ function formatActionOutput(done, details, result, keyWidth = 15) {
295
+ const fmt = new OutputFormatter();
296
+ fmt.text(actionStatusLine(done, result.warning !== undefined, result.effect === 'none'));
297
+ if (result.warning)
284
298
  fmt.text(`⚠ Warning: ${result.warning}`);
299
+ fmt.blank();
300
+ fmt.keyValueList(details, keyWidth);
301
+ if (result.navigation)
302
+ fmt.keyValue('Page', pageNavigationText(result.navigation), keyWidth);
303
+ (result.messages ?? []).forEach((message, index) => {
304
+ const text = newMessageText(message);
305
+ if (index === 0)
306
+ fmt.keyValue('New text', text, keyWidth);
307
+ else
308
+ fmt.text(' '.repeat(keyWidth) + text);
309
+ });
310
+ const omitted = result.triggeredRequestsOmitted;
311
+ const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
312
+ if (requests.length > 0) {
313
+ fmt
314
+ .blank()
315
+ .section(formatTriggeredRequestsTitle(result.triggeredRequests ?? [], omitted), requests);
285
316
  }
286
317
  for (const dialog of result.dialogs ?? []) {
287
318
  fmt.blank();
288
319
  fmt.text(`Dialog: ${dialogConsoleText(dialog)}`);
289
320
  }
321
+ return fmt;
322
+ }
323
+ /**
324
+ * Row naming the element an action hit, e.g.
325
+ * `Element: input.toggle in div.view "Write report"` (just its tag when the
326
+ * page could not describe it), with the list a numeric index refers to
327
+ * (`(index 0 of the last dom query "h3")`).
328
+ *
329
+ * @param result - Action result
330
+ * @returns Label/value row
331
+ */
332
+ function elementRow(result) {
333
+ const source = result.indexSource ? ` (${indexSourceText(result.indexSource)})` : '';
334
+ if (result.element === undefined) {
335
+ return ['Element Type', `${result.elementType ?? 'unknown'}${source}`];
336
+ }
337
+ const viaLabel = result.elementType?.endsWith(VIA_LABEL_SUFFIX) ? VIA_LABEL_SUFFIX : '';
338
+ return ['Element', `${result.element}${viaLabel}${source}`];
339
+ }
340
+ /**
341
+ * Selector row of an action's output; none for an index into a11y query
342
+ * results, whose "selector" is the a11y pattern (the element row names it).
343
+ *
344
+ * @param result - Action result
345
+ * @returns The row, or none
346
+ */
347
+ function selectorRows(result) {
348
+ if (result.indexSource?.command === 'dom a11y query')
349
+ return [];
350
+ return [['Selector', result.selector ?? 'unknown']];
290
351
  }
291
352
  /**
292
353
  * Format fill command output for human-readable display.
293
354
  */
294
355
  function formatFillOutput(result) {
295
- const fmt = new OutputFormatter();
296
- fmt.text('✓ Element Filled');
297
- fmt.blank();
298
- const details = [
299
- ['Selector', result.selector ?? 'unknown'],
300
- ['Element Type', result.elementType ?? 'unknown'],
301
- ];
356
+ const details = [...selectorRows(result), elementRow(result)];
302
357
  if (result.inputType)
303
358
  details.push(['Input Type', result.inputType]);
304
359
  if (result.checked !== undefined) {
305
360
  details.push(['Checked', result.checked ? 'true' : 'false']);
306
361
  }
307
- else if (result.value) {
308
- details.push(['Value', result.value]);
362
+ else if (result.value !== undefined) {
363
+ details.push(['Value', result.value === '' ? '(empty)' : result.value]);
309
364
  }
310
- fmt.keyValueList(details, 15);
311
- appendNotices(fmt, result);
312
- return fmt.build();
365
+ return formatActionOutput('Element Filled', details, result).build();
313
366
  }
314
367
  /**
315
368
  * Format click command output for human-readable display.
316
369
  */
317
370
  function formatClickOutput(result) {
318
- const fmt = new OutputFormatter();
319
- fmt.text(`✓ Element ${POINTER_ACTION_DONE[result.action ?? 'click']}`);
320
- fmt.blank();
321
- fmt.keyValueList([
322
- ['Selector', result.selector ?? 'unknown'],
323
- ['Element Type', result.elementType ?? 'unknown'],
371
+ return formatActionOutput(`Element ${POINTER_ACTION_DONE[result.action ?? 'click']}`, [
372
+ ...selectorRows(result),
373
+ elementRow(result),
324
374
  ['Method', result.method === 'dom' ? 'DOM events' : 'mouse events'],
325
- ], 15);
326
- appendNotices(fmt, result);
327
- return fmt.build();
375
+ ], result).build();
328
376
  }
329
377
  /**
330
378
  * Format submit command output for human-readable display.
331
379
  */
332
380
  function formatSubmitOutput(result) {
333
- const fmt = new OutputFormatter();
334
- fmt.text('✓ Form Submitted');
335
- fmt.blank();
336
381
  const details = [
337
- ['Selector', result.selector ?? 'unknown'],
382
+ ...selectorRows(result),
383
+ ...(result.element !== undefined ? [elementRow(result)] : []),
338
384
  ['Submit Button', result.clicked ? 'used' : 'none'],
339
385
  ];
340
386
  if (result.networkRequests !== undefined)
341
387
  details.push(['Network Requests', result.networkRequests.toString()]);
342
- if (result.navigationOccurred !== undefined)
343
- details.push(['Navigation', result.navigationOccurred ? 'yes' : 'no']);
388
+ if (result.navigationOccurred === false)
389
+ details.push(['Navigation', 'no']);
390
+ if (result.navigationOccurred && !result.navigation)
391
+ details.push(['Navigation', 'yes']);
344
392
  if (result.waitTimeMs !== undefined)
345
393
  details.push(['Wait Time', `${result.waitTimeMs}ms`]);
346
- fmt.keyValueList(details, 20);
347
- appendNotices(fmt, result);
394
+ const fmt = formatActionOutput('Form Submitted', details, result, 20);
348
395
  fmt.hints('Next steps:', [
349
- 'bdg network list --last 10 Check network requests',
350
- 'bdg console --last 5 Check console messages',
351
- 'bdg status Check session state',
396
+ `${sessionCommand('bdg network list --last 10').padEnd(32)} Check network requests`,
397
+ `${sessionCommand('bdg console --last 5').padEnd(32)} Check console messages`,
398
+ `${sessionCommand('bdg status').padEnd(32)} Check session state`,
352
399
  ]);
353
400
  return fmt.build();
354
401
  }
@@ -356,42 +403,34 @@ function formatSubmitOutput(result) {
356
403
  * Format pressKey command output for human-readable display.
357
404
  */
358
405
  function formatPressKeyOutput(result) {
359
- const fmt = new OutputFormatter();
360
- fmt.text('✓ Key Pressed');
361
- fmt.blank();
362
406
  const details = [
363
407
  ['Key', result.key ?? 'unknown'],
364
- ['Selector', result.selector ?? 'unknown'],
365
- ['Element Type', result.elementType ?? 'unknown'],
408
+ ...selectorRows(result),
409
+ elementRow(result),
366
410
  ];
367
411
  if (result.times && result.times > 1)
368
412
  details.push(['Times', result.times.toString()]);
369
413
  if (result.modifiers?.length)
370
414
  details.push(['Modifiers', result.modifiers.join('+')]);
371
- fmt.keyValueList(details, 15);
372
- appendNotices(fmt, result);
373
- return fmt.build();
415
+ return formatActionOutput('Key Pressed', details, result).build();
374
416
  }
375
417
  /**
376
418
  * Format scroll command output for human-readable display.
377
419
  */
378
420
  function formatScrollOutput(result) {
379
- const fmt = new OutputFormatter();
380
- fmt.text('✓ Page Scrolled');
381
- fmt.blank();
382
421
  const details = [['Scroll Type', result.scrollType]];
383
422
  if (result.selector)
384
- details.push(['Selector', result.selector]);
423
+ details.push(...selectorRows(result));
424
+ if (result.element)
425
+ details.push(elementRow(result));
385
426
  if (result.scrolledTo)
386
427
  details.push(['Position', `(${result.scrolledTo.x}, ${result.scrolledTo.y})`]);
387
428
  if (result.scrolledBy && (result.scrolledBy.x !== 0 || result.scrolledBy.y !== 0))
388
429
  details.push(['Scrolled By', `(${result.scrolledBy.x}, ${result.scrolledBy.y})`]);
389
430
  if (result.viewportSize)
390
- details.push(['Viewport', `${result.viewportSize.width}x${result.viewportSize.height}`]);
431
+ details.push(['Viewport', `${result.viewportSize.width}×${result.viewportSize.height}`]);
391
432
  if (result.pageSize)
392
- details.push(['Page Size', `${result.pageSize.width}x${result.pageSize.height}`]);
393
- fmt.keyValueList(details, 15);
394
- appendNotices(fmt, result);
395
- return fmt.build();
433
+ details.push(['Page Size', `${result.pageSize.width}×${result.pageSize.height}`]);
434
+ return formatActionOutput('Page Scrolled', details, result).build();
396
435
  }
397
436
  //# sourceMappingURL=formInteraction.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Readiness of a discovered form (`bdg dom form`): which fields are filled,
3
+ * which required ones are still empty, and which button submits it.
4
+ */
5
+ import type { FormButton, FormField, FormSummary, RawButton, RawForm } from '../../runtime/dom/formTypes.js';
6
+ /**
7
+ * Fields a user fills as one: the options of a radio or checkbox group (same
8
+ * name in the same form), or a single field.
9
+ *
10
+ * @param fields - Form fields
11
+ * @returns Groups, in the order their first field appears
12
+ */
13
+ export declare function groupFields(fields: FormField[]): FormField[][];
14
+ /**
15
+ * Summarize a form's state. Only visible, editable fields count, and a radio
16
+ * or checkbox group counts once (filled when any option is checked; required
17
+ * when any option is). The form is ready when nothing blocks it and at least
18
+ * one field is filled (an untouched form is not ready), or it has no fields.
19
+ *
20
+ * @param fields - Form fields
21
+ * @param buttons - Form buttons
22
+ * @returns Form summary
23
+ */
24
+ export declare function calculateSummary(fields: FormField[], buttons: FormButton[]): FormSummary;
25
+ /**
26
+ * The button that submits the form, as a user would pick it. Buttons that
27
+ * cancel, reset, go back or delete ({@link DISMISSIVE_BUTTON_LABEL}) never
28
+ * are. Then, in order: a button written as a submit button
29
+ * (`type="submit"`, `<input type=submit>`); among the form's untyped
30
+ * `<button>`s (each submits it) one styled as primary, else the last one
31
+ * (forms put Cancel or Back first); else any button styled as primary.
32
+ * Buttons outside a form submit nothing, so only their style counts.
33
+ *
34
+ * @param buttons - Raw buttons of one form
35
+ * @returns Index of the primary button, or undefined when there is none
36
+ */
37
+ export declare function primaryButtonIndex(buttons: RawButton[]): number | undefined;
38
+ /**
39
+ * Put forms in listing order (by {@link visibilityRank}, then by relevance)
40
+ * and number them, their fields and their buttons in that order, so the
41
+ * first field listed is 0 and `bdg dom fill <index>` / `click <index>` reach
42
+ * the element listed with that index (the indices are cached in this order).
43
+ * Within a form, fields come before buttons, both in document order.
44
+ *
45
+ * @param forms - Raw forms in document order
46
+ * @returns Forms in listing order, renumbered (the input is not changed)
47
+ */
48
+ export declare function orderForms(forms: RawForm[]): RawForm[];
49
+ //# sourceMappingURL=formSummary.d.ts.map
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Readiness of a discovered form (`bdg dom form`): which fields are filled,
3
+ * which required ones are still empty, and which button submits it.
4
+ */
5
+ import { REQUIRED_FIELD_EMPTY_REASON } from '../../ui/messages/commands.js';
6
+ /**
7
+ * Button labels that leave or clear a form instead of submitting it (English,
8
+ * German, French, Spanish, Italian and Polish cancel/back words)
9
+ */
10
+ const DISMISSIVE_BUTTON_LABEL = /^(cancel|reset|back|go back|previous|prev|close|clear|discard|delete|remove|abbrechen|zurück|annuler|retour|cancelar|volver|annulla|indietro|anuluj|wstecz)\b/iu;
11
+ /**
12
+ * Fields a user fills as one: the options of a radio or checkbox group (same
13
+ * name in the same form), or a single field.
14
+ *
15
+ * @param fields - Form fields
16
+ * @returns Groups, in the order their first field appears
17
+ */
18
+ export function groupFields(fields) {
19
+ const groups = new Map();
20
+ for (const field of fields) {
21
+ const type = (field.inputType ?? field.type).toLowerCase();
22
+ const choice = (type === 'radio' || type === 'checkbox') && field.name;
23
+ const key = choice ? `${field.formIndex}:${type}:${field.name}` : `#${field.index}`;
24
+ groups.set(key, [...(groups.get(key) ?? []), field]);
25
+ }
26
+ return [...groups.values()];
27
+ }
28
+ /**
29
+ * Whether a field has a value (a checked option for radios and checkboxes).
30
+ *
31
+ * @param field - Form field
32
+ * @returns True when filled or checked
33
+ */
34
+ function isFilled(field) {
35
+ return field.state === 'filled' || field.state === 'checked';
36
+ }
37
+ /**
38
+ * Label of a field group: the group's name (legend, radiogroup label, name)
39
+ * for several options, the field's own label otherwise.
40
+ *
41
+ * @param group - Fields of one group (at least one)
42
+ * @returns Label
43
+ */
44
+ function groupLabel(group) {
45
+ const [first] = group;
46
+ return group.length > 1 ? (first.groupLabel ?? first.label) : first.label;
47
+ }
48
+ /**
49
+ * What keeps a form from being submitted: required groups left empty,
50
+ * invalid fields, a disabled submit button.
51
+ *
52
+ * @param groups - Field groups of the editable, visible fields
53
+ * @param buttons - Form buttons
54
+ * @returns Blockers, required fields first
55
+ */
56
+ function findBlockers(groups, buttons) {
57
+ const blockers = [];
58
+ for (const group of groups) {
59
+ const first = group[0];
60
+ if (group.some((field) => field.required) && !group.some(isFilled)) {
61
+ blockers.push({
62
+ index: first.index,
63
+ label: groupLabel(group),
64
+ reason: REQUIRED_FIELD_EMPTY_REASON,
65
+ command: first.command,
66
+ });
67
+ continue;
68
+ }
69
+ const invalid = group.find((field) => !field.validation.valid);
70
+ if (invalid) {
71
+ blockers.push({
72
+ index: invalid.index,
73
+ label: invalid.label,
74
+ reason: invalid.validation.message ?? 'Validation failed',
75
+ command: invalid.command,
76
+ });
77
+ }
78
+ }
79
+ const submitButton = buttons.find((b) => b.type === 'submit' && b.primary);
80
+ if (submitButton && !submitButton.enabled) {
81
+ blockers.push({
82
+ index: submitButton.index,
83
+ label: submitButton.label,
84
+ reason: submitButton.disabledReason ?? 'Submit button is disabled',
85
+ command: submitButton.command,
86
+ });
87
+ }
88
+ return blockers;
89
+ }
90
+ /**
91
+ * Summarize a form's state. Only visible, editable fields count, and a radio
92
+ * or checkbox group counts once (filled when any option is checked; required
93
+ * when any option is). The form is ready when nothing blocks it and at least
94
+ * one field is filled (an untouched form is not ready), or it has no fields.
95
+ *
96
+ * @param fields - Form fields
97
+ * @param buttons - Form buttons
98
+ * @returns Form summary
99
+ */
100
+ export function calculateSummary(fields, buttons) {
101
+ const editable = fields.filter((f) => !f.hidden && !f.disabled && !f.readOnly);
102
+ const groups = groupFields(editable);
103
+ const filled = groups.filter((group) => group.some(isFilled));
104
+ const empty = groups.filter((group) => !group.some(isFilled));
105
+ const required = groups.filter((group) => group.some((field) => field.required));
106
+ const requiredRemaining = required.filter((group) => !group.some(isFilled)).length;
107
+ const invalid = groups.filter((group) => group.some((field) => !field.validation.valid)).length;
108
+ const blockers = findBlockers(groups, buttons);
109
+ return {
110
+ totalFields: groups.length,
111
+ filledFields: filled.length,
112
+ emptyFields: empty.length,
113
+ validFields: groups.length - invalid,
114
+ invalidFields: invalid,
115
+ requiredTotal: required.length,
116
+ requiredFilled: required.length - requiredRemaining,
117
+ requiredRemaining,
118
+ emptyFieldLabels: empty.map(groupLabel),
119
+ readyToSubmit: blockers.length === 0 && (filled.length > 0 || groups.length === 0),
120
+ blockers,
121
+ };
122
+ }
123
+ /**
124
+ * The button that submits the form, as a user would pick it. Buttons that
125
+ * cancel, reset, go back or delete ({@link DISMISSIVE_BUTTON_LABEL}) never
126
+ * are. Then, in order: a button written as a submit button
127
+ * (`type="submit"`, `<input type=submit>`); among the form's untyped
128
+ * `<button>`s (each submits it) one styled as primary, else the last one
129
+ * (forms put Cancel or Back first); else any button styled as primary.
130
+ * Buttons outside a form submit nothing, so only their style counts.
131
+ *
132
+ * @param buttons - Raw buttons of one form
133
+ * @returns Index of the primary button, or undefined when there is none
134
+ */
135
+ export function primaryButtonIndex(buttons) {
136
+ const candidates = buttons.filter((button) => button.type !== 'reset' && !DISMISSIVE_BUTTON_LABEL.test(button.label.trim()));
137
+ const formDefaults = candidates.filter((button) => button.formDefault);
138
+ const primary = candidates.find((button) => button.explicitSubmit) ??
139
+ formDefaults.find((button) => button.primaryClass) ??
140
+ formDefaults.at(-1) ??
141
+ candidates.find((button) => button.primaryClass);
142
+ return primary?.index;
143
+ }
144
+ /**
145
+ * Where a form is listed: shown in an open dialog first (what the user is
146
+ * looking at), then other visible forms, then hidden ones.
147
+ *
148
+ * @param form - Raw form
149
+ * @returns Rank, lower first
150
+ */
151
+ function visibilityRank(form) {
152
+ if (form.hidden)
153
+ return 2;
154
+ return form.inDialog ? 0 : 1;
155
+ }
156
+ /**
157
+ * Put forms in listing order (by {@link visibilityRank}, then by relevance)
158
+ * and number them, their fields and their buttons in that order, so the
159
+ * first field listed is 0 and `bdg dom fill <index>` / `click <index>` reach
160
+ * the element listed with that index (the indices are cached in this order).
161
+ * Within a form, fields come before buttons, both in document order.
162
+ *
163
+ * @param forms - Raw forms in document order
164
+ * @returns Forms in listing order, renumbered (the input is not changed)
165
+ */
166
+ export function orderForms(forms) {
167
+ const ordered = forms
168
+ .map((form, position) => ({ form, position }))
169
+ .sort((a, b) => visibilityRank(a.form) - visibilityRank(b.form) ||
170
+ b.form.relevanceScore - a.form.relevanceScore ||
171
+ a.position - b.position);
172
+ let next = 0;
173
+ return ordered.map(({ form }, formIndex) => ({
174
+ ...form,
175
+ index: formIndex,
176
+ fields: form.fields.map((field) => ({ ...field, index: next++, formIndex })),
177
+ buttons: form.buttons.map((button) => ({ ...button, index: next++ })),
178
+ }));
179
+ }
180
+ //# sourceMappingURL=formSummary.js.map
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import type { DomFramesCommandOptions } from '../shared/optionTypes.js';
8
8
  /**
9
- * Handle `bdg dom frames`.
9
+ * Handle `bdg dom frames`. The list of a page still loading says it may be
10
+ * incomplete (`readyState` in the data): its iframes may not exist yet.
10
11
  */
11
12
  export declare function handleDomFrames(options: DomFramesCommandOptions): Promise<void>;
12
13
  //# sourceMappingURL=frames.d.ts.map
@@ -4,12 +4,14 @@
4
4
  * CLI-side handler. Frames are discovered in the daemon via the `dom_frames`
5
5
  * IPC command, out-of-process (cross-site) ones included.
6
6
  */
7
+ import { documentReadyState } from './helpers/query.js';
7
8
  import { runCommand } from '../shared/CommandRunner.js';
8
9
  import { domFrames } from '../../ipc/client.js';
9
10
  import { formatDomFrames } from '../../ui/formatters/dom.js';
10
11
  import { EXIT_CODES } from '../../utils/exitCodes.js';
11
12
  /**
12
- * Handle `bdg dom frames`.
13
+ * Handle `bdg dom frames`. The list of a page still loading says it may be
14
+ * incomplete (`readyState` in the data): its iframes may not exist yet.
13
15
  */
14
16
  export async function handleDomFrames(options) {
15
17
  await runCommand(async () => {
@@ -22,7 +24,20 @@ export async function handleDomFrames(options) {
22
24
  ...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
23
25
  };
24
26
  }
25
- return { success: true, data: { frames: response.data.frames } };
27
+ return { success: true, data: await withReadyState(response.data.frames) };
26
28
  }, options, formatDomFrames);
27
29
  }
30
+ /**
31
+ * The frames, with the page's `readyState` while it is still loading (one
32
+ * page evaluation).
33
+ *
34
+ * @param frames - Frames of the page
35
+ * @returns Frames, and `readyState` unless the page has loaded (or did not answer)
36
+ */
37
+ async function withReadyState(frames) {
38
+ const readyState = await documentReadyState();
39
+ return readyState === undefined || readyState === 'complete'
40
+ ? { frames }
41
+ : { frames, readyState };
42
+ }
28
43
  //# sourceMappingURL=frames.js.map