Use Cases

Обработка CAPTCHA в Playwright с CaptchaAI

Playwright сам CAPTCHA не решает — он лишь находит виджет и ждёт реакции пользователя. Решает CaptchaAI: скрипт отправляет ему site key со страницы, получает готовый токен и подставляет его обратно в форму перед отправкой. Ниже — рабочие примеры для Python и Node.js, одинаково применимые к Chromium, Firefox и WebKit: разница между движками для CaptchaAI не имеет значения, важен только сам виджет и его site key.

Playwright, Selenium или Puppeteer: что выбрать

Выбор инструмента почти не влияет на саму CAPTCHA-автоматизацию — CaptchaAI работает со всеми тремя одинаково: извлекает site key, решает через API и возвращает токен. Разница в самих фреймворках, и для QA-команд она часто решающая:

Особенность Playwright Selenium Puppeteer
Языки Python, Node.js, C#, Java Python, Java, C#, Ruby, JS Node.js
Браузеры Chromium, Firefox, WebKit Chrome, Firefox, Edge, Safari Chromium
Автоожидание ✅ Встроено ❌ Ручное ожидание ⚠️ Частично
Перехват сети ⚠️ Ограниченно
Интеграция с CaptchaAI ✅ Один и тот же API ✅ Один и тот же API ✅ Один и тот же API

Если проект уже кроссбраузерный (нужны и Firefox, и WebKit в одном наборе тестов), Playwright обычно выигрывает за счёт встроенного автоожидания и единого API для всех трёх движков. Puppeteer остаётся хорошим выбором, если тесты гоняются только в Chromium и важна лёгкость настройки.

Что понадобится

Нужны сам Playwright, HTTP-клиент и API-ключ CaptchaAI:

Требование Подробности
Python pip install playwright requests, затем playwright install
Node.js npm install playwright axios
API-ключ CaptchaAI получите на captchaai.com

Python: Playwright + CaptchaAI

Настройка

Функция отправляет site key в CaptchaAI и опрашивает результат каждые пять секунд, пока не придёт готовый токен:

from playwright.sync_api import sync_playwright
import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_recaptcha(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(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(result.text)
    raise TimeoutError()

Опрос сделан намеренно простым — 60 попыток по 5 секунд дают до пяти минут на решение, этого хватает даже в редких случаях задержки очереди. При таймауте функция бросает исключение, а не возвращает пустую строку, чтобы ошибка не потерялась в логах CI.

Полный пример

Сценарий целиком: Playwright заполняет форму логина, находит виджет reCAPTCHA, забирает токен через функцию выше и подставляет его в поле ответа перед отправкой:

def login_with_captcha(url, username, password):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        )
        page = context.new_page()
        page.goto(url)

        # Fill login form
        page.fill("#username", username)
        page.fill("#password", password)

        # Check for reCAPTCHA
        recaptcha = page.query_selector(".g-recaptcha")
        if recaptcha:
            site_key = recaptcha.get_attribute("data-sitekey")
            print(f"Solving reCAPTCHA: {site_key}")

            token = solve_recaptcha(site_key, page.url)

            # Inject token
            page.evaluate(f"""
                document.getElementById('g-recaptcha-response').innerHTML = '{token}';
                document.getElementById('g-recaptcha-response').style.display = '';
            """)

        # Submit
        page.click('button[type="submit"]')
        page.wait_for_load_state("networkidle")

        print(f"Current URL: {page.url}")
        content = page.content()

        browser.close()
        return content

result = login_with_captcha(
    "https://staging.example.com/qa-login",
    "[email protected]",
    "password123"
)

Обратите внимание на page.wait_for_load_state("networkidle") после клика — без него скрипт может проверить результат до того, как страница успеет перерисоваться после успешного логина.

Асинхронная версия

Для параллельных прогонов пригодится asyncio-версия, не блокирующая остальные задачи и позволяющая решать несколько CAPTCHA одновременно в разных вкладках:

from playwright.async_api import async_playwright
import aiohttp
import asyncio

async def solve_recaptcha_async(site_key, page_url):
    async with aiohttp.ClientSession() as session:
        params = {
            "key": API_KEY, "method": "userrecaptcha",
            "googlekey": site_key, "pageurl": page_url
        }
        async with session.get("https://ocr.captchaai.com/in.php", params=params) as resp:
            text = await resp.text()
            task_id = text.split("|")[1]

        for _ in range(60):
            await asyncio.sleep(5)
            params = {"key": API_KEY, "action": "get", "id": task_id}
            async with session.get("https://ocr.captchaai.com/res.php", params=params) as resp:
                text = await resp.text()
                if text == "CAPCHA_NOT_READY": continue
                if text.startswith("OK|"): return text.split("|")[1]
                raise Exception(text)
        raise TimeoutError()

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        await page.goto("https://example.com/form")

        site_key = await page.get_attribute(".g-recaptcha", "data-sitekey")
        token = await solve_recaptcha_async(site_key, page.url)

        await page.evaluate(f"document.getElementById('g-recaptcha-response').innerHTML = '{token}'")
        await page.click('button[type="submit"]')
        await browser.close()

asyncio.run(main())

Логика та же, что и в синхронной версии, но aiohttp вместо requests не держит поток занятым во время пятисекундного ожидания — это особенно заметно на CI-раннерах с несколькими параллельными джобами.

Node.js: Playwright + CaptchaAI

Логика та же: получить site key, дождаться токена через API CaptchaAI, подставить его и отправить форму:

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

const API_KEY = "YOUR_API_KEY";

async function solveRecaptcha(siteKey, pageUrl) {
  const submit = await axios.get("https://ocr.captchaai.com/in.php", {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
    },
  });
  const taskId = submit.data.split("|")[1];

  while (true) {
    await new Promise((r) => setTimeout(r, 5000));
    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: taskId },
    });
    if (result.data === "CAPCHA_NOT_READY") continue;
    if (result.data.startsWith("OK|")) return result.data.split("|")[1];
    throw new Error(result.data);
  }
}

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto("https://staging.example.com/qa-login");

  // Fill form
  await page.fill("#username", "[email protected]");
  await page.fill("#password", "password123");

  // Solve CAPTCHA
  const siteKey = await page.getAttribute(".g-recaptcha", "data-sitekey");
  if (siteKey) {
    const token = await solveRecaptcha(siteKey, page.url());
    await page.evaluate(
      (t) => (document.getElementById("g-recaptcha-response").innerHTML = t),
      token
    );
  }

  // Submit
  await page.click('button[type="submit"]');
  await page.waitForLoadState("networkidle");

  console.log("Logged in:", page.url());
  await browser.close();
})();

