Troubleshooting

A automação do navegador CAPTCHA falha, mas a API funciona: guia de depuração

Se a API da CaptchaAI devolve um token válido e o navegador continua travado no mesmo CAPTCHA, o problema está quase sempre em um destes cinco pontos: campo errado, callback que não dispara, token expirado, pageurl incorreto ou detecção de bot que nada tem a ver com o CAPTCHA.

Veja abaixo o diagnóstico rápido e a correção de cada caso, com código para Selenium — a mesma lógica vale para Playwright e Puppeteer.


Por que o token passa na API mas falha no navegador

  • Seletor errado para o campo de resposta → o token não é definido no elemento certo e o formulário é enviado sem ele.
  • Tempo longo demais entre resolver e enviar → o token expira e o site volta a mostrar o CAPTCHA.
  • Callback do grecaptcha não foi chamado → o botão do formulário continua desativado.
  • URL usada na resolução não bate com a real → o pageurl enviado à API está errado e o token fica inválido para o domínio.
  • O navegador entrega sinais de automação → mesmo com o token aceito, a detecção de bot bloqueia o acesso.

Diagnóstico rápido: sintoma → causa → correção

Sintoma Causa provável Correção
Token definido, mas o formulário rejeita Callback não disparou Localize e chame a função de callback
Botão de enviar continua desativado É o callback que libera o botão Dispare o callback logo após definir o token
"CAPTCHA inválido" depois de enviar O token expirou Reduza o tempo entre resolver e enviar
Erro 403 depois de enviar o formulário Detecção de bot, não é o CAPTCHA Ajuste as opções do navegador de QA
Funciona com requests, falha no navegador pageurl diferente da URL real Use driver.current_url como pageurl

Correção 1: como aplicar o token do reCAPTCHA v2 no campo certo

O token precisa ir para o campo g-recaptcha-response e o callback precisa disparar — só definir o valor no textarea não basta. Na prática são dois passos, nessa ordem:

  • Tornar visível e preencher o textarea g-recaptcha-response (pode haver mais de um na página).
  • Chamar a função de callback do grecaptcha para liberar o botão de envio.
from selenium import webdriver
from selenium.webdriver.common.by import By
import time


def inject_recaptcha_token(driver, token):
    """Properly inject reCAPTCHA v2 token in browser."""

    # Step 1: Make the response textarea visible and set the value
    driver.execute_script("""
        // Find all response textareas (may be multiple on page)
        var textareas = document.querySelectorAll('[name="g-recaptcha-response"]');
        textareas.forEach(function(ta) {
            ta.style.display = 'block';
            ta.value = arguments[0];
        });

        // Also set via ID if present
        var byId = document.getElementById('g-recaptcha-response');
        if (byId) {
            byId.style.display = 'block';
            byId.value = arguments[0];
        }
    """, token)

    # Step 2: Trigger the callback
    driver.execute_script("""
        // Try standard callback
        if (typeof ___grecaptcha_cfg !== 'undefined') {
            var clients = ___grecaptcha_cfg.clients;
            for (var key in clients) {
                var client = clients[key];
                // Navigate nested structure to find callback
                for (var prop in client) {
                    var val = client[prop];
                    if (val && typeof val === 'object') {
                        for (var subprop in val) {
                            var subval = val[subprop];
                            if (subval && typeof subval === 'object' && subval.callback) {
                                subval.callback(arguments[0]);
                                return;
                            }
                        }
                    }
                }
            }
        }
        // Fallback: try common callback names
        if (typeof onCaptchaSuccess === 'function') onCaptchaSuccess(arguments[0]);
        else if (typeof captchaCallback === 'function') captchaCallback(arguments[0]);
    """, token)


# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# ... solve CAPTCHA via CaptchaAI ...
inject_recaptcha_token(driver, token)
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()

Correção 2: formulários que só liberam o envio após o callback

  • Muitos formulários mantêm o botão de envio desativado até o callback do CAPTCHA disparar.
  • Localize a função de callback e chame-a manualmente — o snippet abaixo tenta duas abordagens.
