Em qual etapa do checkout o CAPTCHA vai aparecer? Pode ser em qualquer uma — carrinho, endereço, pagamento ou confirmação — e nem sempre na mesma entre um teste e outro. Um script que só resolve CAPTCHA no login quebra no teste em que o desafio aparecer no pagamento. Este guia mostra como estruturar, em Python, um automatizador que resolve reCAPTCHA v2, v3 ou Turnstile antes de cada envio, do carrinho até a confirmação, usando a API da CaptchaAI.
O gatilho também muda pelo motivo do erro: um CEP fora da área de entrega, um cupom inválido ou um cartão recusado podem forçar o CAPTCHA a aparecer numa etapa que, no teste anterior, não tinha desafio nenhum. Por isso a lógica descrita aqui verifica cada tela antes de enviar o formulário, em vez de presumir de antemão em qual etapa o desafio vai estar.
Em que etapas do checkout o CAPTCHA costuma aparecer
| Etapa | Tipos de CAPTCHA mais comuns | Gatilho típico |
|---|---|---|
| Adicionar ao carrinho | reCAPTCHA v3 (invisível) | Prevenção de bots |
| Login ou checkout como visitante | reCAPTCHA v2 | Verificação de conta |
| Endereço de entrega | Cloudflare Turnstile | Rate limit |
| Página de pagamento | reCAPTCHA v2/v3 | Prevenção de fraude |
| Confirmação do pedido | reCAPTCHA v2 | Validação final |
Antes de programar qualquer coisa, vale mapear o que costuma reativar o CAPTCHA no meio de uma sessão de teste:
- Erro de validação no formulário (CEP inválido, cupom expirado, cartão recusado)
- Muitas tentativas seguidas vindas do mesmo IP ou worker
- Primeira requisição de uma sessão de navegador nova
- Troca de dispositivo, user-agent ou viewport no meio do fluxo
Na prática, checar o CAPTCHA antes de cada envio cobre todas essas combinações sem precisar prever qual delas vai ocorrer.
Como estruturar a automação do fluxo de compra
Por que verificar antes de cada clique, e não uma vez só
Um automatizador que resolve o CAPTCHA uma única vez — por exemplo, só no login — quebra assim que o desafio aparecer em outra etapa, mesmo que raramente. A abordagem mais estável é tratar _solve_if_captcha() como um passo de verificação barato: ele varre a página em busca de .cf-turnstile ou [data-sitekey] e só aciona a API quando encontra alguma marcação, então chamá-lo antes de cada clique em "continuar" não adiciona latência perceptível às etapas sem desafio.
┌────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ Cart │───▶│ Shipping │───▶│ Payment │───▶│ Review │───▶│ Confirm │
│ │ │ Address │ │ Info │ │ Order │ │ │
└────────┘ └───────────┘ └──────────┘ └─────────┘ └──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?]
Construindo o automatizador de checkout em Python
A classe que resolve o CAPTCHA a cada etapa
Duas peças: CaptchaSolver, wrapper sobre in.php/res.php, e CheckoutAutomator, que dirige o Selenium e chama _solve_if_captcha() antes de cada clique em "continuar".
O método de detecção é o ponto que mais varia de site para site: ele procura primeiro .cf-turnstile (Turnstile) e, se não encontrar, recorre a [data-sitekey]. Quando o HTML também contém grecaptcha.execute, trata como reCAPTCHA v3 invisível e ajusta o initial_wait para 20 segundos, porque o v3 costuma levar mais tempo para pontuar a sessão do que o v2 tradicional.
import time
import requests
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
class CaptchaSolver:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(resp["request"])
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(result["request"])
raise TimeoutError("Timed out")
class CheckoutAutomator:
def __init__(self, api_key):
self.solver = CaptchaSolver(api_key)
self.driver = webdriver.Chrome()
self.wait = WebDriverWait(self.driver, 15)
def _find(self, selector):
return self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
def _click(self, selector):
self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector))).click()
def _fill(self, selector, value):
el = self._find(selector)
el.clear()
el.send_keys(value)
def _solve_if_captcha(self):
"""Check for and solve any CAPTCHA on the current page."""
html = self.driver.page_source
page_url = self.driver.current_url
# Turnstile
turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
if turnstile:
sitekey = turnstile[0].get_attribute("data-sitekey")
token = self.solver.solve({
"method": "turnstile",
"sitekey": sitekey,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
)
return "turnstile"
# reCAPTCHA
recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
if recaptcha and "recaptcha" in html.lower():
sitekey = recaptcha[0].get_attribute("data-sitekey")
# Check if v3 (invisible)
if "grecaptcha.execute" in html:
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"version": "v3",
"action": "checkout",
"score_qa": "0.9",
}, initial_wait=20)
else:
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
)
return "recaptcha"
return None
# --- Checkout Steps ---
def add_to_cart(self, product_url, quantity=1):
"""Step 1: Navigate to product and add to cart."""
self.driver.get(product_url)
time.sleep(2)
# Set quantity if field exists
qty_fields = self.driver.find_elements(By.CSS_SELECTOR, "input[name='quantity']")
if qty_fields:
qty_fields[0].clear()
qty_fields[0].send_keys(str(quantity))
self._solve_if_captcha()
self._click("[data-action='add-to-cart'], .add-to-cart, #add-to-cart")
time.sleep(2)
return True
def fill_shipping(self, address):
"""Step 2: Fill shipping address."""
self._solve_if_captcha()
field_map = {
"first_name": "#shipping-first-name, [name='firstName']",
"last_name": "#shipping-last-name, [name='lastName']",
"address": "#shipping-address, [name='address1']",
"city": "#shipping-city, [name='city']",
"state": "#shipping-state, [name='state']",
"zip": "#shipping-zip, [name='postalCode']",
"phone": "#shipping-phone, [name='phone']",
}
for field, selectors in field_map.items():
if field in address:
for selector in selectors.split(", "):
elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
if elements:
elements[0].clear()
elements[0].send_keys(address[field])
break
self._solve_if_captcha()
self._click("[data-step='shipping-submit'], .continue-to-payment")
time.sleep(2)
return True
def fill_payment(self, payment):
"""Step 3: Fill payment information."""
self._solve_if_captcha()
# Handle iframe for card fields (common pattern)
iframes = self.driver.find_elements(By.CSS_SELECTOR, "iframe[name*='card']")
if iframes:
self.driver.switch_to.frame(iframes[0])
self._fill("input[name='cardnumber']", payment["card_number"])
self.driver.switch_to.default_content()
if len(iframes) > 1:
self.driver.switch_to.frame(iframes[1])
self._fill("input[name='exp-date']", payment["expiry"])
self.driver.switch_to.default_content()
if len(iframes) > 2:
self.driver.switch_to.frame(iframes[2])
self._fill("input[name='cvc']", payment["cvv"])
self.driver.switch_to.default_content()
else:
# Direct fields
self._fill("[name='cardNumber'], #card-number", payment["card_number"])
self._fill("[name='expiry'], #card-expiry", payment["expiry"])
self._fill("[name='cvv'], #card-cvv", payment["cvv"])
self._solve_if_captcha()
self._click("[data-step='payment-submit'], .continue-to-review")
time.sleep(2)
return True
def confirm_order(self):
"""Step 4: Review and confirm order."""
self._solve_if_captcha()
self._click("[data-step='confirm'], .place-order, #place-order")
time.sleep(3)
# Check for confirmation
html = self.driver.page_source.lower()
if "order confirmed" in html or "thank you" in html or "confirmation" in html:
return True
return False
def run_checkout(self, product_url, address, payment, quantity=1):
"""Run complete checkout flow."""
steps = [
("Add to cart", lambda: self.add_to_cart(product_url, quantity)),
("Proceed to checkout", lambda: self._click(".checkout-btn, a[href*='checkout']")),
("Fill shipping", lambda: self.fill_shipping(address)),
("Fill payment", lambda: self.fill_payment(payment)),
("Confirm order", lambda: self.confirm_order()),
]
for step_name, step_fn in steps:
print(f"Step: {step_name}...")
try:
result = step_fn()
print(f" ✓ {step_name} complete")
except Exception as e:
print(f" ✗ {step_name} failed: {e}")
return False
return True
def close(self):
self.driver.quit()
Exemplo de uso completo
Em QA, use dados fictícios e cartões de teste da plataforma — o mesmo CheckoutAutomator resolve os CAPTCHAs em sandbox e produção. Com workers em sa-east-1 (São Paulo), meça a latência real antes de fixar o initial_wait.
Nunca use CPF, endereço ou número de cartão reais nesses testes, mesmo em ambiente de staging — além do risco óbvio de vazamento, registrar dados pessoais reais em log de teste esbarra nas obrigações da LGPD sobre coleta e retenção de dados. O bloco abaixo segue exatamente esse padrão: nome, endereço e cartão são fictícios do início ao fim.
automator = CheckoutAutomator("YOUR_API_KEY")
try:
success = automator.run_checkout(
product_url="https://store.example.com/product/widget-pro",
address={
"first_name": "Jane",
"last_name": "Smith",
"address": "123 Test Street",
"city": "San Francisco",
"state": "CA",
"zip": "94102",
"phone": "415-555-0100",
},
payment={
"card_number": "4111111111111111",
"expiry": "12/26",
"cvv": "123",
},
quantity=1,
)
print(f"Checkout {'succeeded' if success else 'failed'}")
finally:
automator.close()
Erros comuns ao automatizar o checkout
Antes de entrar nos casos extremos, veja os quatro erros que mais aparecem em suítes de teste de checkout — e a correção direta para cada um:
| Problema | Causa | Correção |
|---|---|---|
| Token CAPTCHA rejeitado no pagamento | O token expirou | Resolva o CAPTCHA imediatamente antes de enviar |
| Campos do cartão não preenchidos | Iframe do Stripe/Braintree | Mude para o iframe antes de preencher |
| Checkout redireciona para login | Sessão expirada | Adicione login antes do checkout |
| O pedido falha silenciosamente | Campos obrigatórios ausentes | Verifique erros após cada etapa |
Na prática, o token expirado é o mais comum de longe: qualquer atraso entre resolver o CAPTCHA e clicar em enviar — um time.sleep extra, uma race condition no Selenium — já é suficiente para o token vencer antes do envio chegar ao servidor.
Tratando exceções durante o checkout automatizado
O CAPTCHA só aparece depois de um erro de validação
Alguns checkouts só mostram CAPTCHA após um erro de validação (CEP inválido, cartão recusado). Envolva a etapa em uma retentativa:
def retry_step_with_captcha(self, step_fn, max_retries=2):
for attempt in range(max_retries + 1):
try:
return step_fn()
except Exception:
if attempt < max_retries:
self._solve_if_captcha()
continue
raise
Sites que exibem o CAPTCHA uma única vez por sessão
Alguns checkouts disparam o desafio só na primeira requisição da sessão. Mantenha os cookies ativos entre etapas, em vez de abrir sessão nova a cada chamada. Use apenas dados fictícios, por cautela com a LGPD.
Campos de formulário que carregam de forma assíncrona
def wait_for_step(self, indicator_selector, timeout=15):
"""Wait for a step to fully load before proceeding."""
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((By.CSS_SELECTOR, indicator_selector))
)
Perguntas frequentes
Preciso resolver o CAPTCHA em toda etapa ou só quando aparecer?
Só quando aparecer. _solve_if_captcha() verifica a página a cada chamada e não faz nada sem .cf-turnstile nem [data-sitekey] — chamá-lo sempre é seguro.
Dá para testar o checkout inteiro sem usar um cartão real?
Sim. A maioria das plataformas tem sandbox com cartões de teste (como 4111111111111111), e a CaptchaAI resolve os mesmos CAPTCHAs em sandbox e produção.
Quantas threads da CaptchaAI eu preciso para rodar checkouts em paralelo?
Depende de quantas instâncias do navegador rodam ao mesmo tempo. O BASIC (US$ 15/mês, 5 threads) cobre 5 checkouts simultâneos; para suítes maiores, o ADVANCE (US$ 90/mês, 50 threads) costuma ser o ponto de partida.
Preciso envolver toda etapa em retry_step_with_captcha(), ou só as sensíveis a erro?
Só as etapas onde o CAPTCHA aparece condicionalmente — normalmente pagamento e endereço. Envolver todas as etapas não quebra nada, mas gasta chamadas de retentativa à toa nas telas em que o desafio é sempre o mesmo.
É seguro rodar esse script de teste contra o ambiente de produção?
Só com dados fictícios e cartão de teste — nunca CPF, endereço ou cartão reais, mesmo em produção. O ideal é manter a suíte apontando para sandbox sempre que a plataforma oferecer um, e reservar produção para a validação final antes do deploy.
Guias relacionados
Para aprofundar o restante do fluxo de automação e da API da CaptchaAI, veja também:
Automatize os testes de checkout — resolva os CAPTCHAs com a CaptchaAI.