Chrome DevTools Agent Skill
Browser automation via Puppeteer scripts with persistent sessions. All scripts output JSON.
Skill Location
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
# Detect skill location SKILL_DIR="" if [ -d ".claude/skills/chrome-devtools/scripts" ]; then SKILL_DIR=".claude/skills/chrome-devtools/scripts" elif [ -d "$HOME/.claude/skills/chrome-devtools/scripts" ]; then SKILL_DIR="$HOME/.claude/skills/chrome-devtools/scripts" fi cd "$SKILL_DIR"
Choosing Your Approach
| Scenario | Approach |
|---|---|
| Source-available sites | Read source code first, write selectors directly |
| Unknown layouts | Use aria-snapshot.js for semantic discovery |
| Visual inspection | Take screenshots to verify rendering |
| Debug issues | Collect console logs, analyze with session storage |
| Accessibility audit | Use ARIA snapshot for semantic structure analysis |
Automation Browsing Running Mode
- •Detect current OS and launch browser as headless only when running on Linux, WSL, or CI environments.
- •For macOS/Windows, browser always runs in headed mode for better debugging.
- •Run multiple scripts/sessions in parallel to simulate real user interactions.
- •Run multiple scripts/sessions in parallel to simulate different device types (mobile, tablet, desktop).
- •Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
ARIA Snapshot (Element Discovery)
When page structure is unknown, use aria-snapshot.js to get a YAML-formatted accessibility tree with semantic roles, accessible names, states, and stable element references.
Get ARIA Snapshot
# Generate ARIA snapshot and output to stdout node aria-snapshot.js --url https://example.com # Save to file in snapshots directory node aria-snapshot.js --url https://example.com --output ./.claude/chrome-devtools/snapshots/page.yaml
Example YAML Output
- banner:
- link "Hacker News" [ref=e1]
/url: https://news.ycombinator.com
- navigation:
- link "new" [ref=e2]
- link "past" [ref=e3]
- link "comments" [ref=e4]
- main:
- list:
- listitem:
- link "Show HN: My new project" [ref=e8]
- text: "128 points by user 3 hours ago"
- contentinfo:
- textbox [ref=e10]
/placeholder: "Search"
Interpreting ARIA Notation
| Notation | Meaning |
|---|---|
[ref=eN] | Stable identifier for interactive elements |
[checked] | Checkbox/radio is selected |
[disabled] | Element is inactive |
[expanded] | Accordion/dropdown is open |
[level=N] | Heading hierarchy (1-6) |
/url: | Link destination |
/placeholder: | Input placeholder text |
/value: | Current input value |
Interact by Ref
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Use select-ref.js to interact with elements by their ref:
# Click element with ref e5 node select-ref.js --ref e5 --action click # Fill input with ref e10 node select-ref.js --ref e10 --action fill --value "search query" # Get text content node select-ref.js --ref e8 --action text # Screenshot specific element node select-ref.js --ref e1 --action screenshot --output ./logo.png # Focus element node select-ref.js --ref e10 --action focus # Hover over element node select-ref.js --ref e5 --action hover
Store Snapshots
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Store snapshots for analysis in <project>/.claude/chrome-devtools/snapshots/:
# Create snapshots directory mkdir -p .claude/chrome-devtools/snapshots # Capture and store with timestamp SESSION="$(date +%Y%m%d-%H%M%S)" node aria-snapshot.js --url https://example.com --output .claude/chrome-devtools/snapshots/$SESSION.yaml
Workflow: Unknown Page Structure
- •
Get snapshot to discover elements:
bashnode aria-snapshot.js --url https://example.com
- •
Identify target from YAML output (e.g.,
[ref=e5]for a button) - •
Interact by ref:
bashnode select-ref.js --ref e5 --action click
- •
Verify result with screenshot or new snapshot:
bashnode screenshot.js --output ./result.png
Local HTML Files
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
IMPORTANT: Never browse local HTML files via file:// protocol. Always serve via local server:
Why: file:// protocol blocks many browser features (CORS, ES modules, fetch API, service workers). Local server ensures proper HTTP behavior.
# Option 1: npx serve (recommended) npx serve ./dist -p 3000 & node navigate.js --url http://localhost:3000 # Option 2: Python http.server python -m http.server 3000 --directory ./dist & node navigate.js --url http://localhost:3000
Note: when port 3000 is busy, find an available port with lsof -i :3000 and use a different one.
Quick Start
# Install dependencies
cd .claude/skills/chrome-devtools/scripts
npm install # Installs puppeteer, sharp, debug, yargs
# Test (browser stays running for session reuse)
node navigate.js --url https://example.com
# Output: {"success": true, "url": "...", "title": "..."}
Linux/WSL only: Run ./install-deps.sh first for Chrome system libraries.
Session Persistence
Browser state persists across script executions via WebSocket endpoint file (.browser-session.json).
Default behavior: Scripts disconnect but keep browser running for session reuse.
# First script: launches browser, navigates, disconnects (browser stays running) node navigate.js --url https://example.com/login # Subsequent scripts: connect to existing browser, reuse page state node fill.js --selector "#email" --value "user@example.com" node fill.js --selector "#password" --value "secret" node click.js --selector "button[type=submit]" # Close browser when done node navigate.js --url about:blank --close true
Session management:
- •
--close true: Close browser and clear session - •Default (no flag): Keep browser running for next script
Available Scripts
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
All in .claude/skills/chrome-devtools/scripts/:
| Script | Purpose |
|---|---|
navigate.js | Navigate to URLs |
screenshot.js | Capture screenshots (auto-compress >5MB via Sharp) |
click.js | Click elements |
fill.js | Fill form fields |
evaluate.js | Execute JS in page context |
snapshot.js | Extract interactive elements (JSON format) |
aria-snapshot.js | Get ARIA accessibility tree (YAML format with refs) |
select-ref.js | Interact with elements by ref from ARIA snapshot |
console.js | Monitor console messages/errors |
network.js | Track HTTP requests/responses |
performance.js | Measure Core Web Vitals |
Workflow Loop
- •Execute focused script for single task
- •Observe JSON output
- •Assess completion status
- •Decide next action
- •Repeat until done
Writing Custom Test Scripts
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
For complex automation, write scripts to <project>/.claude/chrome-devtools/tmp/:
# Create tmp directory for test scripts
mkdir -p $SKILL_DIR/.claude/chrome-devtools/tmp
# Write a test script
cat > $SKILL_DIR/.claude/chrome-devtools/tmp/login-test.js << 'EOF'
import { getBrowser, getPage, disconnectBrowser, outputJSON } from '../scripts/lib/browser.js';
async function loginTest() {
const browser = await getBrowser();
const page = await getPage(browser);
await page.goto('https://example.com/login');
await page.type('#email', 'user@example.com');
await page.type('#password', 'secret');
await page.click('button[type=submit]');
await page.waitForNavigation();
outputJSON({
success: true,
url: page.url(),
title: await page.title()
});
await disconnectBrowser();
}
loginTest();
EOF
# Run the test
node $SKILL_DIR/.claude/chrome-devtools/tmp/login-test.js
Key principles for custom scripts:
- •Single-purpose: one script, one task
- •Always call
disconnectBrowser()at the end (keeps browser running) - •Use
closeBrowser()only when ending session completely - •Output JSON for easy parsing
- •Plain JavaScript only in
page.evaluate()callbacks
Screenshots
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Store screenshots for analysis in <project>/.claude/chrome-devtools/screenshots/:
# Basic screenshot node screenshot.js --url https://example.com --output ./.claude/chrome-devtools/screenshots/page.png # Full page node screenshot.js --url https://example.com --output ./.claude/chrome-devtools/screenshots/page.png --full-page true # Specific element node screenshot.js --url https://example.com --selector ".main-content" --output ./.claude/chrome-devtools/screenshots/element.png
Auto-Compression (Sharp)
Screenshots >5MB auto-compress using Sharp (4-5x faster than ImageMagick):
# Default: compress if >5MB node screenshot.js --url https://example.com --output ./.claude/chrome-devtools/screenshots/page.png # Custom threshold (3MB) node screenshot.js --url https://example.com --output ./.claude/chrome-devtools/screenshots/page.png --max-size 3 # Disable compression node screenshot.js --url https://example.com --output ./.claude/chrome-devtools/screenshots/page.png --no-compress
Store screenshots for analysis in <project>/.claude/chrome-devtools/screenshots/.
Console Log Collection & Analysis
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Capture Logs
# Capture all logs for 10 seconds node console.js --url https://example.com --duration 10000 # Filter by type node console.js --url https://example.com --types error,warn --duration 5000
Session Storage Pattern
Store logs for analysis in <project>/.claude/chrome-devtools/logs/<session>/:
# Create session directory SESSION="$(date +%Y%m%d-%H%M%S)" mkdir -p .claude/chrome-devtools/logs/$SESSION # Capture and store node console.js --url https://example.com --duration 10000 > .claude/chrome-devtools/logs/$SESSION/console.json node network.js --url https://example.com > .claude/chrome-devtools/logs/$SESSION/network.json # View errors jq '.messages[] | select(.type=="error")' .claude/chrome-devtools/logs/$SESSION/console.json
Root Cause Analysis
# 1. Check for JavaScript errors node console.js --url https://example.com --types error,pageerror --duration 5000 | jq '.messages' # 2. Correlate with network failures node network.js --url https://example.com | jq '.requests[] | select(.response.status >= 400)' # 3. Check specific error stack traces node console.js --url https://example.com --types error --duration 5000 | jq '.messages[].stack'
Finding Elements
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Use snapshot.js to discover selectors before interacting:
# Get all interactive elements
node snapshot.js --url https://example.com | jq '.elements[] | {tagName, text, selector}'
# Find buttons
node snapshot.js --url https://example.com | jq '.elements[] | select(.tagName=="button")'
# Find by text content
node snapshot.js --url https://example.com | jq '.elements[] | select(.text | contains("Submit"))'
Error Recovery
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope. If script fails:
# 1. Capture current state (without navigating to preserve state)
node screenshot.js --output ./.claude/skills/chrome-devtools/screenshots/debug.png
# 2. Get console errors
node console.js --url about:blank --types error --duration 1000
# 3. Discover correct selector
node snapshot.js | jq '.elements[] | select(.text | contains("Submit"))'
# 4. Try XPath if CSS fails
node click.js --selector "//button[contains(text(),'Submit')]"
Common Patterns
Web Scraping
node evaluate.js --url https://example.com --script "
Array.from(document.querySelectorAll('.item')).map(el => ({
title: el.querySelector('h2')?.textContent,
link: el.querySelector('a')?.href
}))
" | jq '.result'
Form Automation
node navigate.js --url https://example.com/form node fill.js --selector "#search" --value "query" node click.js --selector "button[type=submit]"
Performance Testing
node performance.js --url https://example.com | jq '.vitals'
Script Options
All scripts support:
- •
--headless false- Show browser window - •
--close true- Close browser completely (default: stay running) - •
--timeout 30000- Set timeout (ms) - •
--wait-until networkidle2- Wait strategy Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Troubleshooting
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
| Error | Solution |
|---|---|
Cannot find package 'puppeteer' | Run npm install in scripts directory |
libnss3.so missing (Linux) | Run ./install-deps.sh |
| Element not found | Use snapshot.js to find correct selector |
| Script hangs | Use --timeout 60000 or --wait-until load |
| Screenshot >5MB | Auto-compressed; use --max-size 3 for lower |
| Session stale | Delete .browser-session.json and retry |
Screenshot Analysis: Missing Images
If images don't appear in screenshots, they may be waiting for animation triggers:
- •
Scroll-triggered animations: Scroll element into view first
bashnode evaluate.js --script "document.querySelector('.lazy-image').scrollIntoView()" # Wait for animation node evaluate.js --script "await new Promise(r => setTimeout(r, 1000))" node screenshot.js --output ./result.png - •
Sequential animation queue: Wait longer and retry
bash# First attempt node screenshot.js --url http://localhost:3000 --output ./attempt1.png # Wait for animations to complete node evaluate.js --script "await new Promise(r => setTimeout(r, 2000))" # Retry screenshot node screenshot.js --output ./attempt2.png
- •
Intersection Observer animations: Trigger by scrolling through page
bashnode evaluate.js --script "window.scrollTo(0, document.body.scrollHeight)" node evaluate.js --script "await new Promise(r => setTimeout(r, 1500))" node evaluate.js --script "window.scrollTo(0, 0)" node screenshot.js --output ./full-loaded.png --full-page true
Reference Documentation
- •
./references/cdp-domains.md- Chrome DevTools Protocol domains - •
./references/puppeteer-reference.md- Puppeteer API patterns - •
./references/performance-guide.md- Core Web Vitals optimization - •
./scripts/README.md- Detailed script options