A suíte passa em tudo e trava no formulário de login: click() na caixa de verificação não muda nada e aumentar o WebDriverWait só adia o timeout. A saída não está no navegador. Com o Selenium você lê a sitekey do widget, manda esse valor e a URL da página para a API da CaptchaAI, recebe um token e o escreve no formulário antes do envio.
São quatro passos em Python, e só o último depende do relógio. Os exemplos usam staging.example.com: o cenário é QA autorizado na sua própria aplicação.
Como o Selenium e a API dividem o trabalho
- O Selenium carrega a página de destino.
- O script lê a
sitekey(chave pública do widget) no DOM. - A CaptchaAI resolve o desafio com a sitekey e a URL.
- O script injeta o token no formulário e envia.
A resolução acontece no servidor da CaptchaAI e o Selenium nunca toca no widget — por isso o modo headless funciona sem ajuste no código.
O que instalar antes de começar
| Requisito | Detalhes |
|---|---|
| Python 3.7+ | Com o pip instalado |
| Selenium | pip install selenium |
| Chrome + ChromeDriver | Versões compatíveis entre si |
| requests | pip install requests |
| Chave de API da CaptchaAI | Disponível em captchaai.com |
Um cenário de QA com CAPTCHA em staging
Pense numa suíte de regressão noturna com quarenta cenários de login em staging.example.com, cujo formulário herdou o reCAPTCHA v2 da produção. A saída improvisada é desligar o widget em staging — e metade dos defeitos escapa, porque o caminho testado deixa de ser o caminho real.
Duas notas práticas: manter os workers em sa-east-1 (São Paulo) corta milissegundos de RTT por requisição, e os cenários devem usar dados fictícios — login com dados reais cria uma obrigação de tratamento sob a LGPD que nenhum staging precisa carregar.
Passo 1: configurar o Chrome e o driver
O ponto de partida é um webdriver.Chrome com opções explícitas. Fixar o user agent mantém a mesma identificação entre execuções e torna comparáveis os logs de noites diferentes.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
driver = webdriver.Chrome(options=options)
As duas opções abaixo removem a barra de aviso "o Chrome está sendo controlado por software de teste automatizado", que polui capturas de tela e desloca elementos alguns pixels na primeira renderização.
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
Passo 2: a função que conversa com a API
São dois endpoints: in.php recebe a tarefa e devolve um ID; res.php entrega o token quando fica pronto. Enquanto a resposta for CAPCHA_NOT_READY, consulte novamente, sempre com intervalo.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(site_key, page_url):
"""Solve reCAPTCHA v2 using CaptchaAI API."""
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY,
"action": "get",
"id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("CAPTCHA solve timed out")
O laço faz até 60 consultas com 5 s de espera e desiste em cinco minutos — teto folgado para o reCAPTCHA v2. Para não bloquear a thread do teste, use pingback e receba o resultado por callback.
Passo 3: ler a sitekey na página
A sitekey fica no atributo data-sitekey do contêiner g-recaptcha e costuma chegar depois do HTML inicial. Espere o elemento em vez de ler o DOM logo após o get().
# Navigate to the target page
driver.get("https://staging.example.com/qa-login")
# Wait for the reCAPTCHA to load
wait = WebDriverWait(driver, 10)
recaptcha = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)
# Extract the site key
site_key = recaptcha.get_attribute("data-sitekey")
page_url = driver.current_url
print(f"Site key: {site_key}")
print(f"Page URL: {page_url}")
# Solve the CAPTCHA
token = solve_recaptcha_v2(site_key, page_url)
print(f"Token received: {token[:50]}...")
Repare que o pageurl vem de driver.current_url, não da URL digitada: fluxos de login redirecionam, e a API precisa do endereço em que o widget foi renderizado.
Passo 4: injetar o token e enviar o formulário
O token entra no textarea oculto g-recaptcha-response. Escrever no campo basta para formulários com POST tradicional; quando a página usa callback JavaScript, o segundo trecho dispara a função registrada pelo widget.
# Inject the token into the reCAPTCHA response field
driver.execute_script(f"""
document.getElementById('g-recaptcha-response').innerHTML = '{token}';
document.getElementById('g-recaptcha-response').style.display = '';
""")
# If the form uses a callback function, trigger it
driver.execute_script(f"""
if (typeof ___grecaptcha_cfg !== 'undefined') {{
Object.keys(___grecaptcha_cfg.clients).forEach(function(key) {{
var client = ___grecaptcha_cfg.clients[key];
if (client.callback) client.callback('{token}');
}});
}}
""")
# Submit the form
driver.find_element(By.CSS_SELECTOR, "form").submit()
# Wait for navigation
wait.until(EC.url_changes(page_url))
print(f"Success! Now on: {driver.current_url}")
Injete e envie na mesma sequência, sem pausas longas: o token do reCAPTCHA v2 vale cerca de 120 s e é de uso único.
Script completo de ponta a ponta
Este é o arquivo que entra direto na suíte. O try/finally fecha o navegador mesmo quando a asserção falha; sem ele, uma noite de execução deixa dezenas de processos do Chrome vivos no runner.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
if not resp.text.startswith("OK|"):
raise Exception(f"Submit failed: {resp.text}")
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("Timed out")
def main():
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://staging.example.com/qa-login")
wait = WebDriverWait(driver, 10)
# Extract site key
recaptcha = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)
site_key = recaptcha.get_attribute("data-sitekey")
# Solve
token = solve_recaptcha_v2(site_key, driver.current_url)
# Inject and submit
driver.execute_script(
f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';"
)
driver.find_element(By.CSS_SELECTOR, "form").submit()
wait.until(EC.url_changes(driver.current_url))
print("Login successful!")
finally:
driver.quit()
if __name__ == "__main__":
main()
Outros tipos de CAPTCHA no mesmo fluxo
A estrutura não muda: trocam-se o method e o nome do parâmetro que carrega a chave do widget.
reCAPTCHA v3
O v3 não exibe caixa de seleção — devolve uma pontuação. Informe version=v3 e a action declarada pela página; se ela divergir, o servidor recusa o token mesmo com pontuação alta.
def solve_recaptcha_v3(site_key, page_url, action="verify"):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action
})
task_id = resp.text.split("|")[1]
# ... same polling logic
Cloudflare Turnstile
Aqui o parâmetro se chama sitekey (não googlekey) e o campo do formulário é cf-turnstile-response.
def solve_turnstile(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url
})
task_id = resp.text.split("|")[1]
# ... same polling logic
Confira o catálogo antes de escrever o teste: a CaptchaAI resolve reCAPTCHA v2 e v3 (incluindo Enterprise), Cloudflare Turnstile e Challenge, GeeTest v3, imagem/OCR, grade de imagens e BLS, mais CaptchaFox (beta), Friendly Captcha (beta) e Lemin (beta). hCaptcha e FunCaptcha (Arkose Labs) não são suportados; o GeeTest v4 está anunciado como "em breve".
Quantas threads a sua suíte precisa
A cobrança é por thread simultânea, não por resolução: cada plano inclui resoluções ilimitadas dentro das threads contratadas, e uma thread é um CAPTCHA em andamento. Conte quantos cenários com CAPTCHA rodam ao mesmo tempo, não quantos rodam por noite.
- BASIC (US$ 15/mês, 5 threads) — suíte pequena, até cinco navegadores em paralelo.
- STANDARD (US$ 30/mês, 15 threads) — pipeline de CI com paralelismo moderado.
- ADVANCE (US$ 90/mês, 50 threads) — várias suítes disparando juntas.
O gargalo raramente é a cota: dez instâncias do Chrome pesam mais no runner do que dez resoluções pesam no plano.
Erros comuns ao resolver CAPTCHA no Selenium
| Sintoma | Causa provável | Correção |
|---|---|---|
| Token recusado | Expirou antes do envio | Injete e envie dentro de 120 s |
| Sitekey não encontrada | Widget renderizado após o HTML inicial | Aumente o WebDriverWait |
NoSuchElementException |
Seletor errado ou elemento em iframe | Ajuste o seletor e entre no iframe |
session not created |
ChromeDriver e Chrome em versões diferentes | Fixe as duas versões na imagem de CI |
| Formulário bloqueado com token válido | Outra camada de verificação | Veja no log do servidor o que mais é exigido |
Perguntas frequentes
Por quanto tempo o token continua válido?
Cerca de 120 segundos no reCAPTCHA v2, e o uso é único. Resolva o mais perto possível do envio e, se o passo seguinte falhar, peça um token novo em vez de repetir o mesmo valor.
E se o formulário de staging usar hCaptcha?
Esse tipo está fora do catálogo: hCaptcha e FunCaptcha (Arkose Labs) não são suportados. Para manter o cenário coberto, use em staging um tipo suportado — reCAPTCHA v2 ou Cloudflare Turnstile.
Quantas threads preciso para rodar dez navegadores em paralelo?
Dez, no pior caso, porque a conta é pelo número de CAPTCHAs em andamento ao mesmo tempo. O STANDARD (US$ 30/mês, 15 threads) cobre esse cenário com folga.
E quando o formulário usa callback em vez de submit?
Dispare o callback com driver.execute_script(), passando o token para a função registrada pelo widget: é o segundo trecho do Passo 4. O caso completo está em como resolver o reCAPTCHA v2 com callback.