Tutorials

Integração completa Node.js Playwright + CaptchaAI

Para resolver reCAPTCHA v2, Cloudflare Turnstile ou CAPTCHA de imagem dentro de um script Playwright em Node.js, o caminho é sempre o mesmo: capturar a sitekey na página, enviar o desafio à API da CaptchaAI, aguardar o token e escrevê-lo de volta no formulário. Este guia monta esse fluxo do início e termina em uma classe PlaywrightAutomation reutilizável que resolve login com CAPTCHA de ponta a ponta.

O Playwright encaixa bem nesse tipo de automação porque roda em Chromium, Firefox e WebKit com a mesma API, tem espera automática e dispensa plugins extras nos cenários de QA. Se o seu público está no Brasil, rodar os workers em uma região próxima — por exemplo, AWS sa-east-1, em São Paulo — reduz o RTT até a página alvo.

Neste guia você vai:

  • Subir um navegador com padrões estáveis para testes autorizados
  • Enviar e consultar desafios pela API da CaptchaAI (in.php e res.php)
  • Resolver reCAPTCHA v2, Turnstile e CAPTCHA de imagem no mesmo projeto
  • Reunir tudo em uma classe de automação pronta para reutilizar

Instalação e pré-requisitos

Instale o Playwright e baixe o Chromium antes de começar:

npm install playwright
npx playwright install chromium

Configuração do navegador para QA

Suba o navegador com padrões estáveis para os seus testes autorizados. O script de inicialização ajusta a flag navigator.webdriver, um sinal que muitas páginas verificam:

const { chromium } = require("playwright");

async function createBrowser() {
  const browser = await chromium.launch({
    headless: false,
    args: ["--disable-blink-features=AutomationControlled"],
  });

  const context = await browser.newContext({
    userAgent:
      "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
      "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    viewport: { width: 1920, height: 1080 },
    locale: "en-US",
  });

  // Remove Playwright detection
  await context.addInitScript(() => {
    Object.defineProperty(navigator, "webdriver", { get: () => undefined });
    delete navigator.__proto__.webdriver;
  });

  const page = await context.newPage();
  return { browser, context, page };
}

Função de resolução com a CaptchaAI

Esta função concentra as duas chamadas da API: in.php envia a tarefa e res.php consulta o resultado. O laço faz o polling a cada 5 segundos, com um teto de 30 tentativas antes de estourar o timeout:

const API_KEY = "YOUR_API_KEY";

async function solveCaptcha(method, params) {
  // Submit
  const submitResp = await fetch("https://ocr.captchaai.com/in.php", {
    method: "POST",
    body: new URLSearchParams({ key: API_KEY, method, json: "1", ...params }),
  });
  const submitData = await submitResp.json();
  if (submitData.status !== 1) throw new Error(`Submit: ${submitData.request}`);

  const taskId = submitData.request;

  // Poll
  for (let i = 0; i < 30; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const pollResp = await fetch(
      `https://ocr.captchaai.com/res.php?${new URLSearchParams({
        key: API_KEY,
        action: "get",
        id: taskId,
        json: "1",
      })}`
    );
    const data = await pollResp.json();
    if (data.status === 1) return data.request;
    if (data.request === "ERROR_CAPTCHA_UNSOLVABLE") throw new Error("Unsolvable");
  }
  throw new Error("Timed out");
}

Trabalhe sempre contra o seu próprio ambiente de staging e, quando a automação envolver coleta de dados, considere as obrigações da LGPD. Este fluxo é para QA e integrações autorizadas, não para acessar sistemas de terceiros sem permissão.

reCAPTCHA v2 com Playwright

Para o reCAPTCHA v2, leia a data-sitekey da página, envie com o método userrecaptcha e escreva o token na textarea g-recaptcha-response. Muitos formulários só validam quando o callback do widget é disparado, então o trecho percorre a configuração do grecaptcha e chama a função manualmente:

async function solveRecaptchaV2(page) {
  // Extract sitekey
  const sitekey = await page.evaluate(() => {
    const el = document.querySelector("[data-sitekey]");
    return el ? el.getAttribute("data-sitekey") : null;
  });
  if (!sitekey) throw new Error("Sitekey not found");

  // Solve
  const token = await solveCaptcha("userrecaptcha", {
    googlekey: sitekey,
    pageurl: page.url(),
  });

  // Inject
  await page.evaluate((t) => {
    const textarea = document.getElementById("g-recaptcha-response");
    if (textarea) {
      textarea.value = t;
      textarea.style.display = "block";
    }

    // Trigger callback
    if (typeof ___grecaptcha_cfg !== "undefined") {
      const clients = ___grecaptcha_cfg.clients;
      for (const key in clients) {
        for (const prop in clients[key]) {
          try {
            const cb = clients[key][prop];
            if (cb && typeof cb.callback === "function") cb.callback(t);
          } catch {}
        }
      }
    }
  }, token);

  return token;
}

Cloudflare Turnstile com Playwright

