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
@@ -4,7 +4,11 @@
4
4
  *
5
5
  * The daemon collects them with `DOMDebugger.getEventListeners`; listeners on
6
6
  * ancestors matter because frameworks (React, jQuery) attach their handlers
7
- * to a root container or the document and dispatch from there.
7
+ * to a root container or the document and dispatch from there. jQuery's
8
+ * dispatcher is replaced by the jQuery handlers it runs for the element,
9
+ * React's `on…` props of the element and its ancestors are listed, and
10
+ * framework roots (React's root container) are summarised per node unless
11
+ * `--all` is given.
8
12
  */
9
13
  import { DomElementResolver } from './DomElementResolver.js';
10
14
  import { runElementCommand } from './helpers/runElementCommand.js';
@@ -24,7 +28,8 @@ export function registerListenersCommand(dom) {
24
28
  .description('List event listeners that run for an element (incl. delegated ones on ancestors)')
25
29
  .argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
26
30
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
27
- .option('--type <types>', 'Only these event types (comma-separated, e.g. click,keydown)', eventTypesOption)
31
+ .option('--type <types>', 'Only these event types (comma-separated, e.g. click,keydown; repeatable)', eventTypesOption)
32
+ .option('--all', 'List every listener of framework roots (React) instead of one line per node')
28
33
  .addOption(jsonOption())
29
34
  .action(async (selectorOrIndex, options) => {
30
35
  await runCommand(() => listElementListeners(selectorOrIndex, options), options, (data) => formatListeners(data, options.type));
@@ -41,8 +46,13 @@ async function listElementListeners(selectorOrIndex, options) {
41
46
  const result = await runElementCommand({
42
47
  selectorOrIndex,
43
48
  index: options.index,
44
- buildRequest: (target) => ({ ...target, ...(options.type && { types: options.type }) }),
49
+ buildRequest: (target) => ({
50
+ ...target,
51
+ ...(options.type && { types: options.type }),
52
+ ...(options.all && { all: true }),
53
+ }),
45
54
  call: domListeners,
55
+ command: 'listeners',
46
56
  action: 'list event listeners',
47
57
  failureSuggestion: 'Verify the selector matches an element: bdg dom query "<selector>"',
48
58
  });
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * `bdg dom query` — find elements by CSS selector and populate the query cache.
3
3
  */
4
- import { queryDOMElements } from './helpers/index.js';
4
+ import { noMatchesError, queryDOMElements } from './helpers/index.js';
5
5
  import { runCommand } from '../shared/CommandRunner.js';
6
- import { noNodesFoundError } from '../../errors/messages.js';
7
6
  import { QueryCacheManager } from '../../session/QueryCacheManager.js';
8
7
  import { formatDomQuery } from '../../ui/formatters/dom.js';
9
8
  import { EXIT_CODES } from '../../utils/exitCodes.js';
@@ -21,7 +20,7 @@ export async function handleDomQuery(selector, options) {
21
20
  const cache = QueryCacheManager.getInstance();
22
21
  if (result.count === 0) {
23
22
  await cache.clear();
24
- const err = noNodesFoundError(selector);
23
+ const err = await noMatchesError(selector);
25
24
  return {
26
25
  success: false,
27
26
  error: err.message,
@@ -13,9 +13,19 @@ import type { DomScreenshotCommandOptions } from '../shared/optionTypes.js';
13
13
  */
14
14
  export declare function resolveImageFormat(outputPath: string, requested?: 'png' | 'jpeg'): 'png' | 'jpeg';
15
15
  /**
16
- * Handle `bdg dom screenshot <path>`.
16
+ * Fold the optional positional target (`bdg dom screenshot out.png "#sel"`,
17
+ * or an index from a query) into `--selector` / `--index`.
18
+ *
19
+ * @param target - Positional selector or index, if given
20
+ * @param options - Command options
21
+ * @returns Options with the target as `selector` or `index`
22
+ * @throws CommandError (81) when the option names a different element
23
+ */
24
+ export declare function withPositionalTarget(target: string | undefined, options: DomScreenshotCommandOptions): DomScreenshotCommandOptions;
25
+ /**
26
+ * Handle `bdg dom screenshot <path> [selector|index]`.
17
27
  *
18
28
  * Dispatches to page, element, or sequence capture based on flags.
19
29
  */
20
- export declare function handleDomScreenshot(outputPath: string, options: DomScreenshotCommandOptions): Promise<void>;
30
+ export declare function handleDomScreenshot(outputPath: string, target: string | undefined, commandOptions: DomScreenshotCommandOptions): Promise<void>;
21
31
  //# sourceMappingURL=screenshot.d.ts.map
@@ -8,7 +8,7 @@ import { runCommand } from '../shared/CommandRunner.js';
8
8
  import { assertFilePath, outputPathError } from '../shared/outputFile.js';
9
9
  import { positiveIntRule } from '../shared/validation.js';
10
10
  import { CommandError } from '../../errors/index.js';
11
- import { conflictingOptionsMessage, genericError } from '../../errors/messages.js';
11
+ import { conflictingOptionsMessage, conflictingTargetError, genericError, } from '../../errors/messages.js';
12
12
  import { missingArgumentError } from '../../errors/messages.js';
13
13
  import { OutputBuilder, buildSuccessResponse } from '../../ui/OutputBuilder.js';
14
14
  import { formatDomScreenshot } from '../../ui/formatters/dom.js';
@@ -90,6 +90,7 @@ function addElementInfo(result, options) {
90
90
  ...(options.selector !== undefined && { selector: options.selector }),
91
91
  ...(options.index !== undefined && { index: options.index }),
92
92
  bounds,
93
+ ...(result.element?.captured && { captured: result.element.captured }),
93
94
  },
94
95
  };
95
96
  }
@@ -227,11 +228,34 @@ function assertScreenshotOptions(outputPath, options) {
227
228
  throw new CommandError(message, {}, EXIT_CODES.INVALID_ARGUMENTS);
228
229
  }
229
230
  /**
230
- * Handle `bdg dom screenshot <path>`.
231
+ * Fold the optional positional target (`bdg dom screenshot out.png "#sel"`,
232
+ * or an index from a query) into `--selector` / `--index`.
233
+ *
234
+ * @param target - Positional selector or index, if given
235
+ * @param options - Command options
236
+ * @returns Options with the target as `selector` or `index`
237
+ * @throws CommandError (81) when the option names a different element
238
+ */
239
+ export function withPositionalTarget(target, options) {
240
+ if (target === undefined)
241
+ return options;
242
+ const isIndex = /^\d+$/.test(target);
243
+ const key = isIndex ? 'index' : 'selector';
244
+ const value = isIndex ? Number(target) : target;
245
+ const given = options[key];
246
+ if (given !== undefined && given !== value) {
247
+ const err = conflictingTargetError(`--${key} ${String(given)}`, target);
248
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
249
+ }
250
+ return { ...options, [key]: value };
251
+ }
252
+ /**
253
+ * Handle `bdg dom screenshot <path> [selector|index]`.
231
254
  *
232
255
  * Dispatches to page, element, or sequence capture based on flags.
233
256
  */
234
- export async function handleDomScreenshot(outputPath, options) {
257
+ export async function handleDomScreenshot(outputPath, target, commandOptions) {
258
+ const options = withPositionalTarget(target, commandOptions);
235
259
  assertScreenshotOptions(outputPath, options);
236
260
  if (options.follow) {
237
261
  await handleSequenceCapture(outputPath, options);
@@ -5,7 +5,7 @@
5
5
  * their surrounding DOM context and to fall back gracefully when only one
6
6
  * of the two data sources is available.
7
7
  */
8
- import { type DomContext } from './helpers/index.js';
8
+ import type { DomContext } from './helpers/index.js';
9
9
  import type { A11yNode } from '../../types.js';
10
10
  /**
11
11
  * Accessibility node paired with its surrounding DOM context for display.
@@ -17,8 +17,12 @@ export interface SemanticNodeWithContext {
17
17
  /**
18
18
  * Format a semantic node together with DOM context for human-readable output.
19
19
  *
20
+ * The role line is followed by up to 500 characters of the element's text
21
+ * (all of it with `dom get --full`) when it is longer than the one-line
22
+ * preview, or, for an element without text or name, what it holds.
23
+ *
20
24
  * @param data - Accessibility node and optional DOM context
21
- * @returns One-line formatted string
25
+ * @returns Role line, plus a text line for elements with longer text
22
26
  */
23
27
  export declare function formatSemanticNodeWithContext(data: SemanticNodeWithContext): string;
24
28
  /**
@@ -30,15 +34,4 @@ export declare function formatSemanticNodeWithContext(data: SemanticNodeWithCont
30
34
  * @param nodeId - CDP nodeId for synthesis
31
35
  */
32
36
  export declare function resolveNodeWithFallback(a11yNode: A11yNode | null, domContext: DomContext | null, nodeId: number | undefined): A11yNode | null;
33
- /**
34
- * Look up a single element by selector to produce its nodeId and DOM context.
35
- *
36
- * Used by `dom get` (semantic mode) when the accessibility tree has no node
37
- * for the selector — the selector-based DOM context allows us to synthesize
38
- * one.
39
- */
40
- export declare function queryDomContextBySelector(selector: string): Promise<{
41
- nodeId: number | undefined;
42
- domContext: DomContext | null;
43
- }>;
44
37
  //# sourceMappingURL=semanticUtils.d.ts.map
@@ -5,8 +5,9 @@
5
5
  * their surrounding DOM context and to fall back gracefully when only one
6
6
  * of the two data sources is available.
7
7
  */
8
- import { getDomContext, resolveBackendNodeIds, } from './helpers/index.js';
9
8
  import { synthesizeA11yNode } from '../../telemetry/roleInference.js';
9
+ import { joinLines } from '../../ui/formatting.js';
10
+ import { elementTextLine, emptyElementLine } from '../../ui/messages/commands.js';
10
11
  function capitalize(str) {
11
12
  return str.charAt(0).toUpperCase() + str.slice(1);
12
13
  }
@@ -29,7 +30,7 @@ function buildContextText(node, domContext) {
29
30
  const classPart = domContext.classes && domContext.classes.length > 0
30
31
  ? `.${domContext.classes.slice(0, 3).join('.')}`
31
32
  : '';
32
- const previewPart = domContext.preview ? ` "${domContext.preview}"` : '';
33
+ const previewPart = domContext.preview && !domContext.text ? ` "${domContext.preview}"` : '';
33
34
  return ` ${tagPart}${classPart}>${previewPart}`;
34
35
  }
35
36
  return '';
@@ -58,8 +59,12 @@ function buildPropertiesText(node) {
58
59
  /**
59
60
  * Format a semantic node together with DOM context for human-readable output.
60
61
  *
62
+ * The role line is followed by up to 500 characters of the element's text
63
+ * (all of it with `dom get --full`) when it is longer than the one-line
64
+ * preview, or, for an element without text or name, what it holds.
65
+ *
61
66
  * @param data - Accessibility node and optional DOM context
62
- * @returns One-line formatted string
67
+ * @returns Role line, plus a text line for elements with longer text
63
68
  */
64
69
  export function formatSemanticNodeWithContext(data) {
65
70
  const { node, domContext } = data;
@@ -67,7 +72,13 @@ export function formatSemanticNodeWithContext(data) {
67
72
  const contextText = buildContextText(node, domContext);
68
73
  const propsText = buildPropertiesText(node);
69
74
  const inferredText = node.inferred ? ' (inferred from DOM)' : '';
70
- return `${roleText}${contextText}${propsText}${inferredText}`;
75
+ const line = `${roleText}${contextText}${propsText}${inferredText}`;
76
+ if (domContext?.text)
77
+ return joinLines(line, elementTextLine(domContext.text));
78
+ if (domContext?.childCount !== undefined && !node.name) {
79
+ return joinLines(line, emptyElementLine(domContext.children ?? [], domContext.childCount));
80
+ }
81
+ return line;
71
82
  }
72
83
  /**
73
84
  * Resolve an a11y node, falling back to a DOM-synthesized one when only
@@ -84,19 +95,4 @@ export function resolveNodeWithFallback(a11yNode, domContext, nodeId) {
84
95
  return synthesizeA11yNode(domContext, nodeId);
85
96
  return null;
86
97
  }
87
- /**
88
- * Look up a single element by selector to produce its nodeId and DOM context.
89
- *
90
- * Used by `dom get` (semantic mode) when the accessibility tree has no node
91
- * for the selector — the selector-based DOM context allows us to synthesize
92
- * one.
93
- */
94
- export async function queryDomContextBySelector(selector) {
95
- const [backendNodeId] = await resolveBackendNodeIds([selector]);
96
- if (backendNodeId === undefined) {
97
- return { nodeId: undefined, domContext: null };
98
- }
99
- const domContext = await getDomContext({ backendNodeId });
100
- return { nodeId: backendNodeId, domContext };
101
- }
102
98
  //# sourceMappingURL=semanticUtils.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `bdg dom wait [selector]` - wait until elements appear, become visible,
3
+ * contain a text or are gone, and/or the page has loaded, instead of
4
+ * polling with `sleep` and `dom eval`.
5
+ */
6
+ import type { Command } from 'commander';
7
+ /**
8
+ * Register `bdg dom wait`.
9
+ *
10
+ * @param dom - The `dom` command group
11
+ */
12
+ export declare function registerWaitCommand(dom: Command): void;
13
+ //# sourceMappingURL=wait.d.ts.map
@@ -0,0 +1,83 @@
1
+ /**
2
+ * `bdg dom wait [selector]` - wait until elements appear, become visible,
3
+ * contain a text or are gone, and/or the page has loaded, instead of
4
+ * polling with `sleep` and `dom eval`.
5
+ */
6
+ import { runCommand } from '../shared/CommandRunner.js';
7
+ import { jsonOption } from '../shared/commonOptions.js';
8
+ import { integerOption } from '../shared/validation.js';
9
+ import { waitTargetRequiredError } from '../../errors/messages.js';
10
+ import { domWait } from '../../ipc/client.js';
11
+ import { WAIT_HELP_EXAMPLES, waitMetMessage } from '../../ui/messages/commands.js';
12
+ import { EXIT_CODES } from '../../utils/exitCodes.js';
13
+ import { filterDefined } from '../../utils/objects.js';
14
+ /** Default --timeout */
15
+ const DEFAULT_WAIT_TIMEOUT_MS = 10_000;
16
+ /** Longest --timeout (10 minutes) */
17
+ const MAX_WAIT_TIMEOUT_MS = 600_000;
18
+ /**
19
+ * Register `bdg dom wait`.
20
+ *
21
+ * @param dom - The `dom` command group
22
+ */
23
+ export function registerWaitCommand(dom) {
24
+ dom
25
+ .command('wait')
26
+ .description('Wait until elements appear, become visible, contain a text or are gone (or the page loads)')
27
+ .argument('[selector]', 'CSS selector (:has-text, :visible allowed; shadow DOM and same-origin iframes searched); optional with --load')
28
+ .option('--text <text>', 'A match must contain this text (case-insensitive)')
29
+ .option('--visible', 'Only count visible matches')
30
+ .option('--gone', 'Wait until no element matches (none visible, with --visible)')
31
+ .option('--load', 'Also wait for the page to finish loading (document.readyState complete)')
32
+ .option('--timeout <ms>', 'Give up after this many milliseconds (exit 102)', integerOption(1, MAX_WAIT_TIMEOUT_MS), DEFAULT_WAIT_TIMEOUT_MS)
33
+ .addOption(jsonOption())
34
+ .addHelpText('after', WAIT_HELP_EXAMPLES)
35
+ .action(async (selector, options) => {
36
+ await runCommand(() => waitFor(selector, options), options, formatWait);
37
+ });
38
+ }
39
+ /**
40
+ * Validate the options and ask the daemon to wait.
41
+ *
42
+ * @param selector - Selector, if given
43
+ * @param options - Command options
44
+ * @returns Command result
45
+ */
46
+ async function waitFor(selector, options) {
47
+ const needsSelector = options.text !== undefined || options.gone === true || !options.load;
48
+ if (selector === undefined && needsSelector) {
49
+ const err = waitTargetRequiredError();
50
+ return {
51
+ success: false,
52
+ error: err.message,
53
+ exitCode: EXIT_CODES.INVALID_ARGUMENTS,
54
+ errorContext: { suggestion: err.suggestion },
55
+ };
56
+ }
57
+ const response = await domWait({
58
+ ...filterDefined({ selector, text: options.text }),
59
+ ...(options.gone && { gone: true }),
60
+ ...(options.visible && { visible: true }),
61
+ ...(options.load && { load: true }),
62
+ timeout: options.timeout,
63
+ });
64
+ if (response.status === 'error' || !response.data) {
65
+ return {
66
+ success: false,
67
+ error: response.error ?? 'Failed to wait',
68
+ exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
69
+ ...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
70
+ };
71
+ }
72
+ return { success: true, data: response.data };
73
+ }
74
+ /**
75
+ * One line for human output.
76
+ *
77
+ * @param data - What the page showed when the condition was met
78
+ * @returns e.g. `✓ #finish visible after 5.1s`
79
+ */
80
+ function formatWait(data) {
81
+ return waitMetMessage(data, data.elapsedMs);
82
+ }
83
+ //# sourceMappingURL=wait.js.map
@@ -97,8 +97,8 @@ function convertCommand(command) {
97
97
  function generateRuntimeState() {
98
98
  const sessionActive = readLiveDaemonPid() !== null;
99
99
  const availableCommands = sessionActive
100
- ? ['peek', 'tail', 'details', 'dom', 'network', 'console', 'cdp', 'status', 'stop']
101
- : ['bdg <url>', 'cleanup', '--help', '--version'];
100
+ ? ['peek', 'tail', 'details', 'dom', 'network', 'console', 'cdp', 'status', 'sessions', 'stop']
101
+ : ['bdg <url>', 'sessions', 'cleanup', '--help', '--version'];
102
102
  return {
103
103
  sessionActive,
104
104
  daemonRunning: sessionActive,
@@ -4,9 +4,9 @@
4
4
  import { Option } from 'commander';
5
5
  import { runCommand } from '../shared/CommandRunner.js';
6
6
  import { jsonOption } from '../shared/commonOptions.js';
7
- import { handleDaemonConnectionError, noteFollowConnected, } from '../shared/daemonErrorHandler.js';
7
+ import { noteFollowConnected } from '../shared/daemonErrorHandler.js';
8
8
  import { fetchNetworkRequests, createErrorResult } from '../shared/dataFetcher.js';
9
- import { setupFollowMode } from '../shared/followMode.js';
9
+ import { followFetchFailure, setupFollowMode, } from '../shared/followMode.js';
10
10
  import { handleValidationError } from '../shared/handleValidationError.js';
11
11
  import { positiveIntRule, resourceTypeRule } from '../shared/validation.js';
12
12
  import { applyFilters, getFilterHelpText, validateFilterString } from '../../telemetry/filterDsl.js';
@@ -127,15 +127,7 @@ async function runFollowMode(options, resourceTypes, lastN) {
127
127
  const showNetwork = async () => {
128
128
  const result = await fetchNetworkRequests(filtersNeedHeaders(options));
129
129
  if (!result.success) {
130
- const errorResult = handleDaemonConnectionError(result.error, {
131
- json: options.json,
132
- follow: true,
133
- retryIntervalMs: FOLLOW_INTERVAL,
134
- exitCode: result.exitCode,
135
- });
136
- if (errorResult.shouldExit)
137
- process.exit(errorResult.exitCode);
138
- return;
130
+ return followFetchFailure(result, { json: options.json, retryIntervalMs: FOLLOW_INTERVAL });
139
131
  }
140
132
  noteFollowConnected();
141
133
  const finished = filterRequests(result.data, options, resourceTypes).filter((request) => request.duration !== undefined && !shown.has(request.requestId));
@@ -164,6 +156,7 @@ async function runFollowMode(options, resourceTypes, lastN) {
164
156
  console.log(text);
165
157
  }
166
158
  started = true;
159
+ return undefined;
167
160
  };
168
161
  await setupFollowMode(showNetwork, {
169
162
  startMessage: followingNetworkMessage,
@@ -7,12 +7,23 @@
7
7
  * @see docs/principles/SELF_DOCUMENTING_SYSTEMS.md
8
8
  */
9
9
  import { MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD, } from './dom/screenshotResize.js';
10
+ /** What DOM actions report about the network requests they triggered */
11
+ const TRIGGERED_REQUESTS_BEHAVIOR = 'Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them';
12
+ /** What every DOM action reports about the page besides its requests */
13
+ const ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "…" (element); JSON messages [{ text, element }], at most 3; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
14
+ /** What `--no-wait` does to a DOM action's triggered requests */
15
+ const NO_WAIT_TRIGGERED_REQUESTS = 'Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)';
10
16
  /**
11
17
  * Behavioral metadata registry.
12
18
  *
13
19
  * Keyed by "command:flag" to support same flag names across different commands.
14
20
  */
15
21
  const OPTION_BEHAVIORS = {
22
+ 'screenshot:--selector': {
23
+ default: 'Captures the page (full page unless --no-full-page)',
24
+ whenEnabled: 'Captures one element; the selector (or a query index) can also be given as the second argument: bdg dom screenshot out.png "#sel". Both given and naming different elements exits 81',
25
+ automaticBehavior: 'The capture covers the border box plus content overflowing it (uncleared floats, positioned children; not what an overflow: hidden ancestor cuts off, nor fixed descendants); JSON element.bounds is the border box and element.captured the larger area when it grew, which human output notes',
26
+ },
16
27
  'screenshot:--no-resize': {
17
28
  default: `Images auto-resized to max ${MAX_EDGE_PX}px longest edge for Claude Vision optimization (~1,600 tokens)`,
18
29
  whenDisabled: `Full resolution capture preserved (may use 10,000+ tokens for large pages)`,
@@ -42,18 +53,30 @@ const OPTION_BEHAVIORS = {
42
53
  whenEnabled: 'Returns full HTML with all attributes and classes',
43
54
  tokenImpact: 'Semantic output uses 70-99% fewer tokens than raw HTML. Use --raw only when you need exact HTML structure.',
44
55
  },
56
+ 'get:--full': {
57
+ default: 'Semantic output shows the element text up to 500 characters (whitespace collapsed; close buttons such as "×" and aria-hidden icons left out)',
58
+ whenEnabled: 'Shows all of the element text; cannot be combined with --raw or --node-id',
59
+ tokenImpact: 'A page-sized container can add thousands of tokens; target the element you need',
60
+ },
45
61
  'get:--all': {
46
62
  default: 'Returns first matching element only',
47
63
  whenEnabled: 'Returns all matching elements (only works with --raw)',
48
64
  },
49
- 'get:--nth': {
50
- default: 'Returns first matching element',
51
- whenEnabled: 'Returns the nth matching element (0-based index, only works with --raw)',
65
+ 'get:--index': {
66
+ default: 'Returns the first matching element (body without a selector)',
67
+ whenEnabled: 'Returns that match of the selector (0-based), in semantic and --raw output; --nth is an alias',
68
+ automaticBehavior: 'Out of range exits 81; with a numeric index argument (a cached query index) it exits 81',
69
+ },
70
+ 'query:--limit': {
71
+ default: 'dom a11y query lists the first 50 matches and says how many more there are; --json returns all of them',
72
+ whenEnabled: 'Lists that many matches (0 = all), in human and JSON output; count is always the total, JSON omitted the rest',
73
+ automaticBehavior: 'All matches are cached for index-based access (bdg dom click 55 works even when 50 are listed); an element the page and frame trees both report is listed once. Indices work with click, fill, hover, pressKey, scroll, submit, layout, get and listeners, also for elements of a cross-origin iframe of the same site (a consent dialog), whose scripts then run in that frame',
74
+ tokenImpact: 'About one line per match; a page can have hundreds of links',
52
75
  },
53
76
  'eval:--frame': {
54
77
  default: "Evaluates in the page's main frame",
55
78
  whenEnabled: "Evaluates in one iframe's main world (its own globals), including cross-origin (out-of-process) iframes; output gains a frame field (its URL)",
56
- automaticBehavior: 'The value is matched as: a 0-based index (bdg dom frames order, main page not counted), else an exact name/id attribute, else a case-insensitive part of the URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again).',
79
+ automaticBehavior: 'The value is matched as: a 0-based index (bdg dom frames order, main page not counted), else an exact name/id attribute, else a case-insensitive part of the name, id or URL. Several matches fail with 81 listing them; none fails with 83 listing all frames. Frames are looked up on every call (a reloaded iframe is found again).',
57
80
  },
58
81
  'console:-H': {
59
82
  default: 'Shows messages from current page load only (most recent navigation)',
@@ -73,14 +96,19 @@ const OPTION_BEHAVIORS = {
73
96
  default: 'Smart summary with errors deduplicated and warnings grouped',
74
97
  whenEnabled: 'Lists all messages chronologically without deduplication',
75
98
  },
99
+ 'console:--last': {
100
+ default: 'Smart summary (without --list); a list shows the last 100 messages',
101
+ whenEnabled: 'Lists the last N messages (0 = all) chronologically, also without --list; JSON gets messages',
102
+ automaticBehavior: 'The [n] shown are positions in the session message list (what bdg details console <n> takes); when the page or level filter left messages out between the listed ones, a note says how many and why',
103
+ },
76
104
  'console:--level': {
77
105
  default: 'Shows all log levels (error, warning, log, info, debug)',
78
106
  whenEnabled: 'Filters to specific level: error, warning, log, info, or debug',
79
107
  },
80
108
  'fill:--no-wait': {
81
- default: 'Waits for network stability after filling input (200ms idle)',
82
- whenDisabled: 'Returns immediately without waiting for network',
83
- automaticBehavior: 'Network wait helps ensure React/Vue state updates complete before next action',
109
+ default: 'Waits for network stability after filling input (150ms idle, up to 2s)',
110
+ whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
111
+ automaticBehavior: `Network wait helps ensure React/Vue state updates complete before next action. The value is read back after filling: when the page rejected or moved it, the output starts with a warning and JSON has valueMismatch { expected, actual } (exit code stays 0), plus movedTo naming the field of the form that got the value instead. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
84
112
  },
85
113
  'fill:--no-blur': {
86
114
  default: 'Triggers blur event after filling (validates most form fields)',
@@ -88,9 +116,10 @@ const OPTION_BEHAVIORS = {
88
116
  automaticBehavior: 'Blur triggers validation in most frameworks - disable only if you need to continue typing',
89
117
  },
90
118
  'click:--no-wait': {
91
- default: 'Waits for network stability after click (200ms idle)',
92
- whenDisabled: 'Returns immediately without waiting for network',
93
- automaticBehavior: 'Network wait helps ensure AJAX requests triggered by click complete. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning)',
119
+ default: 'Waits for network stability after click (150ms idle, up to 2s)',
120
+ whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
121
+ automaticBehavior: `Network wait helps ensure AJAX requests triggered by click complete. ${TRIGGERED_REQUESTS_BEHAVIOR}. The click itself uses real mouse events in the visible part of the element (method "mouse"); if the element is covered or has no size it falls back to DOM events (method "dom", with a warning). Results the page shows later without requests (timers, spinners) are not waited for: use bdg dom wait <selector> --visible. ${ACTION_EFFECTS_BEHAVIOR}. A click with no DOM change, no request and no navigation (checked again 300 ms later, which adds 300 ms plus at most 250 ms for the read) is reported as ⚠ Element Clicked (no visible effect observed: no DOM change, requests or navigation within 300 ms) and effect: "none" in JSON (exit code stays 0); not claimed with --no-wait, for hover or right-click, after a copy or cut, or when the click hit a form control, label, media, iframe, popover button, a mailto:/tel:/javascript: or other non-http link, a link to another window or a custom element with a closed shadow root, or moved focus to an element that is not a button or link. Effects outside the DOM (CSS :hover/:focus-within styles, canvas, clipboard without a copy event) are not seen`,
122
+ tokenImpact: 'A click that navigates lists the whole page load in JSON triggeredRequests',
94
123
  },
95
124
  'click:--double': {
96
125
  default: 'Single click',
@@ -102,17 +131,18 @@ const OPTION_BEHAVIORS = {
102
131
  },
103
132
  'hover:--no-wait': {
104
133
  default: 'Waits for network stability after moving the mouse (menus may load content)',
105
- whenDisabled: 'Returns immediately without waiting for network',
106
- automaticBehavior: 'The mouse stays over the element afterwards, so hover menus stay open until the next mouse action',
134
+ whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
135
+ automaticBehavior: `The mouse stays over the element afterwards, so hover menus stay open until the next mouse action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
107
136
  },
108
137
  'navigate:--no-wait': {
109
138
  default: 'Waits until the new page has loaded and the network and DOM are idle (up to 15 s)',
110
139
  whenDisabled: 'Returns as soon as the navigation has started',
111
- automaticBehavior: 'Also applies to page reload/back/forward; indices from earlier queries become stale (87)',
140
+ automaticBehavior: 'Also applies to page reload/back/forward; indices from earlier queries become stale (87). Triggered requests are not listed (they are the page load; see bdg network list)',
112
141
  },
113
142
  'pressKey:--no-wait': {
114
- default: 'Waits for network stability after key press (200ms idle)',
115
- whenDisabled: 'Returns immediately without waiting for network',
143
+ default: 'Waits for network stability after key press (150ms idle, up to 2s)',
144
+ whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
145
+ automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
116
146
  },
117
147
  'pressKey:--times': {
118
148
  default: 'Presses key once',
@@ -124,16 +154,52 @@ const OPTION_BEHAVIORS = {
124
154
  'submit:--wait-navigation': {
125
155
  default: 'Waits for network stability only',
126
156
  whenEnabled: 'Waits for page navigation to complete (use for forms that redirect)',
157
+ automaticBehavior: 'A navigation is a new document loading in the main frame, also at the same URL (a POST that redirects back to the form after a login error). When the new page loaded but requests were still running at --timeout, the submit succeeds with a warning; without a navigation it exits 102, and the hint says whether a page request was sent (slow server) or not (the form may submit via fetch)',
127
158
  },
128
159
  'submit:--wait-network': {
129
160
  default: 'Default network idle timeout',
130
161
  whenEnabled: 'Custom network idle timeout in ms (use for slow APIs)',
162
+ automaticBehavior: `${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}. A submit with no DOM change, request or navigation reports effect: "none" like dom click`,
163
+ },
164
+ 'wait:--timeout': {
165
+ default: 'Gives up after 10000 ms',
166
+ whenEnabled: 'Gives up after the given milliseconds (1 to 600000)',
167
+ automaticBehavior: 'The page is watched (DOM mutations plus a 100 ms poll for style changes) and answers as soon as the matches change; a navigation during the wait continues it on the new document. A timeout exits 102 (CDP_TIMEOUT) with what the page showed last, e.g. "2 matches, none visible" or "document.readyState: loading", and a next step (dom query for no matches, dom layout for hidden ones, peek for a page still loading)',
168
+ },
169
+ 'wait:--visible': {
170
+ default: 'Counts every match, hidden ones included',
171
+ whenEnabled: 'Counts only visible matches (rendered, non-empty box, visibility: visible; opacity 0 counts as visible, as with :visible)',
172
+ automaticBehavior: 'With --gone, waits until no match is visible (the element may stay in the DOM hidden)',
173
+ },
174
+ 'wait:--gone': {
175
+ default: 'Waits for at least one match',
176
+ whenEnabled: 'Waits until nothing matches (nothing visible with --visible, nothing containing the text with --text), seen twice in a row in the same document once it is no longer loading, so the empty document right after a navigation does not count (a page stuck in readyState loading never meets it)',
177
+ },
178
+ 'wait:--text': {
179
+ default: 'Any match counts',
180
+ whenEnabled: 'Only matches whose text contains the given text count (case-insensitive, whitespace collapsed; hidden elements are matched by their text nodes, as with :has-text)',
181
+ automaticBehavior: 'Needs a selector; use body to look in the whole page',
182
+ },
183
+ 'wait:--load': {
184
+ default: 'Does not look at document.readyState',
185
+ whenEnabled: 'Also waits for document.readyState "complete"; without a selector waits only for that (a script whose server never answers keeps it "loading")',
131
186
  },
132
187
  'listeners:--type': {
133
188
  default: 'Lists listeners of every event type on the element, its ancestors (through open shadow roots), its document and window',
134
- whenEnabled: 'Lists only these event types (comma-separated, case-sensitive like addEventListener)',
135
- automaticBehavior: 'Listeners are grouped by type, nearest first; JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). The Debugger domain is not enabled',
136
- tokenImpact: 'Pages with many window/document listeners produce long lists; --type keeps the output short',
189
+ whenEnabled: 'Lists only these event types (comma-separated or repeated, case-sensitive like addEventListener); when none match, typeSuggestions names close types (Click, onclick → click)',
190
+ automaticBehavior: "Listeners are grouped by type, the element's own handlers first (types handled on the element before delegated ones; nearest first, no-ops last); JSON line/column numbers are 0-based (human output shows them 1-based like DevTools). React's on… props that run for the element (its own and its React parents', through portals; onFocus/onBlur as focusin/focusout; parents' props for non-bubbling events left out) are listed with source and location (framework: \"React\", reactProp: \"onClick\"; at most 50 of the requested types, reactHandlersSkipped counts the rest). jQuery handlers are shown instead of jQuery's dispatcher (framework: \"jQuery\", delegateSelector for delegates the element matches; a dispatcher with no handler for the element is omitted; at most 50 are resolved, jqueryHandlersSkipped counts the rest). Empty handlers (React's onclick placeholder) are marked noop and do not count as the element's own handler. The Debugger domain is not enabled",
191
+ tokenImpact: 'Framework roots are already collapsed; --type keeps the output short on pages with many listeners',
192
+ },
193
+ 'listeners:--all': {
194
+ default: 'Collapses React roots: on an ancestor recognised as a React root container (React\'s keys on the node, or dispatchers named dispatchDiscreteEvent/dispatchContinuousEvent/dispatchEvent), the function objects that each listen for several event types become one line / one "collapsed" entry with its types, phases and dispatchers. Other multi-type handlers are never collapsed',
195
+ whenEnabled: 'Lists every listener of framework roots individually',
196
+ tokenImpact: 'On React pages --all adds a row per event type and phase (about 140 rows, 60 KB of JSON)',
197
+ },
198
+ 'layout:--index': {
199
+ default: 'Reports every match of the selector (human output lists the first 20, JSON up to 100 plus an omitted count); a numeric argument reports that cached query element',
200
+ whenEnabled: 'Reports only the nth match (0-based); out of range exits 81',
201
+ automaticBehavior: 'Coordinates are CSS px: bounds relative to the top-level page (iframe offsets and page scroll included), viewport relative to the visible area. Iframes and overflow containers (scroll lists, overflow: hidden) clip what counts as visible (clippedBy names the one cutting it off). scrollBy brings the whole element into view and is limited to how far the page can scroll: for an element out of view it centres it (aligns its start when it is larger than the viewport; human output says "to centre it"), for a partly visible one it is the smallest scroll that shows all of it (the part cut off at the top or bottom; the start of one larger than the viewport; "partly visible (87%); scroll up 5px to see all of it"). Elements a page script moves on scroll (floating menus) may move again after it; fixed and sticky elements (page scroll does not move them, or only until they stick) and ones beyond that range get offScreenReason instead, which says "page scrolling is locked (…)" when the page cannot scroll because body/html is position: fixed or overflow: hidden, so in-flow content is not called fixed; a visible dialog (dialog[open], [aria-modal=true], [role=dialog|alertdialog]) is named as the likely cause ("likely by dialog div#consent"). page.viewport is the layout viewport without scrollbars, as dom scroll reports it; page.colorScheme is the prefers-color-scheme media feature the page sees (not the theme it renders). Content in a closed <details> or under content-visibility: hidden is hidden. coveredBy is the topmost element at the center of the largest visible box (none for pointer-events: none, nor for an element of the same click target: an overlay inside the link, button or label the element is in, a link to the same URL, or the textless absolutely positioned overlay link spanning the card that holds plain content); inert elements are flagged, not hidden',
202
+ tokenImpact: 'About one line per element; a cheap alternative to screenshots for "where is it?"',
137
203
  },
138
204
  'scroll:--down': {
139
205
  whenEnabled: 'Scrolls page down by specified pixel amount',
@@ -152,11 +218,12 @@ const OPTION_BEHAVIORS = {
152
218
  },
153
219
  'scroll:--bottom': {
154
220
  whenEnabled: 'Scrolls to the very bottom of the page',
221
+ automaticBehavior: 'A page scroll (--down/--up/--left/--right/--top/--bottom) that moved nothing still exits 0 but starts with a warning saying why: the document is no taller (wider) than the viewport, the page was already at that edge, or scrolling is locked; while document.readyState is not complete it adds that the page is still loading (bdg dom wait --load)',
155
222
  },
156
223
  'scroll:--no-wait': {
157
- default: 'Waits for lazy-loaded content to stabilize after scroll (200ms network idle)',
158
- whenDisabled: 'Returns immediately without waiting for lazy-loaded content',
159
- automaticBehavior: 'Wait helps ensure images and infinite scroll content load before next action',
224
+ default: 'Waits for lazy-loaded content to stabilize after scroll (150ms network idle, up to 2s)',
225
+ whenDisabled: NO_WAIT_TRIGGERED_REQUESTS,
226
+ automaticBehavior: `Wait helps ensure images and infinite scroll content load before next action. ${TRIGGERED_REQUESTS_BEHAVIOR}. ${ACTION_EFFECTS_BEHAVIOR}`,
160
227
  },
161
228
  'scroll:--index': {
162
229
  whenEnabled: 'If selector matches multiple elements, scrolls to the nth element (0-based)',
@@ -196,6 +263,11 @@ const OPTION_BEHAVIORS = {
196
263
  default: 'Compact output (truncated URLs, no resource types)',
197
264
  whenEnabled: 'Verbose output with full URLs and resource types',
198
265
  },
266
+ 'bdg:--session': {
267
+ default: 'The default session in ~/.bdg (or $BDG_SESSION_DIR); BDG_SESSION=<name> selects a named session like the flag',
268
+ whenEnabled: 'Uses the named session in ~/.bdg/sessions/<name>/ (or $BDG_SESSION_DIR/sessions/<name>/) with its own daemon, Chrome, profile and port; every command (status, stop, cleanup, ...) acts on that session only',
269
+ automaticBehavior: 'Accepted before or after any subcommand; --session wins over BDG_SESSION. Names are case-insensitive (lower-cased: ALPHA is alpha). Without --port a named session takes the first free port above 9222 not claimed by another running session, and keeps it in port.txt. Names: 1-40 letters, digits, "-" or "_", starting with a letter or digit (exit 81 otherwise, also when the socket path would be too long). Hints and suggestions in its output carry --session <name>',
270
+ },
199
271
  'cleanup:-f': {
200
272
  default: 'Refuses to run while a session is active; removes files left by a crashed session',
201
273
  whenEnabled: 'Kills the running daemon and its Chrome first (use when a session is stuck)',
@@ -203,6 +275,25 @@ const OPTION_BEHAVIORS = {
203
275
  'cleanup:--aggressive': {
204
276
  whenEnabled: 'Alias for --force, kept for compatibility',
205
277
  },
278
+ 'cleanup:--purge': {
279
+ default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt) is kept for its next start",
280
+ whenEnabled: 'After cleaning up, deletes the directory of the session named by --session (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited',
281
+ },
282
+ 'bdg:--viewport': {
283
+ default: 'A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window',
284
+ whenEnabled: 'The page gets exactly that viewport (CSS px, e.g. 1280x800) for the whole session, through navigations and reloads (Emulation.setDeviceMetricsOverride at the display pixel ratio); a launched Chrome also opens its window at that size, so tabs the page opens get it too',
285
+ automaticBehavior: 'Works with --chrome-ws-url: the override belongs to the session, and Chrome drops it when the session ends, so the attached browser gets its own size back. bdg status shows the resulting layout viewport without the scrollbar (Viewport: 1265×800 (--viewport 1280x800)). Invalid sizes (not WxH, a side outside 1-10000) exit 81',
286
+ },
287
+ 'bdg:--color-scheme': {
288
+ default: 'The page sees the system setting for prefers-color-scheme (headless Chrome follows the OS, so a dark OS renders dark pages); bdg status and dom layout show which one',
289
+ whenEnabled: 'Emulates prefers-color-scheme: light or dark for the whole session (Emulation.setEmulatedMedia); other values exit 81 with a suggestion',
290
+ automaticBehavior: 'Applies to the session page (and its same-process iframes); Chrome drops it when the session ends, also for an attached Chrome (--chrome-ws-url)',
291
+ },
292
+ 'bdg:--chrome-ws-url': {
293
+ default: 'bdg launches its own Chrome (closed on stop)',
294
+ whenEnabled: 'Attaches to a running Chrome instead; it keeps running after stop. --port, -u and --[no-]headless cannot be combined with it (exit 81)',
295
+ automaticBehavior: 'A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port)',
296
+ },
206
297
  'stop:--kill-chrome': {
207
298
  default: 'Chrome launched by bdg is always closed on stop; an attached Chrome (--chrome-ws-url) is left running',
208
299
  whenEnabled: 'No additional effect; kept for compatibility',