browser-debugger-cli 0.9.0 → 0.11.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 (139) hide show
  1. package/.claude/skills/bdg/SKILL.md +268 -0
  2. package/README.md +15 -1
  3. package/dist/commands/dom/a11y.js +2 -1
  4. package/dist/commands/dom/formInteraction.js +56 -25
  5. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  6. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  7. package/dist/commands/dom/helpers/query.d.ts +1 -1
  8. package/dist/commands/dom/helpers/query.js +66 -19
  9. package/dist/commands/dom/helpers/runElementCommand.js +4 -3
  10. package/dist/commands/dom/helpers/screenshot.js +85 -12
  11. package/dist/commands/dom/index.d.ts +1 -0
  12. package/dist/commands/dom/index.js +8 -3
  13. package/dist/commands/dom/inspect.d.ts +15 -0
  14. package/dist/commands/dom/inspect.js +82 -0
  15. package/dist/commands/dom/layout.js +2 -2
  16. package/dist/commands/dom/listeners.js +2 -2
  17. package/dist/commands/dom/semanticUtils.d.ts +14 -1
  18. package/dist/commands/dom/semanticUtils.js +44 -3
  19. package/dist/commands/installSkill.d.ts +20 -0
  20. package/dist/commands/installSkill.js +87 -0
  21. package/dist/commands/network/list.js +13 -2
  22. package/dist/commands/optionBehaviors.js +48 -6
  23. package/dist/commands/page.d.ts +1 -1
  24. package/dist/commands/page.js +62 -3
  25. package/dist/commands/shared/commonOptions.d.ts +4 -0
  26. package/dist/commands/shared/commonOptions.js +9 -0
  27. package/dist/commands/shared/optionTypes.d.ts +21 -0
  28. package/dist/commands/shared/startHelpers.d.ts +66 -0
  29. package/dist/commands/shared/startHelpers.js +91 -10
  30. package/dist/commands/shared/validation.d.ts +11 -0
  31. package/dist/commands/shared/validation.js +16 -0
  32. package/dist/commands.js +3 -0
  33. package/dist/daemon/launcher.d.ts +8 -1
  34. package/dist/daemon/launcher.js +3 -1
  35. package/dist/daemon/session/Session.d.ts +7 -0
  36. package/dist/daemon/session/Session.js +23 -1
  37. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  38. package/dist/daemon/session/commandRegistry.js +65 -9
  39. package/dist/daemon/session/interactions.d.ts +18 -5
  40. package/dist/daemon/session/interactions.js +22 -12
  41. package/dist/daemon.js +3565 -329
  42. package/dist/errors/messages.d.ts +85 -0
  43. package/dist/errors/messages.js +128 -1
  44. package/dist/index.js +2151 -960
  45. package/dist/ipc/client.d.ts +9 -0
  46. package/dist/ipc/client.js +13 -0
  47. package/dist/ipc/protocol/commands.d.ts +56 -1
  48. package/dist/ipc/protocol/commands.js +2 -0
  49. package/dist/ipc/protocol/domTypes.d.ts +35 -2
  50. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  51. package/dist/ipc/protocol/inspectTypes.js +10 -0
  52. package/dist/runtime/dom/actionEffects.d.ts +94 -15
  53. package/dist/runtime/dom/actionEffects.js +173 -27
  54. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
  55. package/dist/runtime/dom/actionEffectsScripts.js +224 -32
  56. package/dist/runtime/dom/elementInfo.d.ts +26 -0
  57. package/dist/runtime/dom/elementInfo.js +65 -0
  58. package/dist/runtime/dom/eventListeners.js +14 -4
  59. package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
  60. package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
  61. package/dist/runtime/dom/frameSelection.d.ts +11 -0
  62. package/dist/runtime/dom/frameSelection.js +20 -1
  63. package/dist/runtime/dom/frames.d.ts +38 -5
  64. package/dist/runtime/dom/frames.js +136 -21
  65. package/dist/runtime/dom/inspect.d.ts +28 -0
  66. package/dist/runtime/dom/inspect.js +557 -0
  67. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  68. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  69. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  70. package/dist/runtime/dom/inspectCascade.js +371 -0
  71. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  72. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  73. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  74. package/dist/runtime/dom/inspectHints.js +305 -0
  75. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  76. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  77. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  78. package/dist/runtime/dom/inspectModel.js +184 -0
  79. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  80. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  81. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  82. package/dist/runtime/dom/inspectRules.js +101 -0
  83. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  84. package/dist/runtime/dom/inspectScripts.js +263 -0
  85. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  86. package/dist/runtime/dom/inspectTree.js +134 -0
  87. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  88. package/dist/runtime/dom/inspectVariables.js +94 -0
  89. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  90. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  91. package/dist/runtime/dom/layout.d.ts +5 -1
  92. package/dist/runtime/dom/layout.js +10 -3
  93. package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
  94. package/dist/runtime/dom/listenerPageScripts.js +95 -9
  95. package/dist/runtime/dom/listenerSummary.d.ts +4 -0
  96. package/dist/runtime/dom/listenerSummary.js +26 -9
  97. package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
  98. package/dist/runtime/dom/reactEventHelpers.js +12 -4
  99. package/dist/runtime/page/emulation.d.ts +20 -0
  100. package/dist/runtime/page/emulation.js +37 -0
  101. package/dist/telemetry/a11y.d.ts +10 -0
  102. package/dist/telemetry/a11y.js +78 -1
  103. package/dist/telemetry/console.d.ts +1 -0
  104. package/dist/telemetry/console.js +100 -5
  105. package/dist/telemetry/network.js +3 -1
  106. package/dist/types.d.ts +40 -0
  107. package/dist/ui/formatters/details.d.ts +8 -0
  108. package/dist/ui/formatters/details.js +59 -3
  109. package/dist/ui/formatters/dom.d.ts +2 -1
  110. package/dist/ui/formatters/dom.js +25 -9
  111. package/dist/ui/formatters/inspect.d.ts +39 -0
  112. package/dist/ui/formatters/inspect.js +596 -0
  113. package/dist/ui/formatters/installSkill.d.ts +11 -0
  114. package/dist/ui/formatters/installSkill.js +31 -0
  115. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  116. package/dist/ui/formatters/keyAttributes.js +84 -0
  117. package/dist/ui/formatters/layout.js +2 -2
  118. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  119. package/dist/ui/formatters/networkHeaders.js +23 -3
  120. package/dist/ui/formatters/networkList.d.ts +29 -1
  121. package/dist/ui/formatters/networkList.js +86 -20
  122. package/dist/ui/formatters/status.js +1 -1
  123. package/dist/ui/formatting.d.ts +9 -0
  124. package/dist/ui/formatting.js +6 -3
  125. package/dist/ui/messages/commands.d.ts +123 -7
  126. package/dist/ui/messages/commands.js +181 -10
  127. package/dist/ui/messages/networkMessages.d.ts +14 -0
  128. package/dist/ui/messages/networkMessages.js +18 -0
  129. package/dist/ui/messages/session.d.ts +14 -0
  130. package/dist/ui/messages/session.js +20 -0
  131. package/dist/utils/async.d.ts +9 -0
  132. package/dist/utils/async.js +17 -0
  133. package/dist/utils/color.d.ts +84 -0
  134. package/dist/utils/color.js +376 -0
  135. package/dist/utils/cssValues.d.ts +109 -0
  136. package/dist/utils/cssValues.js +236 -0
  137. package/dist/utils/selectorFilters.d.ts +12 -0
  138. package/dist/utils/selectorFilters.js +29 -0
  139. package/package.json +2 -1
