Install
openclaw skills install chrome-cdp-controllerControl local Chrome browser via Chrome DevTools Protocol (CDP) using Puppeteer. Use when you need to automate browser tasks like navigating pages, clicking elements, filling forms, taking screenshots, executing JavaScript, or intercepting network responses. Works with Chrome instances that already have CDP enabled. Common use cases include web scraping (e.g., "Search iPhone on Taobao and get prices"), automated testing, interacting with web apps (e.g., "Ask ChatGPT a question"), or monitoring network traffic.
openclaw skills install chrome-cdp-controllerControl and automate a Chrome browser that's already running with CDP enabled, using Puppeteer.
Chrome must be running with remote debugging enabled. If not already started:
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 &
# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
# Linux
google-chrome --remote-debugging-port=9222 &
Visit http://localhost:9222/json/version to get the webSocketDebuggerUrl, for example:
ws://127.0.0.1:9222/devtools/browser/FFA7276F8E8E51645BD2AC9BE6B79607
Or use this one-liner:
curl -s http://localhost:9222/json/version | grep -o '"webSocketDebuggerUrl":"[^"]*"' | cut -d'"' -f4
cd chrome-cdp-controller
npm install
This installs puppeteer-core which is lightweight (doesn't download Chromium).
Create a JSON file with commands:
commands.json:
[
{"type": "navigate", "url": "https://www.baidu.com"},
{"type": "wait", "seconds": 2},
{"type": "screenshot", "path": "/tmp/screenshot.png"},
{"type": "evaluate", "script": "document.title"}
]
Execute:
node scripts/cdp_controller.js --ws "ws://127.0.0.1:9222/devtools/browser/..." --commands commands.json
const { CDPController } = require('./scripts/cdp_controller.js');
(async () => {
const controller = new CDPController('ws://127.0.0.1:9222/devtools/browser/...');
await controller.connect();
// Navigate
await controller.navigate('https://www.taobao.com');
// Fill form
await controller.fill('#q', 'iPhone');
await controller.press('Enter');
await controller.wait(3);
// Extract data
const result = await controller.evaluate(`
Array.from(document.querySelectorAll('.item'))
.slice(0, 10)
.map(item => ({
title: item.querySelector('.title')?.textContent.trim(),
price: item.querySelector('.price')?.textContent.trim()
}))
`);
console.log(result.result);
// Intercept network
await controller.startIntercept('*api*');
// ... perform actions ...
const responses = controller.getInterceptedResponses();
await controller.close();
})();
url: Target URLwaitUntil: "load", "domcontentloaded", or "networkidle2" (default)click - Click an element
selector: CSS selectortimeout: Timeout in milliseconds (default: 5000)fill - Fill a form field (selects all, then types)
selector: CSS selectortext: Text to filltimeout: Timeout in milliseconds (default: 5000)type - Type text character by character
selector: CSS selectortext: Text to typedelay: Delay between characters in ms (default: 50)timeout: Timeout in milliseconds (default: 5000)press - Press a key
key: Key name (Enter, Tab, Escape, etc.)get_text - Get text content of a single element
selector: CSS selectorget_all_text - Get text content of all matching elements
selector: CSS selectorevaluate - Execute JavaScript and return result
script: JavaScript codestart_intercept - Start intercepting network responses
url_pattern: URL pattern to match (substring match, e.g., "api")get_intercepted - Get all intercepted responses
clear_intercepted - Clear intercepted responses list
screenshot - Take a screenshot
path: Output file pathfullPage: Capture full page (default: false)wait_for_selector - Wait for element to appear
selector: CSS selectortimeout: Timeout in milliseconds (default: 5000)wait - Sleep for a duration
seconds: Number of seconds to waittaobao-search.json:
[
{"type": "navigate", "url": "https://www.taobao.com"},
{"type": "wait", "seconds": 2},
{"type": "fill", "selector": "#q", "text": "iPhone"},
{"type": "press", "key": "Enter"},
{"type": "wait", "seconds": 5},
{"type": "evaluate", "script": "Array.from(document.querySelectorAll('.item, [class*=\"Item\"]')).slice(0, 10).map(item => ({ title: item.querySelector('.title, [class*=\"title\"]')?.textContent.trim().substring(0, 80), price: item.querySelector('.price, [class*=\"price\"]')?.textContent.trim() }))"}
]
Run:
WS_URL=$(curl -s http://localhost:9222/json/version | grep -o '"webSocketDebuggerUrl":"[^"]*"' | cut -d'"' -f4)
node scripts/cdp_controller.js --ws "$WS_URL" --commands taobao-search.json
Note: Selectors may vary. Use browser DevTools (F12) to inspect elements.
chatgpt-ask.json:
[
{"type": "navigate", "url": "https://chat.openai.com"},
{"type": "wait_for_selector", "selector": "textarea", "timeout": 10000},
{"type": "type", "selector": "textarea", "text": "What is artificial intelligence?"},
{"type": "press", "key": "Enter"},
{"type": "wait", "seconds": 10},
{"type": "get_text", "selector": "[data-message-author-role='assistant']:last-of-type"}
]
intercept-api.json:
[
{"type": "start_intercept", "url_pattern": "graphql"},
{"type": "navigate", "url": "https://example.com"},
{"type": "wait", "seconds": 3},
{"type": "get_intercepted"}
]
For more examples, see references/examples.md.
When automating browser tasks:
Get WebSocket URL:
ws://127.0.0.1:9222/devtools/browser/...), use it directlyhttp://localhost:9222/json/versionDetermine selectors:
evaluate with document.querySelector to test selectorsBuild command sequence:
navigatewait or wait_for_selector between stepsfill for form inputs, click for buttonsevaluate for complex data extractionstart_intercept before navigation if monitoring network trafficExecute:
node scripts/cdp_controller.js --ws "<websocket-url>" --commands <file>Handle failures:
wait_for_selectorwait_for_selector instead of fixed wait when possible#id) > class (.class) > attribute ([attr='value'])evaluate for complex data extraction--remote-debugging-port=9222wait_for_selector before interactioncd chrome-cdp-controller
npm install