Comparisons

WebDriver vs Chrome DevTools Protocol for CAPTCHA Automation

Two protocols control Chrome for automation: WebDriver (W3C standard, used by Selenium) and Chrome DevTools Protocol (CDP, used by Puppeteer/Playwright). Each has different detection profiles, capabilities, and CAPTCHA handling characteristics.


Protocol Architecture

WebDriver (Selenium):
  Script ──HTTP──▶ ChromeDriver ──DevTools──▶ Chrome
  (JSON Wire)        (bridge)      (internal)

CDP (Puppeteer/Playwright):
  Script ──WebSocket──▶ Chrome
  (direct)               (no middle layer)

Head-to-Head Comparison

Feature WebDriver CDP
Protocol HTTP + JSON WebSocket
Standard W3C standard Chrome-specific
Detection surface High (navigator.webdriver = true) Lower (patchable)
Network interception No (needs Selenium Wire) Built-in (Fetch, Network)
JavaScript evaluation executeScript (wrapped) Runtime.evaluate (direct)
Request modification No Yes
Response modification No Yes
Connection overhead HTTP per command Persistent WebSocket
Cross-browser Chrome, Firefox, Safari, Edge Chrome/Chromium only
Stealth potential Low (always sets webdriver flag) High (flag patchable)

Detection Differences

WebDriver Detection (Selenium)

// What sites check for Selenium
navigator.webdriver                    // true (set by ChromeDriver)
document.$cdc_asdjflasutopfhvcZLmcfl  // ChromeDriver internal variable
window.callSelenium                    // Older Selenium versions
window._selenium                      // Selenium internals
document.__webdriver_evaluate          // WebDriver namespace
# Even with flag removal, ChromeDriver leaves traces
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

driver = webdriver.Chrome(options=options)

# Still detectable via $cdc variables in document

CDP Detection (Puppeteer)

// Puppeteer sets webdriver flag too, but it's fully patchable
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'webdriver', {
    get: () => undefined,
  });
  // No $cdc variables to worry about
  // No Selenium-specific traces
});

CAPTCHA Solving: WebDriver Approach

# Selenium (WebDriver) + CaptchaAI
import requests
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

API_KEY = "YOUR_API_KEY"
API_URL = "https://ocr.captchaai.com"

options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)

driver.get("https://example.com/form")
time.sleep(2)

# Extract sitekey
sitekey = driver.execute_script(
    "return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
)

# Solve via CaptchaAI
resp = requests.post(f"{API_URL}/in.php", data={
    "key": API_KEY, "method": "userrecaptcha",
    "googlekey": sitekey, "pageurl": driver.current_url, "json": 1,
})
task_id = resp.json()["request"]

for _ in range(60):
    time.sleep(5)
    resp = requests.get(f"{API_URL}/res.php", params={
        "key": API_KEY, "action": "get", "id": task_id, "json": 1,
    })
    data = resp.json()
    if data["request"] != "CAPCHA_NOT_READY":
        break

token = data["request"]

# Inject token
driver.execute_script(f"""
    document.querySelector('#g-recaptcha-response').value = '{token}';
    document.querySelectorAll('[name="g-recaptcha-response"]')
        .forEach(el => {{ el.value = '{token}'; }});
""")

driver.find_element(By.CSS_SELECTOR, "form").submit()
driver.quit()

CAPTCHA Solving: CDP Approach

// Puppeteer (CDP) + CaptchaAI
const puppeteer = require("puppeteer-extra");
const StealthPlugin = require("puppeteer-extra-plugin-stealth");
const https = require("https");

puppeteer.use(StealthPlugin());

const API_KEY = "YOUR_API_KEY";
const API_URL = "https://ocr.captchaai.com";

function solveCaptcha(siteUrl, sitekey) {
  // Submit + poll (simplified)
  return new Promise(async (resolve, reject) => {
    const submitResp = await httpPost(`${API_URL}/in.php`, {
      key: API_KEY, method: "userrecaptcha",
      googlekey: sitekey, pageurl: siteUrl, json: "1",
    });

    const taskId = submitResp.request;

    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const result = await httpGet(
        `${API_URL}/res.php?key=${API_KEY}&action=get&id=${taskId}&json=1`
      );
      if (result.request !== "CAPCHA_NOT_READY") {
        return resolve(result.request);
      }
    }
    reject(new Error("Timeout"));
  });
}

