browsing
Use when you need direct browser control - teaches Chrome DevTools Protocol for controlling existing browser sessions, multi-tab management, form automation, and content extraction via use_browser MCP tool
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install obra-superpowers-chrome-browsing
Repository
Skill path: skills/browsing
Use when you need direct browser control - teaches Chrome DevTools Protocol for controlling existing browser sessions, multi-tab management, form automation, and content extraction via use_browser MCP tool
Open repositoryBest for
Primary workflow: Write Technical Docs.
Technical facets: Full Stack, Tech Writer, Integration.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: obra.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install browsing into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/obra/superpowers-chrome before adding browsing to shared team environments
- Use browsing for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: browsing
description: Use when you need direct browser control - teaches Chrome DevTools Protocol for controlling existing browser sessions, multi-tab management, form automation, and content extraction via use_browser MCP tool
allowed-tools: mcp__chrome__use_browser
---
# Browsing with Chrome Direct
## Overview
Control Chrome via DevTools Protocol using the `use_browser` MCP tool. Single unified interface with auto-starting Chrome.
**Announce:** "I'm using the browsing skill to control Chrome."
## When to Use
**Use this when:**
- Controlling authenticated sessions
- Managing multiple tabs in running browser
- Playwright MCP unavailable or excessive
**Use Playwright MCP when:**
- Need fresh browser instances
- Generating screenshots/PDFs
- Prefer higher-level abstractions
## The use_browser Tool
Single MCP tool with action-based interface. Chrome auto-starts on first use.
**Parameters:**
- `action` (required): Operation to perform
- `tab_index` (optional): Tab to operate on (default: 0)
- `selector` (optional): CSS selector for element operations
- `payload` (optional): Action-specific data
- `timeout` (optional): Timeout in ms for await operations (default: 5000)
## Actions Reference
### Navigation
- **navigate**: Navigate to URL
- `payload`: URL string
- Example: `{action: "navigate", payload: "https://example.com"}`
- **await_element**: Wait for element to appear
- `selector`: CSS selector
- `timeout`: Max wait time in ms
- Example: `{action: "await_element", selector: ".loaded", timeout: 10000}`
- **await_text**: Wait for text to appear
- `payload`: Text to wait for
- Example: `{action: "await_text", payload: "Welcome"}`
### Interaction
- **click**: Click element
- `selector`: CSS selector
- Example: `{action: "click", selector: "button.submit"}`
- **type**: Type text into input (append `\n` to submit)
- `selector`: CSS selector
- `payload`: Text to type
- Example: `{action: "type", selector: "#email", payload: "[email protected]\n"}`
- **select**: Select dropdown option
- `selector`: CSS selector
- `payload`: Option value(s)
- Example: `{action: "select", selector: "select[name=state]", payload: "CA"}`
### Extraction
- **extract**: Get page content
- `payload`: Format ('markdown'|'text'|'html')
- `selector`: Optional - limit to element
- Example: `{action: "extract", payload: "markdown"}`
- Example: `{action: "extract", payload: "text", selector: "h1"}`
- **attr**: Get element attribute
- `selector`: CSS selector
- `payload`: Attribute name
- Example: `{action: "attr", selector: "a.download", payload: "href"}`
- **eval**: Execute JavaScript
- `payload`: JavaScript code
- Example: `{action: "eval", payload: "document.title"}`
### Export
- **screenshot**: Capture screenshot
- `payload`: Filename
- `selector`: Optional - screenshot specific element
- Example: `{action: "screenshot", payload: "/tmp/page.png"}`
### Tab Management
- **list_tabs**: List all open tabs
- Example: `{action: "list_tabs"}`
- **new_tab**: Create new tab
- Example: `{action: "new_tab"}`
- **close_tab**: Close tab
- `tab_index`: Tab to close
- Example: `{action: "close_tab", tab_index: 2}`
### Browser Mode Control
- **show_browser**: Make browser window visible (headed mode)
- Example: `{action: "show_browser"}`
- ⚠️ **WARNING**: Restarts Chrome, reloads pages via GET, loses POST state
- **hide_browser**: Switch to headless mode (invisible browser)
- Example: `{action: "hide_browser"}`
- ⚠️ **WARNING**: Restarts Chrome, reloads pages via GET, loses POST state
- **browser_mode**: Check current browser mode and profile
- Example: `{action: "browser_mode"}`
- Returns: `{"headless": true|false, "mode": "headless"|"headed", "running": true|false, "profile": "name", "profileDir": "/path"}`
### Profile Management
- **set_profile**: Change Chrome profile (must kill Chrome first)
- Example: `{action: "set_profile", "payload": "browser-user"}`
- ⚠️ **WARNING**: Chrome must be stopped first
- **get_profile**: Get current profile name and directory
- Example: `{action: "get_profile"}`
- Returns: `{"profile": "name", "profileDir": "/path"}`
**Default behavior**: Chrome starts in **headless mode** with **"superpowers-chrome" profile**.
**Critical caveats when toggling modes**:
1. **Chrome must restart** - Cannot switch headless/headed mode on running Chrome
2. **Pages reload via GET** - All open tabs are reopened with GET requests
3. **POST state is lost** - Form submissions, POST results, and POST-based navigation will be lost
4. **Session state is lost** - Any client-side state (JavaScript variables, etc.) is cleared
5. **Cookies/auth may persist** - Uses same user data directory, so logged-in sessions may survive
**When to use headed mode**:
- Debugging visual rendering issues
- Demonstrating browser behavior to user
- Testing features that only work with visible browser
- Debugging issues that don't reproduce in headless mode
**When to stay in headless mode** (default):
- All other cases - faster, cleaner, less intrusive
- Screenshots work perfectly in headless mode
- Most automation works identically in both modes
**Profile management**:
Profiles store persistent browser data (cookies, localStorage, extensions, auth sessions).
**Profile locations**:
- macOS: `~/Library/Caches/superpowers/browser-profiles/{name}/`
- Linux: `~/.cache/superpowers/browser-profiles/{name}/`
- Windows: `%LOCALAPPDATA%/superpowers/browser-profiles/{name}/`
**When to use separate profiles**:
- **Default profile ("superpowers-chrome")**: General automation, shared sessions
- **Agent-specific profiles**: Isolate different agents' browser state
- Example: browser-user agent uses "browser-user" profile
- **Task-specific profiles**: Testing with different user contexts
- Example: "test-logged-in" vs "test-logged-out"
**Profile data persists across**:
- Chrome restarts
- Mode toggles (headless ↔ headed)
- System reboots (data is in cache directory)
**To use a different profile**:
1. Kill Chrome if running: `await chromeLib.killChrome()`
2. Set profile: `{action: "set_profile", "payload": "my-profile"}`
3. Start Chrome: Next navigate/action will use new profile
## Quick Start Pattern
```
Navigate and extract:
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "h1"}
{action: "extract", payload: "text", selector: "h1"}
```
## Common Patterns
### Fill and Submit Form
```
{action: "navigate", payload: "https://example.com/login"}
{action: "await_element", selector: "input[name=email]"}
{action: "type", selector: "input[name=email]", payload: "[email protected]"}
{action: "type", selector: "input[name=password]", payload: "pass123\n"}
{action: "await_text", payload: "Welcome"}
```
The `\n` at the end of the password submits the form.
### Multi-Tab Workflow
```
{action: "list_tabs"}
{action: "click", tab_index: 2, selector: "a.email"}
{action: "await_element", tab_index: 2, selector: ".content"}
{action: "extract", tab_index: 2, payload: "text", selector: ".amount"}
```
### Dynamic Content
```
{action: "navigate", payload: "https://example.com"}
{action: "type", selector: "input[name=q]", payload: "query"}
{action: "click", selector: "button.search"}
{action: "await_element", selector: ".results"}
{action: "extract", payload: "text", selector: ".result-title"}
```
### Get Link Attribute
```
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "a.download"}
{action: "attr", selector: "a.download", payload: "href"}
```
### Execute JavaScript
```
{action: "eval", payload: "document.querySelectorAll('a').length"}
{action: "eval", payload: "Array.from(document.querySelectorAll('a')).map(a => a.href)"}
```
## Tips
**Always wait before interaction:**
Don't click or fill immediately after navigate - pages need time to load.
```
// BAD - might fail if page slow
{action: "navigate", payload: "https://example.com"}
{action: "click", selector: "button"} // May fail!
// GOOD - wait first
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "button"}
{action: "click", selector: "button"}
```
**Use specific selectors:**
Avoid generic selectors that match multiple elements.
```
// BAD - matches first button
{action: "click", selector: "button"}
// GOOD - specific
{action: "click", selector: "button[type=submit]"}
{action: "click", selector: "#login-button"}
```
**Submit forms with \n:**
Append newline to text to submit forms automatically.
```
{action: "type", selector: "#search", payload: "query\n"}
```
**Check content first:**
Extract page content to verify selectors before building workflow.
```
{action: "extract", payload: "html"}
```
## Troubleshooting
**Element not found:**
- Use `await_element` before interaction
- Verify selector with `extract` action using 'html' format
**Timeout errors:**
- Increase timeout: `{timeout: 30000}` for slow pages
- Wait for specific element instead of text
**Tab index out of range:**
- Use `list_tabs` to get current indices
- Tab indices change when tabs close
**eval returns `[object Object]`:**
- Use `JSON.stringify()` for complex objects: `{action: "eval", payload: "JSON.stringify({name: 'test'})"}`
- For async functions: `{action: "eval", payload: "JSON.stringify(await yourAsyncFunction())"}`
## Test Automation (Advanced)
<details>
<summary>Click to expand test automation guidance</summary>
When building test automation, you have two approaches:
### Approach 1: use_browser MCP (Simple Tests)
Best for: Single-step tests, direct Claude control during conversation
```json
{"action": "navigate", "payload": "https://app.com"}
{"action": "click", "selector": "#test-button"}
{"action": "eval", "payload": "JSON.stringify({passed: document.querySelector('.success') !== null})"}
```
### Approach 2: chrome-ws CLI (Complex Tests)
Best for: Multi-step test suites, standalone automation scripts
**Key insight**: `chrome-ws` is the reference implementation showing proper Chrome DevTools Protocol usage. When `use_browser` doesn't work as expected, examine how `chrome-ws` handles the same operation.
```bash
# Example: Automated form testing
./chrome-ws navigate 0 "https://app.com/form"
./chrome-ws fill 0 "#email" "[email protected]"
./chrome-ws click 0 "button[type=submit]"
./chrome-ws wait-text 0 "Success"
```
### When use_browser Fails
1. **Check chrome-ws source code** - It shows the correct CDP pattern
2. **Use chrome-ws to verify** - Test the same operation via CLI
3. **Adapt the pattern** - Apply the working CDP approach to use_browser
### Common Test Automation Patterns
- **Form validation**: Fill forms, check error states
- **UI state testing**: Click elements, verify DOM changes
- **Performance testing**: Measure load times, capture metrics
- **Screenshot comparison**: Capture before/after states
</details>
## Advanced Usage
For command-line usage outside Claude Code, see [COMMANDLINE-USAGE.md](COMMANDLINE-USAGE.md).
For detailed examples, see [EXAMPLES.md](EXAMPLES.md).
## Protocol Reference
Full CDP documentation: https://chromedevtools.github.io/devtools-protocol/
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### COMMANDLINE-USAGE.md
```markdown
# Command-Line Usage: chrome-ws Tool
Direct command-line access to Chrome DevTools Protocol via the `chrome-ws` bash tool.
**Note**: For use within Claude Code, the MCP `use_browser` tool is recommended. This document is for direct command-line usage or integration with other tools.
## Setup
```bash
cd ~/.claude/plugins/cache/using-chrome-directly/skills/using-chrome-directly
chmod +x chrome-ws
./chrome-ws start # Auto-detects platform, launches Chrome
./chrome-ws tabs # Verify running
```
Chrome starts with `--remote-debugging-port=9222` and separate profile in `/tmp/chrome-debug` (or `C:\temp\chrome-debug` on Windows).
## Command Reference
**Setup:**
```bash
chrome-ws start # Launch Chrome (auto-detects platform)
```
**Tab Management:**
```bash
chrome-ws tabs # List tabs
chrome-ws new <url> # Create tab
chrome-ws close <ws-url> # Close tab
```
**Navigation:**
```bash
chrome-ws navigate <tab> <url> # Navigate
chrome-ws wait-for <tab> <selector> # Wait for element
chrome-ws wait-text <tab> <text> # Wait for text
```
**Interaction:**
```bash
chrome-ws click <tab> <selector> # Click
chrome-ws fill <tab> <selector> <value> # Fill input
chrome-ws select <tab> <selector> <value> # Select dropdown
```
**Extraction:**
```bash
chrome-ws eval <tab> <js> # Execute JavaScript
chrome-ws extract <tab> <selector> # Get text content
chrome-ws attr <tab> <selector> <attr> # Get attribute
chrome-ws html <tab> [selector] # Get HTML
```
**Export:**
```bash
chrome-ws screenshot <tab> <file.png> # Capture screenshot
chrome-ws markdown <tab> <file.md> # Save as markdown
```
**Raw Protocol:**
```bash
chrome-ws raw <ws-url> <json-rpc> # Direct CDP access
```
`<tab>` accepts either tab index (0, 1, 2) or full WebSocket URL.
## Examples
### Basic Operations
**Extract page content:**
```bash
chrome-ws navigate 0 "https://example.com"
chrome-ws wait-for 0 "h1"
# Get page title
TITLE=$(chrome-ws eval 0 "document.title")
# Get main heading
HEADING=$(chrome-ws extract 0 "h1")
# Get first link URL
LINK=$(chrome-ws attr 0 "a" "href")
```
**Get all links:**
```bash
chrome-ws navigate 0 "https://example.com"
LINKS=$(chrome-ws eval 0 "Array.from(document.querySelectorAll('a')).map(a => ({
text: a.textContent.trim(),
href: a.href
}))")
echo "$LINKS"
```
**Extract table data:**
```bash
chrome-ws navigate 0 "https://example.com/data"
chrome-ws wait-for 0 "table"
# Convert table to JSON array
TABLE=$(chrome-ws eval 0 "
Array.from(document.querySelectorAll('table tr')).map(row =>
Array.from(row.cells).map(cell => cell.textContent.trim())
)
")
```
### Form Automation
**Simple login:**
```bash
chrome-ws navigate 0 "https://app.example.com/login"
chrome-ws wait-for 0 "input[name=email]"
# Fill credentials
chrome-ws fill 0 "input[name=email]" "[email protected]"
chrome-ws fill 0 "input[name=password]" "securepass123"
# Submit and wait for dashboard
chrome-ws click 0 "button[type=submit]"
chrome-ws wait-text 0 "Dashboard"
```
**Multi-step form:**
```bash
chrome-ws navigate 0 "https://example.com/register"
# Step 1: Personal information
chrome-ws fill 0 "input[name=firstName]" "John"
chrome-ws fill 0 "input[name=lastName]" "Doe"
chrome-ws fill 0 "input[name=email]" "[email protected]"
chrome-ws click 0 "button.next"
# Wait for step 2 to load
chrome-ws wait-for 0 "input[name=address]"
# Step 2: Address
chrome-ws fill 0 "input[name=address]" "123 Main St"
chrome-ws select 0 "select[name=state]" "IL"
chrome-ws fill 0 "input[name=zip]" "62701"
chrome-ws click 0 "button.submit"
chrome-ws wait-text 0 "Registration complete"
```
**Search with filters:**
```bash
chrome-ws navigate 0 "https://library.example.com/search"
chrome-ws wait-for 0 "form"
# Select category dropdown
chrome-ws select 0 "select[name=category]" "books"
# Fill search term
chrome-ws fill 0 "input[name=query]" "chrome devtools"
# Submit search
chrome-ws click 0 "button[type=submit]"
chrome-ws wait-for 0 ".results"
# Count results
RESULTS=$(chrome-ws eval 0 "document.querySelectorAll('.result').length")
echo "Found $RESULTS results"
```
### Web Scraping
**Article content:**
```bash
chrome-ws navigate 0 "https://blog.example.com/article"
chrome-ws wait-for 0 "article"
# Extract metadata
TITLE=$(chrome-ws extract 0 "article h1")
AUTHOR=$(chrome-ws extract 0 ".author-name")
DATE=$(chrome-ws extract 0 "time")
CONTENT=$(chrome-ws extract 0 "article .content")
# Save to file
cat > article.txt <<EOF
Title: $TITLE
Author: $AUTHOR
Date: $DATE
$CONTENT
EOF
```
**Product information:**
```bash
chrome-ws navigate 0 "https://shop.example.com/product/123"
chrome-ws wait-for 0 ".product-details"
NAME=$(chrome-ws extract 0 "h1.product-name")
PRICE=$(chrome-ws extract 0 ".price")
IMAGE=$(chrome-ws attr 0 ".product-image img" "src")
STOCK=$(chrome-ws extract 0 ".stock-status")
# Output as JSON
cat <<EOF
{
"name": "$NAME",
"price": "$PRICE",
"image": "$IMAGE",
"in_stock": "$STOCK"
}
EOF
```
**Batch process URLs:**
```bash
URLS=("page1" "page2" "page3")
for URL in "${URLS[@]}"; do
chrome-ws navigate 0 "https://example.com/$URL"
chrome-ws wait-for 0 "h1"
TITLE=$(chrome-ws extract 0 "h1")
echo "$URL: $TITLE" >> results.txt
done
```
### Multi-Tab Workflows
**Email extraction:**
```bash
# List all tabs
chrome-ws tabs
# Use the email tab index from output (e.g., tab 2)
EMAIL_TAB=2
# Click specific email
chrome-ws click $EMAIL_TAB "a[title*='Organization receipt']"
# Wait for email to load
chrome-ws wait-for $EMAIL_TAB ".email-body"
# Extract donation amount
AMOUNT=$(chrome-ws extract $EMAIL_TAB ".donation-amount")
echo "Donation: $AMOUNT"
```
**Price comparison:**
```bash
chrome-ws navigate 0 "https://store1.com/product"
chrome-ws new "https://store2.com/product"
chrome-ws new "https://store3.com/product"
sleep 3 # Let pages load
PRICE1=$(chrome-ws extract 0 ".price")
PRICE2=$(chrome-ws extract 1 ".price")
PRICE3=$(chrome-ws extract 2 ".price")
echo "Store 1: $PRICE1"
echo "Store 2: $PRICE2"
echo "Store 3: $PRICE3"
```
**Cross-reference between sites:**
```bash
# Get phone number from company site
chrome-ws navigate 0 "https://company.com/contact"
chrome-ws wait-for 0 ".phone"
PHONE=$(chrome-ws extract 0 ".phone")
# Look up phone number in verification site
chrome-ws new "https://lookup.com"
chrome-ws fill 1 "input[name=phone]" "$PHONE"
chrome-ws click 1 "button.search"
chrome-ws wait-for 1 ".results"
chrome-ws extract 1 ".verification-status"
```
### Dynamic Content
**Wait for AJAX to complete:**
```bash
chrome-ws navigate 0 "https://app.com/dashboard"
# Wait for spinner to disappear
chrome-ws eval 0 "new Promise(resolve => {
const check = () => {
if (!document.querySelector('.spinner')) {
resolve(true);
} else {
setTimeout(check, 100);
}
};
check();
})"
# Now safe to extract
chrome-ws extract 0 ".dashboard-data"
```
**Infinite scroll:**
```bash
chrome-ws navigate 0 "https://example.com/feed"
chrome-ws wait-for 0 ".feed-item"
# Scroll 5 times
for i in {1..5}; do
chrome-ws eval 0 "window.scrollTo(0, document.body.scrollHeight)"
sleep 2
done
# Count loaded items
chrome-ws eval 0 "document.querySelectorAll('.feed-item').length"
```
**Monitor for changes:**
```bash
chrome-ws navigate 0 "https://example.com/status"
END=$(($(date +%s) + 300))
while [ $(date +%s) -lt $END ]; do
STATUS=$(chrome-ws extract 0 ".status")
echo "[$(date +%H:%M:%S)] $STATUS"
if [[ "$STATUS" == *"ERROR"* ]]; then
echo "ALERT: Error detected"
break
fi
sleep 10
done
```
### Advanced Patterns
**Multi-step workflow:**
```bash
chrome-ws navigate 0 "https://booking.example.com"
# Search
chrome-ws fill 0 "input[name=destination]" "San Francisco"
chrome-ws fill 0 "input[name=checkin]" "2025-12-01"
chrome-ws click 0 "button.search"
# Select hotel
chrome-ws wait-for 0 ".hotel-results"
chrome-ws click 0 ".hotel-card:first-child .select"
# Choose room
chrome-ws wait-for 0 ".room-options"
chrome-ws click 0 ".room[data-type=deluxe] .book"
# Fill guest info
chrome-ws wait-for 0 "form.guest-info"
chrome-ws fill 0 "input[name=firstName]" "Jane"
chrome-ws fill 0 "input[name=lastName]" "Smith"
chrome-ws fill 0 "input[name=email]" "[email protected]"
# Review
chrome-ws click 0 "button.review"
chrome-ws wait-for 0 ".summary"
# Extract confirmation
HOTEL=$(chrome-ws extract 0 ".hotel-name")
TOTAL=$(chrome-ws extract 0 ".total-price")
echo "$HOTEL: $TOTAL"
```
**Cookies and localStorage:**
```bash
# Get cookies
chrome-ws eval 0 "document.cookie"
# Set cookie
chrome-ws eval 0 "document.cookie = 'theme=dark; path=/'"
# Get localStorage
chrome-ws eval 0 "JSON.stringify(localStorage)"
# Set localStorage
chrome-ws eval 0 "localStorage.setItem('lastVisit', new Date().toISOString())"
```
**Handle modals:**
```bash
chrome-ws click 0 "button.open-modal"
chrome-ws wait-for 0 ".modal.visible"
# Fill modal form
chrome-ws fill 0 ".modal input[name=username]" "testuser"
chrome-ws click 0 ".modal button.submit"
# Wait for modal to close
chrome-ws eval 0 "new Promise(resolve => {
const check = () => {
if (!document.querySelector('.modal.visible')) {
resolve(true);
} else {
setTimeout(check, 100);
}
};
check();
})"
```
**Network monitoring with raw CDP:**
```bash
# Enable network monitoring
chrome-ws raw 0 '{"id":1,"method":"Network.enable","params":{}}'
# Navigate and capture traffic
chrome-ws navigate 0 "https://api.example.com"
# Get performance metrics
chrome-ws raw 0 '{"id":2,"method":"Performance.getMetrics","params":{}}'
```
**Screenshots and PDF:**
```bash
# Capture screenshot
chrome-ws screenshot 0 "page.png"
# Or use raw CDP for more control
SCREENSHOT=$(chrome-ws raw 0 '{
"id":1,
"method":"Page.captureScreenshot",
"params":{"format":"png","quality":80}
}')
# Extract base64 and save
echo "$SCREENSHOT" | node -pe "JSON.parse(require('fs').readFileSync(0)).result.data" | base64 -d > screenshot.png
```
## Error Handling
**Check element exists:**
```bash
# Verify button exists
EXISTS=$(chrome-ws eval 0 "!!document.querySelector('.important-button')")
if [ "$EXISTS" = "true" ]; then
chrome-ws click 0 ".important-button"
else
echo "Button not found on page"
fi
```
**Verify command success:**
```bash
if ! chrome-ws navigate 0 "https://example.com"; then
echo "Navigation failed - Chrome not running?"
exit 1
fi
```
**Retry pattern:**
```bash
for attempt in {1..3}; do
if chrome-ws click 0 ".submit-button"; then
echo "Click succeeded"
break
fi
echo "Attempt $attempt failed, retrying..."
sleep 2
done
```
## Best Practices
**Always wait before interaction:**
```bash
# BAD - might fail if page slow to load
chrome-ws navigate 0 "https://example.com"
chrome-ws click 0 "button" # May fail!
# GOOD - wait for element first
chrome-ws navigate 0 "https://example.com"
chrome-ws wait-for 0 "button"
chrome-ws click 0 "button"
```
**Use specific selectors:**
```bash
# BAD - matches first button on page
chrome-ws click 0 "button"
# GOOD - specific selector
chrome-ws click 0 "button[type=submit]"
chrome-ws click 0 "button.login-button"
chrome-ws click 0 "#submit-form"
```
**Test selectors with html command:**
```bash
# Check page structure
chrome-ws html 0 | grep "submit"
# Check specific element exists
chrome-ws html 0 "form"
```
**Escape special characters:**
```bash
# Use double quotes for variables
chrome-ws fill 0 "input[name=search]" "$SEARCH_TERM"
# Use single quotes for literal strings with special chars
chrome-ws eval 0 'document.querySelector(".item").textContent'
```
## Common Pitfalls
**Don't cache tab indices** - they change when tabs close:
```bash
# BAD - index might be stale
TAB=2
# ... much later ...
chrome-ws click $TAB "button" # Tab 2 might not exist anymore
# GOOD - fetch fresh before use
chrome-ws tabs
chrome-ws click 2 "button"
```
**Don't forget to wait for dynamic content:**
```bash
# BAD - tries to extract before content loads
chrome-ws navigate 0 "https://app.com"
chrome-ws extract 0 ".user-name" # Might be empty!
# GOOD - wait for content
chrome-ws navigate 0 "https://app.com"
chrome-ws wait-for 0 ".user-name"
chrome-ws extract 0 ".user-name"
```
**Handle element state:**
```bash
# Check if button is disabled
DISABLED=$(chrome-ws eval 0 "document.querySelector('button.submit').disabled")
if [ "$DISABLED" = "false" ]; then
chrome-ws click 0 "button.submit"
else
echo "Button is disabled"
fi
```
## Troubleshooting
**Connection refused:** Verify Chrome running with `curl http://127.0.0.1:9222/json`
**Element not found:** Check page structure with `chrome-ws html 0`
**Timeout:** Use `wait-for` before interaction. Chrome has 30s timeout.
**Tab index out of range:** Run `chrome-ws tabs` to get current indices.
## Protocol Reference
Full CDP documentation: https://chromedevtools.github.io/devtools-protocol/
Common methods via `raw` command:
- `Page.navigate`
- `Runtime.evaluate`
- `Network.enable`
- `Performance.getMetrics`
```
### EXAMPLES.md
```markdown
# Chrome Direct Access Examples (MCP Tool)
Examples using the `use_browser` MCP tool. For command-line bash examples, see [COMMANDLINE-USAGE.md](COMMANDLINE-USAGE.md).
## Table of Contents
1. [Basic Operations](#basic-operations)
2. [Form Automation](#form-automation)
3. [Web Scraping](#web-scraping)
4. [Multi-Tab Workflows](#multi-tab-workflows)
5. [Dynamic Content](#dynamic-content)
6. [Advanced Patterns](#advanced-patterns)
---
## Basic Operations
### Extract Page Content
Navigate to a page and extract various elements:
```
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "h1"}
// Get page title
{action: "eval", payload: "document.title"}
// Get main heading text
{action: "extract", payload: "text", selector: "h1"}
// Get first link URL
{action: "attr", selector: "a", payload: "href"}
```
### Get All Links
Use JavaScript evaluation to get structured data:
```
{action: "navigate", payload: "https://example.com"}
{action: "eval", payload: "Array.from(document.querySelectorAll('a')).map(a => ({ text: a.textContent.trim(), href: a.href }))"}
```
### Extract Table Data
Convert HTML table to structured data:
```
{action: "navigate", payload: "https://example.com/data"}
{action: "await_element", selector: "table"}
// Convert table to JSON array
{action: "eval", payload: "Array.from(document.querySelectorAll('table tr')).map(row => Array.from(row.cells).map(cell => cell.textContent.trim()))"}
```
### Get Page as Markdown
Extract entire page content in markdown format:
```
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "body"}
{action: "extract", payload: "markdown"}
```
---
## Form Automation
### Simple Login
Navigate, fill credentials, and submit:
```
{action: "navigate", payload: "https://app.example.com/login"}
{action: "await_element", selector: "input[name=email]"}
// Fill credentials
{action: "type", selector: "input[name=email]", payload: "[email protected]"}
{action: "type", selector: "input[name=password]", payload: "securepass123\n"}
// Wait for successful login
{action: "await_text", payload: "Dashboard"}
```
Note: The `\n` at the end of the password submits the form.
### Multi-Step Form
Handle forms that show steps progressively:
```
{action: "navigate", payload: "https://example.com/register"}
// Step 1: Personal information
{action: "type", selector: "input[name=firstName]", payload: "John"}
{action: "type", selector: "input[name=lastName]", payload: "Doe"}
{action: "type", selector: "input[name=email]", payload: "[email protected]"}
{action: "click", selector: "button.next"}
// Wait for step 2 to load
{action: "await_element", selector: "input[name=address]"}
// Step 2: Address
{action: "type", selector: "input[name=address]", payload: "123 Main St"}
{action: "select", selector: "select[name=state]", payload: "IL"}
{action: "type", selector: "input[name=zip]", payload: "62701"}
{action: "click", selector: "button.submit"}
{action: "await_text", payload: "Registration complete"}
```
### Search with Filters
Use dropdowns and text inputs together:
```
{action: "navigate", payload: "https://library.example.com/search"}
{action: "await_element", selector: "form"}
// Select category dropdown
{action: "select", selector: "select[name=category]", payload: "books"}
// Fill search term
{action: "type", selector: "input[name=query]", payload: "chrome devtools"}
// Submit and count results
{action: "click", selector: "button[type=submit]"}
{action: "await_element", selector: ".results"}
// Count results
{action: "eval", payload: "document.querySelectorAll('.result').length"}
```
---
## Web Scraping
### Article Content
Extract article metadata and content:
```
{action: "navigate", payload: "https://blog.example.com/article"}
{action: "await_element", selector: "article"}
// Extract metadata
{action: "extract", payload: "text", selector: "article h1"}
{action: "extract", payload: "text", selector: ".author-name"}
{action: "extract", payload: "text", selector: "time"}
{action: "extract", payload: "text", selector: "article .content"}
```
### Product Information
Scrape product details from e-commerce site:
```
{action: "navigate", payload: "https://shop.example.com/product/123"}
{action: "await_element", selector: ".product-details"}
// Extract product data
{action: "extract", payload: "text", selector: "h1.product-name"}
{action: "extract", payload: "text", selector: ".price"}
{action: "attr", selector: ".product-image img", payload: "src"}
{action: "extract", payload: "text", selector: ".stock-status"}
```
### Batch Extract Structured Data
Get multiple products at once using JavaScript:
```
{action: "navigate", payload: "https://shop.example.com/category/electronics"}
{action: "await_element", selector: ".product-grid"}
// Extract all products as structured data
{action: "eval", payload: `
Array.from(document.querySelectorAll('.product-card')).map(card => ({
name: card.querySelector('.product-name').textContent,
price: card.querySelector('.price').textContent,
image: card.querySelector('img').src,
url: card.querySelector('a').href
}))
`}
```
---
## Multi-Tab Workflows
### Email Extraction
List tabs, then extract data from specific tab:
```
// Find email tab
{action: "list_tabs"}
// Use tab 2 for email (from list_tabs output)
{action: "click", tab_index: 2, selector: "a[title*='Organization receipt']"}
{action: "await_element", tab_index: 2, selector: ".email-body"}
// Extract donation amount
{action: "extract", tab_index: 2, payload: "text", selector: ".donation-amount"}
```
### Price Comparison
Open multiple stores and compare prices:
```
// Navigate first tab
{action: "navigate", payload: "https://store1.com/product"}
// Open additional tabs
{action: "new_tab"}
{action: "navigate", tab_index: 1, payload: "https://store2.com/product"}
{action: "new_tab"}
{action: "navigate", tab_index: 2, payload: "https://store3.com/product"}
// Wait for all to load and extract prices
{action: "await_element", tab_index: 0, selector: ".price"}
{action: "extract", tab_index: 0, payload: "text", selector: ".price"}
{action: "await_element", tab_index: 1, selector: ".price"}
{action: "extract", tab_index: 1, payload: "text", selector: ".price"}
{action: "await_element", tab_index: 2, selector: ".price"}
{action: "extract", tab_index: 2, payload: "text", selector: ".price"}
```
### Cross-Reference Between Sites
Extract data from one site and use in another:
```
// Get phone number from company site
{action: "navigate", payload: "https://company.com/contact"}
{action: "await_element", selector: ".phone"}
{action: "extract", payload: "text", selector: ".phone"}
// Store the result, then open verification site
{action: "new_tab"}
{action: "navigate", tab_index: 1, payload: "https://lookup.com"}
{action: "await_element", tab_index: 1, selector: "input[name=phone]"}
// Fill with extracted phone number
{action: "type", tab_index: 1, selector: "input[name=phone]", payload: "<phone-from-previous-extract>"}
{action: "click", tab_index: 1, selector: "button.search"}
{action: "await_element", tab_index: 1, selector: ".results"}
{action: "extract", tab_index: 1, payload: "text", selector: ".verification-status"}
```
---
## Dynamic Content
### Wait for AJAX to Complete
Wait for loading spinner to disappear:
```
{action: "navigate", payload: "https://app.com/dashboard"}
// Wait for spinner to disappear using custom JavaScript
{action: "eval", payload: `
new Promise(resolve => {
const check = () => {
if (!document.querySelector('.spinner')) {
resolve(true);
} else {
setTimeout(check, 100);
}
};
check();
})
`}
// Now safe to extract
{action: "extract", payload: "text", selector: ".dashboard-data"}
```
### Infinite Scroll
Scroll to load more content:
```
{action: "navigate", payload: "https://example.com/feed"}
{action: "await_element", selector: ".feed-item"}
// Scroll multiple times
{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"}
{action: "await_element", selector: ".feed-item", timeout: 2000}
{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"}
{action: "await_element", selector: ".feed-item", timeout: 2000}
{action: "eval", payload: "window.scrollTo(0, document.body.scrollHeight)"}
{action: "await_element", selector: ".feed-item", timeout: 2000}
// Count loaded items
{action: "eval", payload: "document.querySelectorAll('.feed-item').length"}
```
### Wait for Element to Become Enabled
Wait for button to be clickable:
```
{action: "click", selector: "button.start"}
// Wait for continue button to enable
{action: "eval", payload: `
new Promise(resolve => {
const check = () => {
const btn = document.querySelector('button.continue');
if (btn && !btn.disabled) {
resolve(true);
} else {
setTimeout(check, 100);
}
};
check();
})
`}
{action: "click", selector: "button.continue"}
```
---
## Advanced Patterns
### Multi-Step Workflow
Complete booking flow with validation:
```
{action: "navigate", payload: "https://booking.example.com"}
// Search
{action: "type", selector: "input[name=destination]", payload: "San Francisco"}
{action: "type", selector: "input[name=checkin]", payload: "2025-12-01"}
{action: "click", selector: "button.search"}
// Select hotel
{action: "await_element", selector: ".hotel-results"}
{action: "click", selector: ".hotel-card:first-child .select"}
// Choose room
{action: "await_element", selector: ".room-options"}
{action: "click", selector: ".room[data-type=deluxe] .book"}
// Fill guest info
{action: "await_element", selector: "form.guest-info"}
{action: "type", selector: "input[name=firstName]", payload: "Jane"}
{action: "type", selector: "input[name=lastName]", payload: "Smith"}
{action: "type", selector: "input[name=email]", payload: "[email protected]"}
// Review (don't complete)
{action: "click", selector: "button.review"}
{action: "await_element", selector: ".summary"}
// Extract confirmation details
{action: "extract", payload: "text", selector: ".hotel-name"}
{action: "extract", payload: "text", selector: ".total-price"}
```
### Cookies and LocalStorage
Access browser storage:
```
// Get cookies
{action: "eval", payload: "document.cookie"}
// Set cookie
{action: "eval", payload: "document.cookie = 'theme=dark; path=/'"}
// Get localStorage
{action: "eval", payload: "JSON.stringify(localStorage)"}
// Set localStorage
{action: "eval", payload: "localStorage.setItem('lastVisit', new Date().toISOString())"}
```
### Handle Modals
Interact with modal dialogs:
```
{action: "click", selector: "button.open-modal"}
{action: "await_element", selector: ".modal.visible"}
// Fill modal form
{action: "type", selector: ".modal input[name=username]", payload: "testuser"}
{action: "click", selector: ".modal button.submit"}
// Wait for modal to close
{action: "eval", payload: `
new Promise(resolve => {
const check = () => {
if (!document.querySelector('.modal.visible')) {
resolve(true);
} else {
setTimeout(check, 100);
}
};
check();
})
`}
```
### Screenshots
Capture full page or specific elements:
```
// Full page screenshot
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "body"}
{action: "screenshot", payload: "/tmp/page.png"}
// Element-specific screenshot
{action: "screenshot", payload: "/tmp/element.png", selector: ".important-section"}
```
### Check Element State
Verify element properties before interaction:
```
// Check if button is disabled
{action: "eval", payload: "document.querySelector('button.submit').disabled"}
// Check if element is visible
{action: "eval", payload: "!!document.querySelector('.important-button') && window.getComputedStyle(document.querySelector('.important-button')).display !== 'none'"}
// Check element exists
{action: "eval", payload: "!!document.querySelector('.important-button')"}
```
---
## Tips and Best Practices
### Always Wait Before Interaction
Don't interact with elements immediately after navigation:
```
// BAD - might fail if page slow to load
{action: "navigate", payload: "https://example.com"}
{action: "click", selector: "button"} // May fail!
// GOOD - wait for element first
{action: "navigate", payload: "https://example.com"}
{action: "await_element", selector: "button"}
{action: "click", selector: "button"}
```
### Use Specific Selectors
Avoid generic selectors that match multiple elements:
```
// BAD - matches first button on page
{action: "click", selector: "button"}
// GOOD - specific selector
{action: "click", selector: "button[type=submit]"}
{action: "click", selector: "button.login-button"}
{action: "click", selector: "#submit-form"}
```
### Verify Selectors First
Check page structure before building workflow:
```
// Check page HTML
{action: "extract", payload: "html"}
// Or check specific element
{action: "extract", payload: "html", selector: "form"}
```
### Handle Dynamic Content
Wait for content to load before extraction:
```
// BAD - tries to extract before content loads
{action: "navigate", payload: "https://app.com"}
{action: "extract", payload: "text", selector: ".user-name"} // Might be empty!
// GOOD - wait for content
{action: "navigate", payload: "https://app.com"}
{action: "await_element", selector: ".user-name"}
{action: "extract", payload: "text", selector: ".user-name"}
```
### Use \n for Form Submission
Append newline to auto-submit forms:
```
// Submit search without explicit click
{action: "type", selector: "#search-input", payload: "my query\n"}
// Submit login form
{action: "type", selector: "input[name=email]", payload: "[email protected]"}
{action: "type", selector: "input[name=password]", payload: "password123\n"}
```
---
## Common Pitfalls
### Don't Cache Tab Indices
Tab indices change when tabs close - always get fresh list:
```
// BAD - index might be stale after closing tabs
// (using tab 2 without checking if it still exists)
{action: "click", tab_index: 2, selector: "button"}
// GOOD - list tabs first
{action: "list_tabs"}
// Then use appropriate index from the output
{action: "click", tab_index: 2, selector: "button"}
```
### Increase Timeout for Slow Pages
Default timeout is 5000ms, increase if needed:
```
// For slow-loading elements
{action: "await_element", selector: ".lazy-content", timeout: 30000}
// For slow AJAX requests
{action: "await_text", payload: "Data loaded", timeout: 15000}
```
### Extract Structured Data with JavaScript
For complex data extraction, use JavaScript evaluation:
```
// Instead of multiple extract calls, use one eval
{action: "eval", payload: `
{
title: document.querySelector('h1').textContent,
author: document.querySelector('.author').textContent,
date: document.querySelector('time').textContent,
links: Array.from(document.querySelectorAll('a')).map(a => a.href)
}
`}
```
---
## Reference
- [SKILL.md](SKILL.md) - Complete tool reference
- [COMMANDLINE-USAGE.md](COMMANDLINE-USAGE.md) - Command-line bash examples
- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) - Full protocol documentation
```