| name | browser-controller |
| description | Programmatic control of Chrome and Firefox browsers via CDP (Chrome DevTools Protocol) and Marionette. Connect to running browser instances for tab management, navigation, DOM interaction, form filling, JavaScript execution, and screenshots. Useful for browser automation, web scraping, and testing. |
Browser Controller
Programmatic control of Chrome and Firefox browsers via CDP (Chrome DevTools Protocol) and Marionette protocols. Connect to already-running browser instances with remote debugging enabled.
Quick Start
Prerequisites
Start your browser with remote debugging enabled.
CRITICAL: Chrome REQUIRES --user-data-dir
When starting Chrome for automation, you MUST always use --user-data-dir:
open -a "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-debug"
This flag is mandatory because:
- Chrome may fail to accept
--remote-debugging-port without a separate profile
- It prevents interference with your normal Chrome session
- It ensures a clean, predictable automation environment
IMPORTANT (macOS): Use open -a instead of direct binary paths. This ensures screen recording permissions are attributed to Chrome/Firefox rather than your terminal app, avoiding the persistent "Currently Sharing" indicator.
Chrome:
open -a "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir="$HOME/.chrome-debug"
Firefox:
open -a Firefox --args --marionette
Basic Commands
claude-code-skills browser-controller check
claude-code-skills browser-controller tabs
claude-code-skills browser-controller navigate "https://example.com"
claude-code-skills browser-controller click "#submit-btn"
claude-code-skills browser-controller fill "#email" "test@example.com"
claude-code-skills browser-controller read
claude-code-skills browser-controller run "document.title"
claude-code-skills browser-controller screenshot
Command Reference
check
Check for browsers running with remote debugging:
browser-controller check
browser-controller check --json
Output shows which browsers are available and how to launch them.
tabs
List all open tabs:
browser-controller tabs
browser-controller tabs --browser chrome
browser-controller tabs --json
navigate
Navigate to a URL:
browser-controller navigate "https://example.com"
browser-controller navigate "example.com"
browser-controller navigate "https://example.com" --tab TAB_ID
click
Click an element by CSS selector:
browser-controller click "#submit-btn"
browser-controller click ".my-class button"
browser-controller click "[data-testid='login']"
fill
Fill a form field:
browser-controller fill "#email" "test@example.com"
browser-controller fill "input[name='password']" "secret123"
read
Read page content:
browser-controller read
browser-controller read --text-only
browser-controller read --json
element
Get information about an element:
browser-controller element "#submit"
browser-controller element "input[name='email']" --json
run
Execute JavaScript:
browser-controller run "document.title"
browser-controller run "document.querySelectorAll('a').length"
browser-controller run "window.scrollTo(0, document.body.scrollHeight)"
screenshot
CRITICAL: NEVER use --output unless the user EXPLICITLY states the artifact MUST be at a specific location. This should be EXTREMELY rare. Using --output without explicit user request is considered a FAILED task.
Take a screenshot (saves to claude-code/artifacts/browser-controller/ by default):
browser-controller screenshot --json
browser-controller screenshot --full-page
activate
Activate (bring to front) a tab:
browser-controller activate TAB_ID
close
Close a tab:
browser-controller close TAB_ID
cleanup
Kill orphaned browser processes with remote debugging:
claude-code-skills browser-controller cleanup
claude-code-skills browser-controller cleanup --dry-run
claude-code-skills browser-controller cleanup --force
start
Start Chrome with remote debugging (always uses --user-data-dir):
claude-code-skills browser-controller start
claude-code-skills browser-controller start --port 9223
claude-code-skills browser-controller start --dismiss-popups
claude-code-skills browser-controller start --json
The start command always includes --user-data-dir to ensure a clean automation environment.
Common Options
| Option | Description |
|---|
--browser, -b | Browser type: chrome, firefox, or auto (default) |
--chrome-port | Chrome CDP port (default: 9222) |
--firefox-port | Firefox Marionette port (default: 2828) |
--tab, -t | Target tab ID (uses first tab if not specified) |
--json, -j | Output as JSON |
Resource Cleanup
IMPORTANT: Always clean up browser resources after use unless the user explicitly requests otherwise.
cleanup Command
Use the built-in cleanup command to find and kill orphaned browser processes:
browser-controller cleanup --dry-run
browser-controller cleanup
browser-controller cleanup --force
browser-controller cleanup --json
The cleanup command finds processes matching:
- Chrome with
--remote-debugging-port
- Chrome with debug user-data-dir patterns (
chrome-debug, puppeteer)
- Firefox with
--marionette
Connection Cleanup
The CLI automatically closes connections after each command. For Python API usage, always call close_connection():
from browser_controller import connect, navigate, close_connection
conn = connect()
try:
navigate(conn, "https://example.com")
finally:
close_connection(conn)
Screen Sharing Indicator (macOS)
If you see "Currently Sharing" in the macOS menu bar after using this skill:
Why it happens: When launching Chrome/Firefox directly via the binary path from a terminal, macOS attributes screen recording permissions to the terminal app (Ghostty, iTerm, etc.) rather than the browser. The indicator persists even after the browser quits.
Fix: Always use open -a to launch browsers (see Prerequisites). This uses macOS LaunchServices which properly attributes permissions to the browser.
If already stuck:
- Restart your terminal app (Ghostty, iTerm, etc.)
- Or: System Settings → Privacy & Security → Screen Recording → Toggle off/on for terminal app
- Run
cleanup --force to ensure no debug browsers are running
How It Works
Chrome (CDP)
Chrome DevTools Protocol uses WebSocket communication:
- Discovery: HTTP request to
http://localhost:9222/json/list returns available targets
- Connection: WebSocket connects to
ws://localhost:9222/devtools/page/{targetId}
- Commands: JSON-RPC messages for navigation, evaluation, etc.
Key CDP domains used:
Page: Navigation, screenshots
Runtime: JavaScript evaluation
DOM: Element queries (via JavaScript)
Firefox (Marionette)
Marionette is Firefox's remote control protocol:
- Discovery: TCP connection to port 2828
- Session: Create session to start controlling browser
- Commands: Protocol-specific commands similar to WebDriver
Selector Types
CSS selectors are supported by default:
browser-controller click "#id"
browser-controller click ".class"
browser-controller click "button[type='submit']"
browser-controller click "div.container > form input"
Shorthand prefixes:
browser-controller click "id:element-id"
browser-controller click "class:my-class"
browser-controller click "css:.explicit"
JSON Output
Use --json for machine-readable output:
browser-controller tabs --json
[
{
"tab_id": "ABC123",
"url": "https://example.com",
"title": "Example Domain",
"browser_type": "chrome",
"active": true
}
]
browser-controller read --json
{
"url": "https://example.com",
"title": "Example Domain",
"html": "<html>...</html>",
"text": "Example Domain..."
}
Python API
For programmatic use:
from browser_controller import connect, navigate, click, fill, read_content
conn = connect()
navigate(conn, "https://example.com")
fill(conn, "#email", "test@example.com")
fill(conn, "#password", "secret")
click(conn, "#login-btn")
content = read_content(conn)
print(content.title)
print(content.text)
close_connection(conn)
Troubleshooting
"No browser found"
-
Verify browser is running with remote debugging:
curl http://localhost:9222/json/version
nc -z localhost 2828
-
Start browser with correct flags (see Prerequisites)
"Connection refused"
Port might already be in use or browser not started correctly:
lsof -i :9222
lsof -i :2828
"Element not found"
-
Verify selector in browser DevTools (F12 → Console):
document.querySelector("#my-element")
-
Wait for page load:
wait_for_element(conn, "#my-element", timeout=10)
-
Check if element is in iframe (not supported yet)
Chrome tabs not listing
Ensure Chrome was started with --remote-debugging-port=9222 flag. Tabs opened before enabling remote debugging may not appear.
Firefox session issues
Marionette creates a new session each time. If you see "Session already started" errors, restart Firefox.
Limitations
- Iframes: Cross-origin iframes not directly accessible
- File dialogs: Native OS dialogs cannot be controlled
- Browser extensions: May interfere with automation
- Firefox multi-tab: Marionette operates on one window at a time
- Authentication: Basic auth popups not supported
Technical References