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-логинов проще и безопаснее заводить отдельные тестовые аккаунты.
Похожие материалы
- Обработка CAPTCHA в Selenium на Python
- Решение CAPTCHA в Puppeteer на Node.js
- Автоматический логин с обработкой CAPTCHA