Integrations

Puppeteer + CaptchaAI для QA-тестов в собственных браузерных workflow

CAPTCHA-виджет на staging-форме — частая причина, из-за которой end-to-end тест зависает и роняет прогон CI: браузер долистывает страницу до виджета и застревает, потому что решить его некому. Связка Puppeteer и CaptchaAI закрывает этот разрыв: Puppeteer управляет собственной staging-страницей, а CaptchaAI отдаёт токен для reCAPTCHA или Cloudflare Turnstile, который тест подставляет в форму и продолжает сценарий дальше. Ниже — рабочая схема для QA-инженера, который проверяет собственную интеграцию CAPTCHA, а не автоматизирует чужой сайт.

Область применения: это руководство — только для собственных или явно авторизованных QA-, staging- и production-сред. Ниже описаны диагностика и тестирование вашей собственной CAPTCHA-интеграции, а не сценарий для чужих сайтов и не для несанкционированных workflow.

Зачем CAPTCHA-тест нужен в QA-пайплайне

Ручная проверка формы регистрации или чекаута перед каждым релизом не масштабируется: релизы выходят чаще, чем тестировщик успевает вручную пройти все ветки сценария. Puppeteer запускает headless-браузер с изолированным профилем, повторяет действия пользователя один в один и пишет результат в CI-лог. Единственное место, где такой прогон обычно ломается, — интерактивный CAPTCHA-виджет: без валидного токена браузер не может отправить форму дальше. CaptchaAI закрывает именно этот шаг — тест получает токен по API и продолжает сценарий так, будто его прошёл живой пользователь.

Что можно автоматизировать, а что нет

  • собственные или явно авторизованные staging-домены — не чужие production-сайты;
  • фиктивные тестовые учётные записи, формы и платежи, помеченные как тестовые;
  • внутренние QA endpoints, которые принимают токен и возвращают pass/fail;
  • запись результата каждого прогона в собственный CI/CD pipeline.

Как настроить Puppeteer для QA-прогона

Первый шаг — поднять браузер с отдельным профилем под конкретную форму, чтобы куки и localStorage не пересекались между параллельными прогонами:

import puppeteer from 'puppeteer';

const PAGE = 'https://staging.example.com/qa-form';

export async function runQa() {
  const browser = await puppeteer.launch({
    headless: 'new',
    userDataDir: './qa-profiles/qa-form',
  });
  const page = await browser.newPage();
  await page.goto(PAGE, { waitUntil: 'networkidle0' });
  return { browser, page };
}

userDataDir создаёт изолированный профиль на каждый сценарий: параллельные джобы CI не путают куки друг друга, а сам профиль можно удалить перед следующим прогоном, если тест завис на середине сценария.

Как найти CAPTCHA-виджет на своей staging-странице

Дальше тест ищет сам виджет и достаёт из него sitekey — без него отправлять запрос в CaptchaAI бессмысленно:

const sitekey = await page.$eval(
  '.g-recaptcha, .cf-turnstile',
  el => el.getAttribute('data-sitekey'),
);
if (!sitekey) throw new Error('sitekey не найден на staging-странице');

Если селектор ничего не находит, вероятная причина — виджет рендерится асинхронно уже после отрисовки формы; тогда перед проверкой нужно дождаться его появления через page.waitForSelector, а не опрашивать DOM сразу после page.goto.

Как отправить sitekey в CaptchaAI и получить токен

Когда sitekey найден, тест отправляет задачу на in.php и опрашивает res.php, пока CaptchaAI не вернёт готовый токен:

import fetch from 'node-fetch';

const KEY = process.env.CAPTCHAAI_KEY;

export async function solveRecaptcha(sitekey, pageUrl) {
  const submit = await fetch('https://ocr.captchaai.com/in.php', {
    method: 'POST',
    body: new URLSearchParams({
      key: KEY, method: 'userrecaptcha',
      googlekey: sitekey, pageurl: pageUrl, json: '1',
    }),
  }).then(r => r.json());
  const id = submit.request;
  for (let i = 0; i < 40; i++) {
    await new Promise(r => setTimeout(r, 3000));
    const res = await fetch(`https://ocr.captchaai.com/res.php?key=${KEY}&action=get&id=${id}&json=1`)
      .then(r => r.json());
    if (res.status === 1) return res.request;
  }
  throw new Error(`timeout for task ${id}`);
}

Цикл опроса ограничен 40 попытками по 3 секунды — это около двух минут на задачу, с запасом даже для более тяжёлых reCAPTCHA-сценариев. Если лимит регулярно исчерпывается, дело обычно не в CaptchaAI, а в неверном pageurl или устаревшем sitekey на странице.