def find_and_trigger_callback(driver, token):
    """Find the reCAPTCHA callback and trigger it."""

    # Method 1: Extract callback from data attribute
    callback_name = driver.execute_script("""
        var widget = document.querySelector('.g-recaptcha');
        if (widget) {
            return widget.getAttribute('data-callback');
        }
        return null;
    """)

    if callback_name:
        driver.execute_script(f"window['{callback_name}'](arguments[0]);", token)
        return True

    # Method 2: Extract from grecaptcha config
    triggered = driver.execute_script("""
        try {
            var widgetId = 0;
            var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
            if (typeof callback === 'function') {
                callback(arguments[0]);
                return true;
            }
        } catch(e) {}
        return false;
    """, token)

    return triggered

Correção 3: tempo de vida do token — resolva e use na hora

O token do reCAPTCHA v2 expira poucos minutos após a resolução, e a janela útil é menor do que parece. Dois fatores já consomem tempo real antes mesmo de você tentar enviar o formulário:

  • Latência de rede até os workers — mesmo com workers em sa-east-1 (São Paulo), a viagem de ida e volta soma tempo.
  • Tempo de polling em in.php/res.php — cada ciclo de consulta acrescenta alguns segundos até o status virar 1.
import requests
import time


def solve_and_inject_fast(driver, api_key, sitekey, page_url):
    """Solve CAPTCHA and inject token with minimal delay."""
    # Submit solve request
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    task_id = resp.json()["request"]

    # Poll for result
    time.sleep(15)
    for _ in range(24):
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()

        if data.get("status") == 1:
            token = data["request"]

            # IMMEDIATELY inject — don't wait
            inject_recaptcha_token(driver, token)

            # Submit form within 5 seconds
            time.sleep(0.5)
            return True

        if data["request"] != "CAPCHA_NOT_READY":
            raise RuntimeError(data["request"])
        time.sleep(5)

    raise TimeoutError("Solve timeout")

Correção 4: pageurl correto em SPAs e CAPTCHA dentro de iframe

  • CAPTCHA dentro de iframe: use a URL da página pai (driver.current_url), não a URL do iframe.
  • SPA com navegação client-side: a URL atual pode já ter mudado desde o carregamento inicial — sempre leia driver.current_url no momento da resolução.

Se a API recebe um pageurl que não bate com nenhum desses dois casos, o token sai atrelado ao domínio ou à rota errada e o site rejeita:

def get_correct_pageurl(driver):
    """Get the URL that reCAPTCHA actually sees."""

    # Check if CAPTCHA is in an iframe
    iframes = driver.find_elements(By.TAG_NAME, "iframe")

    for iframe in iframes:
        src = iframe.get_attribute("src") or ""
        if "recaptcha" in src or "anchor" in src:
            # CAPTCHA is in iframe — use parent page URL
            return driver.current_url

    # For SPAs, use current URL (may differ from initial load)
    return driver.current_url


# WRONG — using the URL you navigated to
pageurl = "https://example.com/form"  # May have redirected

# CORRECT — using the URL the browser is actually on
pageurl = get_correct_pageurl(driver)

Correção 5: quando o bloqueio não é do CAPTCHA, e sim do navegador

Um token aceito não garante acesso: muitos sites rodam uma checagem de automação separada e bloqueiam mesmo depois do CAPTCHA passar. Antes de reenviar, ajuste pelo menos estes dois pontos no navegador de QA:

  • Remova os indicadores de automação (--disable-blink-features=AutomationControlled, excludeSwitches).
  • Sobrescreva o sinal de navegador automatizado via CDP antes do primeiro carregamento da página.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service


def create_qa_browser_driver():
    """Create a browser that avoids basic bot detection."""
    options = Options()

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

    # Set realistic window size
    options.add_argument("--window-size=1920,1080")

    driver = webdriver.Chrome(options=options)

    # Override sinal de navegador automatizado
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
    })

    return driver


driver = create_qa_browser_driver()

Correção 6: token do Cloudflare Turnstile no navegador

