browser-debugger-cli 0.6.9 → 0.6.11

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 (259) hide show
  1. package/README.md +10 -0
  2. package/dist/commands/cdp.js +1 -1
  3. package/dist/commands/cdp.js.map +1 -1
  4. package/dist/commands/cleanup.js +1 -1
  5. package/dist/commands/cleanup.js.map +1 -1
  6. package/dist/commands/console.d.ts +4 -28
  7. package/dist/commands/console.d.ts.map +1 -1
  8. package/dist/commands/console.js +82 -192
  9. package/dist/commands/console.js.map +1 -1
  10. package/dist/commands/details.d.ts.map +1 -1
  11. package/dist/commands/details.js +27 -8
  12. package/dist/commands/details.js.map +1 -1
  13. package/dist/commands/dom/DomElementResolver.d.ts +25 -9
  14. package/dist/commands/dom/DomElementResolver.d.ts.map +1 -1
  15. package/dist/commands/dom/DomElementResolver.js +65 -17
  16. package/dist/commands/dom/DomElementResolver.js.map +1 -1
  17. package/dist/commands/dom/__tests__/screenshotResize.unit.test.d.ts +2 -0
  18. package/dist/commands/dom/__tests__/screenshotResize.unit.test.d.ts.map +1 -0
  19. package/dist/commands/dom/__tests__/screenshotResize.unit.test.js +184 -0
  20. package/dist/commands/dom/__tests__/screenshotResize.unit.test.js.map +1 -0
  21. package/dist/commands/dom/a11y.d.ts.map +1 -1
  22. package/dist/commands/dom/a11y.js +9 -9
  23. package/dist/commands/dom/a11y.js.map +1 -1
  24. package/dist/commands/dom/evalHelpers.d.ts.map +1 -1
  25. package/dist/commands/dom/evalHelpers.js +10 -7
  26. package/dist/commands/dom/evalHelpers.js.map +1 -1
  27. package/dist/commands/dom/formFillHelpers.d.ts +2 -1
  28. package/dist/commands/dom/formFillHelpers.d.ts.map +1 -1
  29. package/dist/commands/dom/formFillHelpers.js +22 -20
  30. package/dist/commands/dom/formFillHelpers.js.map +1 -1
  31. package/dist/commands/dom/formInteraction.d.ts.map +1 -1
  32. package/dist/commands/dom/formInteraction.js +26 -10
  33. package/dist/commands/dom/formInteraction.js.map +1 -1
  34. package/dist/commands/dom/formSubmitHelpers.d.ts +2 -1
  35. package/dist/commands/dom/formSubmitHelpers.d.ts.map +1 -1
  36. package/dist/commands/dom/formSubmitHelpers.js.map +1 -1
  37. package/dist/commands/dom/helpers.d.ts +47 -4
  38. package/dist/commands/dom/helpers.d.ts.map +1 -1
  39. package/dist/commands/dom/helpers.js +406 -21
  40. package/dist/commands/dom/helpers.js.map +1 -1
  41. package/dist/commands/dom/index.d.ts +1 -1
  42. package/dist/commands/dom/index.d.ts.map +1 -1
  43. package/dist/commands/dom/index.js +414 -151
  44. package/dist/commands/dom/index.js.map +1 -1
  45. package/dist/commands/dom/reactEventHelpers.d.ts +4 -4
  46. package/dist/commands/dom/reactEventHelpers.d.ts.map +1 -1
  47. package/dist/commands/dom/reactEventHelpers.js +11 -11
  48. package/dist/commands/dom/screenshotResize.d.ts +81 -0
  49. package/dist/commands/dom/screenshotResize.d.ts.map +1 -0
  50. package/dist/commands/dom/screenshotResize.js +103 -0
  51. package/dist/commands/dom/screenshotResize.js.map +1 -0
  52. package/dist/commands/helpJson.d.ts +20 -0
  53. package/dist/commands/helpJson.d.ts.map +1 -1
  54. package/dist/commands/helpJson.js +48 -39
  55. package/dist/commands/helpJson.js.map +1 -1
  56. package/dist/commands/network/har.d.ts.map +1 -1
  57. package/dist/commands/network/har.js +3 -2
  58. package/dist/commands/network/har.js.map +1 -1
  59. package/dist/commands/network/list.d.ts +0 -8
  60. package/dist/commands/network/list.d.ts.map +1 -1
  61. package/dist/commands/network/list.js +134 -208
  62. package/dist/commands/network/list.js.map +1 -1
  63. package/dist/commands/network/shared.d.ts.map +1 -1
  64. package/dist/commands/network/shared.js +6 -0
  65. package/dist/commands/network/shared.js.map +1 -1
  66. package/dist/commands/optionBehaviors.d.ts +26 -0
  67. package/dist/commands/optionBehaviors.d.ts.map +1 -0
  68. package/dist/commands/optionBehaviors.js +184 -0
  69. package/dist/commands/optionBehaviors.js.map +1 -0
  70. package/dist/commands/peek.d.ts +2 -4
  71. package/dist/commands/peek.d.ts.map +1 -1
  72. package/dist/commands/peek.js +109 -97
  73. package/dist/commands/peek.js.map +1 -1
  74. package/dist/commands/shared/CommandRunner.js +1 -1
  75. package/dist/commands/shared/CommandRunner.js.map +1 -1
  76. package/dist/commands/shared/dataFetcher.d.ts +58 -0
  77. package/dist/commands/shared/dataFetcher.d.ts.map +1 -0
  78. package/dist/commands/shared/dataFetcher.js +98 -0
  79. package/dist/commands/shared/dataFetcher.js.map +1 -0
  80. package/dist/commands/shared/followMode.d.ts +50 -0
  81. package/dist/commands/shared/followMode.d.ts.map +1 -0
  82. package/dist/commands/shared/followMode.js +58 -0
  83. package/dist/commands/shared/followMode.js.map +1 -0
  84. package/dist/commands/shared/optionTypes.d.ts +18 -4
  85. package/dist/commands/shared/optionTypes.d.ts.map +1 -1
  86. package/dist/commands/shared/startHelpers.d.ts.map +1 -1
  87. package/dist/commands/shared/startHelpers.js +6 -5
  88. package/dist/commands/shared/startHelpers.js.map +1 -1
  89. package/dist/commands/shared/validation.d.ts +0 -45
  90. package/dist/commands/shared/validation.d.ts.map +1 -1
  91. package/dist/commands/shared/validation.js +65 -107
  92. package/dist/commands/shared/validation.js.map +1 -1
  93. package/dist/commands/status.d.ts.map +1 -1
  94. package/dist/commands/status.js +18 -9
  95. package/dist/commands/status.js.map +1 -1
  96. package/dist/commands/stop.d.ts.map +1 -1
  97. package/dist/commands/stop.js +15 -4
  98. package/dist/commands/stop.js.map +1 -1
  99. package/dist/commands/tail.d.ts +2 -7
  100. package/dist/commands/tail.d.ts.map +1 -1
  101. package/dist/commands/tail.js +67 -79
  102. package/dist/commands/tail.js.map +1 -1
  103. package/dist/commands/types.d.ts +11 -5
  104. package/dist/commands/types.d.ts.map +1 -1
  105. package/dist/connection/cdp.d.ts.map +1 -1
  106. package/dist/connection/cdp.js +5 -3
  107. package/dist/connection/cdp.js.map +1 -1
  108. package/dist/connection/diagnostics.d.ts +9 -0
  109. package/dist/connection/diagnostics.d.ts.map +1 -1
  110. package/dist/connection/diagnostics.js +14 -1
  111. package/dist/connection/diagnostics.js.map +1 -1
  112. package/dist/connection/errors.d.ts +0 -7
  113. package/dist/connection/errors.d.ts.map +1 -1
  114. package/dist/connection/errors.js +0 -7
  115. package/dist/connection/errors.js.map +1 -1
  116. package/dist/connection/handlers.d.ts +1 -8
  117. package/dist/connection/handlers.d.ts.map +1 -1
  118. package/dist/connection/handlers.js.map +1 -1
  119. package/dist/connection/launcher/binaryResolver.d.ts.map +1 -1
  120. package/dist/connection/launcher/binaryResolver.js +3 -15
  121. package/dist/connection/launcher/binaryResolver.js.map +1 -1
  122. package/dist/connection/launcher/preferencesLoader.d.ts.map +1 -1
  123. package/dist/connection/launcher/preferencesLoader.js +2 -1
  124. package/dist/connection/launcher/preferencesLoader.js.map +1 -1
  125. package/dist/connection/launcher.d.ts.map +1 -1
  126. package/dist/connection/launcher.js +5 -16
  127. package/dist/connection/launcher.js.map +1 -1
  128. package/dist/connection/pageReadiness.d.ts.map +1 -1
  129. package/dist/connection/pageReadiness.js +13 -16
  130. package/dist/connection/pageReadiness.js.map +1 -1
  131. package/dist/connection/typed-cdp.d.ts +3 -5
  132. package/dist/connection/typed-cdp.d.ts.map +1 -1
  133. package/dist/connection/typed-cdp.js +1 -1
  134. package/dist/connection/typed-cdp.js.map +1 -1
  135. package/dist/daemon/handlers/BaseHandler.d.ts.map +1 -1
  136. package/dist/daemon/handlers/BaseHandler.js +1 -1
  137. package/dist/daemon/handlers/BaseHandler.js.map +1 -1
  138. package/dist/daemon/handlers/QueryHandlers.d.ts.map +1 -1
  139. package/dist/daemon/handlers/QueryHandlers.js +1 -1
  140. package/dist/daemon/handlers/QueryHandlers.js.map +1 -1
  141. package/dist/daemon/handlers/SessionHandlers.d.ts.map +1 -1
  142. package/dist/daemon/handlers/SessionHandlers.js +1 -1
  143. package/dist/daemon/handlers/SessionHandlers.js.map +1 -1
  144. package/dist/daemon/handlers/responseHandler.d.ts.map +1 -1
  145. package/dist/daemon/handlers/responseHandler.js +10 -0
  146. package/dist/daemon/handlers/responseHandler.js.map +1 -1
  147. package/dist/daemon/ipcServer.js +1 -1
  148. package/dist/daemon/ipcServer.js.map +1 -1
  149. package/dist/daemon/launcher.d.ts.map +1 -1
  150. package/dist/daemon/launcher.js +3 -2
  151. package/dist/daemon/launcher.js.map +1 -1
  152. package/dist/daemon/lifecycle/signalHandlers.d.ts.map +1 -1
  153. package/dist/daemon/lifecycle/signalHandlers.js +1 -1
  154. package/dist/daemon/lifecycle/signalHandlers.js.map +1 -1
  155. package/dist/daemon/lifecycle/workerCleanup.d.ts.map +1 -1
  156. package/dist/daemon/lifecycle/workerCleanup.js +4 -3
  157. package/dist/daemon/lifecycle/workerCleanup.js.map +1 -1
  158. package/dist/daemon/lifecycle/workerConfig.d.ts.map +1 -1
  159. package/dist/daemon/lifecycle/workerConfig.js +1 -1
  160. package/dist/daemon/lifecycle/workerConfig.js.map +1 -1
  161. package/dist/daemon/lifecycle/workerIpc.d.ts.map +1 -1
  162. package/dist/daemon/lifecycle/workerIpc.js +1 -1
  163. package/dist/daemon/lifecycle/workerIpc.js.map +1 -1
  164. package/dist/daemon/server/JsonlParser.d.ts.map +1 -1
  165. package/dist/daemon/server/JsonlParser.js +1 -1
  166. package/dist/daemon/server/JsonlParser.js.map +1 -1
  167. package/dist/daemon/startSession.d.ts.map +1 -1
  168. package/dist/daemon/startSession.js +1 -1
  169. package/dist/daemon/startSession.js.map +1 -1
  170. package/dist/daemon/worker/TelemetryStore.d.ts +2 -1
  171. package/dist/daemon/worker/TelemetryStore.d.ts.map +1 -1
  172. package/dist/daemon/worker/TelemetryStore.js +4 -0
  173. package/dist/daemon/worker/TelemetryStore.js.map +1 -1
  174. package/dist/daemon/worker/plugins.d.ts.map +1 -1
  175. package/dist/daemon/worker/plugins.js +9 -2
  176. package/dist/daemon/worker/plugins.js.map +1 -1
  177. package/dist/index.js +23 -7
  178. package/dist/index.js.map +1 -1
  179. package/dist/ipc/utils/errors.js +1 -1
  180. package/dist/ipc/utils/errors.js.map +1 -1
  181. package/dist/session/QueryCacheManager.d.ts.map +1 -1
  182. package/dist/session/QueryCacheManager.js +1 -1
  183. package/dist/session/QueryCacheManager.js.map +1 -1
  184. package/dist/session/chrome.js +1 -1
  185. package/dist/session/chrome.js.map +1 -1
  186. package/dist/session/cleanup.js +5 -5
  187. package/dist/session/cleanup.js.map +1 -1
  188. package/dist/session/lock.js +1 -1
  189. package/dist/session/lock.js.map +1 -1
  190. package/dist/session/metadata.d.ts.map +1 -1
  191. package/dist/session/metadata.js +1 -1
  192. package/dist/session/metadata.js.map +1 -1
  193. package/dist/session/pid.js +1 -1
  194. package/dist/session/pid.js.map +1 -1
  195. package/dist/telemetry/dom.d.ts.map +1 -1
  196. package/dist/telemetry/dom.js +1 -1
  197. package/dist/telemetry/dom.js.map +1 -1
  198. package/dist/telemetry/filterPresets.d.ts.map +1 -1
  199. package/dist/telemetry/filterPresets.js +5 -2
  200. package/dist/telemetry/filterPresets.js.map +1 -1
  201. package/dist/telemetry/navigation.d.ts.map +1 -1
  202. package/dist/telemetry/navigation.js +7 -0
  203. package/dist/telemetry/navigation.js.map +1 -1
  204. package/dist/telemetry/network.d.ts +12 -1
  205. package/dist/telemetry/network.d.ts.map +1 -1
  206. package/dist/telemetry/network.js +117 -1
  207. package/dist/telemetry/network.js.map +1 -1
  208. package/dist/types.d.ts +148 -0
  209. package/dist/types.d.ts.map +1 -1
  210. package/dist/types.js +33 -1
  211. package/dist/types.js.map +1 -1
  212. package/dist/ui/OutputBuilder.d.ts +2 -44
  213. package/dist/ui/OutputBuilder.d.ts.map +1 -1
  214. package/dist/ui/OutputBuilder.js +71 -103
  215. package/dist/ui/OutputBuilder.js.map +1 -1
  216. package/dist/ui/errors/CommandError.d.ts +0 -2
  217. package/dist/ui/errors/CommandError.d.ts.map +1 -1
  218. package/dist/ui/errors/CommandError.js.map +1 -1
  219. package/dist/ui/formatters/dom.d.ts +10 -30
  220. package/dist/ui/formatters/dom.d.ts.map +1 -1
  221. package/dist/ui/formatters/dom.js +13 -36
  222. package/dist/ui/formatters/dom.js.map +1 -1
  223. package/dist/ui/logging/logger.d.ts +1 -1
  224. package/dist/ui/logging/logger.d.ts.map +1 -1
  225. package/dist/ui/logging/logger.js +1 -1
  226. package/dist/ui/logging/logger.js.map +1 -1
  227. package/dist/ui/messages/dom.d.ts +14 -0
  228. package/dist/ui/messages/dom.d.ts.map +1 -0
  229. package/dist/ui/messages/dom.js +28 -0
  230. package/dist/ui/messages/dom.js.map +1 -0
  231. package/dist/ui/messages/errors.d.ts +83 -0
  232. package/dist/ui/messages/errors.d.ts.map +1 -1
  233. package/dist/ui/messages/errors.js +171 -0
  234. package/dist/ui/messages/errors.js.map +1 -1
  235. package/dist/utils/async.d.ts +10 -0
  236. package/dist/utils/async.d.ts.map +1 -0
  237. package/dist/utils/async.js +12 -0
  238. package/dist/utils/async.js.map +1 -0
  239. package/dist/utils/atomicFile.js +1 -1
  240. package/dist/utils/atomicFile.js.map +1 -1
  241. package/dist/utils/errorMapping.d.ts.map +1 -1
  242. package/dist/utils/errorMapping.js +4 -1
  243. package/dist/utils/errorMapping.js.map +1 -1
  244. package/dist/utils/exitCodes.d.ts +17 -16
  245. package/dist/utils/exitCodes.d.ts.map +1 -1
  246. package/dist/utils/exitCodes.js +86 -16
  247. package/dist/utils/exitCodes.js.map +1 -1
  248. package/dist/utils/suggestions.d.ts +19 -0
  249. package/dist/utils/suggestions.d.ts.map +1 -0
  250. package/dist/utils/suggestions.js +34 -0
  251. package/dist/utils/suggestions.js.map +1 -0
  252. package/dist/utils/taskMappings.d.ts.map +1 -1
  253. package/dist/utils/taskMappings.js +35 -0
  254. package/dist/utils/taskMappings.js.map +1 -1
  255. package/dist/utils/typeGuards.d.ts +11 -4
  256. package/dist/utils/typeGuards.d.ts.map +1 -1
  257. package/dist/utils/typeGuards.js +1 -17
  258. package/dist/utils/typeGuards.js.map +1 -1
  259. package/package.json +4 -4
