Use Cases

Automação de checkout em várias etapas com solução CAPTCHA

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.

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