O Turnstile é mais direto: o método é turnstile e o token vai para o campo cf-turnstile-response. Como o widget nem sempre traz a classe .cf-turnstile, o trecho tem um plano B — procurar qualquer data-sitekey que comece com 0x:

async function solveTurnstile(page) {
  // Extract sitekey
  const sitekey = await page.evaluate(() => {
    const el = document.querySelector(".cf-turnstile[data-sitekey]");
    if (el) return el.getAttribute("data-sitekey");

    // Fallback: any data-sitekey starting with 0x
    const all = document.querySelectorAll("[data-sitekey]");
    for (const item of all) {
      const key = item.getAttribute("data-sitekey");
      if (key && key.startsWith("0x")) return key;
    }
    return null;
  });
  if (!sitekey) throw new Error("Turnstile sitekey not found");

  // Solve
  const token = await solveCaptcha("turnstile", {
    sitekey,
    pageurl: page.url(),
  });

  // Inject
  await page.evaluate((t) => {
    document
      .querySelectorAll('[name="cf-turnstile-response"]')
      .forEach((el) => (el.value = t));
  }, token);

  return token;
}

Detecção e resolução automática

Em vez de escolher a função na mão, deixe o código identificar o tipo de desafio presente na página e despachar para o método certo. Assim o mesmo fluxo cobre reCAPTCHA, Turnstile e CAPTCHA de imagem:

async function detectAndSolve(page) {
  const captchaInfo = await page.evaluate(() => {
    // Check reCAPTCHA
    const recaptcha = document.querySelector("[data-sitekey]");
    if (
      recaptcha &&
      (document.querySelector(".g-recaptcha") ||
        document.querySelector('script[src*="recaptcha"]'))
    ) {
      return { type: "recaptcha", sitekey: recaptcha.getAttribute("data-sitekey") };
    }

    // Check Turnstile
    const turnstile = document.querySelector(".cf-turnstile[data-sitekey]");
    if (turnstile) {
      return { type: "turnstile", sitekey: turnstile.getAttribute("data-sitekey") };
    }

    // Check image CAPTCHA
    const captchaImg = document.querySelector(
      'img.captcha, img[alt*="captcha"], img[src*="captcha"]'
    );
    if (captchaImg) {
      return { type: "image" };
    }

    return { type: null };
  });

  if (!captchaInfo.type) return null;

  console.log(`Detected: ${captchaInfo.type}`);

  switch (captchaInfo.type) {
    case "recaptcha":
      return await solveCaptcha("userrecaptcha", {
        googlekey: captchaInfo.sitekey,
        pageurl: page.url(),
      });

    case "turnstile":
      return await solveCaptcha("turnstile", {
        sitekey: captchaInfo.sitekey,
        pageurl: page.url(),
      });

    case "image":
      return await solveImageCaptcha(page);

    default:
      return null;
  }
}

CAPTCHA de imagem com Playwright

Para desafios de imagem (OCR), tire um screenshot do elemento, converta para base64 e envie com o método base64. Depois é só digitar a resposta no campo de entrada:

async function solveImageCaptcha(page) {
  const captchaImg = page.locator(
    'img.captcha, img[alt*="captcha"], img[src*="captcha"]'
  ).first();

  // Screenshot the CAPTCHA element
  const imgBuffer = await captchaImg.screenshot();
  const imgBase64 = imgBuffer.toString("base64");

  // Solve via CaptchaAI
  const answer = await solveCaptcha("base64", { body: imgBase64 });

  // Type the answer
  const input = page.locator(
    'input[name="captcha"], input[name="code"], input.captcha-input'
  ).first();
  await input.fill(answer);

  return answer;
}

Interceptação de rotas para capturar parâmetros do CAPTCHA

Alguns desafios, como o GeeTest, entregam parâmetros dinâmicos em respostas de rede. Escute os eventos response da página para capturar esses valores antes de montar a tarefa:

async function interceptCaptchaRoutes(page, url) {
  const captchaParams = {};

  // Intercept responses
  page.on("response", async (response) => {
    const respUrl = response.url();

    // GeeTest parameters
    if (respUrl.includes("geetest") || respUrl.includes("gt=")) {
      try {
        const data = await response.json();
        if (data.gt) {
          captchaParams.type = "geetest";
          captchaParams.gt = data.gt;
          captchaParams.challenge = data.challenge;
        }
      } catch {}
    }
  });

  await page.goto(url, { waitUntil: "networkidle" });
  return captchaParams;
}

Classe de automação completa

Agora reúna tudo em uma única classe. A PlaywrightAutomation cuida do ciclo inteiro — subir o navegador, preencher o formulário, detectar e resolver o CAPTCHA, inserir o token e enviar. O exemplo de uso aponta para um endpoint de staging, o padrão recomendado para testes autorizados:

const { chromium } = require("playwright");

class PlaywrightAutomation {
  #apiKey;
  #browser;
  #context;
  #page;

  constructor(apiKey) {
    this.#apiKey = apiKey;
  }