Node.js-версия структурно повторяет Python: тот же эндпоинт in.php для отправки, тот же res.php для опроса. Если проект уже на TypeScript, типизация ответа axios добавляется без изменения самой логики.

Решение Cloudflare Turnstile в Playwright

Turnstile определяется по классу .cf-turnstile, а не .g-recaptcha, но логика обращения к API та же — меняется только параметр method:

# Detect Turnstile
turnstile = page.query_selector(".cf-turnstile")
if turnstile:
    site_key = turnstile.get_attribute("data-sitekey")

    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]

    # Poll and inject...

CaptchaAI обычно решает Cloudflare Turnstile менее чем за 10 секунд, так что для staging-тестов с Turnstile таймаут в 60 попыток по 5 секунд можно смело сокращать — это ускорит прогон, не увеличивая риск ложных таймаутов.

Потоки и стоимость для QA-команд

Тарификация CaptchaAI идёт по числу параллельных потоков, а не по количеству решений — это даёт предсказуемую стоимость даже при нестабильном объёме прогонов, что удобно и фрилансерам, и небольшим QA-командам, выставляющим счета в долларах:

  • Один CI-раннер и последовательные staging-тесты — хватает BASIC ($15/мес, 5 потоков).
  • Несколько параллельных пайплайнов или матрица браузеров (Chromium + Firefox + WebKit одновременно) — нужен STANDARD ($30/мес, 15 потоков), иначе задачи встанут в очередь.

Число нужных потоков считается по пиковой параллельности прогонов, а не по общему числу тестов в наборе — десять тестов, запущенных последовательно, займут один поток, а те же десять параллельно — до десяти.

Типичные проблемы

Проблема Решение
page.query_selector возвращает null CAPTCHA подгружается динамически — используйте page.wait_for_selector()
Токен не подставляется в форму Проверьте, не отличается ли ID у textarea с ответом
Playwright падает в Docker Установите системные зависимости браузера: playwright install-deps
CAPTCHA появляется снова после решения Сайту может требоваться отдельный callback — вызовите его через page.evaluate()

Если ни один из этих пунктов не помог, для начала проверьте DevTools-консоль на самой странице — часто виджет молча падает из-за CSP-заголовка, который блокирует скрипт CAPTCHA-провайдера, и тогда проблема не в Playwright и не в CaptchaAI, а в самой странице.

Часто задаваемые вопросы

Как передать токен CaptchaAI обратно в Playwright?

Токен приходит строкой в ответе res.php. Передайте его через page.evaluate() в поле g-recaptcha-response (для Turnstile — cf-turnstile-response) и отправьте форму.

Работает ли CaptchaAI одинаково в Chromium, Firefox и WebKit?

Да. CaptchaAI решает CAPTCHA на своей стороне и просто отдаёт токен — движок Playwright значения не имеет.

Сколько потоков нужно для параллельных прогонов Playwright?

Зависит от пиковой параллельности прогонов. Одному CI-раннеру обычно хватает BASIC ($15/мес, 5 потоков); при нескольких параллельных пайплайнах — STANDARD ($30/мес, 15 потоков).

Помогает ли автоожидание Playwright с CAPTCHA?

Нет — оно проверяет, что элемент виден, но саму CAPTCHA не решает. За решение отвечает CaptchaAI.

Можно ли использовать в тестах реальные персональные данные пользователей?

Технически да, но собирайте и храните в тестовых окружениях только те данные, которые вы вправе обрабатывать, — это касается и 152-ФЗ, и GDPR. Для staging-логинов проще и безопаснее заводить отдельные тестовые аккаунты.

Похожие материалы

Комментарии для этой статьи отключены.