Chrome DevTools Protocol (CDP) gives you direct control over Chrome at the protocol level — no WebDriver, no extra abstraction layers. For CAPTCHA automation, this means better stealth, lower overhead, and fine-grained control over network requests, page execution, and browser behavior.
Why CDP Over WebDriver?
| Feature | WebDriver | CDP |
|---|---|---|
| Detection surface | High (navigator.webdriver) |
Minimal |
| Network interception | Requires Selenium Wire | Built-in (Fetch.requestPaused) |
| JavaScript injection | executeScript (detectable) |
Runtime.evaluate (stealth) |
| Performance overhead | Medium-high | Low |
| Protocol support | HTTP + JSON Wire | WebSocket (real-time) |
Direct CDP Connection (Node.js)
Connect to Chrome
# Launch Chrome with remote debugging
chrome --remote-debugging-port=9222 --no-first-run --no-default-browser-check
const WebSocket = require("ws");
const http = require("http");
class CDPClient {
constructor() {
this.ws = null;
this.id = 0;
this.callbacks = new Map();
this.eventHandlers = new Map();
}
async connect(port = 9222) {
// Get WebSocket URL from Chrome
const targets = await this.httpGet(
`http://127.0.0.1:${port}/json/list`
);
const target = targets.find((t) => t.type === "page");
return new Promise((resolve, reject) => {
this.ws = new WebSocket(target.webSocketDebuggerUrl);
this.ws.on("open", () => resolve());
this.ws.on("error", reject);
this.ws.on("message", (data) => this.handleMessage(JSON.parse(data)));
});
}
httpGet(url) {
return new Promise((resolve, reject) => {
http.get(url, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
}).on("error", reject);
});
}
handleMessage(msg) {
if (msg.id && this.callbacks.has(msg.id)) {
this.callbacks.get(msg.id)(msg);
this.callbacks.delete(msg.id);
}
if (msg.method && this.eventHandlers.has(msg.method)) {
for (const handler of this.eventHandlers.get(msg.method)) {
handler(msg.params);
}
}
}
send(method, params = {}) {
return new Promise((resolve) => {
const id = ++this.id;
this.callbacks.set(id, resolve);
this.ws.send(JSON.stringify({ id, method, params }));
});
}
on(method, handler) {
if (!this.eventHandlers.has(method)) {
this.eventHandlers.set(method, []);
}
this.eventHandlers.get(method).push(handler);
}
}
CaptchaAI + CDP Integration
const https = require("https");
class CDPCaptchaSolver {
constructor(apiKey) {
this.apiKey = apiKey;
this.cdp = new CDPClient();
this.API = "https://ocr.captchaai.com";
}
async init(port = 9222) {
await this.cdp.connect(port);
// Enable required domains
await this.cdp.send("Page.enable");
await this.cdp.send("Runtime.enable");
await this.cdp.send("Network.enable");
await this.cdp.send("DOM.enable");
}
async navigate(url) {
const result = await this.cdp.send("Page.navigate", { url });
await this.waitForLoad();
return result;
}
async waitForLoad() {
return new Promise((resolve) => {
this.cdp.on("Page.loadEventFired", () => resolve());
});
}
async detectSitekey() {
const result = await this.cdp.send("Runtime.evaluate", {
expression: `
(() => {
// reCAPTCHA
const recaptcha = document.querySelector('[data-sitekey]');
if (recaptcha) {
return {
type: 'recaptcha_v2',
sitekey: recaptcha.getAttribute('data-sitekey'),
};
}
// Turnstile
const turnstile = document.querySelector('.cf-turnstile[data-sitekey]');
if (turnstile) {
return {
type: 'turnstile',
sitekey: turnstile.getAttribute('data-sitekey'),
};
}
// reCAPTCHA v3 (script-based)
const scripts = document.querySelectorAll('script[src*="recaptcha"]');
for (const s of scripts) {
const match = s.src.match(/render=([\\w-]+)/);
if (match && match[1] !== 'explicit') {
return { type: 'recaptcha_v3', sitekey: match[1] };
}
}
return null;
})()
`,
returnByValue: true,
});
return result.result?.value || null;
}
async solveCaptcha(siteUrl, sitekey, type = "recaptcha_v2") {
const submitData = {
key: this.apiKey,
pageurl: siteUrl,
json: "1",
};
if (type === "turnstile") {
submitData.method = "turnstile";
submitData.sitekey = sitekey;
} else {
submitData.method = "userrecaptcha";
submitData.googlekey = sitekey;
}
const submitResp = await this.httpPost(
`${this.API}/in.php`,
submitData
);
if (submitResp.status !== 1) {
throw new Error(`Submit: ${submitResp.request}`);
}
const taskId = submitResp.request;
for (let i = 0; i < 60; i++) {
await this.sleep(5000);
const params = new URLSearchParams({
key: this.apiKey,
action: "get",
id: taskId,
json: "1",
});
const result = await this.httpGet(
`${this.API}/res.php?${params}`
);
if (result.request === "CAPCHA_NOT_READY") continue;
if (result.status !== 1) throw new Error(`Solve: ${result.request}`);
return result.request;
}
throw new Error("Timeout");
}
async injectToken(token, type = "recaptcha_v2") {
if (type === "turnstile") {
await this.cdp.send("Runtime.evaluate", {
expression: `
const input = document.querySelector('input[name="cf-turnstile-response"]');
if (input) {
input.value = '${token}';
input.dispatchEvent(new Event('change', { bubbles: true }));
}
`,
});
} else {
await this.cdp.send("Runtime.evaluate", {
expression: `
// Set response textarea
const textarea = document.querySelector('#g-recaptcha-response');
if (textarea) {
textarea.style.display = 'block';
textarea.value = '${token}';
}
// Set all hidden fields
document.querySelectorAll('[name="g-recaptcha-response"]')
.forEach(el => { el.value = '${token}'; });
// Trigger callback
if (typeof ___grecaptcha_cfg !== 'undefined') {
const clients = ___grecaptcha_cfg.clients;
for (const key in clients) {
const client = clients[key];
for (const prop in client) {
const val = client[prop];
if (val && typeof val === 'object') {
for (const p in val) {
if (typeof val[p]?.callback === 'function') {
val[p].callback('${token}');
}
}
}
}
}
}
`,
});
}
}
// Full workflow
async solveOnPage(url) {
await this.navigate(url);
await this.sleep(2000);
const captcha = await this.detectSitekey();
if (!captcha) {
console.log("No CAPTCHA detected");
return null;
}
console.log(`Detected: ${captcha.type} (${captcha.sitekey})`);
const token = await this.solveCaptcha(url, captcha.sitekey, captcha.type);
await this.injectToken(token, captcha.type);
console.log("Token injected");
return token;
}
// HTTP helpers
httpPost(url, data) {
return new Promise((resolve, reject) => {
const params = new URLSearchParams(data).toString();
const u = new URL(url);
const req = https.request({
hostname: u.hostname, path: u.pathname,
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
});
req.on("error", reject);
req.write(params);
req.end();
});
}
httpGet(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
}).on("error", reject);
});
}
sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
}
Network Interception with CDP
async function interceptCaptchaRequests(solver) {
// Enable Fetch domain for request interception
await solver.cdp.send("Fetch.enable", {
patterns: [
{ urlPattern: "*recaptcha*", requestStage: "Request" },
{ urlPattern: "*turnstile*", requestStage: "Request" },
{ urlPattern: "*challenges.cloudflare.com*", requestStage: "Request" },
],
});
solver.cdp.on("Fetch.requestPaused", async (params) => {
const { requestId, request } = params;
console.log(`Intercepted: ${request.method} ${request.url}`);
// Log CAPTCHA-related requests for debugging
if (request.url.includes("userverify") || request.url.includes("reload")) {
console.log("CAPTCHA verification request detected");
}
// Continue the request
await solver.cdp.send("Fetch.continueRequest", { requestId });
});
}
Stealth-Configuredion with CDP
async function applyStealthPatches(solver) {
// Remove webdriver flag
await solver.cdp.send("Page.addScriptToEvaluateOnNewDocument", {
source: `
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
// Fix chrome object
window.chrome = { runtime: {}, loadTimes: () => ({}) };
// Fix permissions
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (params) => {
if (params.name === 'notifications') {
return Promise.resolve({ state: Notification.permission });
}
return originalQuery.call(navigator.permissions, params);
};
// Fix plugins
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5],
});
// Fix languages
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
});
`,
});
}
Python CDP Integration
import asyncio
import aiohttp
import json
class CDPCaptchaSolver:
CAPTCHAAI_URL = "https://ocr.captchaai.com"
def __init__(self, api_key, cdp_port=9222):
self.api_key = api_key
self.cdp_port = cdp_port
self.ws = None
self.msg_id = 0
async def connect(self):
async with aiohttp.ClientSession() as session:
async with session.get(
f"http://127.0.0.1:{self.cdp_port}/json/list"
) as resp:
targets = await resp.json()
target = next(t for t in targets if t["type"] == "page")
self.ws = await asyncio.get_event_loop().create_connection(
lambda: CDPProtocol(self),
target["webSocketDebuggerUrl"],
)
async def send(self, method, params=None):
self.msg_id += 1
msg = {"id": self.msg_id, "method": method, "params": params or {}}
self.ws.send(json.dumps(msg))
# Wait for response (simplified)
return await self._wait_response(self.msg_id)
async def solve_and_inject(self, url):
await self.send("Page.navigate", {"url": url})
await asyncio.sleep(3)
# Detect sitekey via Runtime.evaluate
result = await self.send("Runtime.evaluate", {
"expression": "document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')",
"returnByValue": True,
})
sitekey = result.get("result", {}).get("value")
if not sitekey:
return None
# Solve via CaptchaAI
token = await self._solve_recaptcha(url, sitekey)
# Inject
await self.send("Runtime.evaluate", {
"expression": f"""
document.querySelector('#g-recaptcha-response').value = '{token}';
document.querySelectorAll('[name="g-recaptcha-response"]')
.forEach(el => {{ el.value = '{token}'; }});
""",
})
return token
async def _solve_recaptcha(self, site_url, sitekey):
import requests
resp = requests.post(f"{self.CAPTCHAAI_URL}/in.php", data={
"key": self.api_key, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": site_url, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(60):
await asyncio.sleep(5)
resp = requests.get(f"{self.CAPTCHAAI_URL}/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
})
data = resp.json()
if data["request"] == "CAPCHA_NOT_READY":
continue
if data["status"] == 1:
return data["request"]
raise TimeoutError("CAPTCHA solve timeout")
Usage Example
// Full workflow
async function main() {
const solver = new CDPCaptchaSolver("YOUR_API_KEY");
await solver.init(9222);
// Apply stealth
await applyStealthPatches(solver);
// Enable network monitoring
await interceptCaptchaRequests(solver);
// Solve CAPTCHA on target page
const token = await solver.solveOnPage("https://example.com/login");
if (token) {
// Submit form
await solver.cdp.send("Runtime.evaluate", {
expression: `document.querySelector('form').submit()`,
});
}
}
main().catch(console.error);
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Can't connect to Chrome | Remote debugging not enabled | Launch with --remote-debugging-port=9222 |
| WebSocket closes | Chrome crashed or tab closed | Add reconnection logic |
Runtime.evaluate fails |
Page not loaded | Wait for Page.loadEventFired |
| CAPTCHA still detected | Incomplete stealth patches | Add User-Agent override, fix navigator.plugins |
| Token injection fails | Shadow DOM contains CAPTCHA | Use DOM.describeNode to traverse shadow roots |
FAQ
Is CDP harder to use than WebDriver?
CDP is lower-level but more powerful. For CAPTCHA automation, the stealth advantages outweigh the complexity.
Can I use CDP with Puppeteer?
Yes — Puppeteer uses CDP under the hood. You can access the CDP session directly via page._client() or page.createCDPSession().
Does CDP work with Firefox?
Firefox has partial CDP support. For full CDP, use Chromium-based browsers.
Is CDP detection-proof?
CDP is harder to detect than WebDriver, but not invisible. Combine with fingerprint spoofing for best results.
Related Guides
Get protocol-level control over CAPTCHA automation — get your CaptchaAI key and integrate with Chrome DevTools Protocol.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.