  async start(headless = false) {
    this.#browser = await chromium.launch({
      headless,
      args: ["--disable-blink-features=AutomationControlled"],
    });
    this.#context = await this.#browser.newContext({
      userAgent:
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
      viewport: { width: 1920, height: 1080 },
    });
    await this.#context.addInitScript(() => {
      Object.defineProperty(navigator, "webdriver", { get: () => undefined });
    });
    this.#page = await this.#context.newPage();
  }

  async stop() {
    await this.#browser?.close();
  }

  async navigate(url) {
    await this.#page.goto(url, { waitUntil: "networkidle" });
  }

  async fillForm(fields) {
    for (const [selector, value] of Object.entries(fields)) {
      await this.#page.fill(selector, value);
    }
  }

  async solveCaptcha() {
    return await detectAndSolve(this.#page);
  }

  async submit(selector = 'button[type="submit"]') {
    await this.#page.click(selector);
    await this.#page.waitForLoadState("networkidle");
    return this.#page.url();
  }

  async loginWithCaptcha(url, fields, submitSelector) {
    await this.navigate(url);
    await this.fillForm(fields);

    const token = await this.solveCaptcha();
    if (token) {
      // Inject token
      await this.#page.evaluate((t) => {
        const re = document.getElementById("g-recaptcha-response");
        if (re) re.value = t;
        document
          .querySelectorAll('[name="cf-turnstile-response"]')
          .forEach((el) => (el.value = t));
      }, token);
    }

    return await this.submit(submitSelector);
  }

  get page() {
    return this.#page;
  }
}

// Usage
const bot = new PlaywrightAutomation("YOUR_API_KEY");
await bot.start();

try {
  const result = await bot.loginWithCaptcha(
    "https://staging.example.com/qa-login",
    {
      "#email": "user@example.com",
      "#password": "pass123",
    },
    "#login-btn"
  );
  console.log(`Redirected to: ${result}`);
} finally {
  await bot.stop();
}

Playwright vs Puppeteer: quando usar cada um

Para projetos novos que precisam de vários navegadores e espera automática, o Playwright costuma ser a opção mais confortável. O Puppeteer segue sólido para quem já vive no ecossistema Chrome:

Recurso Playwright Puppeteer
Multi-navegador Chromium, Firefox, WebKit Somente Chromium
Estilo de API Baseado em localizador Baseado em seletor
Espera automática Integrada Esperas manuais
Interceptação de rede Baseada em rota Baseada em requisição
Configuração para QA Bons padrões Requer plugin adicional
TypeScript Nativo Tipos da comunidade

Solução de problemas

Os sintomas mais comuns na integração e como resolvê-los:

Sintoma Causa Correção
page.evaluate retorna nulo Elemento ainda não carregou Use waitForSelector antes
Cloudflare Turnstile não é detectado Widget carrega via JS depois da página Aguarde o seletor .cf-turnstile
Formulário não avança após inserir o token Callback não foi disparado Chame o callback do reCAPTCHA manualmente
Página identifica a automação Script de inicialização ausente Adicione a substituição de navigator.webdriver
Timeout em networkidle Scripts de long-polling na página Use domcontentloaded no lugar

Perguntas frequentes

A CaptchaAI resolve Turnstile e reCAPTCHA com a mesma função?

Sim. A função solveCaptcha() é genérica: você só troca o method (turnstile, userrecaptcha, base64) e os parâmetros do desafio. A CaptchaAI resolve reCAPTCHA v2/v3, Cloudflare Turnstile e Challenge, GeeTest v3 e CAPTCHAs de imagem/OCR pelo mesmo endpoint.

Preciso configurar proxies para a resolução funcionar?

Não. A CaptchaAI resolve o desafio do lado do servidor e devolve o token; o Playwright continua acessando o site pela sua própria rede. Proxy é uma decisão de infraestrutura sua, não um requisito da API.

O hCaptcha é suportado neste fluxo?

Não. O hCaptcha e o FunCaptcha (Arkose Labs) não são suportados atualmente. Para os tipos suportados — reCAPTCHA, Turnstile, GeeTest v3 e imagem — o fluxo deste guia funciona sem alterações.

Quanto custa resolver CAPTCHAs em volume?

Os planos são cobrados por thread simultânea, com resolução ilimitada por thread no mês. O BASIC custa US$ 15/mês com 5 threads; se você processa muitos logins em paralelo, o STANDARD (US$ 30/mês, 15 threads) ou o ADVANCE (US$ 90/mês, 50 threads) ampliam a concorrência sem cobrança por CAPTCHA resolvido.

Conclusão

Com o Playwright no Node.js você monta uma pilha de automação moderna: detecção automática do tipo de desafio, interceptação de rotas e suporte a vários formatos de CAPTCHA. A classe PlaywrightAutomation concentra todo o fluxo de login com CAPTCHA em um único componente reutilizável — pronto para os seus testes de QA autorizados.

Artigos relacionados

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