Tutorials

CAPTCHA en modos emergentes: detección e inyección de tokens

Un usuario hace clic en "Enviar" y aparece un modal con un desafío CAPTCHA. La sitekey no está en el código fuente de la página inicial: se carga dinámicamente cuando se abre el modal. Tu script de automatización debe activar el modal, esperar a que el CAPTCHA se renderice, extraer los parámetros, resolver e inyectar el token antes de que expire el modal o caduque la sesión.

Patrones modales CAPTCHA

Patrón gatillo Desafío
Iniciar sesión modal Haga clic en el botón "Iniciar sesión" CAPTCHA se carga dentro del div superpuesto
Intersticial anti-bot Automático después de un comportamiento sospechoso Página de bloques modales en pantalla completa
Confirmación de pago Enviar formulario de pago Aparece modal para verificación
Diálogo de límite de velocidad Se detectaron demasiadas solicitudes Modal con puerta CAPTCHA
Consentimiento de cookies + CAPTCHA Primera visita CAPTCHA integrado en el diálogo de consentimiento

Python: controlador CAPTCHA modal de dramaturgo

import requests
import time
from playwright.sync_api import sync_playwright

API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"


def solve_captcha(sitekey, pageurl, method="userrecaptcha"):
    """Submit and poll a CAPTCHA."""
    params = {
        "key": API_KEY,
        "method": method,
        "json": 1,
    }
    if method == "userrecaptcha":
        params["googlekey"] = sitekey
        params["pageurl"] = pageurl
    elif method == "turnstile":
        params["sitekey"] = sitekey
        params["pageurl"] = pageurl

    resp = requests.post(SUBMIT_URL, data=params, timeout=30).json()
    if resp.get("status") != 1:
        raise RuntimeError(f"Submit failed: {resp.get('request')}")

    task_id = resp["request"]
    for _ in range(60):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15).json()

        if poll.get("request") == "CAPCHA_NOT_READY":
            continue
        if poll.get("status") == 1:
            return poll["request"]
        raise RuntimeError(f"Solve failed: {poll.get('request')}")

    raise RuntimeError("Timeout")


def detect_modal_captcha(page):
    """
    Detect CAPTCHA inside a visible modal/dialog.
    Returns (sitekey, method) or (None, None).
    """
    return page.evaluate("""
        () => {
            // Find visible modals
            const modalSelectors = [
                '.modal.show',
                '.modal[style*="display: block"]',
                'dialog[open]',
                '[role="dialog"]:not([aria-hidden="true"])',
                '.overlay.visible',
                '.popup.active',
                '[class*="modal"][class*="open"]',
            ];

            let modal = null;
            for (const sel of modalSelectors) {
                const el = document.querySelector(sel);
                if (el && el.offsetParent !== null) {
                    modal = el;
                    break;
                }
            }

            // If no modal found, search entire document
            const searchRoot = modal || document;

            // Check for reCAPTCHA
            const recaptcha = searchRoot.querySelector('.g-recaptcha[data-sitekey]');
            if (recaptcha) {
                return { sitekey: recaptcha.dataset.sitekey, method: 'userrecaptcha' };
            }

            // Check for Turnstile
            const turnstile = searchRoot.querySelector('.cf-turnstile[data-sitekey]');
            if (turnstile) {
                return { sitekey: turnstile.dataset.sitekey, method: 'turnstile' };
            }

            // Check for hCaptcha
            const hcaptcha = searchRoot.querySelector('.h-captcha[data-sitekey]');
            if (hcaptcha) {
                return { sitekey: hcaptcha.dataset.sitekey, method: 'hcaptcha' };
            }

            return null;
        }
    """)


def inject_token_in_modal(page, token, method="userrecaptcha"):
    """Inject token into the CAPTCHA inside the modal."""
    if method == "userrecaptcha":
        page.evaluate("""
            (token) => {
                // Find response textarea (may be inside modal)
                const textareas = document.querySelectorAll('#g-recaptcha-response, [name="g-recaptcha-response"]');
                textareas.forEach(ta => {
                    ta.value = token;
                    ta.style.display = 'block';
                });

                // Trigger callback
                if (typeof ___grecaptcha_cfg !== 'undefined') {
                    Object.values(___grecaptcha_cfg.clients).forEach(client => {
                        Object.values(client).forEach(val => {
                            if (val && typeof val === 'object') {
                                Object.values(val).forEach(v => {
                                    if (v && typeof v.callback === 'function') v.callback(token);
                                });
                            }
                        });
                    });
                }
            }
        """, token)
    elif method == "turnstile":
        page.evaluate("""
            (token) => {
                const inputs = document.querySelectorAll('[name="cf-turnstile-response"]');
                inputs.forEach(inp => { inp.value = token; });

                if (typeof window.turnstileCallback === 'function') {
                    window.turnstileCallback(token);
                }
            }
        """, token)


