| name | chrome-devtools |
| description | Automate browsers with Puppeteer CLI scripts and persistent sessions. Use for screenshots, performance analysis, network monitoring, web scraping, form automation, JavaScript debugging. |
| license | Apache-2.0 |
| version | 1.1.0 |
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.
SKILL_DIR=""
if [ -d ".opencode/skills/chrome-devtools/scripts" ]; then
SKILL_DIR=".opencode/skills/chrome-devtools/scripts"
elif [ -d "$HOME/.opencode/skills/chrome-devtools/scripts" ]; then
SKILL_DIR="$HOME/.opencode/skills/chrome-devtools/scripts"
fi
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
node "$SKILL_DIR/aria-snapshot.js" --url https://example.com
node "$SKILL_DIR/aria-snapshot.js" --url https://example.com --output ./.opencode/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:
node "$SKILL_DIR/select-ref.js" --ref e5 --action click
node "$SKILL_DIR/select-ref.js" --ref e10 --action fill --value "search query"
node "$SKILL_DIR/select-ref.js" --ref e8 --action text
node "$SKILL_DIR/select-ref.js" --ref e1 --action screenshot --output ./logo.png
node "$SKILL_DIR/select-ref.js" --ref e10 --action focus
node "$SKILL_DIR/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>/.opencode/chrome-devtools/snapshots/:
mkdir -p .opencode/chrome-devtools/snapshots
SESSION="$(date +%Y%m%d-%H%M%S)"
node "$SKILL_DIR/aria-snapshot.js" --url https://example.com --output .opencode/chrome-devtools/snapshots/$SESSION.yaml
Workflow: Unknown Page Structure
-
Get snapshot to discover elements:
node "$SKILL_DIR/aria-snapshot.js" --url https://example.com
-
Identify target from YAML output (e.g., [ref=e5] for a button)
-
Interact by ref:
node "$SKILL_DIR/select-ref.js" --ref e5 --action click
-
Verify result with screenshot or new snapshot:
node "$SKILL_DIR/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.
npx serve ./dist -p 3000 &
node "$SKILL_DIR/navigate.js" --url http://localhost:3000
python -m http.server 3000 --directory ./dist &
node "$SKILL_DIR/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
npm install --prefix "$SKILL_DIR"
node "$SKILL_DIR/navigate.js" --url https://example.com
Linux/WSL only: Run "$SKILL_DIR/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.
node "$SKILL_DIR/navigate.js" --url https://example.com/login
node "$SKILL_DIR/fill.js" --selector "#email" --value "user@example.com"
node "$SKILL_DIR/fill.js" --selector "#password" --value "secret"
node "$SKILL_DIR/click.js" --selector "button[type=submit]"
node "$SKILL_DIR/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 .opencode/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 |
ws-debug.js | Debug WebSocket connections (basic) |
ws-full-debug.js | Debug WebSocket with full events/frames |
inject-auth.js | Inject cookies/tokens for authentication |
import-cookies.js | Import cookies from JSON/Netscape file |
connect-chrome.js | Connect to Chrome with remote debugging |
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>/.opencode/chrome-devtools/tmp/:
mkdir -p $SKILL_DIR/.opencode/chrome-devtools/tmp
cat > $SKILL_DIR/.opencode/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
node $SKILL_DIR/.opencode/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>/.opencode/chrome-devtools/screenshots/:
node "$SKILL_DIR/screenshot.js" --url https://example.com --output ./.opencode/chrome-devtools/screenshots/page.png
node "$SKILL_DIR/screenshot.js" --url https://example.com --output ./.opencode/chrome-devtools/screenshots/page.png --full-page true
node "$SKILL_DIR/screenshot.js" --url https://example.com --selector ".main-content" --output ./.opencode/chrome-devtools/screenshots/element.png
Auto-Compression (Sharp)
Screenshots >5MB auto-compress using Sharp (4-5x faster than ImageMagick):
node "$SKILL_DIR/screenshot.js" --url https://example.com --output ./.opencode/chrome-devtools/screenshots/page.png
node "$SKILL_DIR/screenshot.js" --url https://example.com --output ./.opencode/chrome-devtools/screenshots/page.png --max-size 3
node "$SKILL_DIR/screenshot.js" --url https://example.com --output ./.opencode/chrome-devtools/screenshots/page.png --no-compress
Store screenshots for analysis in <project>/.opencode/chrome-devtools/screenshots/.
Console Log Collection & Analysis
Skills can exist in project-scope or user-scope. Priority: project-scope > user-scope.
Capture Logs
node "$SKILL_DIR/console.js" --url https://example.com --duration 10000
node "$SKILL_DIR/console.js" --url https://example.com --types error,warn --duration 5000
Session Storage Pattern
Store logs for analysis in <project>/.opencode/chrome-devtools/logs/<session>/:
SESSION="$(date +%Y%m%d-%H%M%S)"
mkdir -p .opencode/chrome-devtools/logs/$SESSION
node "$SKILL_DIR/console.js" --url https://example.com --duration 10000 > .opencode/chrome-devtools/logs/$SESSION/console.json
node "$SKILL_DIR/network.js" --url https://example.com > .opencode/chrome-devtools/logs/$SESSION/network.json
jq '.messages[] | select(.type=="error")' .opencode/chrome-devtools/logs/$SESSION/console.json
Root Cause Analysis
node "$SKILL_DIR/console.js" --url https://example.com --types error,pageerror --duration 5000 | jq '.messages'
node "$SKILL_DIR/network.js" --url https://example.com | jq '.requests[] | select(.response.status >= 400)'
node "$SKILL_DIR/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:
node "$SKILL_DIR/snapshot.js" --url https://example.com | jq '.elements[] | {tagName, text, selector}'
node "$SKILL_DIR/snapshot.js" --url https://example.com | jq '.elements[] | select(.tagName=="button")'
node "$SKILL_DIR/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:
node "$SKILL_DIR/screenshot.js" --output ./.opencode/skills/chrome-devtools/screenshots/debug.png
node "$SKILL_DIR/console.js" --url about:blank --types error --duration 1000
node "$SKILL_DIR/snapshot.js" | jq '.elements[] | select(.text | contains("Submit"))'
node "$SKILL_DIR/click.js" --selector "//button[contains(text(),'Submit')]"
Common Patterns
Web Scraping
node "$SKILL_DIR/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 "$SKILL_DIR/navigate.js" --url https://example.com/form
node "$SKILL_DIR/fill.js" --selector "#search" --value "query"
node "$SKILL_DIR/click.js" --selector "button[type=submit]"
Performance Testing
node "$SKILL_DIR/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
node "$SKILL_DIR/evaluate.js" --script "document.querySelector('.lazy-image').scrollIntoView()"
node "$SKILL_DIR/evaluate.js" --script "await new Promise(r => setTimeout(r, 1000))"
node "$SKILL_DIR/screenshot.js" --output ./result.png
-
Sequential animation queue: Wait longer and retry
node "$SKILL_DIR/screenshot.js" --url http://localhost:3000 --output ./attempt1.png
node "$SKILL_DIR/evaluate.js" --script "await new Promise(r => setTimeout(r, 2000))"
node "$SKILL_DIR/screenshot.js" --output ./attempt2.png
-
Intersection Observer animations: Trigger by scrolling through page
node "$SKILL_DIR/evaluate.js" --script "window.scrollTo(0, document.body.scrollHeight)"
node "$SKILL_DIR/evaluate.js" --script "await new Promise(r => setTimeout(r, 1500))"
node "$SKILL_DIR/evaluate.js" --script "window.scrollTo(0, 0)"
node "$SKILL_DIR/screenshot.js" --output ./full-loaded.png --full-page true
Authentication & Cookies
For accessing protected/authenticated pages, use one of these methods:
Method 1: Inject Cookies Directly
Use when you have cookie values (from DevTools or manual extraction):
node "$SKILL_DIR/inject-auth.js" --url https://site.com \
--cookies '[{"name":"session","value":"abc123","domain":".site.com"}]'
node "$SKILL_DIR/inject-auth.js" --url https://site.com \
--cookies '[{"name":"session","value":"abc","domain":".site.com","httpOnly":true,"secure":true}]'
node "$SKILL_DIR/inject-auth.js" --url https://api.site.com \
--token "Bearer eyJhbG..." --header Authorization
Method 2: Import from Browser Extension
Best for complex auth (OAuth, multi-cookie sessions):
node "$SKILL_DIR/import-cookies.js" --file ./cookies.json --url https://site.com
node "$SKILL_DIR/import-cookies.js" --file ./cookies.txt --format netscape --url https://site.com
node "$SKILL_DIR/import-cookies.js" --file ./cookies.json --url https://site.com --strict-domain
Method 3: Use Your Chrome Profile
Most reliable for complex auth (2FA, OAuth, SSO). Uses your existing Chrome session:
node "$SKILL_DIR/navigate.js" --url https://site.com --use-default-profile true
node "$SKILL_DIR/navigate.js" --url https://site.com --profile "/path/to/chrome/profile"
⚠️ Important: Chrome must be fully closed when using its profile (single instance lock).
Profile paths by OS:
- macOS:
~/Library/Application Support/Google/Chrome
- Windows:
%LOCALAPPDATA%/Google/Chrome/User Data
- Linux:
~/.config/google-chrome
Method 4: Connect to Running Chrome
Best for debugging (can see browser window while scripts run):
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
google-chrome --remote-debugging-port=9222
node "$SKILL_DIR/connect-chrome.js" --browser-url http://localhost:9222 --url https://site.com
node "$SKILL_DIR/connect-chrome.js" --launch --port 9222 --url https://site.com
Session Persistence
Auth sessions are saved to .auth-session.json for 24-hour reuse:
node "$SKILL_DIR/inject-auth.js" --url https://site.com --cookies '[...]'
node "$SKILL_DIR/navigate.js" --url https://site.com/dashboard
node "$SKILL_DIR/screenshot.js" --url https://site.com/profile --output ./profile.png
node "$SKILL_DIR/inject-auth.js" --url https://site.com --clear true
Choosing the Right Method
| Method | Best For | Complexity |
|---|
| Inject cookies | Simple session cookies, API tokens | Low |
| Import from extension | Multi-cookie auth, OAuth tokens | Medium |
| Chrome profile | 2FA, SSO, complex OAuth flows | Low* |
| Connect to Chrome | Debugging, visual verification | Medium |
*Requires Chrome to be closed first
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