Как решать капчу в тестах SeleniumBase (Python)

SeleniumBase управляет браузером. Саму капчу он за вас не читает. Поэтому шаг с капчей в SeleniumBase состоит из трёх действий: достать sitekey со страницы, запросить токен у решателя, затем записать этот токен обратно в форму и отправить её. Решение происходит вне браузера, и именно поэтому один и тот же код работает в запуске с окном, в headless-режиме и в задаче CI. Дальше он приведён в обоих форматах синтаксиса, которыми вы скорее всего пользуетесь.
Что понадобится
- Python 3.10 или новее, с установленными SeleniumBase и CapSkip SDK.
- Страница, которая действительно рисует виджет. Тестовые ключи reCAPTCHA от Google проходят всегда, поэтому про ваш код они ничего не доказывают.
- Запущенный CapSkip: либо в режиме Local на адресе loopback, либо в режиме Server на машине, доступной вашему тест-раннеру. Оба режима описаны в разделе Настройки подключения, а какой из них нужен вам, зависит от того, где выполняются тесты.
# Both packages, one command. pip install seleniumbase capskip
Что делает UC mode и чего он не делает
SeleniumBase поставляется с UC mode, и стоит точно описать его задачу, потому что за ним часто идут, ожидая получить решатель капчи. UC mode снижает количество автоматизационных сигналов, которые выдаёт браузер, а его GUI-помощники умеют кликнуть по чекбоксу на промежуточной странице Cloudflare. Это совсем другая задача, чем получение токена reCAPTCHA. Ничто в UC mode не читает искажённое изображение и не выбирает плитки с автобусом.
# pip install seleniumbase
from seleniumbase import SB
with SB(uc=True) as sb:
# Disconnects the driver briefly so the page loads unobserved.
sb.uc_open_with_reconnect("https://example.com/protected", reconnect_time=3)
# Drives the real mouse pointer, so it needs a desktop session
# or a virtual display. It clicks a checkbox; it solves nothing.
sb.uc_gui_click_captcha()Отсюда следуют два вывода. GUI-помощникам нужен настоящий дисплей, что исключает их из большинства headless-сборок CI. И для reCAPTCHA и GeeTest вам всё равно нужен токен, о чём и рассказывает остальная часть поста. Используйте оба подхода вместе: UC mode снижает частоту проверок, а решатель закрывает те проверки, которые всё-таки приходят.
Шаг 1: считайте sitekey со страницы
Sitekey публичный. Он лежит на элементе виджета как атрибут, и SeleniumBase читает его одним вызовом, так что вам не придётся прописывать его вручную под каждое окружение.
# pip install seleniumbase
from seleniumbase import SB
PAGE = "https://example.com/page-with-recaptcha"
with SB() as sb:
sb.open(PAGE)
# Present on the widget div for v2, and on the script tag for v3.
sitekey = sb.get_attribute("[data-sitekey]", "data-sitekey")
print(sitekey) # this plus the page URL is all the solver needsШаг 2: получите токен от решателя
Эта часть никак не связана с SeleniumBase. Это обычный вызов на Python к сервису на вашей же машине, и он возвращает строку токена, которую страница получила бы, если бы виджет прошёл человек.
# pip install capskip
from capskip import CapSkip
solver = CapSkip(host="127.0.0.1", port=8080)
# v2 checkbox is the default shape. Pass invisible=1 or
# enterprise=1 for those variants, or version="v3" for v3.
result = solver.recaptcha(
sitekey=sitekey,
url=PAGE,
)
token = result["code"] # the value the form is waiting forТот же клиент обрабатывает и другие типы, которые встретятся вам в тестовом наборе. Для Turnstile это solver.turnstile с sitekey и URL страницы, для GeeTest это solver.geetest со значениями gt и challenge, а для капчи-изображения это solver.normal с путём к файлу, URL или base64 data URI. Страница сервиса распознавания капчи для Python описывает весь доступный набор возможностей.
Шаг 3: внедрите токен и отправьте форму
Google размещает в форме скрытую textarea и ждёт токен в её значении. Запишите его туда вызовом скрипта, а затем отправьте форму так, как это сделал бы собственный интерфейс страницы.
# The response field is hidden, so a normal type() will not reach it.
sb.execute_script(
"document.getElementById('g-recaptcha-response').value = arguments[0];",
token,
)
sb.click("button[type='submit']")
sb.assert_element(".signup-success")Если страница вешает на виджет собственную callback-функцию вместо того, чтобы читать поле при отправке, вызовите эту функцию с токеном, а не кликайте. Что именно вам нужно, видно в разметке виджета: атрибут data-callback называет функцию, а его отсутствие означает, что форма читает поле.
Полный тест
Вот всё целиком в виде класса BaseCase: этот формат встаёт в существующий набор pytest без дополнительной обвязки.
# pip install seleniumbase capskip
from seleniumbase import BaseCase
from capskip import CapSkip
BaseCase.main(__name__, __file__)
PAGE = "https://example.com/page-with-recaptcha"
class RecaptchaTest(BaseCase):
def test_signup_form(self):
self.open(PAGE)
sitekey = self.get_attribute("[data-sitekey]", "data-sitekey")
solver = CapSkip(host="127.0.0.1", port=8080)
token = solver.recaptcha(sitekey=sitekey, url=PAGE)["code"]
self.execute_script(
"document.getElementById('g-recaptcha-response').value = arguments[0];",
token,
)
self.click("button[type='submit']")
self.assert_element(".signup-success")Решайте поздно. Токен reCAPTCHA живёт около двух минут, поэтому получить его в начале теста и использовать после четырёх других шагов значит устроить гонку, которую вы будете периодически проигрывать. Ставьте решение непосредственно перед отправкой, там же место и для повтора.
Запуск решателя на сервере
Тест-раннер редко оказывается той машиной, за которой вы сидите. CapSkip закрывает это вторым режимом подключения, и в вашем коде меняется только хост.
| Режим | Прослушивает | Когда использовать |
|---|---|---|
| Локально | 127.0.0.1, только это устройство | Тесты и решатель работают на одной машине |
| Сервер | Ваш сетевой адрес или публичный IP | К нему обращаются CI-раннер, VPS или тестовый грид |
# pip install capskip
import os
from capskip import CapSkip
# The SDK reads these names from the environment too, so one test
# file runs unchanged on a laptop and on a shared runner.
solver = CapSkip(
host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
port=int(os.environ.get("CAPSKIP_PORT", "8080")),
)Статический публичный IP рекомендуется, когда вызывающие стороны находятся за пределами вашей сети. Режим Server остаётся вашим железом и остаётся без тарификации: он меняет то, где работает решатель, но не то, кому он принадлежит и сколько стоит каждое решение. Полный набор опций описан в разделе Настройки подключения.
Частые ошибки
| Что вы видите | Причина | Исправить |
|---|---|---|
| NetworkException | На этом хосте и порту никто не слушает | Запустите CapSkip или направьте хост на сервер, где он работает |
| TimeoutException | Опрос вышел за recaptchaTimeout | Увеличьте тайм-аут или проверьте, что sitekey рабочий |
| ERROR_GOOGLEKEY | Sitekey не пришёл или повреждён | Выведите в лог то, что вернул get_attribute, перед отправкой |
| ERROR_PAGEURL | URL страницы отсутствует или не является полным URL | Отправляйте тот же абсолютный URL, на котором стоит браузер |
| Форма отклоняет действительный токен | Страница использует callback, а не поле | Вызовите функцию из data-callback с токеном |
Все коды, которые может вернуть API, перечислены в документации CapSkip API, вместе с тем, что вызывает каждый из них.
FAQ
Решает ли UC mode reCAPTCHA сам по себе?
Нет. UC mode делает браузер менее похожим на автоматизированный и умеет кликнуть по чекбоксу на промежуточной странице Cloudflare через GUI-помощники. Ни то, ни другое не производит токен reCAPTCHA. Токен вы всё так же получаете от решателя и записываете в форму, ровно как показано выше. Запускайте оба: UC mode снижает частоту проверок, а решатель закрывает те, что вам всё-таки выдали.
Можно ли запускать это headless в CI?
Да, потому что решение вообще не касается браузера. Передайте headless в SB или добавьте флаг в командной строке pytest, и три шага останутся прежними. Чего headless-раннер не переживает, так это GUI-кликов UC mode: они двигают настоящий указатель мыши и требуют сессии рабочего стола или виртуального дисплея.
Мои тесты идут на Linux-раннере. Где размещать решатель?
На машине с Windows, которой вы управляете, доступной в режиме Server. CapSkip представляет собой приложение для Windows, поэтому схема такая: один экземпляр решателя на Windows-машине и любое количество раннеров, которые обращаются к нему по API. Задайте CAPSKIP_HOST на раннере, и больше в тесте ничего не меняется.
Нужен ли прокси, чтобы решение совпадало с моим браузером?
Иногда да, и это поддерживается для reCAPTCHA, Turnstile и GeeTest. Передайте словарь прокси с типом и URI, и решение пойдёт через него, что важно, когда сайту не всё равно, приходят ли токен и сессия с одного адреса. Капчи-изображения прокси не принимают, потому что ничто в них не привязано к сетевому маршруту.
Коротко
Считайте sitekey через get_attribute, получите токен, запишите его в скрытое поле ответа, отправьте форму. Держите решение в том же шаге, что и отправку, чтобы токен был свежим, и держите хост в переменной окружения, чтобы набор тестов вёл себя одинаково везде. Более широкую картину по Selenium смотрите на странице сервиса распознавания капчи для Selenium, а конкретно по v2 смотрите на странице сервиса распознавания reCAPTCHA v2. Остаётся стоимость, и именно по ней сильнее всего бьёт набор тестов, который гоняется на каждый коммит: локальный сервис распознавания капчи означает, что каждое решение происходит на железе, которым вы уже владеете, без счётчика, сколько бы раз ни запускались тесты.
