Tutorials

Node.js Playwright + CaptchaAI Complete Integration

Playwright for Node.js offers the best combination of speed, stealth-configuredion, and multi-browser support. This guide covers the complete integration with CaptchaAI for all CAPTCHA types.


Prerequisites

npm install playwright
npx playwright install chromium

Stealth-configuredion browser setup

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 };
}

CaptchaAI solver

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");
}

reCAPTCHA v2 with Playwright

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 with Playwright

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;
}

Auto-detect and solve

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;
  }
}

Image CAPTCHA with Playwright

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;
}

Route interception for CAPTCHA parameters

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;
}

Complete automation class

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://example.com/login",
    {
      "#email": "user@example.com",
      "#password": "pass123",
    },
    "#login-btn"
  );
  console.log(`Redirected to: ${result}`);
} finally {
  await bot.stop();
}

Playwright vs Puppeteer comparison

Feature Playwright Puppeteer
Multi-browser Chromium, Firefox, WebKit Chromium only
API style Locator-based Selector-based
Auto-waiting Built-in Manual waits
Network interception Route-based Request-based
Stealth-configuredion Good defaults Needs stealth plugin
TypeScript Native Community types

Troubleshooting

Symptom Cause Fix
page.evaluate returns null Element not loaded Use waitForSelector first
Turnstile not detected Loaded via JS after page load Wait for .cf-turnstile selector
Token injection doesn't submit Missing callback trigger Call reCAPTCHA callback explicitly
Browser detection Missing init script Add webdriver override
networkidle timeout Long-polling scripts Use domcontentloaded instead

Frequently asked questions

Should I use Playwright or Puppeteer for new projects?

Playwright. It has better defaults, native TypeScript support, auto-waiting, and multi-browser testing.

Can I run Playwright in headless mode?

Yes. Set headless: true in launch(). CaptchaAI solves independently, so headless doesn't affect solve success.

How do I handle multiple CAPTCHAs on one page?

Call detectAndSolve() after each form step. Some pages have CAPTCHAs on multiple steps.


Summary

Node.js Playwright + CaptchaAI provides a modern automation stack with auto-detection, route interception, and multi-CAPTCHA support. The PlaywrightAutomation class handles the complete login-with-CAPTCHA workflow.

Discussions (0)

No comments yet.

Related Posts

Tutorials Python Playwright + CaptchaAI Complete Integration Guide
Complete guide to integrating Captcha AI with Python Playwright.

Complete guide to integrating Captcha AI with Python Playwright. Solve re CAPTCHA, Turnstile, and image CAPTCH...

Python Automation Cloudflare Turnstile
Mar 14, 2026
Tutorials Node.js Puppeteer + CaptchaAI Advanced Patterns
Advanced Puppeteer + Captcha AI integration patterns: stealth mode, request interception, iframe handling, multi-page flows, and parallel solving.

Advanced Puppeteer + Captcha AI integration patterns: stealth mode, request interception, iframe handling, mul...

Automation Cloudflare Turnstile Node.js
Feb 24, 2026
Tutorials Solving Cloudflare Turnstile with Node.js and CaptchaAI
Complete Node.js tutorial for solving Cloudflare Turnstile using Captcha AI.

Complete Node.js tutorial for solving Cloudflare Turnstile using Captcha AI. Extract sitekey, solve via API, s...

Automation Cloudflare Turnstile Node.js
Jan 25, 2026
Use Cases Playwright CAPTCHA Handling with CaptchaAI
Playwright provides reliable browser automation across Chromium, Firefox, and Web Kit.

Playwright provides reliable browser automation across Chromium, Firefox, and Web Kit. When target pages serve...

Automation Cloudflare Turnstile Playwright
Jan 16, 2026
Reference CAPTCHA Token Injection Methods Reference
Complete reference for injecting solved CAPTCHA tokens into web pages.

Complete reference for injecting solved CAPTCHA tokens into web pages. Covers re CAPTCHA, Turnstile, and Cloud...

Python Automation Cloudflare Turnstile
Apr 08, 2026
Tutorials Pytest Fixtures for CaptchaAI API Testing
Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI.

Build reusable pytest fixtures to test CAPTCHA-solving workflows with Captcha AI. Covers mocking, live integra...

Python Automation Cloudflare Turnstile
Apr 08, 2026
API Tutorials Solving CAPTCHAs with Swift and CaptchaAI API
Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Swift using Captcha AI's HTTP API with URLSession, async/await, and Alamofire.

Complete guide to solving re CAPTCHA, Turnstile, and image CAPTCHAs in Swift using Captcha AI's HTTP API with...

Automation Cloudflare Turnstile reCAPTCHA v2
Apr 05, 2026
Troubleshooting ERROR_PAGEURL: URL Mismatch Troubleshooting Guide
Fix ERROR_PAGEURL when using Captcha AI.

Fix ERROR_PAGEURL when using Captcha AI. Diagnose URL mismatch issues, handle redirects, SPAs, and dynamic URL...

Python Automation Cloudflare Turnstile
Mar 23, 2026
Tutorials CAPTCHA Solving Fallback Chains
Implement fallback chains for CAPTCHA solving with Captcha AI.

Implement fallback chains for CAPTCHA solving with Captcha AI. Cascade through solver methods, proxy pools, an...

Python Automation Cloudflare Turnstile
Apr 06, 2026
Tutorials Handling Multiple CAPTCHAs on a Single Page
how to detect and solve multiple CAPTCHAs on a single web page using Captcha AI.

Learn how to detect and solve multiple CAPTCHAs on a single web page using Captcha AI. Covers multi-iframe ext...

Python Cloudflare Turnstile reCAPTCHA v2
Apr 09, 2026
Tutorials Streaming Batch Results: Processing CAPTCHA Solutions as They Arrive
Process CAPTCHA solutions the moment they arrive instead of waiting for tasks to complete — use async generators, event emitters, and callback patterns for stre...

Process CAPTCHA solutions the moment they arrive instead of waiting for all tasks to complete — use async gene...

Python Automation All CAPTCHA Types
Apr 07, 2026
Tutorials Bulkhead Pattern: Isolating CAPTCHA Solving Failures
Apply the bulkhead pattern to isolate CAPTCHA solving failures — partition resources into independent pools so a slow or failing solver type doesn't starve othe...

Apply the bulkhead pattern to isolate CAPTCHA solving failures — partition resources into independent pools so...

Python Automation All CAPTCHA Types
Apr 07, 2026