@@ -1,175 +1,172 @@
1
1
  import { DomElementResolver } from '../../commands/dom/DomElementResolver.js';
2
2
  import { registerA11yCommands } from '../../commands/dom/a11y.js';
3
- import { queryDOMElements, getDOMElements, capturePageScreenshot, getDomContext, } from '../../commands/dom/helpers.js';
3
+ import { queryDOMElements, getDOMElements, capturePageScreenshot, captureElementScreenshot, resolveSelector, getDomContext, } from '../../commands/dom/helpers.js';
4
4
  import { runCommand } from '../../commands/shared/CommandRunner.js';
5
+ import { setupFollowMode } from '../../commands/shared/followMode.js';
5
6
  import { positiveIntRule } from '../../commands/shared/validation.js';
6
7
  import { QueryCacheManager } from '../../session/QueryCacheManager.js';
7
8
  import { resolveA11yNode } from '../../telemetry/a11y.js';
8
9
  import { synthesizeA11yNode } from '../../telemetry/roleInference.js';
9
10
  import { CommandError } from '../../ui/errors/index.js';
10
11
  import { formatDomQuery, formatDomGet, formatDomEval, formatDomScreenshot, } from '../../ui/formatters/dom.js';
11
- import { elementNotFoundError } from '../../ui/messages/errors.js';
12
+ import { createLogger } from '../../ui/logging/index.js';
13
+ import { missingArgumentError, elementAtIndexNotFoundError, noNodesFoundError, } from '../../ui/messages/errors.js';
12
14
  import { EXIT_CODES } from '../../utils/exitCodes.js';