@@ -0,0 +1,268 @@
1
+ ---
2
+ name: bdg
3
+ description: Use bdg CLI to drive and debug a real Chrome via Chrome DevTools Protocol - navigate, click, fill and submit forms, check what an action changed (navigation, new messages, pending requests), inspect elements without screenshots (box, layout, fonts, colors, a11y), read network requests and console errors, and call any CDP method. Use this skill when you need to verify a UI change in a running app, debug a page, automate a browser flow, or scrape dynamic content.
4
+ ---
5
+
6
+ # bdg - Browser Automation CLI
7
+
8
+ ## Quick Start
9
+
10
+ ```bash
11
+ bdg https://example.com # Start session (launches Chrome)
12
+ bdg dom screenshot /tmp/page.png # Take screenshot
13
+ bdg stop # End session
14
+ ```
15
+
16
+ ## Session Management
17
+
18
+ ```bash
19
+ bdg <url> # Start session (1920x1080, headless if no display)
20
+ bdg <url> --headless # Force headless mode
21
+ bdg <url> --no-headless # Force visible browser window
22
+ bdg status # Check session status
23
+ bdg peek # Preview collected telemetry
24
+ bdg stop # End session (use sparingly)
25
+ bdg cleanup # Clean up after a crashed session
26
+ bdg cleanup --force # Kill a stuck session (daemon + its Chrome)
27
+ ```
28
+
29
+ **Sessions run indefinitely by default** (no timeout). With HMR/hot-reload dev servers, keep the session running:
30
+
31
+ ```bash
32
+ bdg http://localhost:5173 # Start once
33
+ # ... make code changes, HMR updates the page ...
34
+ bdg dom screenshot /tmp/s.png # Check anytime
35
+ bdg peek # Preview collected data
36
+ # No need to stop/restart - Chrome stays on the page
37
+ ```
38
+
39
+ **Don't stop sessions prematurely** - use `bdg peek` to inspect data. Only call `bdg stop` when completely done with browser automation.
40
+
41
+ ## Screenshots
42
+
43
+ Always use `bdg dom screenshot` (raw CDP is blocked):
44
+
45
+ ```bash
46
+ bdg dom screenshot /tmp/page.png # Full page
47
+ bdg dom screenshot /tmp/viewport.png --no-full-page # Viewport only
48
+ bdg dom screenshot /tmp/el.png --selector "#main" # Element only
49
+ bdg dom screenshot /tmp/scroll.png --scroll "#target" # Scroll to element first
50
+ ```
51
+
52
+ ## Actions Report What Changed
53
+
54
+ `dom click`, `fill`, `submit`, `pressKey`, `hover` and `scroll` wait for the requests the action starts, then say what happened. Read this before reaching for a screenshot:
55
+
56
+ ```text
57
+ ✓ Element Clicked
58
+ Page: navigated to https://app.test/secure (200) # navigation (or "URL changed ... (same document)")
59
+ New text: "Your password is invalid!" (div#flash) # alert/status/aria-live messages that appeared
60
+ ⚠ Element Clicked (no visible effect observed ...) # nothing changed - wrong element or a broken handler
61
+ ```
62
+
63
+ - In `--json`: `navigation`, `messages`, `effect: "none"` and pending work (timers, spinners) are fields on `data`.
64
+ - Results the page shows later are not waited for: follow up with `bdg dom wait` (below).
65
+
66
+ ## Form Interaction
67
+
68
+ ```bash
69
+ # Discover forms
70
+ bdg dom form --brief # Quick scan: field names, types, required
71
+
72
+ # Fill and interact
73
+ bdg dom fill "input[name='user']" "myuser" # Fill by selector
74
+ bdg dom fill 0 "value" # Fill by index (from query)
75
+ bdg dom click "button.submit" # Click element
76
+ bdg dom submit "form" --wait-navigation # Submit and wait for page load
77
+ bdg dom pressKey "input" Enter # Press Enter key
78
+
79
+ # Options
80
+ --no-wait # Skip network stability wait
81
+ --wait-navigation # Wait for page navigation (traditional forms)
82
+ --wait-network <ms> # Wait for network idle (SPA forms)
83
+ --index <n> # Select nth element when multiple match
84
+ ```
85
+
86
+ ## DOM Inspection
87
+
88
+ ```bash
89
+ bdg dom query "selector" # Find elements, returns [0], [1], [2]... (0-based)
90
+ bdg dom get "selector" # Get semantic a11y info (token-efficient)
91
+ bdg dom get "selector" --raw # Get full HTML
92
+ bdg dom eval "js expression" # Run JavaScript
93
+ bdg dom a11y "role:button" # Query by accessibility role/name
94
+ ```
95
+
96
+ Selectors search open shadow roots and same-origin iframes, and accept `:has-text("...")` and `:visible`.
97
+
98
+ ### Look Without a Screenshot
99
+
100
+ ```bash
101
+ bdg dom inspect "button.primary" # Box, layout, rendered font, colors + WCAG contrast, borders, state (~60-100 tokens)
102
+ bdg dom inspect ".card" --why color # Which CSS rule set a property, and what it overrode
103
+ bdg dom layout ".card" # Positions/sizes of every match: above/below the fold, hidden, covered
104
+ bdg dom listeners "#save" # Event listeners that run for an element (incl. delegated, React/Preact)
105
+ bdg page emulate --viewport 390x844 --color-scheme dark # Responsive/theme check mid-session
106
+ ```
107
+
108
+ ### Wait for Something
109
+
110
+ ```bash
111
+ bdg dom wait '#result' --visible # Appears and is visible
112
+ bdg dom wait '.toast' --text 'Saved' # Contains text
113
+ bdg dom wait '#loading' --gone # Spinner went away
114
+ bdg dom wait --load # Page finished loading
115
+ ```
116
+
117
+ ## Network and Console
118
+
119
+ ```bash
120
+ bdg network list # Requests (DevTools-style)
121
+ bdg network list --filter "status-code:>=400" # Failed requests
122
+ bdg details network <id> # Headers, timing, body of one request
123
+ bdg console --level error # Console errors on the current page
124
+ bdg console --follow # Stream messages live
125
+ bdg network har /tmp/session.har # Export HAR 1.2
126
+ ```
127
+
128
+ ## CDP Access
129
+
130
+ Direct access to Chrome DevTools Protocol:
131
+
132
+ ```bash
133
+ # Execute any CDP method
134
+ bdg cdp Runtime.evaluate --params '{"expression": "document.title", "returnByValue": true}'
135
+ bdg cdp Page.navigate --params '{"url": "https://example.com"}'
136
+ bdg cdp Page.reload --params '{"ignoreCache": true}'
137
+
138
+ # Discovery
139
+ bdg cdp --list # List all domains
140
+ bdg cdp Network --list # List methods in domain
141
+ bdg cdp Network.getCookies --describe # Show method schema
142
+ bdg cdp --search cookie # Search methods
143
+ ```
144
+
145
+ **Important**: Always use `returnByValue: true` for Runtime.evaluate to get serialized values.
146
+
147
+ ## Common Patterns
148
+
149
+ ### Login Flow
150
+ ```bash
151
+ bdg https://example.com/login
152
+ bdg dom form --brief
153
+ bdg dom fill "input[name='username']" "$USER"
154
+ bdg dom fill "input[name='password']" "$PASS"
155
+ bdg dom submit "button[type='submit']" --wait-navigation
156
+ bdg dom screenshot /tmp/result.png
157
+ bdg stop
158
+ ```
159
+
160
+ ### Verify a UI Change (dev server with HMR)
161
+ ```bash
162
+ bdg http://localhost:5173 # Once; keep the session running
163
+ bdg dom click "button.save" # Read the reported effect
164
+ bdg dom wait '.toast' --text 'Saved'
165
+ bdg console --level error # Anything thrown?
166
+ bdg dom inspect ".toast" # Looks right? (no screenshot needed)
167
+ ```
168
+
169
+ ### Extract Data
170
+ ```bash
171
+ bdg cdp Runtime.evaluate --params '{
172
+ "expression": "Array.from(document.querySelectorAll(\"a\")).map(a => ({text: a.textContent, href: a.href}))",
173
+ "returnByValue": true
174
+ }' | jq '.data.result.result.value'
175
+ ```
176
+
177
+ ## JSON Output and Exit Codes
178
+
179
+ Add `--json` (`-j`) to any command for `{ version, success, data }` (or `{ success: false, error, exitCode, suggestion }`). `bdg --help --json` lists every command, flag and exit code.
180
+
181
+ | Code | Meaning | Action |
182
+ |------|---------|--------|
183
+ | 0 | Success | - |
184
+ | 81 | Invalid arguments (incl. blocked raw CDP methods) | Read the suggestion, use the alternative |
185
+ | 83 | Resource not found | Element/session doesn't exist |
186
+ | 85 | Session busy (still starting/stopping) | Retry shortly |
187
+ | 87 | Stale index (page changed since `dom query`) | Re-run the query |
188
+ | 91 | `dom eval` script threw | Fix the JavaScript |
189
+ | 101 | CDP connection failure | Run `bdg cleanup --force` and retry |
190
+ | 102 | Timeout (CDP, or `dom wait --timeout`) | Increase timeout or check page load |
191
+
192
+ ## Troubleshooting
193
+
194
+ ```bash
195
+ bdg status --verbose # Full diagnostics
196
+ bdg cleanup # Clean up after a crashed session
197
+ bdg cleanup --force # Kill a stuck session (daemon + its Chrome)
198
+ ```
199
+
200
+ **Chrome won't launch?** Run `bdg cleanup --force` then retry.
201
+
202
+ **Session stuck?** Run `bdg cleanup --force` to reset.
203
+
204
+ ### Custom Chrome Flags
205
+
206
+ Use `--chrome-flags` or `BDG_CHROME_FLAGS` for self-signed certificates, CORS, etc.:
207
+
208
+ ```bash
209
+ # CLI option
210
+ bdg https://localhost:5173 --chrome-flags="--ignore-certificate-errors"
211
+
212
+ # Environment variable
213
+ BDG_CHROME_FLAGS="--ignore-certificate-errors" bdg https://localhost:5173
214
+
215
+ # Multiple flags
216
+ bdg https://example.com --chrome-flags="--ignore-certificate-errors --disable-web-security"
217
+ ```
218
+
219
+ **Common flags for development:**
220
+ - `--ignore-certificate-errors` - Self-signed SSL certs
221
+ - `--disable-web-security` - CORS issues in development
222
+ - `--allow-insecure-localhost` - Insecure localhost
223
+ - `--disable-features=IsolateOrigins,site-per-process` - Cross-origin iframes
224
+
225
+ ## Verification Best Practices
226
+
227
+ **Prefer DOM queries over screenshots** for verification:
228
+
229
+ ```bash
230
+ # GOOD: Fast, precise, scriptable
231
+ bdg cdp Runtime.evaluate --params '{
232
+ "expression": "document.querySelector(\".error-message\")?.textContent",
233
+ "returnByValue": true
234
+ }'
235
+
236
+ # GOOD: Check element exists
237
+ bdg dom query ".submit-btn"
238
+
239
+ # GOOD: Check text content
240
+ bdg cdp Runtime.evaluate --params '{
241
+ "expression": "document.body.innerText.includes(\"Success\")",
242
+ "returnByValue": true
243
+ }'
244
+
245
+ # AVOID: Screenshots for simple verification (slow, requires visual inspection)
246
+ bdg dom screenshot /tmp/check.png # Only use when you need visual proof
247
+ ```
248
+
249
+ **When to use screenshots:**
250
+ - Visual regression testing
251
+ - Capturing proof for user review
252
+ - Debugging layout issues
253
+ - When DOM structure is unknown
254
+
255
+ **When to use DOM queries:**
256
+ - Verifying text content appeared
257
+ - Checking element exists/visible
258
+ - Validating form state
259
+ - Counting elements
260
+ - Any programmatic assertion
261
+
262
+ ## When NOT to Use bdg
263
+
264
+ - **Static HTML** - Use `curl` + `htmlq`/`pq`
265
+ - **API calls** - Use `curl` + `jq`
266
+ - **Simple HTTP** - Use `wget`/`curl`
267
+
268
+ Use bdg when you need: JavaScript execution, dynamic content, browser APIs, screenshots, or network manipulation.
package/README.md CHANGED
@@ -43,6 +43,17 @@ npm install -g browser-debugger-cli
43
43
  - ✅ Windows via WSL