def handle_modal_captcha(page, trigger_selector=None, timeout=10000):
    """
    Full workflow: trigger modal, detect CAPTCHA, solve, inject.
    """
    # Step 1: Trigger the modal if needed
    if trigger_selector:
        print(f"Clicking trigger: {trigger_selector}")
        page.click(trigger_selector)

    # Step 2: Wait for modal to become visible
    print("Waiting for modal...")
    modal_selectors = [
        ".modal.show",
        "dialog[open]",
        '[role="dialog"]:not([aria-hidden="true"])',
        ".popup.active",
    ]

    modal_visible = False
    for selector in modal_selectors:
        try:
            page.wait_for_selector(selector, timeout=timeout)
            modal_visible = True
            print(f"  Modal detected: {selector}")
            break
        except Exception:
            continue

    if not modal_visible:
        print("  No modal detected")
        return None

    # Step 3: Wait for CAPTCHA to render inside modal
    time.sleep(2)  # Brief pause for dynamic CAPTCHA loading

    captcha_info = detect_modal_captcha(page)
    if not captcha_info:
        print("  No CAPTCHA found in modal")
        return None

    sitekey = captcha_info["sitekey"]
    method = captcha_info["method"]
    print(f"  Found {method} CAPTCHA: {sitekey[:20]}...")

    # Step 4: Solve via CaptchaAI
    token = solve_captcha(sitekey, page.url, method)
    print(f"  Solved: {token[:30]}...")

    # Step 5: Inject token
    inject_token_in_modal(page, token, method)
    print("  Token injected")

    return token


def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("https://example.com")
        page.wait_for_load_state("networkidle")

        # Handle CAPTCHA that appears in login modal
        token = handle_modal_captcha(
            page,
            trigger_selector="button#login-btn",
            timeout=10000,
        )

        if token:
            # Fill form fields inside modal
            page.fill('dialog input[name="email"]', "user@example.com")
            page.fill('dialog input[name="password"]', "password123")

            # Submit modal form
            page.click('dialog button[type="submit"]')
            page.wait_for_load_state("networkidle")

        browser.close()


main()

JavaScript: controlador modal titiritero

const puppeteer = require("puppeteer");

const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";

async function solveCaptcha(sitekey, pageurl, method = "userrecaptcha") {
  const body = new URLSearchParams({ key: API_KEY, method, json: "1" });
  if (method === "userrecaptcha") { body.set("googlekey", sitekey); body.set("pageurl", pageurl); }
  else if (method === "turnstile") { body.set("sitekey", sitekey); body.set("pageurl", pageurl); }

  const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
  if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);

  const taskId = resp.request;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
    const poll = await (await fetch(url)).json();
    if (poll.request === "CAPCHA_NOT_READY") continue;
    if (poll.status === 1) return poll.request;
    throw new Error(`Solve: ${poll.request}`);
  }
  throw new Error("Timeout");
}

async function waitForModal(page, timeout = 10000) {
  const selectors = [".modal.show", "dialog[open]", '[role="dialog"]', ".popup.active"];
  for (const sel of selectors) {
    try {
      await page.waitForSelector(sel, { visible: true, timeout });
      return sel;
    } catch {}
  }
  return null;
}

async function detectModalCaptcha(page) {
  return page.evaluate(() => {
    const checks = [
      { sel: ".g-recaptcha[data-sitekey]", method: "userrecaptcha" },
      { sel: ".cf-turnstile[data-sitekey]", method: "turnstile" },
    ];
    for (const { sel, method } of checks) {
      const el = document.querySelector(sel);
      if (el && el.dataset.sitekey) return { sitekey: el.dataset.sitekey, method };
    }
    return null;
  });
}