(async () => {
  const browser = await puppeteer.launch({
    headless: "new",
    args: ["--no-sandbox", "--window-size=1920,1080"],
  });

  const page = await browser.newPage();

  // CDP: Enable network interception
  const cdp = await page.createCDPSession();
  await cdp.send("Network.enable");

  // Monitor CAPTCHA-related requests
  cdp.on("Network.requestWillBeSent", (params) => {
    if (params.request.url.includes("recaptcha")) {
      console.log(`CAPTCHA request: ${params.request.url}`);
    }
  });

  await page.goto("https://example.com/form", { waitUntil: "networkidle0" });

  const sitekey = await page.evaluate(() =>
    document.querySelector("[data-sitekey]")?.getAttribute("data-sitekey")
  );

  if (sitekey) {
    const token = await solveCaptcha(page.url(), sitekey);

    // CDP: Inject via Runtime.evaluate (lower-level than page.evaluate)
    await cdp.send("Runtime.evaluate", {
      expression: `
        document.querySelector('#g-recaptcha-response').value = '${token}';
        document.querySelector('form').submit();
      `,
    });
  }

  await browser.close();
})();

Capability Comparison for CAPTCHA Workflows

Network Interception

# WebDriver: Cannot intercept requests natively
# Requires selenium-wire (third-party)
from seleniumwire import webdriver

driver = webdriver.Chrome()
# selenium-wire proxies all traffic through a local proxy
// CDP: Built-in request interception
await cdp.send("Fetch.enable", {
  patterns: [{ urlPattern: "*recaptcha*" }],
});

cdp.on("Fetch.requestPaused", async ({ requestId, request }) => {
  console.log(`Intercepted: ${request.url}`);
  await cdp.send("Fetch.continueRequest", { requestId });
});

Response Modification

// CDP: Can modify responses (WebDriver cannot)
cdp.on("Fetch.requestPaused", async ({ requestId, request }) => {
  if (request.url.includes("captcha-config")) {
    // Return modified response
    await cdp.send("Fetch.fulfillRequest", {
      requestId,
      responseCode: 200,
      body: btoa(JSON.stringify({ enabled: false })),
    });
  } else {
    await cdp.send("Fetch.continueRequest", { requestId });
  }
});
# WebDriver: Standard cookie API
driver.add_cookie({"name": "session", "value": "abc123"})
cookies = driver.get_cookies()
// CDP: Full cookie control including httpOnly
await cdp.send("Network.setCookie", {
  name: "session",
  value: "abc123",
  domain: "example.com",
  httpOnly: true,
  secure: true,
});

// Get all cookies including httpOnly (WebDriver can't access these)
const { cookies } = await cdp.send("Network.getAllCookies");

Decision Matrix

Scenario Recommended Why
Multi-browser testing WebDriver Cross-browser support
Maximum stealth CDP Lower detection surface
Network debugging CDP Built-in interception
Team unfamiliar with CDP WebDriver Simpler API, more docs
High-scale scraping CDP Lower overhead per session
CI/CD integration WebDriver Better tooling support
CAPTCHA-heavy sites CDP + CaptchaAI Stealth + solve combo
Simple form filling WebDriver + CaptchaAI Easier to implement

Hybrid Approach: WebDriver + CDP

Selenium 4 supports CDP commands through BiDi (bidirectional):

from selenium import webdriver

driver = webdriver.Chrome()

# Use WebDriver for navigation
driver.get("https://example.com")

# Use CDP for stealth patches
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
        Object.defineProperty(navigator, 'webdriver', {
            get: () => undefined
        });
    """
})

# Use CDP for network events
driver.execute_cdp_cmd("Network.enable", {})

Performance Benchmarks

Metric WebDriver (Selenium) CDP (Puppeteer) CDP (Playwright)
Session start ~2.5s ~1.5s ~1.2s
Page navigation ~500ms overhead ~100ms overhead ~80ms overhead
Script execution ~200ms per call ~50ms per call ~40ms per call
Memory per session ~350 MB ~280 MB ~260 MB
CAPTCHA solve (CaptchaAI) 15-30s 15-30s 15-30s

CaptchaAI solve time is identical — it's an API call independent of the browser protocol.


Troubleshooting

Issue WebDriver Fix CDP Fix
Detected as bot excludeSwitches + options Stealth plugin + patches
Can't intercept requests Use selenium-wire Use Fetch.enable
Slow execution Reduce execute_script calls Batch CDP commands
Token injection fails Check iframe context Use Runtime.evaluate with context
Session instability Update ChromeDriver Check WebSocket connection

FAQ

Which protocol gives better CAPTCHA success rates?

Success rates are identical — CaptchaAI solves server-side. The protocol only affects detection frequency (how often CAPTCHAs appear).

Can I mix WebDriver and CDP?

Yes. Selenium 4 exposes execute_cdp_cmd(). Use WebDriver for navigation and CDP for stealth patches.

Is Playwright better than both?

Playwright uses CDP internally but provides a higher-level API with auto-wait and better error handling. It's a good middle ground.

Should I switch from Selenium to Puppeteer for CAPTCHA work?

If detection is your main problem, yes. If you need cross-browser support, stay with Selenium + CaptchaAI.



Choose the right automation protocol for your CAPTCHA workflow — get your CaptchaAI key and solve CAPTCHAs regardless of protocol.

Discussions (0)

No comments yet.