Un formulario de registro protegido por reCAPTCHA no debería frenar tu pipeline de CI. La forma de probarlo de extremo a extremo es directa: en lugar de resolver el CAPTCHA a mano, tu suite pide un token a la API de CaptchaAI, lo inyecta en el formulario y sigue con las aserciones como en cualquier otra prueba. Esta guía reúne los patrones para hacerlo con Pytest, Selenium y CI/CD, con código que puedes pegar en tu propio repositorio.
Qué significan las pruebas "autorizadas"
"Autorizadas" quiere decir que pruebas flujos que controlas o que tienes permiso para probar: tu propio staging, tu formulario de registro, tu API interna. Resolver el CAPTCHA de tu login de prueba para validar una regresión es QA legítimo; hacerlo contra un sitio ajeno no lo es. En proyectos sujetos a GDPR, LOPDGDD o la LFPDPPP mexicana, mantener las pruebas sobre entornos propios evita además tocar datos personales reales. Por eso todos los ejemplos de esta guía apuntan a staging.example.com, nunca a producción ni a un tercero.
Cómo encaja la resolución de CAPTCHA en tu suite
El patrón es el mismo sin importar el framework de pruebas que uses:
- Tu prueba lee el
sitekeydel formulario que va a ejercitar. - Envía ese
sitekeyy la URL de la página a la API de CaptchaAI. - Sondea el resultado hasta recibir el token resuelto.
- Inyecta el token en el campo del formulario y continúa con tus aserciones habituales.
A partir de ahí, resolver el CAPTCHA es un paso más de la prueba, no un obstáculo que obliga a intervenir a mano.
Cuándo un equipo de QA necesita resolver CAPTCHA
Estos son los escenarios donde automatizar la resolución ahorra horas de trabajo manual:
- Pruebas de regresión: confirmar que los formularios siguen funcionando tras cada despliegue, sin resolver el CAPTCHA a mano en cada corrida.
- Pruebas de extremo a extremo: recorrer flujos completos, del registro al checkout, tal como los vive un usuario real.
- Pruebas de carga: reproducir flujos con CAPTCHA a escala realista, en vez de desactivarlo y probar un camino que no existe en producción.
- Pruebas entre navegadores: validar que el widget se renderiza igual en Chrome, Firefox y Edge.
- Pruebas de accesibilidad: verificar los flujos alternativos para personas con discapacidad.
Cómo controlar el costo de las pruebas
No hace falta resolver un CAPTCHA en cada commit. Estas estrategias mantienen el gasto bajo control antes de escribir una sola prueba:
| Estrategia | Beneficio |
|---|---|
| Usa un entorno de staging | Menor dificultad del CAPTCHA |
| Ejecuta las pruebas con CAPTCHA por programación, no en cada push | Menos llamadas a la API |
| Cachea el resultado de las pruebas inestables | Evitas re-resoluciones innecesarias |
| Usa una variable de entorno para omitir el CAPTCHA en local | Ahorras durante el desarrollo |
| Agrupa las pruebas con CAPTCHA en un job de CI dedicado | Controlas el costo por pipeline |
Como CaptchaAI factura por thread y no por resolución, el gasto de tu suite es predecible en USD, algo que agradecen las agencias y los equipos que facturan en monedas locales volátiles.
Integración con Pytest
El punto de partida es un helper que envía la tarea, sondea el resultado y devuelve el token, expuesto como fixture de sesión para compartirlo entre todas las pruebas:
import pytest
import requests
import time
class CaptchaTestHelper:
"""Helper for solving CAPTCHAs in test environments."""
def __init__(self, api_key):
self.api_key = api_key
def solve_recaptcha(self, sitekey, pageurl, timeout=120):
"""Solve reCAPTCHA and return token."""
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": 1,
}, timeout=30)
result = resp.json()
assert result.get("status") == 1, f"Submit failed: {result}"
task_id = result["request"]
deadline = time.time() + timeout
time.sleep(10)
while time.time() < deadline:
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
return data["request"]
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(f"Solve error: {data['request']}")
time.sleep(5)
raise TimeoutError("CAPTCHA solve timeout")
@pytest.fixture(scope="session")
def captcha_helper():
"""Provide CaptchaAI helper for test session."""
import os
api_key = os.environ.get("CAPTCHAAI_API_KEY")
if not api_key:
pytest.skip("CAPTCHAAI_API_KEY not set")
return CaptchaTestHelper(api_key)
class TestLoginFlow:
"""Test login flow behind reCAPTCHA."""
SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
LOGIN_URL = "https://staging.staging.example.com/qa-login"
def test_login_with_valid_credentials(self, captcha_helper):
"""Verify login succeeds with valid creds and solved CAPTCHA."""
token = captcha_helper.solve_recaptcha(self.SITEKEY, self.LOGIN_URL)
assert token and len(token) > 100
resp = requests.post(self.LOGIN_URL, data={
"username": "test_user",
"password": "test_pass",
"g-recaptcha-response": token,
})
assert resp.status_code == 200
assert "Welcome" in resp.text
def test_login_with_invalid_credentials(self, captcha_helper):
"""Verify login fails gracefully with bad creds but valid CAPTCHA."""
token = captcha_helper.solve_recaptcha(self.SITEKEY, self.LOGIN_URL)
resp = requests.post(self.LOGIN_URL, data={
"username": "wrong_user",
"password": "wrong_pass",
"g-recaptcha-response": token,
})
assert resp.status_code in (200, 401)
assert "Invalid" in resp.text or "error" in resp.text.lower()
def test_login_without_captcha_fails(self):
"""Verify login rejects submissions without CAPTCHA."""
resp = requests.post(self.LOGIN_URL, data={
"username": "test_user",
"password": "test_pass",
})
assert resp.status_code in (400, 403, 422)
Las tres pruebas cubren el camino feliz, el fallo controlado con credenciales incorrectas y el rechazo cuando falta el token: justo lo que quieres asegurar en cada release.
Prueba E2E con Selenium
Para validar el renderizado real del widget en el navegador, Selenium abre la página, extrae el sitekey del DOM, resuelve el token con el mismo helper y lo inyecta antes de enviar el formulario:
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
@pytest.fixture
def browser():
"""Create browser for testing."""
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
yield driver
driver.quit()
class TestRegistrationFlow:
"""Test registration form with CAPTCHA."""
REG_URL = "https://staging.example.com/register"
def test_registration_form_submits(self, browser, captcha_helper):
"""Full registration flow with CAPTCHA solving."""
browser.get(self.REG_URL)
# Fill form
browser.find_element(By.ID, "email").send_keys("test@example.com")
browser.find_element(By.ID, "password").send_keys("SecurePass123!")
browser.find_element(By.ID, "confirm_password").send_keys("SecurePass123!")
# Extract sitekey from page
captcha_div = browser.find_element(By.CSS_SELECTOR, ".g-recaptcha")
sitekey = captcha_div.get_attribute("data-sitekey")
# Solve via API
token = captcha_helper.solve_recaptcha(sitekey, browser.current_url)
# Inject token
browser.execute_script("""
document.querySelector('[name="g-recaptcha-response"]').value = arguments[0];
""", token)
# Trigger callback if needed
callback = captcha_div.get_attribute("data-callback")
if callback:
browser.execute_script(f"window['{callback}'](arguments[0]);", token)
# Submit
browser.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
# Verify success
WebDriverWait(browser, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".success-message"))
)
def test_captcha_renders_on_page(self, browser):
"""Verify CAPTCHA widget loads on registration page."""
browser.get(self.REG_URL)
captcha = WebDriverWait(browser, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".g-recaptcha, iframe[src*='recaptcha']"))
)
assert captcha.is_displayed()
Fíjate en el paso del data-callback: muchos formularios de registro no envían hasta que se dispara la función de callback de reCAPTCHA, así que inyectar el token no basta si no la invocas también.
Configuración de las pruebas
Separa las pruebas con CAPTCHA del resto con un marcador de Pytest. Así el pipeline rápido no gasta saldo de la API y solo el job dedicado hace resoluciones reales:
# conftest.py
import os
# Mark tests that need CAPTCHA solving
def pytest_configure(config):
config.addinivalue_line(
"markers", "captcha: tests requiring CAPTCHA solving (may be slow)"
)
# pytest.ini or pyproject.toml
"""
[tool.pytest.ini_options]
markers = [
"captcha: tests requiring CAPTCHA solving (may be slow)",
]
"""
Ejecuta solo las pruebas con CAPTCHA:
pytest -m captcha -v
Ejecuta el resto sin las pruebas con CAPTCHA (pipeline rápido):
pytest -m "not captcha" -v
Con esta separación, cada commit corre en segundos y las resoluciones reales quedan reservadas para el job nocturno o programado.
Preguntas frecuentes
¿Qué plan de CaptchaAI necesito para un pipeline de QA?
Para la mayoría de las suites basta el plan BASIC ($15/mes, 5 threads): las pruebas corren en paralelo, pero rara vez saturan cinco resoluciones a la vez. Si tu CI lanza cientos de pruebas con CAPTCHA simultáneas, sube a STANDARD ($30/mes, 15 threads). Al facturarse por thread y no por resolución, el costo mensual es fijo.
¿Es legal resolver el CAPTCHA en mis pruebas?
Sí, cuando pruebas un flujo que controlas o tienes autorización para probar: tu propio staging, tu formulario, tu API. Respeta los términos de servicio del sitio y la normativa de protección de datos aplicable (GDPR, LOPDGDD, LFPDPPP). Esta guía trata únicamente de QA sobre entornos propios.
¿Cómo evito que cada commit gaste saldo de la API?
Marca las pruebas con CAPTCHA con el marcador de Pytest (@pytest.mark.captcha) y ejecútalas solo en un job programado o nocturno. El pipeline de cada commit corre con pytest -m "not captcha" y no toca la API.
¿Funciona con Turnstile o solo con reCAPTCHA?
Con ambos. El ejemplo usa reCAPTCHA v2, pero CaptchaAI también resuelve Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3 e imágenes/OCR: basta con ajustar el parámetro method del envío según el tipo de CAPTCHA que quieras probar.
Guías relacionadas
Crea tu cuenta y conecta CaptchaAI a tu suite de pruebas hoy mismo.