async function handleModalCaptcha(page, triggerSelector) {
  // Trigger modal
  if (triggerSelector) await page.click(triggerSelector);

  // Wait for modal
  const modalSel = await waitForModal(page);
  if (!modalSel) { console.log("No modal found"); return null; }
  console.log(`Modal visible: ${modalSel}`);

  // Wait for CAPTCHA render
  await new Promise((r) => setTimeout(r, 2000));

  const info = await detectModalCaptcha(page);
  if (!info) { console.log("No CAPTCHA in modal"); return null; }

  console.log(`Found ${info.method}: ${info.sitekey.substring(0, 20)}...`);
  const token = await solveCaptcha(info.sitekey, page.url(), info.method);
  console.log(`Solved: ${token.substring(0, 30)}...`);

  // Inject token
  await page.evaluate((t, method) => {
    if (method === "userrecaptcha") {
      document.querySelectorAll("#g-recaptcha-response").forEach((el) => { el.value = t; });
    } else if (method === "turnstile") {
      document.querySelectorAll('[name="cf-turnstile-response"]').forEach((el) => { el.value = t; });
    }
  }, token, info.method);

  return token;
}

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto("https://example.com", { waitUntil: "networkidle2" });

  const token = await handleModalCaptcha(page, "button#login-btn");
  if (token) {
    await page.type('dialog input[name="email"]', "user@example.com");
    await page.click('dialog button[type="submit"]');
    await page.waitForNavigation();
  }

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

Consideraciones de sincronización modal

factores Impacto Mitigación
Tiempo de espera de cierre automático modal Modal puede cerrarse antes de que se complete la resolución Comience a resolver inmediatamente después de la detección
Caducidad de la sesión durante la resolución La sesión del servidor caduca en espera modal Mantenga viva la sesión con latidos de fondo
CAPTCHA renderiza retraso en modal El widget tarda entre 1 y 3 segundos en cargarse en modal Espere 2 segundos después de que el modal sea visible antes de extraer la clave del sitio
Caducidad del token durante el llenado del formulario El token caduca al completar el formulario modal Resuelva CAPTCHA al final, después de completar otros campos

Solución de problemas

Problema causa Solución
Modal detectado pero no se encontró CAPTCHA CAPTCHA se carga de forma asincrónica después de que se abre modal Aumentar el tiempo de espera; use MutationObserver para detectar la inserción de widgets
Token inyectado pero modal no se cierra. Función de devolución de llamada no activada Busque e invoque la devolución de llamada CAPTCHA explícitamente
Modal se cierra durante la resolución. Tiempo de espera de descarte automático Deshabilite el tiempo de espera modal a través de JS: clearTimeout() en el temporizador modal
La clave del sitio CAPTCHA es diferente cada vez Modal genera instancias CAPTCHA dinámicas Extraiga siempre la clave del sitio nueva del DOM modal, nunca la almacene en caché
El disparador de clic no abre modal Elemento no interactivo o detrás de superposición Utilice page.dispatchEvent o espere a que se pueda hacer clic en el elemento

Preguntas frecuentes

¿Cómo detecto un modal que se abre automáticamente sin un disparador de clic?

Utilice un MutationObserver para observar la aparición de nuevos elementos en el DOM. Configúrelo antes de navegar a la página. Cuando se agrega un elemento modal y se vuelve visible, su observador se activa y puede iniciar el flujo de detección de CAPTCHA.

¿Qué pasa si el CAPTCHA está dentro de un iframe modal?

Si el modal contiene un iframe con CAPTCHA, combine este enfoque con el manejo de iframe. Después de detectar el modal, cambie al contexto del iframe dentro del modal para extraer la clave del sitio.

¿Debo completar los campos del formulario antes o después de resolver el CAPTCHA?

Antes. Primero complete todos los demás campos del formulario y luego resuelva el CAPTCHA al final. Esto minimiza el tiempo entre la obtención del token y el envío del formulario, lo que reduce el riesgo de caducidad.

Artículos relacionados

  • Cómo resolver callback de reCAPTCHA v2 con la API
  • Manejo de reCAPTCHA v2 y Turnstile en el mismo sitio

Maneja CAPTCHA en modales emergentes sin problemas: obtén tu API key de CaptchaAI e implementa la detección modal.

Guías relacionadas:

  • Manejo de múltiples CAPTCHA en una sola página
  • Manejo de Shadow DOM CAPTCHA
  • Extracción de CAPTCHA en iframes anidados
Los comentarios están deshabilitados para este artículo.

Publicaciones relacionadas

Explainers Salida de red móvil en pruebas QA propias (editorial)
Vista editorial sobre el uso de salidas de red móviles autorizadas en pruebas QA propias y su impacto en CAPTCHA.

Vista editorial sobre el uso de salidas de red móviles autorizadas en pruebas QA propias y su impacto en CAPTC...

Apr 19, 2026