Browser Verify
Testing-grade browser automation using Chrome controlled through the Chrome DevTools
Protocol. All interactions use real CDP Input domain events (mouse coordinates,
key events) — not synthetic JS .click(). Zero external dependencies beyond
Node 22's built-in WebSocket.
Architecture
Scripts live in the scripts/ directory alongside this SKILL.md:
chrome-launcher.sh— Start/stop/status Chrome with remote debuggingcdp.mjs— Full CDP client with 50+ commands
No npm install required. Uses Node 22 built-in WebSocket.
Resolving Script Paths
The scripts are in the scripts/ subdirectory of this skill. Resolve the path
from your .claude/skills/browser-verify/ install location:
SCRIPTS=".claude/skills/browser-verify/scripts"
bash "$SCRIPTS/chrome-launcher.sh" start --headless
node "$SCRIPTS/cdp.mjs" navigate "https://example.com"
Workflow
1. Start Chrome
bash "$SCRIPTS/chrome-launcher.sh" start
# Headless (no window, faster):
bash "$SCRIPTS/chrome-launcher.sh" start --headless
2. Run Commands
node "$SCRIPTS/cdp.mjs" <command> [args] [--flags]
3. Stop Chrome
bash "$SCRIPTS/chrome-launcher.sh" stop
Command Reference
Navigation
| Command | Example | Purpose |
|---|---|---|
navigate <url> |
navigate "https://example.com" |
Open URL, wait for load |
reload |
reload |
Reload current page |
back |
back |
Browser back |
forward |
forward |
Browser forward |
page-info |
page-info |
Title, URL, counts, viewport, meta |
list-targets |
list-targets |
List open tabs |
new-tab [url] |
new-tab "https://example.com/about" |
Open new tab |
close-tab [id] |
close-tab |
Close tab |
Visual
| Command | Example | Purpose |
|---|---|---|
screenshot |
screenshot --output /tmp/page.png |
Full page capture |
screenshot --selector <s> |
screenshot --selector ".card" |
Element capture |
screenshot --full |
screenshot --full --output /tmp/full.png |
Beyond viewport |
viewport <w> <h> |
viewport 375 812 |
Set viewport size |
pdf |
pdf --output /tmp/page.pdf |
Save as PDF |
emulate <device> |
emulate mobile |
Device emulation |
Available devices: mobile, iphone, ipad, tablet, android, desktop, laptop
DOM Inspection
| Command | Example | Purpose |
|---|---|---|
evaluate <js> |
evaluate "document.title" |
Run JS in page context |
get-html [--selector s] |
get-html --selector "#app" |
Get outerHTML |
get-text [--selector s] |
get-text --selector ".message" |
Get textContent |
get-attribute <sel> <attr> |
get-attribute "a" "href" |
Get attribute |
get-value <sel> |
get-value "input[name=email]" |
Get input value |
get-styles <sel> |
get-styles ".btn" --props "color,fontSize" |
Computed styles |
query-all <sel> |
query-all "li.item" |
Info on all matches |
Interaction (Real CDP Input Events)
| Command | Example | Purpose |
|---|---|---|
click <sel> |
click "button.submit" |
Left click |
click <sel> --button right |
click ".menu" --button right |
Right click |
click <sel> --count 2 |
click ".item" --count 2 |
Double click |
hover <sel> |
hover ".tooltip-trigger" |
Mouse hover |
type <sel> <text> |
type "#email" "test@test.com" |
Type text |
type <sel> <text> --clear |
type "#search" "new query" --clear |
Clear then type |
press <key> |
press Enter |
Press key |
press <key> --modifiers "ctrl" |
press a --modifiers "ctrl" |
Key combo |
clear <sel> |
clear "#search" |
Clear input field |
focus <sel> |
focus "#input" |
Focus element |
blur <sel> |
blur "#input" |
Blur element |
select <sel> <value> |
select "#country" "US" |
Select dropdown |
check <sel> |
check "#agree" |
Check checkbox |
uncheck <sel> |
uncheck "#newsletter" |
Uncheck checkbox |
scroll <sel> |
scroll ".footer" |
Scroll to element |
scroll --x 0 --y 500 |
scroll --x 0 --y 500 |
Scroll to coords |
drag <from> <to> |
drag ".item" ".dropzone" |
Drag and drop |
upload <sel> <path> |
upload "#file" "/tmp/doc.pdf" |
File upload |
Waiting
| Command | Example | Purpose |
|---|---|---|
wait <sel> |
wait ".loaded" |
Wait for DOM element |
wait-visible <sel> |
wait-visible ".modal" |
Wait until visible |
wait-hidden <sel> |
wait-hidden ".spinner" |
Wait until hidden |
wait-text <text> |
wait-text "Success" |
Wait for text on page |
wait-url <pattern> |
wait-url "/dashboard" |
Wait for URL change |
wait-network-idle |
wait-network-idle |
Wait for no requests |
sleep <ms> |
sleep 1000 |
Fixed delay |
All wait commands accept --timeout <ms> (default 10000).
Debugging
| Command | Example | Purpose |
|---|---|---|
get-console |
get-console |
All console messages |
get-network |
get-network |
Network requests + timing |
get-network --status 4xx |
get-network --status error |
Filter by status (4xx, 5xx, error, or exact code) |
get-network --type xhr |
get-network --type fetch |
Filter by initiator type |
get-cookies |
get-cookies |
All cookies |
set-cookie <n> <v> |
set-cookie "token" "abc" --domain localhost |
Set cookie |
clear-cookies |
clear-cookies |
Clear all cookies |
QA Inspection
| Command | Example | Purpose |
|---|---|---|
get-storage |
get-storage |
localStorage + sessionStorage combined |
get-performance |
get-performance |
Timing, resource sizes, LCP/FID/CLS |
get-meta |
get-meta |
Meta tags, OG, Twitter cards, structured data |
inject-axe |
inject-axe |
Run axe-core WCAG 2.x audit, returns violations |
inject-axe --selector s |
inject-axe --selector "main" |
Scope axe to a subtree |
Common Patterns
Login Flow
node "$SCRIPTS/cdp.mjs" navigate "https://app.example.com/login"
node "$SCRIPTS/cdp.mjs" type "#email" "user@test.com" --clear
node "$SCRIPTS/cdp.mjs" type "#password" "pass123" --clear
node "$SCRIPTS/cdp.mjs" click "button[type=submit]"
node "$SCRIPTS/cdp.mjs" wait-url "/dashboard" --timeout 5000
node "$SCRIPTS/cdp.mjs" screenshot --output /tmp/dashboard.png
Responsive Verification
node "$SCRIPTS/cdp.mjs" navigate "https://example.com"
node "$SCRIPTS/cdp.mjs" emulate mobile
node "$SCRIPTS/cdp.mjs" screenshot --output /tmp/mobile.png
node "$SCRIPTS/cdp.mjs" emulate desktop
node "$SCRIPTS/cdp.mjs" screenshot --output /tmp/desktop.png
Privacy Inspection
node "$SCRIPTS/cdp.mjs" navigate "https://example.com"
node "$SCRIPTS/cdp.mjs" get-cookies
node "$SCRIPTS/cdp.mjs" get-storage
node "$SCRIPTS/cdp.mjs" evaluate "JSON.stringify([...document.querySelectorAll('script[src]')].map(s=>s.src))"
Accessibility Audit (axe-core)
node "$SCRIPTS/cdp.mjs" navigate "https://example.com"
node "$SCRIPTS/cdp.mjs" inject-axe
# Scope to main content only:
node "$SCRIPTS/cdp.mjs" inject-axe --selector "main"
Performance Snapshot
node "$SCRIPTS/cdp.mjs" navigate "https://example.com"
node "$SCRIPTS/cdp.mjs" get-performance
# Check for failed requests:
node "$SCRIPTS/cdp.mjs" get-network --status error
SEO / Meta Audit
node "$SCRIPTS/cdp.mjs" navigate "https://example.com"
node "$SCRIPTS/cdp.mjs" get-meta
Notes
- Always start Chrome before running commands; stop when done
- Screenshots saved as PNG — cite the file path as evidence. Do NOT Read
the PNG back into context by default: a viewed screenshot rides along on
every remaining turn of your session. View pixels only when the verdict
genuinely needs visual judgment (layout/rendering checks); for
presence/state checks prefer
evaluate/DOM queries, console, and network capture — they're cheaper and more precise - All
click/type/hoveruse real CDP Input events at element coordinates - Elements are automatically scrolled into view before interaction
type --clearuses Select All + Delete before typing (framework-safe)- Console/network capture is per-session (per
navigatecall)
Reference
See references/cdp-commands.md for extended evaluate patterns, QA-specific
inspection scripts, and troubleshooting.