13
15
  import { filterDefined } from '../../utils/objects.js';
16
+ const log = createLogger('dom');
14
17
  /**
15
- * Handle bdg dom query <selector> command
18
+ * Build filtered screenshot options from command options.
16
19
  *
17
- * Queries the DOM using a CSS selector and displays matching elements.
18
- * Uses CDP relay through worker's persistent connection.
19
- * Results are cached for index-based access via "bdg dom get <index>".
20
+ * @param options - Raw command options
21
+ * @returns Filtered options with undefined values removed
22
+ */
23
+ function buildPageScreenshotOptions(options) {
24
+ return filterDefined({
25
+ format: options.format,
26
+ quality: options.quality,
27
+ fullPage: options.fullPage,
28
+ noResize: options.resize === false,
29
+ scroll: options.scroll,
30
+ });
31
+ }
32
+ /**
33
+ * Build filtered element screenshot options from command options.
20
34
  *
21
- * @param selector - CSS selector to query (e.g., ".error", "#app", "button")
22
- * @param options - Command options
35
+ * @param options - Raw command options
36
+ * @returns Filtered options with undefined values removed
23
37
  */
24
- async function handleDomQuery(selector, options) {
25
- await runCommand(async () => {
26
- const result = await queryDOMElements(selector);
27
- const cacheManager = QueryCacheManager.getInstance();
28
- const navigationId = await cacheManager.getCurrentNavigationId();
29
- const resultWithNavId = {
30
- ...result,
31
- ...(navigationId !== null && { navigationId }),
32
- };
33
- await cacheManager.set(resultWithNavId);
34
- return { success: true, data: result };
35
- }, options, formatDomQuery);
38
+ function buildElementScreenshotOptions(options) {
39
+ return filterDefined({
40
+ format: options.format,
41
+ quality: options.quality,
42
+ noResize: options.resize === false,
43
+ });
36
44
  }
37
45
  /**
38
- * Handle bdg dom get command
46
+ * Check if options specify an element target.
39
47
  *
40
- * Retrieves semantic accessibility structure by default (70% token reduction).
41
- * Use --raw flag for full HTML output.
42
- * Supports index-based access from query results (e.g., "bdg dom get 0").
48
+ * @param options - Screenshot command options
49
+ * @returns True if selector or index is specified
50
+ */
51
+ function hasElementTarget(options) {
52
+ return options.selector !== undefined || options.index !== undefined;
53
+ }
54
+ /**
55
+ * Resolve element nodeId from selector or cached index.
43
56
  *
44
- * @param selectorOrIndex - CSS selector (e.g., ".error") or numeric index from query results
45
- * @param options - Command options including --all, --nth, nodeId, and raw
57
+ * @param options - Options containing selector or index
58
+ * @returns CDP nodeId
59
+ * @throws CommandError if neither selector nor index provided
46
60
  */
47
- async function handleDomGet(selectorOrIndex, options) {
48
- const isNumericIndex = /^\d+$/.test(selectorOrIndex);
49
- if (isNumericIndex) {
50
- await handleIndexGet(parseInt(selectorOrIndex, 10), options);
61
+ async function resolveElementNodeId(options) {
62
+ if (options.index !== undefined) {
63
+ const resolver = DomElementResolver.getInstance();
64
+ const node = await resolver.getNodeIdForIndex(options.index);
65
+ return node.nodeId;
51
66
  }
52
- else {
53
- await handleSelectorGet(selectorOrIndex, options);
67
+ if (options.selector !== undefined) {
68
+ return resolveSelector(options.selector);
54
69
  }
70
+ const err = missingArgumentError('--selector "css-selector" or --index N from a previous query');
71
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
55
72
  }
56
73
  /**
57
- * Handle get command with numeric index
74
+ * Add element metadata to screenshot result.
75
+ *
76
+ * @param result - Base screenshot result
77
+ * @param options - Options containing selector or index
78
+ * @returns Screenshot result with element info
58
79
  */
59
- async function handleIndexGet(index, options) {
60
- const resolver = DomElementResolver.getInstance();
61
- if (options.raw) {
62
- await runCommand(async () => {
63
- const targetNode = await resolver.getNodeIdForIndex(index);
64
- const getOptions = filterDefined({
65
- nodeId: targetNode.nodeId,
66
- });
67
- const result = await getDOMElements(getOptions);
68
- return { success: true, data: result };
69
- }, options, formatDomGet);
70
- }
71
- else {
72
- await runCommand(async () => {
73
- const targetNode = await resolver.getNodeIdForIndex(index);
74
- const [a11yNode, domContext] = await Promise.all([
75
- resolveA11yNode('', targetNode.nodeId),
76
- getDomContext(targetNode.nodeId),
77
- ]);
78
- // Graceful degradation: synthesize node from DOM context when a11y unavailable
79
- const node = a11yNode ?? (domContext ? synthesizeA11yNode(domContext, targetNode.nodeId) : null);
80
- if (!node) {
81
- throw new CommandError(elementNotFoundError(`index ${index}`), {}, EXIT_CODES.RESOURCE_NOT_FOUND);
82
- }
83
- return { success: true, data: { node, domContext } };
84
- }, options, formatSemanticNodeWithContext);
85
- }
80
+ function addElementInfo(result, options) {
81
+ const bounds = {
82
+ x: 0,
83
+ y: 0,
84
+ width: result.width,
85
+ height: result.height,
86
+ };
87
+ return {
88
+ ...result,
89
+ element: {
90
+ ...(options.selector !== undefined && { selector: options.selector }),
91
+ ...(options.index !== undefined && { index: options.index }),
92
+ bounds,
93
+ },
94
+ };
86
95
  }
87
96
  /**
88
- * Handle get command with CSS selector
97
+ * Ensure directory exists, creating it if necessary.
98
+ *
99
+ * @param dirPath - Directory path to ensure
100
+ * @param fs - File system module
89
101
  */
90
- async function handleSelectorGet(selector, options) {
91
- if (options.raw) {
92
- await runCommand(async () => {
93
- const getOptions = filterDefined({
94
- selector,
95
- all: options.all,
96
- nth: options.nth,
97
- nodeId: options.nodeId,
98
- });
99
- const result = await getDOMElements(getOptions);
100
- return { success: true, data: result };
101
- }, options, formatDomGet);
102
- }
103
- else {
104
- await runCommand(async () => {
105
- const a11yNode = await resolveA11yNode(selector);
106
- // Fetch DOM context for enrichment or fallback
107
- let domContext = null;
108
- let nodeId;
109
- if (a11yNode?.backendDOMNodeId) {
110
- nodeId = a11yNode.backendDOMNodeId;
111
- domContext = await getDomContext(nodeId);
112
- }
113
- else if (!a11yNode) {
114
- // Try to get DOM context by querying the selector directly
115
- const { callCDP } = await import('../../ipc/client.js');
116
- const docResponse = await callCDP('DOM.getDocument', {});
117
- const doc = docResponse.data?.result;
118
- if (doc?.root?.nodeId) {
119
- const queryResponse = await callCDP('DOM.querySelector', {
120
- nodeId: doc.root.nodeId,
121
- selector,
122
- });
123
- const queryResult = queryResponse.data?.result;
124
- if (queryResult?.nodeId) {
125
- nodeId = queryResult.nodeId;
126
- domContext = await getDomContext(nodeId);
127
- }
128
- }
129
- }
130
- // Graceful degradation: synthesize node from DOM context when a11y unavailable
131
- const node = a11yNode ?? (domContext && nodeId ? synthesizeA11yNode(domContext, nodeId) : null);
132
- if (!node) {
133
- throw new CommandError(elementNotFoundError(selector), {}, EXIT_CODES.RESOURCE_NOT_FOUND);
134
- }
135
- return { success: true, data: { node, domContext } };
136
- }, options, formatSemanticNodeWithContext);
102
+ function ensureDirectory(dirPath, fs) {
103
+ if (!fs.existsSync(dirPath)) {
104
+ fs.mkdirSync(dirPath, { recursive: true });
137
105
  }
138
106
  }
139
107
  /**
140
- * Formatter for single semantic node with DOM context fallback.
108
+ * Format frame filename with zero-padded number.
141
109
  *
142
- * When a11y name is missing, shows DOM context (tag, classes, text preview)
143
- * to provide useful information instead of just "[Role]".
144
- * Shows "(inferred from DOM)" indicator when node is synthesized.
110
+ * @param frameNumber - Frame number (1-based)
111
+ * @param format - Image format extension
112
+ * @returns Formatted filename
145
113
  */
146
- function formatSemanticNodeWithContext(data) {
147
- const { node, domContext } = data;
148
- // Build the role text
149
- let roleText = `[${capitalize(node.role)}]`;
114
+ function formatFrameFilename(frameNumber, format) {
115
+ return `${String(frameNumber).padStart(3, '0')}.${format}`;
116
+ }
117
+ /**
118
+ * Capitalize first letter of string.
119
+ *
120
+ * @param str - Input string
121
+ * @returns String with first letter capitalized
122
+ */
123
+ function capitalize(str) {
124
+ return str.charAt(0).toUpperCase() + str.slice(1);
125
+ }
126
+ /**
127
+ * Build role text for accessibility node display.
128
+ *
129
+ * @param node - Accessibility node
130
+ * @returns Formatted role text
131
+ */
132
+ function buildRoleText(node) {
150
133
  if (node.role.toLowerCase() === 'heading' && node.properties?.['level'] !== undefined) {
151
134
  const level = node.properties['level'];
152
135
  const levelNum = typeof level === 'number' ? level : Number(level);
153
136
  if (!isNaN(levelNum)) {
154
- roleText = `[Heading L${levelNum}]`;
137
+ return `[Heading L${levelNum}]`;
155
138
  }
156
139
  }
157
- // Build the name/context text
158
- let contextText = '';
140
+ return `[${capitalize(node.role)}]`;
141
+ }
142
+ /**
143
+ * Build context text from a11y name or DOM context fallback.
144
+ *
145
+ * @param node - Accessibility node
146
+ * @param domContext - DOM context for fallback
147
+ * @returns Formatted context text
148
+ */
149
+ function buildContextText(node, domContext) {
159
150
  if (node.name) {
160
- // Use a11y name when available
161
- contextText = ` "${node.name}"`;
151
+ return ` "${node.name}"`;
162
152
  }
163
- else if (domContext) {
164
- // Fallback to DOM context when a11y name is missing
153
+ if (domContext) {
165
154
  const tagPart = `<${domContext.tag}`;
166
155
  const classPart = domContext.classes && domContext.classes.length > 0
167
156
  ? `.${domContext.classes.slice(0, 3).join('.')}`
168
157
  : '';
169
158
  const previewPart = domContext.preview ? ` "${domContext.preview}"` : '';
170
- contextText = ` ${tagPart}${classPart}>${previewPart}`;
159
+ return ` ${tagPart}${classPart}>${previewPart}`;
171
160
  }
172
- // Build properties text
161
+ return '';
162
+ }
163
+ /**
164
+ * Build properties text from accessibility node state.
165
+ *
166
+ * @param node - Accessibility node
167
+ * @returns Formatted properties text
168
+ */
169
+ function buildPropertiesText(node) {
173
170
  const props = [];
174
171
  if (node.focusable)
175
172
  props.push('focusable');
@@ -179,47 +176,306 @@ function formatSemanticNodeWithContext(data) {
179
176
  props.push('disabled');
180
177
  if (node.required)
181
178
  props.push('required');
182
- const propsText = props.length > 0 ? ` (${props.join(', ')})` : '';
183
- // Add inferred indicator when node is synthesized from DOM
179
+ return props.length > 0 ? ` (${props.join(', ')})` : '';
180
+ }
181
+ /**
182
+ * Format semantic node with DOM context for display.
183
+ *
184
+ * @param data - Node and context data
185
+ * @returns Formatted string representation
186
+ */
187
+ function formatSemanticNodeWithContext(data) {
188
+ const { node, domContext } = data;
189
+ const roleText = buildRoleText(node);
190
+ const contextText = buildContextText(node, domContext);
191
+ const propsText = buildPropertiesText(node);
184
192
  const inferredText = node.inferred ? ' (inferred from DOM)' : '';
185
193
  return `${roleText}${contextText}${propsText}${inferredText}`;
186
194
  }
187
195
  /**
188
- * Capitalize first letter of string.
196
+ * Resolve node with graceful degradation to synthesized node.
197
+ *
198
+ * @param a11yNode - Accessibility node or null
199
+ * @param domContext - DOM context for synthesis fallback
200
+ * @param nodeId - Node ID for synthesis
201
+ * @returns Resolved node or null
189
202
  */
190
- function capitalize(str) {
191
- return str.charAt(0).toUpperCase() + str.slice(1);
203
+ function resolveNodeWithFallback(a11yNode, domContext, nodeId) {
204
+ if (a11yNode)
205
+ return a11yNode;
206
+ if (domContext && nodeId)
207
+ return synthesizeA11yNode(domContext, nodeId);
208
+ return null;
192
209
  }
193
210
  /**
194
- * Handle bdg dom screenshot <path> command
211
+ * Query DOM context by selector when a11y node is unavailable.
195
212
  *
196
- * Captures a screenshot of the current page and saves it to disk.
197
- * Supports PNG and JPEG formats with customizable quality and viewport options.
198
- * Uses CDP relay through worker's persistent connection.
213
+ * @param selector - CSS selector
214
+ * @returns Object with nodeId and domContext
215
+ */
216
+ async function queryDomContextBySelector(selector) {
217
+ const { callCDP } = await import('../../ipc/client.js');
218
+ const docResponse = await callCDP('DOM.getDocument', {});
219
+ const doc = docResponse.data?.result;
220
+ if (!doc?.root?.nodeId) {
221
+ return { nodeId: undefined, domContext: null };
222
+ }
223
+ const queryResponse = await callCDP('DOM.querySelector', {
224
+ nodeId: doc.root.nodeId,
225
+ selector,
226
+ });
227
+ const queryResult = queryResponse.data?.result;
228
+ if (!queryResult?.nodeId) {
229
+ return { nodeId: undefined, domContext: null };
230
+ }
231
+ const domContext = await getDomContext(queryResult.nodeId);
232
+ return { nodeId: queryResult.nodeId, domContext };
233
+ }
234
+ /**
235
+ * Handle bdg dom query command.
199
236
  *
200
- * @param path - Output file path (absolute or relative)
201
- * @param options - Screenshot options (format, quality, fullPage)
237
+ * @param selector - CSS selector to query
238
+ * @param options - Command options
202
239
  */
203
- async function handleDomScreenshot(path, options) {
240
+ async function handleDomQuery(selector, options) {
204
241
  await runCommand(async () => {
205
- const screenshotOptions = filterDefined({
206
- format: options.format,
207
- quality: options.quality,
208
- fullPage: options.fullPage,
242
+ const result = await queryDOMElements(selector);
243
+ const cacheManager = QueryCacheManager.getInstance();
244
+ const navigationId = await cacheManager.getCurrentNavigationId();
245
+ const resultWithNavId = {
246
+ ...result,
247
+ ...(navigationId !== null && { navigationId }),
248
+ };
249
+ await cacheManager.set(resultWithNavId);
250
+ return { success: true, data: result };
251
+ }, options, formatDomQuery);
252
+ }
253
+ /**
254
+ * Handle get command with numeric index in raw mode.
255
+ *
256
+ * @param index - Element index
257
+ * @param options - Command options
258
+ */
259
+ async function handleIndexGetRaw(index, options) {
260
+ const resolver = DomElementResolver.getInstance();
261
+ await runCommand(async () => {
262
+ const targetNode = await resolver.getNodeIdForIndex(index);
263
+ const getOptions = filterDefined({ nodeId: targetNode.nodeId });
264
+ const result = await getDOMElements(getOptions);
265
+ return { success: true, data: result };
266
+ }, options, formatDomGet);
267
+ }
268
+ /**
269
+ * Handle get command with numeric index in semantic mode.
270
+ *
271
+ * @param index - Element index
272
+ * @param options - Command options
273
+ */
274
+ async function handleIndexGetSemantic(index, options) {
275
+ const resolver = DomElementResolver.getInstance();
276
+ await runCommand(async () => {
277
+ const targetNode = await resolver.getNodeIdForIndex(index);
278
+ const [a11yNode, domContext] = await Promise.all([
279
+ resolveA11yNode('', targetNode.nodeId),
280
+ getDomContext(targetNode.nodeId),
281
+ ]);
282
+ const node = resolveNodeWithFallback(a11yNode, domContext, targetNode.nodeId);
283
+ if (!node) {
284
+ const err = elementAtIndexNotFoundError(index, 'cached query');
285
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
286
+ }
287
+ return { success: true, data: { node, domContext } };
288
+ }, options, formatSemanticNodeWithContext);
289
+ }
290
+ /**
291
+ * Handle get command with numeric index.
292
+ *
293
+ * @param index - Element index
294
+ * @param options - Command options
295
+ */
296
+ async function handleIndexGet(index, options) {
297
+ if (options.raw) {
298
+ await handleIndexGetRaw(index, options);
299
+ }
300
+ else {
301
+ await handleIndexGetSemantic(index, options);
302
+ }
303
+ }
304
+ /**
305
+ * Handle get command with CSS selector in raw mode.
306
+ *
307
+ * @param selector - CSS selector
308
+ * @param options - Command options
309
+ */
310
+ async function handleSelectorGetRaw(selector, options) {
311
+ await runCommand(async () => {
312
+ const getOptions = filterDefined({
313
+ selector,
314
+ all: options.all,
315
+ nth: options.nth,
316
+ nodeId: options.nodeId,
209
317
  });
210
- const result = await capturePageScreenshot(path, screenshotOptions);
318
+ const result = await getDOMElements(getOptions);
319
+ return { success: true, data: result };
320
+ }, options, formatDomGet);
321
+ }
322
+ /**
323
+ * Handle get command with CSS selector in semantic mode.
324
+ *
325
+ * @param selector - CSS selector
326
+ * @param options - Command options
327
+ */
328
+ async function handleSelectorGetSemantic(selector, options) {
329
+ await runCommand(async () => {
330
+ const a11yNode = await resolveA11yNode(selector);
331
+ let domContext = null;
332
+ let nodeId;
333
+ if (a11yNode?.backendDOMNodeId) {
334
+ nodeId = a11yNode.backendDOMNodeId;
335
+ domContext = await getDomContext(nodeId);
336
+ }
337
+ else if (!a11yNode) {
338
+ const queryResult = await queryDomContextBySelector(selector);
339
+ nodeId = queryResult.nodeId;
340
+ domContext = queryResult.domContext;
341
+ }
342
+ const node = resolveNodeWithFallback(a11yNode, domContext, nodeId);
343
+ if (!node) {
344
+ const err = noNodesFoundError(selector);
345
+ throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND);
346
+ }
347
+ return { success: true, data: { node, domContext } };
348
+ }, options, formatSemanticNodeWithContext);
349
+ }
350
+ /**
351
+ * Handle get command with CSS selector.
352
+ *
353
+ * @param selector - CSS selector
354
+ * @param options - Command options
355
+ */
356
+ async function handleSelectorGet(selector, options) {
357
+ if (options.raw) {
358
+ await handleSelectorGetRaw(selector, options);
359
+ }
360
+ else {
361
+ await handleSelectorGetSemantic(selector, options);
362
+ }
363
+ }
364
+ /**
365
+ * Handle bdg dom get command.
366
+ *
367
+ * @param selectorOrIndex - CSS selector or numeric index
368
+ * @param options - Command options
369
+ */
370
+ async function handleDomGet(selectorOrIndex, options) {
371
+ const isNumericIndex = /^\d+$/.test(selectorOrIndex);
372
+ if (isNumericIndex) {
373
+ await handleIndexGet(parseInt(selectorOrIndex, 10), options);
374
+ }
375
+ else {
376
+ await handleSelectorGet(selectorOrIndex, options);
377
+ }
378
+ }
379
+ /**
380
+ * Handle page-level screenshot capture.
381
+ *
382
+ * @param outputPath - Output file path
383
+ * @param options - Screenshot options
384
+ */
385
+ async function handlePageScreenshot(outputPath, options) {
386
+ await runCommand(async () => {
387
+ const screenshotOptions = buildPageScreenshotOptions(options);
388
+ const result = await capturePageScreenshot(outputPath, screenshotOptions);
211
389
  return { success: true, data: result };
212
390
  }, options, formatDomScreenshot);
213
391
  }
214
392
  /**
215
- * Handle bdg dom eval <script> command
393
+ * Handle element-level screenshot capture.
216
394
  *
217
- * Evaluates arbitrary JavaScript in the browser context and returns the result.
218
- * Requires an active session. Uses CDP Runtime.evaluate with async support.
219
- * Note: This command uses direct CDP connection (not IPC) so it follows a different pattern.
395
+ * @param outputPath - Output file path
396
+ * @param options - Screenshot options with selector or index
397
+ */
398
+ async function handleElementScreenshot(outputPath, options) {
399
+ await runCommand(async () => {
400
+ const nodeId = await resolveElementNodeId(options);
401
+ const screenshotOptions = buildElementScreenshotOptions(options);
402
+ const result = await captureElementScreenshot(outputPath, nodeId, screenshotOptions);
403
+ const elementResult = addElementInfo(result, options);
404
+ return { success: true, data: elementResult };
405
+ }, options, formatDomScreenshot);
406
+ }
407
+ /**
408
+ * Capture a single frame in sequence mode.
409
+ *
410
+ * @param outputPath - Full path for the frame file
411
+ * @param options - Screenshot options
412
+ */
413
+ async function captureSequenceFrame(outputPath, options) {
414
+ if (hasElementTarget(options)) {
415
+ const nodeId = await resolveElementNodeId(options);
416
+ const elementOptions = buildElementScreenshotOptions(options);
417
+ await captureElementScreenshot(outputPath, nodeId, elementOptions);
418
+ }
419
+ else {
420
+ const pageOptions = buildPageScreenshotOptions(options);
421
+ await capturePageScreenshot(outputPath, pageOptions);
422
+ }
423
+ }
424
+ /**
425
+ * Handle screenshot sequence capture to directory.
426
+ *
427
+ * @param outputDir - Output directory path
428
+ * @param options - Screenshot options with interval and limit
429
+ */
430
+ async function handleSequenceCapture(outputDir, options) {
431
+ const fs = await import('fs');
432
+ const path = await import('path');
433
+ const absoluteDir = path.resolve(outputDir);
434
+ ensureDirectory(absoluteDir, fs);
435
+ const intervalRule = positiveIntRule({ min: 100, max: 60000, default: 1000 });
436
+ const limitRule = positiveIntRule({ min: 1, max: 10000, required: false });
437
+ const interval = intervalRule.validate(options.interval);
438
+ const limit = options.limit ? limitRule.validate(options.limit) : 0;
439
+ const format = options.format ?? 'png';
440
+ let frameCount = 0;
441
+ const captureFrame = async () => {
442
+ frameCount++;
443
+ const filename = formatFrameFilename(frameCount, format);
444
+ const outputPath = path.join(absoluteDir, filename);
445
+ await captureSequenceFrame(outputPath, options);
446
+ log.info(`Frame ${frameCount}: ${filename}`);
447
+ if (limit > 0 && frameCount >= limit) {
448
+ process.emit('SIGINT');
449
+ }
450
+ };
451
+ await setupFollowMode(captureFrame, {
452
+ startMessage: () => `Capturing to ${absoluteDir} every ${interval}ms...`,
453
+ stopMessage: () => `Captured ${frameCount} frames`,
454
+ intervalMs: interval,
455
+ });
456
+ }
457
+ /**
458
+ * Handle bdg dom screenshot command.
220
459
  *
221
- * @param script - JavaScript expression to evaluate (e.g., "document.title", "window.location.href")
222
- * @param options - Command options including port and json formatting
460
+ * @param outputPath - Output file path or directory
461
+ * @param options - Screenshot options
462
+ */
463
+ async function handleDomScreenshot(outputPath, options) {
464
+ if (options.follow) {
465
+ await handleSequenceCapture(outputPath, options);
466
+ return;
467
+ }
468
+ if (hasElementTarget(options)) {
469
+ await handleElementScreenshot(outputPath, options);
470
+ return;
471
+ }
472
+ await handlePageScreenshot(outputPath, options);
473
+ }
474
+ /**
475
+ * Handle bdg dom eval command.
476
+ *
477
+ * @param script - JavaScript expression to evaluate
478
+ * @param options - Command options
223
479
  */
224
480
  async function handleDomEval(script, options) {
225
481
  await runCommand(async () => {
@@ -243,7 +499,7 @@ async function handleDomEval(script, options) {
243
499
  }, options, formatDomEval);
244
500
  }
245
501
  /**
246
- * Register DOM telemetry commands
502
+ * Register DOM telemetry commands.
247
503
  *
248
504
  * @param program - Commander.js Command instance
249
505
  */
@@ -284,11 +540,18 @@ export function registerDomCommands(program) {
284
540
  });
285
541
  dom
286
542
  .command('screenshot')
287
- .description('Capture page screenshot')
288
- .argument('<path>', 'Output file path (e.g., "./screenshot.png")')
543
+ .description('Capture page or element screenshot')
544
+ .argument('<path>', 'Output file path, or directory for --follow mode')
545
+ .option('--selector <selector>', 'CSS selector for element capture')
546
+ .option('--index <number>', 'Cached element index (0-based) from previous query', parseInt)
289
547
  .option('--format <format>', 'Image format: png or jpeg (default: png)')
290
548
  .option('--quality <number>', 'JPEG quality 0-100 (default: 90)', parseInt)
291
549
  .option('--no-full-page', 'Capture viewport only (default: full page)')
550
+ .option('--no-resize', 'Disable auto-resize (full resolution)')
551
+ .option('--scroll <selector>', 'Scroll element into view before capture')
552
+ .option('-f, --follow', 'Continuous capture mode to directory')
553
+ .option('--interval <ms>', 'Capture interval for --follow (default: 1000)')
554
+ .option('--limit <count>', 'Max frames for --follow')
292
555
  .option('-j, --json', 'Output as JSON')
293
556
  .action(async (path, options) => {
294
557
  await handleDomScreenshot(path, options);