browser-debugger-cli 0.11.0 → 0.12.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/README.md +142 -79
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/formInteraction.js +36 -6
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +158 -38
- package/dist/commands/dom/index.js +4 -1
- package/dist/commands/dom/screenshot.js +10 -6
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.js +1 -1
- package/dist/commands/optionBehaviors.js +21 -6
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/optionTypes.d.ts +5 -1
- package/dist/commands/start.d.ts +13 -0
- package/dist/commands/start.js +19 -2
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/daemon/session/commandRegistry.js +7 -1
- package/dist/daemon/session/plugins.js +4 -52
- package/dist/daemon.js +7986 -6848
- package/dist/errors/messages.d.ts +34 -4
- package/dist/errors/messages.js +68 -5
- package/dist/index.js +610 -186
- package/dist/ipc/client.d.ts +4 -0
- package/dist/ipc/client.js +8 -0
- package/dist/ipc/protocol/auditTypes.d.ts +129 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +23 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +4 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +71 -8
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +4 -1
- package/dist/runtime/dom/actionEffects.js +8 -4
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +36 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +215 -0
- package/dist/runtime/dom/auditScripts.d.ts +107 -0
- package/dist/runtime/dom/auditScripts.js +112 -0
- package/dist/runtime/dom/elementGeometry.d.ts +8 -2
- package/dist/runtime/dom/elementGeometry.js +24 -8
- package/dist/runtime/dom/elementInfo.d.ts +3 -2
- package/dist/runtime/dom/elementInfo.js +8 -2
- package/dist/runtime/dom/formFillHelpers/fill.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +88 -23
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +89 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +3 -0
- package/dist/runtime/dom/inspectModel.js +30 -7
- package/dist/runtime/dom/inspectPaintModel.d.ts +48 -22
- package/dist/runtime/dom/inspectPaintModel.js +180 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +85 -12
- package/dist/runtime/dom/inspectScripts.js +314 -28
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +31 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +7 -0
- package/dist/runtime/dom/reactEventHelpers.js +27 -9
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/types.d.ts +4 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +106 -0
- package/dist/ui/formatters/dom.d.ts +1 -1
- package/dist/ui/formatters/dom.js +6 -3
- package/dist/ui/formatters/inspect.js +42 -15
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/messages/commands.d.ts +44 -7
- package/dist/ui/messages/commands.js +83 -11
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,127 +1,190 @@
|
|
|
1
|
-
# Browser Debugger CLI
|
|
1
|
+
# bdg - Browser Debugger CLI
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/browser-debugger-cli)
|
|
4
4
|
[](https://github.com/szymdzum/browser-debugger-cli/actions/workflows/ci.yml)
|
|
5
5
|
[](https://github.com/szymdzum/browser-debugger-cli/actions/workflows/security.yml)
|
|
6
|
-
[](https://github.com/szymdzum/browser-debugger-cli/pulls)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**Give your AI agent a real browser. And the DevTools to go with it.**
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
📖 **[Wiki](https://github.com/szymdzum/browser-debugger-cli/wiki)**: [Getting Started](https://github.com/szymdzum/browser-debugger-cli/wiki/Getting-Started) · [Commands](https://github.com/szymdzum/browser-debugger-cli/wiki/Commands) · [For AI Agents](https://github.com/szymdzum/browser-debugger-cli/wiki/For-AI-Agents) · [Recipes](https://github.com/szymdzum/browser-debugger-cli/wiki/Recipes) · [Quick Reference](https://github.com/szymdzum/browser-debugger-cli/wiki/Quick-Reference) · [Troubleshooting](https://github.com/szymdzum/browser-debugger-cli/wiki/Troubleshooting) · [CLI reference](docs/CLI_REFERENCE.md)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- **Token efficient** - No overhead from MCP tool definitions; progressive discovery loads only what's needed
|
|
14
|
-
- **Self-correcting** - Errors clearly exposed with semantic exit codes and suggestions
|
|
15
|
-
- **Composable** - Unix philosophy: pipes, jq, shell scripts work naturally
|
|
12
|
+
bdg keeps a browser session open in the background and lets you drive it one shell command at a time. Click, fill, navigate, then read what actually happened: requests, console errors, layout and styles. It is built for coding agents like Claude Code, Codex and Gemini CLI, and it's just as handy in your own terminal. Every command is a plain process with compact output, so it pipes into `jq` and costs an agent few tokens.
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g browser-debugger-cli
|
|
16
|
+
bdg localhost:3000
|
|
17
|
+
```
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
## Two ways to use it
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
### Debug a page: browser telemetry on demand
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+

|
|
26
24
|
|
|
25
|
+
The cart button does nothing, and the page doesn't say why. Three commands later the agent knows: the click fired a POST that returned 500, the console says the `cart_id` cookie is missing, and the cookie jar confirms it. Network, console, cookies and the DOM are there whenever the agent asks, without a debugger UI.
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
### Automate without writing a script
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+

|
|
31
30
|
|
|
31
|
+
No Playwright script written up front. The agent runs one command, reads what the browser reports back, and picks the next step from that: the click says which text appeared, `dom inspect` says what the button looks like. When the page does something unexpected, the agent adapts on the spot instead of failing at line 40 of a test.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## More examples
|
|
34
|
+
|
|
35
|
+
### Reuse the browser's login from the shell
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
$ bdg network getCookies
|
|
39
|
+
[1] session_id
|
|
40
|
+
Value: s%3A9f8e7d6c
|
|
41
|
+
HttpOnly: Yes
|
|
42
|
+
SameSite: Lax
|
|
43
|
+
|
|
44
|
+
$ bdg network headers 89565.2 --header authorization
|
|
45
|
+
Request Headers:
|
|
46
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZGEifQ.demo
|
|
47
|
+
|
|
48
|
+
$ bdg eval "localStorage.getItem('access_token')"
|
|
49
|
+
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZGEifQ.demo
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`HttpOnly` cookies are included, even though the page's own JavaScript can't read them. Log in once in the browser, then reuse the session from the shell:
|
|
34
53
|
|
|
35
54
|
```bash
|
|
36
|
-
|
|
55
|
+
COOKIES=$(bdg network getCookies --json | jq -r '[.data[] | "\(.name)=\(.value)"] | join("; ")')
|
|
56
|
+
curl -H "Cookie: $COOKIES" localhost:3000/api/me
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### The rest of DevTools
|
|
60
|
+
|
|
61
|
+
```console
|
|
62
|
+
$ bdg dom a11y tree
|
|
63
|
+
[RootWebArea] "My App" (focused)
|
|
64
|
+
[Heading] "My App"
|
|
65
|
+
[Button] "Sign in" (focusable)
|
|
66
|
+
[Image] ← no accessible name
|
|
67
|
+
|
|
68
|
+
$ bdg cdp Performance.enable
|
|
69
|
+
$ bdg cdp Performance.getMetrics --json | jq '.data.result.metrics | from_entries'
|
|
70
|
+
{ "Nodes": 51, "LayoutDuration": 0.000115, "ScriptDuration": 0.000775, "JSHeapUsedSize": 772124, ... }
|
|
71
|
+
|
|
72
|
+
$ bdg cdp Emulation.setCPUThrottlingRate --params '{"rate":4}' # Slow CPU
|
|
73
|
+
$ bdg cdp Network.emulateNetworkConditions --params '{"offline":false,"latency":400,"downloadThroughput":50000,"uploadThroughput":20000}'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Accessibility, performance metrics, CPU profiling, heap snapshots, code coverage, throttling, storage, service workers: everything Chrome DevTools can do, bdg can do too, through raw CDP. Can't find the method you need? `bdg cdp --search heap`.
|
|
77
|
+
|
|
78
|
+
## Agents learn it on their own
|
|
79
|
+
|
|
80
|
+
No docs to paste into the prompt. The agent asks bdg:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
bdg --help --json # Every command, flag and exit code, plus "task → command" mappings
|
|
84
|
+
bdg cdp --search cookie # 13 matching methods across all CDP domains, each with an example call
|
|
85
|
+
bdg cdp Network.getCookies --describe # Parameters, return types, an example
|
|
37
86
|
```
|
|
38
87
|
|
|
39
|
-
|
|
88
|
+
And when it gets something wrong, bdg tells it what it meant:
|
|
89
|
+
|
|
90
|
+
```console
|
|
91
|
+
$ bdg dom clik "a"
|
|
92
|
+
error: unknown command 'clik'
|
|
93
|
+
(Did you mean click?)
|
|
94
|
+
|
|
95
|
+
$ bdg cdp Network.getCookie
|
|
96
|
+
"error": "Method 'Network.getCookie' not found",
|
|
97
|
+
"suggestion": "... Did you mean: Network.getCookies, Network.setCookie, Network.setCookies"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each mistake exits with code 81 (invalid arguments), so the agent knows to fix the call rather than retry it. CDP method names are case-insensitive, and raw CDP calls point to the friendlier command when one exists. More in the [Agent-Friendly Tools](docs/principles/AGENT_FRIENDLY_TOOLS.md) principles bdg follows.
|
|
101
|
+
|
|
102
|
+
## Benchmark: CLI vs MCP
|
|
103
|
+
|
|
104
|
+
We gave an AI agent five real debugging tasks, from a single JS error up to a memory leak, and ran each one with bdg and with the official Chrome DevTools MCP server (November 2025).
|
|
40
105
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
106
|
+
| | bdg | Chrome DevTools MCP |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| **Score** | **77 / 100** | 60 / 100 |
|
|
109
|
+
| **Token efficiency** | **202** | 152 |
|
|
110
|
+
| Tokens used | ~38.1K | ~39.4K |
|
|
45
111
|
|
|
46
|
-
|
|
112
|
+
bdg scored 17 points higher on about the same token budget, so its token efficiency was 33% better. Part of the gap is reach: memory profiling, HAR export and batch JS execution have no MCP equivalent. [Read the full analysis →](docs/benchmarks/ARTICLE_MCP_VS_CLI_FOR_AGENTS.md)
|
|
47
113
|
|
|
48
|
-
|
|
114
|
+
## Use it with your agent
|
|
115
|
+
|
|
116
|
+
bdg ships an agent skill that teaches the workflow: start once, act, read what changed, inspect without screenshots, check network and console.
|
|
49
117
|
|
|
50
118
|
```bash
|
|
51
|
-
bdg install-skill # ~/.claude/skills
|
|
119
|
+
bdg install-skill # Claude Code (~/.claude/skills) + Codex, Gemini CLI, ... (~/.agents/skills)
|
|
52
120
|
bdg install-skill --claude # Claude Code only
|
|
53
121
|
```
|
|
54
122
|
|
|
55
|
-
Start a new agent session afterwards
|
|
123
|
+
Start a new agent session afterwards. In Claude Code the skill loads when a task needs a browser, or on demand with `/bdg`. Re-run `bdg install-skill` after upgrading bdg.
|
|
56
124
|
|
|
57
|
-
## Quick
|
|
125
|
+
## Quick start
|
|
58
126
|
|
|
59
127
|
```bash
|
|
60
|
-
bdg example.com
|
|
61
|
-
bdg
|
|
62
|
-
bdg https://localhost:5173 --chrome-flags="--disable-web-security" # Disable CORS
|
|
63
|
-
bdg cdp --search cookie # Discover commands
|
|
64
|
-
bdg cdp Network.getCookies # Run any CDP method
|
|
65
|
-
bdg dom query "button" # High-level helpers
|
|
66
|
-
bdg dom fill 'input[name="q"]' "shoes"
|
|
128
|
+
bdg example.com # Start a session (the browser stays open)
|
|
129
|
+
bdg dom fill 'input[name="q"]' "shoes" # Interact
|
|
67
130
|
bdg dom click 'button:has-text("Search")'
|
|
68
|
-
bdg
|
|
69
|
-
bdg
|
|
70
|
-
bdg
|
|
71
|
-
bdg dom
|
|
72
|
-
bdg dom
|
|
73
|
-
bdg dom
|
|
74
|
-
bdg
|
|
75
|
-
bdg
|
|
76
|
-
bdg
|
|
77
|
-
bdg
|
|
78
|
-
bdg
|
|
131
|
+
bdg dom wait "#result" --visible # Wait for an element instead of sleeping
|
|
132
|
+
bdg network list --preset errors # Failed requests
|
|
133
|
+
bdg console # Console messages
|
|
134
|
+
bdg dom layout "#save" # Where is it? Visible? Covered?
|
|
135
|
+
bdg dom inspect "#save" --why color # Which CSS rule sets the color
|
|
136
|
+
bdg dom audit contrast # Page-wide: text below WCAG AA, weakest first
|
|
137
|
+
bdg css search -- --brand # Where a token is set, in every stylesheet
|
|
138
|
+
bdg dom listeners "#save" # Which event listeners run
|
|
139
|
+
bdg page emulate --viewport 900x700 # Responsive check mid-session (--mobile for a phone)
|
|
140
|
+
bdg eval "document.title" # Run JavaScript (--frame for iframes)
|
|
141
|
+
bdg cdp Network.getCookies # Any CDP method
|
|
142
|
+
bdg stop # End the session
|
|
79
143
|
```
|
|
80
144
|
|
|
81
|
-
|
|
145
|
+
Local dev servers with self-signed certificates: `bdg https://localhost:5173 --chrome-flags="--ignore-certificate-errors"`. Need parallel sessions? `bdg example.com --session agent2`.
|
|
82
146
|
|
|
83
|
-
|
|
147
|
+
## What it covers
|
|
84
148
|
|
|
85
|
-
|
|
149
|
+
| Area | Commands |
|
|
150
|
+
|---|---|
|
|
151
|
+
| **Page** | navigate, reload, back/forward, viewport, phone (`--mobile`) and color-scheme emulation |
|
|
152
|
+
| **Interaction** | click (double, right), fill (React-compatible, file inputs), hover, keys, forms, scroll, wait; shadow DOM and iframes included |
|
|
153
|
+
| **Inspection** | element styles with the CSS cascade, layout and visibility, page-wide audits (contrast, overflow, layers, animations), stylesheet search, accessibility tree, event listeners, screenshots |
|
|
154
|
+
| **Telemetry** | network requests and headers (incl. `Authorization`), HAR export, cookies (incl. `HttpOnly`), console messages, live `peek --follow` |
|
|
155
|
+
| **Accessibility** | accessibility tree, semantic queries (`role:button name:Submit`), contrast checks |
|
|
156
|
+
| **Performance** | metrics, CPU and heap profiling, tracing, CPU and network throttling (raw CDP) |
|
|
157
|
+
| **Sessions** | several named sessions side by side, or attach to a browser you already have open (`--chrome-ws-url`) |
|
|
158
|
+
| **Everything else** | raw CDP: `bdg cdp --list`, `--search`, `--describe` |
|
|
86
159
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
bdg cdp Network --list # Methods in one domain
|
|
91
|
-
bdg cdp Network.getCookies --describe # Full schema + examples
|
|
92
|
-
bdg cdp Network.getCookies # Execute
|
|
93
|
-
|
|
94
|
-
# Search across all domains
|
|
95
|
-
bdg cdp --search screenshot # Find relevant methods
|
|
96
|
-
bdg cdp --search cookie # methods mentioning cookies
|
|
97
|
-
```
|
|
160
|
+
The [CLI reference](docs/CLI_REFERENCE.md) documents every command. `bdg --help --json` gives agents the machine-readable version.
|
|
161
|
+
|
|
162
|
+
## Install
|
|
98
163
|
|
|
99
|
-
|
|
164
|
+
**Requirements:** Node.js 22.12+ and a Chromium-based browser: Chrome, Chromium or Microsoft Edge.
|
|
100
165
|
|
|
101
|
-
|
|
166
|
+
**Platforms:** macOS, Linux and Windows via WSL. Native PowerShell and Git Bash are not supported yet.
|
|
102
167
|
|
|
103
|
-
|
|
104
|
-
- [Commands](https://github.com/szymdzum/browser-debugger-cli/wiki/Commands)
|
|
105
|
-
- [For AI Agents](https://github.com/szymdzum/browser-debugger-cli/wiki/For-AI-Agents)
|
|
106
|
-
- [Recipes](https://github.com/szymdzum/browser-debugger-cli/wiki/Recipes)
|
|
107
|
-
- [Quick Reference](https://github.com/szymdzum/browser-debugger-cli/wiki/Quick-Reference)
|
|
108
|
-
- [Architecture](https://github.com/szymdzum/browser-debugger-cli/wiki/Architecture)
|
|
109
|
-
- [Troubleshooting](https://github.com/szymdzum/browser-debugger-cli/wiki/Troubleshooting)
|
|
168
|
+
**Browsers:** bdg launches Chrome by default. To use Edge, point `CHROME_PATH` at its binary, or attach to an Edge you started with `--remote-debugging-port`:
|
|
110
169
|
|
|
111
|
-
|
|
170
|
+
```bash
|
|
171
|
+
CHROME_PATH="/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" bdg example.com # macOS
|
|
172
|
+
CHROME_PATH=/usr/bin/microsoft-edge bdg example.com # Linux
|
|
173
|
+
bdg example.com --chrome-ws-url 9222 # Attach to a running browser
|
|
174
|
+
```
|
|
112
175
|
|
|
113
|
-
|
|
176
|
+
Firefox and Safari are not supported: bdg speaks the Chrome DevTools Protocol, which they do not implement.
|
|
114
177
|
|
|
115
|
-
|
|
116
|
-
- **Semantic exit codes** - Machine-parseable error handling
|
|
117
|
-
- **Structured output** - JSON by default, human-readable optional
|
|
118
|
-
- **Progressive disclosure** - Simple commands, deep capabilities
|
|
178
|
+
## When to use something else
|
|
119
179
|
|
|
120
|
-
|
|
180
|
+
- **Playwright / Puppeteer**: long scripted test suites and a mature testing ecosystem.
|
|
181
|
+
- **Chrome DevTools MCP**: if your setup is already built around MCP servers.
|
|
121
182
|
|
|
122
|
-
|
|
183
|
+
bdg is for when an agent or a developer needs to poke at a live page, step by step, and understand what is going on.
|
|
184
|
+
|
|
185
|
+
## Contributing
|
|
123
186
|
|
|
124
|
-
See `docs/` for
|
|
187
|
+
[Issues](https://github.com/szymdzum/browser-debugger-cli/issues) for bugs, [Discussions](https://github.com/szymdzum/browser-debugger-cli/discussions) for ideas. PRs welcome. See the [Architecture](https://github.com/szymdzum/browser-debugger-cli/wiki/Architecture) page and `docs/` for contributor guides.
|
|
125
188
|
|
|
126
189
|
## License
|
|
127
190
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg css search <text>` - find text in the page's stylesheets, including
|
|
3
|
+
* cross-origin ones that page scripts cannot read: where a token is set, which
|
|
4
|
+
* rules use `oklch(`, a class or a custom property.
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Register the `css` command group.
|
|
9
|
+
*
|
|
10
|
+
* @param program - Root command
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerCssCommands(program: Command): void;
|
|
13
|
+
//# sourceMappingURL=css.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg css search <text>` - find text in the page's stylesheets, including
|
|
3
|
+
* cross-origin ones that page scripts cannot read: where a token is set, which
|
|
4
|
+
* rules use `oklch(`, a class or a custom property.
|
|
5
|
+
*/
|
|
6
|
+
import { runCommand } from './shared/CommandRunner.js';
|
|
7
|
+
import { jsonOption } from './shared/commonOptions.js';
|
|
8
|
+
import { integerOption } from './shared/validation.js';
|
|
9
|
+
import { cssSearch } from '../ipc/client.js';
|
|
10
|
+
import { formatCssSearch } from '../ui/formatters/audit.js';
|
|
11
|
+
import { CSS_SEARCH_HELP_EXAMPLES } from '../ui/messages/commands.js';
|
|
12
|
+
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
13
|
+
/**
|
|
14
|
+
* Register the `css` command group.
|
|
15
|
+
*
|
|
16
|
+
* @param program - Root command
|
|
17
|
+
*/
|
|
18
|
+
export function registerCssCommands(program) {
|
|
19
|
+
const css = program.command('css').description("The page's stylesheets: search <text>");
|
|
20
|
+
css
|
|
21
|
+
.command('search')
|
|
22
|
+
.description('Find text in every stylesheet of the page (cross-origin ones too), with the rule and file:line')
|
|
23
|
+
.argument('<text>', 'Text to find (case-insensitive), e.g. oklch( or .btn-primary; put -- before one that starts with - (-- --brand)')
|
|
24
|
+
.option('--limit <n>', 'Matches listed (default: 20)', integerOption(1, 500))
|
|
25
|
+
.addOption(jsonOption())
|
|
26
|
+
.addHelpText('after', CSS_SEARCH_HELP_EXAMPLES)
|
|
27
|
+
.action(async (text, options) => {
|
|
28
|
+
await runCommand(() => search(text, options), options, formatCssSearch);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ask the daemon to search the stylesheets.
|
|
33
|
+
*
|
|
34
|
+
* @param text - Text to find
|
|
35
|
+
* @param options - Limit
|
|
36
|
+
* @returns Command result
|
|
37
|
+
*/
|
|
38
|
+
async function search(text, options) {
|
|
39
|
+
const response = await cssSearch({
|
|
40
|
+
query: text,
|
|
41
|
+
...(options.limit !== undefined && { limit: options.limit }),
|
|
42
|
+
});
|
|
43
|
+
if (response.status === 'error' || !response.data) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: response.error ?? 'Failed to search the stylesheets',
|
|
47
|
+
exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
|
|
48
|
+
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { success: true, data: response.data };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=css.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom audit [check...]` - page-wide checks without a screenshot or
|
|
3
|
+
* `dom eval`: text below a WCAG contrast level, what makes the page scroll
|
|
4
|
+
* sideways and cut-off text and scaled images, fixed and sticky layers, and
|
|
5
|
+
* running animations.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from 'commander';
|
|
8
|
+
/**
|
|
9
|
+
* Register `bdg dom audit`.
|
|
10
|
+
*
|
|
11
|
+
* @param dom - The `dom` command group
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerAuditCommand(dom: Command): void;
|
|
14
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom audit [check...]` - page-wide checks without a screenshot or
|
|
3
|
+
* `dom eval`: text below a WCAG contrast level, what makes the page scroll
|
|
4
|
+
* sideways and cut-off text and scaled images, fixed and sticky layers, and
|
|
5
|
+
* running animations.
|
|
6
|
+
*/
|
|
7
|
+
import { InvalidArgumentError } from 'commander';
|
|
8
|
+
import { runCommand } from '../shared/CommandRunner.js';
|
|
9
|
+
import { jsonOption } from '../shared/commonOptions.js';
|
|
10
|
+
import { integerOption } from '../shared/validation.js';
|
|
11
|
+
import { unknownAuditCheckMessage } from '../../errors/messages.js';
|
|
12
|
+
import { domAudit } from '../../ipc/client.js';
|
|
13
|
+
import { AUDIT_CHECKS } from '../../ipc/protocol/auditTypes.js';
|
|
14
|
+
import { formatAudit } from '../../ui/formatters/audit.js';
|
|
15
|
+
import { AUDIT_HELP_EXAMPLES } from '../../ui/messages/commands.js';
|
|
16
|
+
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
17
|
+
import { findSimilar } from '../../utils/suggestions.js';
|
|
18
|
+
/**
|
|
19
|
+
* Register `bdg dom audit`.
|
|
20
|
+
*
|
|
21
|
+
* @param dom - The `dom` command group
|
|
22
|
+
*/
|
|
23
|
+
export function registerAuditCommand(dom) {
|
|
24
|
+
dom
|
|
25
|
+
.command('audit')
|
|
26
|
+
.description('Page-wide checks: text below WCAG contrast, sideways scroll and cut-off text, scaled images, fixed/sticky layers, animations')
|
|
27
|
+
.argument('[checks...]', `Checks to run: ${AUDIT_CHECKS.join(', ')} (default: all)`, checkList)
|
|
28
|
+
.option('--level <level>', 'WCAG level text must reach: AA or AAA (default: AA)', levelOption)
|
|
29
|
+
.option('--limit <n>', 'Findings listed per check (default: 20)', integerOption(1, 500))
|
|
30
|
+
.addOption(jsonOption())
|
|
31
|
+
.addHelpText('after', AUDIT_HELP_EXAMPLES)
|
|
32
|
+
.action(async (checks, options) => {
|
|
33
|
+
await runCommand(() => audit(checks, options), options, formatAudit);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse the checks, rejecting unknown names with the closest one.
|
|
38
|
+
*
|
|
39
|
+
* @param value - One check name
|
|
40
|
+
* @param previous - Checks so far
|
|
41
|
+
* @returns Checks
|
|
42
|
+
* @throws InvalidArgumentError for an unknown name
|
|
43
|
+
*/
|
|
44
|
+
function checkList(value, previous = []) {
|
|
45
|
+
const check = AUDIT_CHECKS.find((name) => name === value.trim().toLowerCase());
|
|
46
|
+
if (!check) {
|
|
47
|
+
throw new InvalidArgumentError(unknownAuditCheckMessage(value, findSimilar(value, [...AUDIT_CHECKS]), AUDIT_CHECKS));
|
|
48
|
+
}
|
|
49
|
+
return previous.includes(check) ? previous : [...previous, check];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse `--level`.
|
|
53
|
+
*
|
|
54
|
+
* @param value - Level
|
|
55
|
+
* @returns AA or AAA
|
|
56
|
+
* @throws InvalidArgumentError for another value
|
|
57
|
+
*/
|
|
58
|
+
function levelOption(value) {
|
|
59
|
+
const level = value.trim().toUpperCase();
|
|
60
|
+
if (level === 'AA' || level === 'AAA')
|
|
61
|
+
return level;
|
|
62
|
+
throw new InvalidArgumentError('Use AA or AAA');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ask the daemon to audit the page.
|
|
66
|
+
*
|
|
67
|
+
* @param checks - Checks given (all when none)
|
|
68
|
+
* @param options - Level and limit
|
|
69
|
+
* @returns Command result
|
|
70
|
+
*/
|
|
71
|
+
async function audit(checks, options) {
|
|
72
|
+
const response = await domAudit({
|
|
73
|
+
checks: checks && checks.length > 0 ? checks : [...AUDIT_CHECKS],
|
|
74
|
+
...(options.level && { level: options.level }),
|
|
75
|
+
...(options.limit !== undefined && { limit: options.limit }),
|
|
76
|
+
});
|
|
77
|
+
if (response.status === 'error' || !response.data) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: response.error ?? 'Failed to audit the page',
|
|
81
|
+
exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
|
|
82
|
+
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { success: true, data: response.data };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -12,12 +12,12 @@ import { runCommand } from '../shared/CommandRunner.js';
|
|
|
12
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
|
-
import { VIA_LABEL_SUFFIX, conflictingOptionsMessage, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
|
|
16
|
-
import { domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
|
|
15
|
+
import { VIA_LABEL_SUFFIX, conflictingOptionsMessage, hoverOffWithTargetError, missingArgumentError, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
|
|
16
|
+
import { callCDP, domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
|
|
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, POINTER_ACTION_NOUN, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
|
|
20
|
+
import { CLICK_RESULT_WAIT_HELP, HOVER_OFF_DONE, HOVER_USAGE, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, actionStatusLine, dialogConsoleText, moreMessagesText, 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
23
|
/** Help of `--strict` on click and hover */
|
|
@@ -96,13 +96,18 @@ export function registerFormInteractionCommands(program) {
|
|
|
96
96
|
});
|
|
97
97
|
domCommand
|
|
98
98
|
.command('hover')
|
|
99
|
-
.description('Move the mouse over an element (shows hover menus and tooltips)')
|
|
100
|
-
.argument('
|
|
99
|
+
.description('Move the mouse over an element (shows hover menus and tooltips); --off moves it off the page')
|
|
100
|
+
.argument('[selectorOrIndex]', SELECTOR_OR_INDEX_ARGUMENT)
|
|
101
101
|
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
102
|
+
.option('--off', 'Move the mouse off the page instead (closes menus that open on hover)')
|
|
102
103
|
.option('--strict', STRICT_OPTION_HELP)
|
|
103
104
|
.option('--no-wait', 'Skip waiting for network stability after hovering')
|
|
104
105
|
.addOption(jsonOption())
|
|
105
106
|
.action(async (selectorOrIndex, options) => {
|
|
107
|
+
if (options.off || selectorOrIndex === undefined) {
|
|
108
|
+
await hoverOff(selectorOrIndex, options);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
106
111
|
await runPointerCommand(selectorOrIndex, options, 'hover');
|
|
107
112
|
});
|
|
108
113
|
domCommand
|
|
@@ -260,6 +265,28 @@ function scrollOptionsProblem(selector, options) {
|
|
|
260
265
|
* @param options - Command options
|
|
261
266
|
* @param action - Pointer action
|
|
262
267
|
*/
|
|
268
|
+
/**
|
|
269
|
+
* `bdg dom hover --off`: move the mouse off the page, so `mouseleave` and
|
|
270
|
+
* `:hover` end and menus that open on hover close.
|
|
271
|
+
*
|
|
272
|
+
* @param selectorOrIndex - Must be absent with --off
|
|
273
|
+
* @param options - Command options
|
|
274
|
+
*/
|
|
275
|
+
async function hoverOff(selectorOrIndex, options) {
|
|
276
|
+
await runCommand(async () => {
|
|
277
|
+
if (!options.off || selectorOrIndex !== undefined || options.index !== undefined) {
|
|
278
|
+
const err = options.off ? hoverOffWithTargetError() : missingArgumentError(HOVER_USAGE);
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
error: err.message,
|
|
282
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
283
|
+
errorContext: { suggestion: err.suggestion },
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
await callCDP('Input.dispatchMouseEvent', { type: 'mouseMoved', x: -1, y: -1 });
|
|
287
|
+
return { success: true, data: { pointer: 'off' } };
|
|
288
|
+
}, options, () => HOVER_OFF_DONE);
|
|
289
|
+
}
|
|
263
290
|
async function runPointerCommand(selectorOrIndex, options, action) {
|
|
264
291
|
await runCommand(() => options.double && options.right
|
|
265
292
|
? Promise.resolve({
|
|
@@ -316,7 +343,10 @@ function formatActionOutput(done, details, result, options = {}) {
|
|
|
316
343
|
fmt.keyValueList(details, keyWidth);
|
|
317
344
|
if (result.navigation)
|
|
318
345
|
fmt.keyValue('Page', pageNavigationText(result.navigation), keyWidth);
|
|
319
|
-
listRows(fmt, 'New text',
|
|
346
|
+
listRows(fmt, 'New text', [
|
|
347
|
+
...(result.messages ?? []).map(newMessageText),
|
|
348
|
+
...(result.moreMessages ? [moreMessagesText(result.moreMessages)] : []),
|
|
349
|
+
], keyWidth);
|
|
320
350
|
listRows(fmt, 'Shown', (result.shown ?? []).map(shownElementText), keyWidth);
|
|
321
351
|
const omitted = result.triggeredRequestsOmitted;
|
|
322
352
|
const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ElementState, KeyAttributes } from '../../../types.js';
|
|
7
7
|
/**
|
|
8
|
-
* The key attributes of an element:
|
|
9
|
-
*
|
|
8
|
+
* The key attributes of an element: its shadow `part` name (how a page's CSS
|
|
9
|
+
* reaches it with `::part()`), the identifying attributes of its type that
|
|
10
|
+
* are set (empty ones left out) and the live state of a form control
|
|
10
11
|
* (type, current value, checked, selected options). Values arrive masked
|
|
11
12
|
* from the page (`ELEMENT_STATE_JS`); a hidden input's value is never read,
|
|
12
13
|
* and a sensitive field's value is masked here again in case it was not.
|
|
@@ -16,8 +16,9 @@ const ATTRIBUTES_BY_TAG = {
|
|
|
16
16
|
form: ['action', 'method'],
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
|
-
* The key attributes of an element:
|
|
20
|
-
*
|
|
19
|
+
* The key attributes of an element: its shadow `part` name (how a page's CSS
|
|
20
|
+
* reaches it with `::part()`), the identifying attributes of its type that
|
|
21
|
+
* are set (empty ones left out) and the live state of a form control
|
|
21
22
|
* (type, current value, checked, selected options). Values arrive masked
|
|
22
23
|
* from the page (`ELEMENT_STATE_JS`); a hidden input's value is never read,
|
|
23
24
|
* and a sensitive field's value is masked here again in case it was not.
|
|
@@ -29,9 +30,10 @@ const ATTRIBUTES_BY_TAG = {
|
|
|
29
30
|
*/
|
|
30
31
|
export function keyAttributes(tag, attributes, state = {}) {
|
|
31
32
|
const names = ATTRIBUTES_BY_TAG[tag];
|
|
33
|
+
const part = attributes['part'];
|
|
32
34
|
if (!names)
|
|
33
|
-
return undefined;
|
|
34
|
-
const result = {};
|
|
35
|
+
return part ? { part } : undefined;
|
|
36
|
+
const result = part ? { part } : {};
|
|
35
37
|
const type = state.type ?? attributes['type'];
|
|
36
38
|
if (type)
|
|
37
39
|
result['type'] = type;
|