44
44
  - ❌ PowerShell/Git Bash (not yet)
45
45
 
46
+ ## Use with Claude Code and Other Agents
47
+
48
+ bdg ships an agent skill (`SKILL.md`) that teaches the workflow: start once, act, read what the action changed, inspect without screenshots, check network and console.
49
+
50
+ ```bash
51
+ bdg install-skill # ~/.claude/skills/bdg (Claude Code) + ~/.agents/skills/bdg (Codex, Gemini CLI, ...)
52
+ bdg install-skill --claude # Claude Code only
53
+ ```
54
+
55
+ Start a new agent session afterwards, and re-run `bdg install-skill` after upgrading bdg. In Claude Code the skill loads when a task needs a browser, or on demand with `/bdg`.
56
+
46
57
  ## Quick Start
47
58
 
48
59
  ```bash
@@ -59,6 +70,9 @@ bdg eval "document.title" # Run JavaScript in the page (--frame for ifr
59
70
  bdg network list --preset errors # Network requests, console: bdg console
60
71
  bdg dom listeners "#save" # Which event listeners run for an element
61
72
  bdg dom layout "#save" # Where it is, whether it is visible or covered
73
+ bdg dom inspect "#save" # What it looks like (Figma-like styles), no screenshot
74
+ bdg dom inspect "#save" --why color # Which CSS rule sets a value, and what it beats
75
+ bdg page emulate --viewport 900x700 # Responsive check mid-session (or --color-scheme)
62
76
  bdg dom wait "#result" --visible # Wait for an element instead of sleeping
63
77
  bdg example.com --session agent2 --viewport 1280x800 # A second, independent session
64
78
  bdg stop # End session
@@ -66,7 +80,7 @@ bdg stop # End session
66
80
 
67
81
  ## Current State
68
82
 
69
- **Raw CDP access is complete.** Every protocol method works now. High-level commands cover the common work: page navigation, DOM queries and interaction (click, fill, hover, keys, forms, shadow DOM and iframes), accessibility tree, screenshots, network requests and HAR export, console messages and event listeners. Actions report what they changed (navigation, new messages, requests, or no visible effect), and several named sessions can run side by side. See the [CLI reference](docs/CLI_REFERENCE.md) for every command, and `bdg --help --json` for the machine-readable version.
83
+ **Raw CDP access is complete.** Every protocol method works now. High-level commands cover the common work: page navigation, DOM queries and interaction (click, fill, hover, keys, forms, shadow DOM and iframes), accessibility tree, screenshots, element styles with the CSS cascade (`dom inspect`: what an element looks like, which rule sets each value, and declarations that have no effect), network requests and HAR export, console messages and event listeners. Actions report what they changed (navigation, new messages, requests, or no visible effect), and several named sessions can run side by side. See the [CLI reference](docs/CLI_REFERENCE.md) for every command, and `bdg --help --json` for the machine-readable version.
70
84
 
71
85
  ## Agent Discovery Pattern
72
86
 
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { DomElementResolver } from './DomElementResolver.js';
12
12
  import { getDomContext, resolveBackendNodeIds } from './helpers/index.js';
13
+ import { withSecretMasked } from './semanticUtils.js';
13
14
  import { runCommand, runJsonCommand } from '../shared/CommandRunner.js';
14
15
  import { jsonOption } from '../shared/commonOptions.js';
15
16
  import { integerOption } from '../shared/validation.js';
@@ -164,7 +165,7 @@ async function handleA11yDescribe(selectorOrIndex, options) {
164
165
  if (domNodeId) {
165
166
  domContext = await getDomContext({ backendNodeId: domNodeId });
166
167
  }
167
- return { node, domContext };
168
+ return { node: withSecretMasked(node, domContext), domContext };
168
169
  }
169
170
  if (options.json) {
170
171
  await runJsonCommand(fetchA11yNodeData);
@@ -9,7 +9,7 @@
9
9
  import { InvalidArgumentError } from 'commander';
10
10
  import { runElementCommand } from './helpers/runElementCommand.js';
11
11
  import { runCommand } from '../shared/CommandRunner.js';
12
- import { jsonOption } from '../shared/commonOptions.js';
12
+ import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
13
13
  import { integerOption } from '../shared/validation.js';
14
14
  import { CommandError } from '../../errors/index.js';
15
15
  import { VIA_LABEL_SUFFIX, conflictingOptionsMessage, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
@@ -17,9 +17,11 @@ import { domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/
17
17
  import { findUnknownModifiers } from '../../runtime/dom/keyMapping.js';
18
18
  import { formatTriggeredRequestLines, formatTriggeredRequestsTitle, } from '../../ui/formatters/triggeredRequests.js';
19
19
  import { OutputFormatter } from '../../ui/formatting.js';
20
- import { CLICK_RESULT_WAIT_HELP, POINTER_ACTION_DONE, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, } from '../../ui/messages/commands.js';
20
+ import { CLICK_RESULT_WAIT_HELP, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
21
21
  import { sessionCommand } from '../../ui/messages/sessionCommand.js';
22
22
  import { EXIT_CODES } from '../../utils/exitCodes.js';
23
+ /** Help of `--strict` on click and hover */
24
+ const STRICT_OPTION_HELP = 'Fail (exit 90) instead of using DOM events when a real mouse cannot reach the element (covered, hidden, zero-size)';
23
25
  /**
24
26
  * Commander parser for `--modifiers`: rejects unknown names instead of
25
27
  * silently pressing the bare key.
@@ -54,7 +56,7 @@ export function registerFormInteractionCommands(program) {
54
56
  domCommand
55
57
  .command('fill')
56
58
  .description('Fill a form field with a value (React-compatible, waits for stability)')
57
- .argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
59
+ .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
58
60
  .argument('<value>', 'Value to fill (file inputs: paths separated by commas, "" clears)')
59
61
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
60
62
  .option('--no-blur', 'Do not blur after filling (keeps focus on element)')
@@ -80,10 +82,11 @@ export function registerFormInteractionCommands(program) {
80
82
  domCommand
81
83
  .command('click')
82
84
  .description('Click an element and wait for stability (accepts selector or index)')
83
- .argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
85
+ .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
84
86
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
85
87
  .option('--double', 'Double-click')
86
88
  .option('--right', 'Right-click (opens the context menu)')
89
+ .option('--strict', STRICT_OPTION_HELP)
87
90
  .option('--no-wait', 'Skip waiting for network stability after click')
88
91
  .addOption(jsonOption())
89
92
  .addHelpText('after', CLICK_RESULT_WAIT_HELP)
@@ -94,8 +97,9 @@ export function registerFormInteractionCommands(program) {
94
97
  domCommand
95
98
  .command('hover')
96
99
  .description('Move the mouse over an element (shows hover menus and tooltips)')
97
- .argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
100
+ .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
98
101
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
102
+ .option('--strict', STRICT_OPTION_HELP)
99
103
  .option('--no-wait', 'Skip waiting for network stability after hovering')
100
104
  .addOption(jsonOption())
101
105
  .action(async (selectorOrIndex, options) => {
@@ -104,7 +108,7 @@ export function registerFormInteractionCommands(program) {
104
108
  domCommand
105
109
  .command('submit')
106
110
  .description('Submit a form by clicking submit button and waiting for completion')
107
- .argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
111
+ .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
108
112
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
109
113
  .option('--wait-navigation', 'Wait for page navigation after submit')
110
114
  .option('--wait-network <ms>', 'Wait for network idle after submit (milliseconds)', integerOption(0), 1000)
@@ -132,7 +136,7 @@ export function registerFormInteractionCommands(program) {
132
136
  domCommand
133
137
  .command('pressKey')
134
138
  .description('Press a key on an element (for Enter-to-submit, keyboard navigation)')
135
- .argument('<selectorOrIndex>', 'CSS selector or numeric index from query results (0-based)')
139
+ .argument('<selectorOrIndex>', SELECTOR_OR_INDEX_ARGUMENT)
136
140
  .argument('<key>', 'Key to press (Enter, Tab, Escape, Space, ArrowUp, etc.)')
137
141
  .option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
138
142
  .option('--times <n>', 'Press key multiple times (default: 1)', integerOption(1, 1000))
@@ -270,6 +274,7 @@ async function runPointerCommand(selectorOrIndex, options, action) {
270
274
  ...target,
271
275
  wait: options.wait !== false,
272
276
  ...(action !== 'click' && { action }),
277
+ ...(options.strict && { strict: true }),
273
278
  }),
274
279
  call: domClick,
275
280
  command: action === 'hover' ? 'hover' : 'click',
@@ -279,34 +284,40 @@ async function runPointerCommand(selectorOrIndex, options, action) {
279
284
  }
280
285
  /**
281
286
  * Build an action's output: the status line ("✓ Element Clicked",
282
- * "⚠ Element Clicked (with warnings)" with the warning right below it, or
283
- * "⚠ Element Clicked (no visible effect: …)"), the details, what changed on
284
- * the page (`Page:` navigation, `New text:` messages), then the network
285
- * requests it triggered and the dialogs it caused. No request list is shown
286
- * when there were none (JSON has an empty `triggeredRequests` then).
287
+ * "⚠ Element Clicked (with warnings)" with the warning right below it,
288
+ * "⚠ Element Clicked (page still changing)" with what it was still working
289
+ * on, or "⚠ Element Clicked (no visible effect: …)"), the details, what
290
+ * changed on the page (`Page:` navigation, `New text:` messages, `Shown:`
291
+ * elements), then the network requests it triggered and the dialogs it
292
+ * caused. No request list is shown when there were none (JSON has an empty
293
+ * `triggeredRequests` then).
287
294
  *
288
295
  * @param done - What was done, e.g. "Element Clicked"
289
296
  * @param details - Label/value rows
290
297
  * @param result - Action result
291
- * @param keyWidth - Width of the labels
298
+ * @param options - Width of the labels; what the action is called in notes (e.g. "click")
292
299
  * @returns Output being built (more can be appended)
293
300
  */
294
- function formatActionOutput(done, details, result, keyWidth = 15) {
301
+ function formatActionOutput(done, details, result, options = {}) {
302
+ const keyWidth = options.keyWidth ?? 15;
295
303
  const fmt = new OutputFormatter();
296
- fmt.text(actionStatusLine(done, result.warning !== undefined, result.effect === 'none'));
304
+ const stillChanging = result.settled === false && result.pending !== undefined;
305
+ fmt.text(actionStatusLine(done, {
306
+ warned: result.warning !== undefined,
307
+ noEffect: result.effect === 'none',
308
+ stillChanging,
309
+ }));
297
310
  if (result.warning)
298
311
  fmt.text(`⚠ Warning: ${result.warning}`);
312
+ if (stillChanging && result.pending) {
313
+ fmt.text(`⚠ ${stillChangingNote(options.action ?? 'action', result.pending)}`);
314
+ }
299
315
  fmt.blank();
300
316
  fmt.keyValueList(details, keyWidth);
301
317
  if (result.navigation)
302
318
  fmt.keyValue('Page', pageNavigationText(result.navigation), keyWidth);
303
- (result.messages ?? []).forEach((message, index) => {
304
- const text = newMessageText(message);
305
- if (index === 0)
306
- fmt.keyValue('New text', text, keyWidth);
307
- else
308
- fmt.text(' '.repeat(keyWidth) + text);
309
- });
319
+ listRows(fmt, 'New text', (result.messages ?? []).map(newMessageText), keyWidth);
320
+ listRows(fmt, 'Shown', (result.shown ?? []).map(shownElementText), keyWidth);
310
321
  const omitted = result.triggeredRequestsOmitted;
311
322
  const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
312
323
  if (requests.length > 0) {
@@ -320,6 +331,23 @@ function formatActionOutput(done, details, result, keyWidth = 15) {
320
331
  }
321
332
  return fmt;
322
333
  }
334
+ /**
335
+ * Rows of a list under one label: the label on the first row, the rest
336
+ * indented below it.
337
+ *
338
+ * @param fmt - Output being built
339
+ * @param label - Label, e.g. "New text"
340
+ * @param texts - One text per row
341
+ * @param keyWidth - Width of the labels
342
+ */
343
+ function listRows(fmt, label, texts, keyWidth) {
344
+ texts.forEach((text, index) => {
345
+ if (index === 0)
346
+ fmt.keyValue(label, text, keyWidth);
347
+ else
348
+ fmt.text(' '.repeat(keyWidth) + text);
349
+ });
350
+ }
323
351
  /**
324
352
  * Row naming the element an action hit, e.g.
325
353
  * `Element: input.toggle in div.view "Write report"` (just its tag when the
@@ -372,7 +400,7 @@ function formatClickOutput(result) {
372
400
  ...selectorRows(result),
373
401
  elementRow(result),
374
402
  ['Method', result.method === 'dom' ? 'DOM events' : 'mouse events'],
375
- ], result).build();
403
+ ], result, { action: POINTER_ACTION_NOUN[result.action ?? 'click'] }).build();
376
404
  }
377
405
  /**
378
406
  * Format submit command output for human-readable display.
@@ -391,7 +419,10 @@ function formatSubmitOutput(result) {
391
419
  details.push(['Navigation', 'yes']);
392
420
  if (result.waitTimeMs !== undefined)
393
421
  details.push(['Wait Time', `${result.waitTimeMs}ms`]);
394
- const fmt = formatActionOutput('Form Submitted', details, result, 20);
422
+ const fmt = formatActionOutput('Form Submitted', details, result, {
423
+ keyWidth: 20,
424
+ action: 'submit',
425
+ });
395
426
  fmt.hints('Next steps:', [
396
427
  `${sessionCommand('bdg network list --last 10').padEnd(32)} Check network requests`,
397
428
  `${sessionCommand('bdg console --last 5').padEnd(32)} Check console messages`,
@@ -412,7 +443,7 @@ function formatPressKeyOutput(result) {
412
443
  details.push(['Times', result.times.toString()]);
413
444
  if (result.modifiers?.length)
414
445
  details.push(['Modifiers', result.modifiers.join('+')]);
415
- return formatActionOutput('Key Pressed', details, result).build();
446
+ return formatActionOutput('Key Pressed', details, result, { action: 'key press' }).build();
416
447
  }
417
448
  /**
418
449
  * Format scroll command output for human-readable display.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The attributes that identify an element by its type (an image's `src`, a
3
+ * link's `href`, a field's name and value), shown by `dom query`, `dom get`
4
+ * and `dom a11y describe`.
5
+ */
6
+ import type { ElementState, KeyAttributes } from '../../../types.js';
7
+ /**
8
+ * The key attributes of an element: the identifying attributes of its type
9
+ * that are set (empty ones left out) and the live state of a form control
10
+ * (type, current value, checked, selected options). Values arrive masked
11
+ * from the page (`ELEMENT_STATE_JS`); a hidden input's value is never read,
12
+ * and a sensitive field's value is masked here again in case it was not.
13
+ *
14
+ * @param tag - Lower-case tag name
15
+ * @param attributes - Element attributes
16
+ * @param state - Live state read in the page (`ELEMENT_STATE_JS`)
17
+ * @returns Key attributes, or undefined for an element type without any
18
+ */
19
+ export declare function keyAttributes(tag: string, attributes: Record<string, string>, state?: ElementState): KeyAttributes | undefined;
20
+ //# sourceMappingURL=keyAttributes.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The attributes that identify an element by its type (an image's `src`, a
3
+ * link's `href`, a field's name and value), shown by `dom query`, `dom get`
4
+ * and `dom a11y describe`.
5
+ */
6
+ import { MASKED_VALUE } from '../../../runtime/dom/elementInfo.js';
7
+ /** Attributes read for each element type (live state is added for form controls) */
8
+ const ATTRIBUTES_BY_TAG = {
9
+ img: ['src', 'alt'],
10
+ a: ['href'],
11
+ input: ['name', 'placeholder'],
12
+ textarea: ['name', 'placeholder'],
13
+ button: ['name'],
14
+ select: ['name'],
15
+ iframe: ['src'],
16
+ form: ['action', 'method'],
17
+ };
18
+ /**
19
+ * The key attributes of an element: the identifying attributes of its type
20
+ * that are set (empty ones left out) and the live state of a form control
21
+ * (type, current value, checked, selected options). Values arrive masked
22
+ * from the page (`ELEMENT_STATE_JS`); a hidden input's value is never read,
23
+ * and a sensitive field's value is masked here again in case it was not.
24
+ *
25
+ * @param tag - Lower-case tag name
26
+ * @param attributes - Element attributes
27
+ * @param state - Live state read in the page (`ELEMENT_STATE_JS`)
28
+ * @returns Key attributes, or undefined for an element type without any
29
+ */
30
+ export function keyAttributes(tag, attributes, state = {}) {
31
+ const names = ATTRIBUTES_BY_TAG[tag];
32
+ if (!names)
33
+ return undefined;
34
+ const result = {};
35
+ const type = state.type ?? attributes['type'];
36
+ if (type)
37
+ result['type'] = type;
38
+ for (const name of names) {
39
+ const value = attributes[name];
40
+ if (value)
41
+ result[name] = value;
42
+ }
43
+ if (type === 'hidden')
44
+ return result;
45
+ const secret = state.sensitive === true || type === 'password';
46
+ if (state.value)
47
+ result['value'] = secret ? MASKED_VALUE : state.value;
48
+ if (state.checked !== undefined)
49
+ result['checked'] = state.checked;
50
+ if (state.selected)
51
+ result['selected'] = secret ? MASKED_VALUE : state.selected;
52
+ return Object.keys(result).length > 0 ? result : undefined;
53
+ }
54
+ //# sourceMappingURL=keyAttributes.js.map
@@ -46,7 +46,7 @@ export declare function documentReadyState(): Promise<string | undefined>;
46
46
  */
47
47
  export declare function queryDOMElements(selector: string): Promise<DomQueryResult>;
48
48
  /**
49
- * Get DOM context (tag, classes, text preview) for a node: a one-line
49
+ * Get DOM context (tag, classes, key attributes, text preview) for a node: a one-line
50
50
  * preview, and up to {@link ELEMENT_TEXT_LENGTH} characters of text when it
51
51
  * is longer (all of it with `full`).
52
52
  *