Selenium is dead. Playwright is heavy. Puppeteer is Google-only. If you want real browser automation — the kind that runs on your own hardware, with your own browser, under your own control — you use CDP.
CDP stands for Chrome DevTools Protocol. It is the same protocol Chrome uses to talk to its developer tools. It is also the protocol you can use to talk to Chrome directly. No middleman. No abstraction. Just you and the browser.
Why CDP?
Most browser automation tools wrap CDP in layers. Each layer adds weight, failure points, and dependencies. CDP is the layer beneath all of them. When you learn CDP, you no longer need Playwright, Puppeteer, or Selenium. You can control any Chromium browser — Chrome, Brave, Edge, Opera — with a few HTTP requests and WebSocket messages.
The benefits:
- No extra dependencies — Just Python's
requestsandwebsocket-client - Works with any Chromium browser — Including Brave, which blocks ads and trackers by default
- Full access to the browser — Network interception, DOM inspection, screenshot capture, PDF generation
- Runs locally — No cloud service, no API keys, no usage limits
Starting the Browser
First, launch your browser with remote debugging enabled:
brave-browser-stable \ --remote-debugging-port=9223 \ --remote-debugging-address=127.0.0.1 \ --no-first-run \ --no-default-browser-check \ about:blank
This starts Brave with a CDP server listening on 127.0.0.1:9223. You can replace brave-browser-stable with google-chrome-stable or chromium and it will work the same way.
Getting the Debug URL
Once the browser is running, visit http://127.0.0.1:9223/json/version to verify CDP is active. Then visit http://127.0.0.1:9223/json/list to see all open tabs and their WebSocket debugger URLs.
import requests
r = requests.get('http://127.0.0.1:9223/json/list')
tabs = r.json()
ws_url = tabs[0]['webSocketDebuggerUrl']
print(ws_url)
Connecting via WebSocket
With the WebSocket URL, you can send CDP commands directly to the browser:
import websocket, json
ws = websocket.create_connection(ws_url)
def send(cmd, params=None):
msg = {"id": 1, "method": cmd, "params": params or {}}
ws.send(json.dumps(msg))
return json.loads(ws.recv())
# Navigate to a page
send("Page.navigate", {"url": "https://example.com"})
# Take a screenshot
result = send("Page.captureScreenshot")
img_data = result["result"]["data"]
with open("screenshot.png", "wb") as f:
f.write(base64.b64decode(img_data))
Common Commands
Here are the CDP commands I use most often:
Page.navigate— Load a URLPage.captureScreenshot— Take a screenshotRuntime.evaluate— Run JavaScript in the pageDOM.querySelector— Find an elementInput.dispatchMouseEvent— ClickInput.dispatchKeyEvent— TypeNetwork.enable— Listen to network requestsFetch.enable— Intercept and modify requests
Practical Example: Screenshot + Click
import requests, websocket, json, base64
# Connect to CDP
tabs = requests.get('http://127.0.0.1:9223/json/list').json()
ws = websocket.create_connection(tabs[0]['webSocketDebuggerUrl'])
def cdp(method, params=None, msg_id=1):
ws.send(json.dumps({"id": msg_id, "method": method, "params": params or {}}))
return json.loads(ws.recv())
# Navigate and wait for load
cdp("Page.navigate", {"url": "https://gumroad.com"})
cdp("Page.enable")
# Evaluate JS to find a button's coordinates
js = """document.querySelector('button.primary').getBoundingClientRect()"""
result = cdp("Runtime.evaluate", {"expression": js, "returnByValue": True})
rect = result["result"]["value"]
# Click it
cdp("Input.dispatchMouseEvent", {
"type": "mousePressed",
"x": rect["x"] + rect["width"]/2,
"y": rect["y"] + rect["height"]/2,
"button": "left",
"clickCount": 1
})
cdp("Input.dispatchMouseEvent", {
"type": "mouseReleased",
"x": rect["x"] + rect["width"]/2,
"y": rect["y"] + rect["height"]/2,
"button": "left",
"clickCount": 1
})
# Screenshot
screenshot = cdp("Page.captureScreenshot")
with open("result.png", "wb") as f:
f.write(base64.b64decode(screenshot["result"]["data"]))
ws.close()
Why This Matters
The modern web is built on browsers. If you can control a browser programmatically, you can automate almost anything: publishing, scraping, testing, monitoring, purchasing, data extraction. And if you do it with CDP, you are not dependent on any vendor's API or pricing.
This is the foundation of the income automation system we run at Drake Enterprise. Products upload themselves. Descriptions update themselves. The browser becomes a node in the mesh, not a tool for human browsing.
Going Deeper
If you want the full course on CDP automation — including network interception, request modification, PDF generation, headless operation, and integration with the Drake Enterprise mesh — it is part of the Technical Academy.
The browser is yours. Control it directly.