O Cloudflare Turnstile usa um campo e um fluxo de callback diferentes do reCAPTCHA:

  • Campo de resposta: cf-turnstile-response (mantenha g-recaptcha-response como fallback — alguns sites reaproveitam o mesmo formulário).
  • Callback: leia data-callback no widget e dispare a função manualmente, como no reCAPTCHA.
def inject_turnstile_token(driver, token):
    """Inject Turnstile token into the page."""
    driver.execute_script("""
        // Find Turnstile response input
        var inputs = document.querySelectorAll(
            'input[name="cf-turnstile-response"], ' +
            'input[name="g-recaptcha-response"]'
        );

        inputs.forEach(function(input) {
            input.value = arguments[0];
        });

        // Trigger change event
        inputs.forEach(function(input) {
            input.dispatchEvent(new Event('change', { bubbles: true }));
            input.dispatchEvent(new Event('input', { bubbles: true }));
        });

        // Try Turnstile callback
        if (window.turnstile) {
            var widgets = document.querySelectorAll('[data-callback]');
            widgets.forEach(function(w) {
                var cb = w.getAttribute('data-callback');
                if (typeof window[cb] === 'function') {
                    window[cb](arguments[0]);
                }
            });
        }
    """, token)

Lista de verificação de depuração

Se nada acima resolveu, rode este script no console (ou via driver.execute_script) antes de abrir um chamado. Ele reúne, em um só lugar:

  • Se o textarea de resposta existe e já tem valor.
  • Se o grecaptcha foi carregado e qual é o nome do callback configurado no widget.
  • A URL atual do navegador, para comparar com o pageurl enviado à API.
def debug_captcha_injection(driver, token):
    """Print debug info to diagnose injection failures."""
    info = driver.execute_script("""
        var result = {};

        // Check textarea exists
        var ta = document.querySelector('[name="g-recaptcha-response"]');
        result.textarea_found = !!ta;
        result.textarea_value_set = ta ? ta.value.length > 0 : false;

        // Check reCAPTCHA loaded
        result.grecaptcha_loaded = typeof grecaptcha !== 'undefined';

        // Check for callback
        var widget = document.querySelector('.g-recaptcha');
        result.has_data_callback = widget ? !!widget.getAttribute('data-callback') : false;
        result.callback_name = widget ? widget.getAttribute('data-callback') : null;

        // Current URL
        result.current_url = window.location.href;

        // Check for errors in console (basic)
        result.has_submit_button = !!document.querySelector('button[type=submit], input[type=submit]');

        return result;
    """)

    for key, value in info.items():
        print(f"  {key}: {value}")

    return info

Perguntas frequentes

Por que o mesmo token funciona pela API, mas falha no navegador?

Quase sempre porque o callback do reCAPTCHA não disparou. O formulário depende dele para liberar o botão de envio ou preencher campos ocultos — só definir o valor no textarea raramente basta.

Preciso pagar de novo a cada nova tentativa de reenvio do token?

Não. A CaptchaAI cobra por thread simultânea, não por tentativa — o plano BASIC (US$ 15/mês, 5 threads) deixa testar o mesmo fluxo quantas vezes precisar, sem custo extra. O que consome tempo é o polling em in.php/res.php, não o número de tentativas de aplicar o token.

A lógica do callback é igual em Playwright, Puppeteer e Selenium?

Sim: definir o token no campo certo e disparar o callback via JavaScript — só muda a forma de rodar o script:

  • Selenium: driver.execute_script(...)
  • Playwright: page.evaluate(...)
  • Puppeteer: page.evaluate(...)

Devo automatizar em modo headless ou headful?

Comece em modo headful para confirmar que o fluxo funciona. Alguns sites detectam navegadores headless e rejeitam o token mesmo com o CAPTCHA resolvido — migre para headless só depois de validar esse comportamento.


Guias Relacionados

Continue o diagnóstico com estes guias:



Próximos passos

Depois de resolver a injeção, estes passos ajudam a consolidar o fluxo:

Os comentários estão desativados para este artigo.