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.
- package/.claude/skills/bdg/SKILL.md +268 -0
- package/README.md +15 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/installSkill.d.ts +20 -0
- package/dist/commands/installSkill.js +87 -0
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/commands.js +3 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +85 -0
- package/dist/errors/messages.js +128 -1
- package/dist/index.js +2151 -960
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +40 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/installSkill.d.ts +11 -0
- package/dist/ui/formatters/installSkill.js +31 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- 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>',
|
|
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>',
|
|
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>',
|
|
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>',
|
|
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>',
|
|
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,
|
|
283
|
-
* "⚠ Element Clicked (
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
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
|
|
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,
|
|
301
|
+
function formatActionOutput(done, details, result, options = {}) {
|
|
302
|
+
const keyWidth = options.keyWidth ?? 15;
|
|
295
303
|
const fmt = new OutputFormatter();
|
|
296
|
-
|
|
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 ?? []).
|
|
304
|
-
|
|
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,
|
|
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
|
*
|