Проверка токена на собственном backend

Клиентский код не должен сам решать, прошёл тест или нет: после получения токена он отправляется на собственный QA endpoint, который выполняет серверную верификацию у провайдера CAPTCHA и уже на её основе пишет pass/fail в журнал прогона. Такой порядок закрывает очевидную дыру — доверять решению на стороне браузера рискованно: токен в DOM легко подставить вручную, минуя реальную проверку, и тест будет зелёным там, где формы на самом деле не работают.

Логи и метрики для сравнения релизов

Структурированные логи — единственный способ быстро понять, стал ли релиз медленнее решать CAPTCHA или чаще ловить ошибку:

import json, time, logging

log = logging.getLogger('captcha-qa')

def record(event: str, **fields) -> None:
    payload = {'ts': time.time(), 'event': event, **fields}
    log.info(json.dumps(payload, ensure_ascii=False))

Минимальный набор полей для каждой попытки: slug, captcha_type, task_id, wait_seconds, verify_status, env. Этого достаточно, чтобы построить дашборд с медианой, P90 и P99 по типу CAPTCHA и по среде — и сразу увидеть, если новый релиз внёс регрессию именно на Turnstile или именно на staging, а не на production-подобном стенде.

Типичные проблемы и на что смотреть

Симптом Что сделать
sitekey не найден Проверьте, рендерится ли виджет в staging, и дождитесь его через waitForSelector
Токен невалиден Сверьте pageurl и sitekey — токен привязан к конкретному URL
Бесконечный pending Проверьте баланс потоков CaptchaAI в личном кабинете
Браузер не запускается Удалите старый userDataDir — профиль мог остаться заблокированным
Тест падает только в CI Сравните версию Chromium локально и в CI-образе

Чек-лист перед merge

  • Запрос отправляется только на собственные или явно авторизованные endpoints — не на чужой домен.
  • Тестовые учётные записи, события и платежи помечены как фиктивные и не попадают в боевую аналитику.
  • CAPTCHA-токен проверяется на собственном backend, а не принимается на веру клиентским кодом.
  • Логи содержат task_id, тип CAPTCHA, время ожидания и итоговый pass/fail.
  • Скрипт возвращает корректный exit code, чтобы CI мог принять решение автоматически, без ручного просмотра лога.

Пример: QA формы бронирования в команде из СНГ

Небольшая команда, которая тестирует форму бронирования перед каждым релизом в GitLab CI, обычно запускает 3–5 параллельных браузеров с разными тестовыми аккаунтами. Для такой нагрузки хватает плана BASIC ($15/мес, 5 потоков) — CaptchaAI тарифицирует по количеству одновременных задач, а не по числу решённых CAPTCHA, поэтому нужное число потоков можно посчитать заранее по количеству параллельных job'ов в пайплайне. Фиксированная цена в долларах удобна и фрилансеру, и агентству, которое выставляет счёт клиенту в другой валюте: стоимость QA-инфраструктуры не плавает вместе с курсом.

FAQ

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

Нет. Схема рассчитана только на собственные или явно авторизованные среды. Для стороннего ресурса нужно письменное разрешение владельца — без него автоматизация чужой формы находится вне зоны, которую описывает это руководство.

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

Ориентируйтесь на число одновременных браузеров в CI: 5 параллельных job'ов — 5 потоков, то есть план BASIC. Если тестов больше или прогоны идут сразу по нескольким формам, посчитайте пиковую параллельность и берите план с запасом — свободные потоки просто не расходуются.

Что делать, если CaptchaAI вернул ошибку?

Залогируйте task_id, тип CAPTCHA и текст ошибки, повторите запрос с экспоненциальной задержкой и фиксируйте долю ошибок на дашборде. Стабильный рост доли ошибок — повод проверить sitekey и саму страницу, а не сразу списывать всё на провайдера.

Как сравнивать результаты между релизами?

Храните логи в одном формате и стройте отчёт по медиане, P90 и P99 на одинаковом наборе сценариев. Сравнивать имеет смысл только сопоставимые выборки в своей среде — разные staging-конфигурации дадут разные цифры даже при одинаковом коде приложения.

Нужен ли в CI отдельный тестовый API-ключ?

Используйте обычный рабочий API-ключ CaptchaAI, но храните его как секрет CI (GitHub Actions secrets, GitLab CI/CD variables), а не в коде репозитория. Риск снижает не тип ключа, а дисциплина: ключ не попадает в git, ротируется при подозрении на утечку, а тест обращается только к вашим собственным staging-формам.

Связанные материалы

Готовы убрать CAPTCHA-виджет из списка причин красного билда? Получите ключ CaptchaAI и подключите его к QA-пайплайну.

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