If you've ever built E2E tests with Selenium, you've hit this wall: your test suite runs perfectly until a login page throws up a reCAPTCHA, and suddenly your CI pipeline is broken.
Here are three approaches I've used, with honest pros and cons for each.
Approach 1: Disable CAPTCHAs in Test Environments
The simplest fix — just turn them off.
# Django example: settings_test.py
RECAPTCHA_TESTING = True
# Or use an env flag
# CAPTCHA_ENABLED=false pytest
When it works: Internal apps where you control the backend.
When it doesn't: Testing against third-party sites, or when you need to verify the CAPTCHA integration itself works in production.
Approach 2: Try to Click Through It
from selenium.webdriver.common.by import By
# Switch to reCAPTCHA iframe
iframe = driver.find_element(By.CSS_SELECTOR, "iframe[src*='recaptcha']")
driver.switch_to.frame(iframe)
# Click the checkbox
checkbox = driver.find_element(By.CLASS_NAME, "recaptcha-checkbox")
checkbox.click()
Reality check: This works maybe 20% of the time. Modern reCAPTCHA detects Selenium via navigator.webdriver, missing plugins, robotic mouse patterns, and CDP fingerprints.
Approach 3: API-Based Token Injection
Instead of fighting the CAPTCHA in the browser, solve it externally and inject the token:
from selenium import webdriver
from selenium.webdriver.common.by import By
import os, requests
def solve_captcha_and_inject(driver):
sitekey = driver.find_element(
By.CSS_SELECTOR, "[data-sitekey]"
).get_attribute("data-sitekey")
resp = requests.post("https://api.passxapi.com/solve", json={
"type": "recaptcha_v2",
"sitekey": sitekey,
"url": driver.current_url
}, headers={"x-api-key": os.getenv("PASSXAPI_KEY")})
token = resp.json()["token"]
driver.execute_script(
f"document.getElementById('g-recaptcha-response').value = '{token}';"
)
return token
The CAPTCHA system never sees Selenium. It sees a normal API request, solves the challenge server-side, and returns a valid token.
CI/CD Pattern: Mock in CI, Real in Staging
import pytest, os
@pytest.fixturedef captcha_solver(monkeypatch):
if os.getenv("CI"):
monkeypatch.setattr(
"myapp.captcha.solve",
lambda **kw: "mock_token_for_ci"
)
return CaptchaSolver(api_key=os.getenv("PASSXAPI_KEY"))
Auto-Detecting CAPTCHA Type
def detect_and_solve(driver):
source = driver.page_source.lower()
captcha_types = {
"recaptcha": ["recaptcha", "g-recaptcha"],
"hcaptcha": ["hcaptcha", "h-captcha"],
"turnstile": ["cf-turnstile", "challenges.cloudflare.com"],
}
for ctype, indicators in captcha_types.items():
if any(ind in source for ind in indicators):
sitekey = driver.find_element(
By.CSS_SELECTOR, "[data-sitekey]"
).get_attribute("data-sitekey")
return solve_via_api(type=ctype, sitekey=sitekey, url=driver.current_url)
return None
Comparison
| Disable in test env | Click through | API + inject | |
|---|---|---|---|
| Works on 3rd-party sites | No | Sometimes | Yes |
| CI/CD friendly | Yes | No (flaky) | Yes |
| Success rate | N/A | ~20% | 95%+ |
Wrapping Up
For internal apps, disabling CAPTCHAs in test mode is fine. For anything else, API-based solving is the most reliable approach.
Full Python SDK: passxapi-python on GitHub
What's your approach to CAPTCHAs in test automation? Drop a comment below.
Top comments (0)