browser-debugger-cli 0.6.8 → 0.6.10

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