QA-скрипт проходит корзину, а падает на шаге доставки или оплаты — CAPTCHA всплывает в разных точках у разных сайтов: где-то reCAPTCHA v3 незаметно проверяет добавление в корзину, где-то Turnstile ловит слишком быстрое заполнение адреса. Ниже — один класс на Python, который решает CAPTCHA перед каждым шагом чекаута, от корзины до подтверждения.
Где в чекауте обычно стоит CAPTCHA
| Этап оформления заказа | Какая CAPTCHA чаще встречается | Почему её показывают |
|---|---|---|
| Добавление в корзину | reCAPTCHA v3 (невидимая) | Защита от ботов |
| Вход или гостевой чекаут | reCAPTCHA v2 | Проверка аккаунта |
| Адрес доставки | Cloudflare Turnstile | Ограничение частоты запросов |
| Страница оплаты | reCAPTCHA v2 или v3 | Защита от мошенничества |
| Подтверждение заказа | reCAPTCHA v2 | Финальная проверка |
Что подготовить перед автоматизацией
Прежде чем гонять сценарий на реальном чекауте, соберите четыре вещи:
- API-ключ CaptchaAI — берётся в панели управления после регистрации, без него
CaptchaSolverне сможет отправить задачу. - Staging-версия магазина или тестовый режим платёжного провайдера — прогонять полный чекаут на боевом каталоге не нужно.
- Тестовые данные карты — Stripe, Braintree и большинство платформ выдают отдельные номера для sandbox; они не проходят как реальные платежи.
- Список селекторов под конкретный сайт —
_find,_clickи_fillв примере ниже используют CSS-селекторы с запасными вариантами (selector1, selector2); их придётся подстроить под разметку целевой страницы.
Архитектура: пять точек, где может понадобиться решение CAPTCHA
┌────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ Cart │───▶│ Shipping │───▶│ Payment │───▶│ Review │───▶│ Confirm │
│ │ │ Address │ │ Info │ │ Order │ │ │
└────────┘ └───────────┘ └──────────┘ └─────────┘ └──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?]
Класс для автоматизации чекаута
Solver и автоматизатор шагов
CaptchaSolver отправляет задачу в CaptchaAI и опрашивает результат, CheckoutAutomator вызывает _solve_if_captcha() перед каждым шагом.
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class CaptchaSolver:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(resp["request"])
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(result["request"])
raise TimeoutError("Timed out")
class CheckoutAutomator:
def __init__(self, api_key):
self.solver = CaptchaSolver(api_key)
self.driver = webdriver.Chrome()
self.wait = WebDriverWait(self.driver, 15)
def _find(self, selector):
return self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
def _click(self, selector):
self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector))).click()
def _fill(self, selector, value):
el = self._find(selector)
el.clear()
el.send_keys(value)
def _solve_if_captcha(self):
"""Check for and solve any CAPTCHA on the current page."""
html = self.driver.page_source
page_url = self.driver.current_url
# Turnstile
turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
if turnstile:
sitekey = turnstile[0].get_attribute("data-sitekey")
token = self.solver.solve({
"method": "turnstile",
"sitekey": sitekey,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
)
return "turnstile"
# reCAPTCHA
recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
if recaptcha and "recaptcha" in html.lower():
sitekey = recaptcha[0].get_attribute("data-sitekey")
# Check if v3 (invisible)
if "grecaptcha.execute" in html:
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"version": "v3",
"action": "checkout",
}, initial_wait=20)
else:
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
)
return "recaptcha"
return None
# --- Checkout Steps ---
def add_to_cart(self, product_url, quantity=1):
"""Step 1: Navigate to product and add to cart."""
self.driver.get(product_url)
time.sleep(2)
# Set quantity if field exists
qty_fields = self.driver.find_elements(By.CSS_SELECTOR, "input[name='quantity']")
if qty_fields:
qty_fields[0].clear()
qty_fields[0].send_keys(str(quantity))
self._solve_if_captcha()
self._click("[data-action='add-to-cart'], .add-to-cart, #add-to-cart")
time.sleep(2)
return True
def fill_shipping(self, address):
"""Step 2: Fill shipping address."""
self._solve_if_captcha()
field_map = {
"first_name": "#shipping-first-name, [name='firstName']",
"last_name": "#shipping-last-name, [name='lastName']",
"address": "#shipping-address, [name='address1']",
"city": "#shipping-city, [name='city']",
"state": "#shipping-state, [name='state']",
"zip": "#shipping-zip, [name='postalCode']",
"phone": "#shipping-phone, [name='phone']",
}
for field, selectors in field_map.items():
if field in address:
for selector in selectors.split(", "):
elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
if elements:
elements[0].clear()
elements[0].send_keys(address[field])
break
self._solve_if_captcha()
self._click("[data-step='shipping-submit'], .continue-to-payment")
time.sleep(2)
return True
def fill_payment(self, payment):
"""Step 3: Fill payment information."""
self._solve_if_captcha()
# Handle iframe for card fields (common pattern)
iframes = self.driver.find_elements(By.CSS_SELECTOR, "iframe[name*='card']")
if iframes:
self.driver.switch_to.frame(iframes[0])
self._fill("input[name='cardnumber']", payment["card_number"])
self.driver.switch_to.default_content()
if len(iframes) > 1:
self.driver.switch_to.frame(iframes[1])
self._fill("input[name='exp-date']", payment["expiry"])
self.driver.switch_to.default_content()
if len(iframes) > 2:
self.driver.switch_to.frame(iframes[2])
self._fill("input[name='cvc']", payment["cvv"])
self.driver.switch_to.default_content()
else:
# Direct fields
self._fill("[name='cardNumber'], #card-number", payment["card_number"])
self._fill("[name='expiry'], #card-expiry", payment["expiry"])
self._fill("[name='cvv'], #card-cvv", payment["cvv"])
self._solve_if_captcha()
self._click("[data-step='payment-submit'], .continue-to-review")
time.sleep(2)
return True
def confirm_order(self):
"""Step 4: Review and confirm order."""
self._solve_if_captcha()
self._click("[data-step='confirm'], .place-order, #place-order")
time.sleep(3)
# Check for confirmation
html = self.driver.page_source.lower()
if "order confirmed" in html or "thank you" in html or "confirmation" in html:
return True
return False
def run_checkout(self, product_url, address, payment, quantity=1):
"""Run complete checkout flow."""
steps = [
("Add to cart", lambda: self.add_to_cart(product_url, quantity)),
("Proceed to checkout", lambda: self._click(".checkout-btn, a[href*='checkout']")),
("Fill shipping", lambda: self.fill_shipping(address)),
("Fill payment", lambda: self.fill_payment(payment)),
("Confirm order", lambda: self.confirm_order()),
]
for step_name, step_fn in steps:
print(f"Step: {step_name}...")
try:
result = step_fn()
print(f" ✓ {step_name} complete")
except Exception as e:
print(f" ✗ {step_name} failed: {e}")
return False
return True
def close(self):
self.driver.quit()
Как _solve_if_captcha() выбирает тип виджета
Метод не гадает, а проверяет DOM в строго заданном порядке. Сначала ищет .cf-turnstile — если селектор найден, дальше идёт только Turnstile-ветка, sitekey берётся из data-sitekey, а токен подставляется в скрытое поле cf-turnstile-response. Если Turnstile-контейнера нет, метод переходит к reCAPTCHA: смотрит на [data-sitekey] и строку recaptcha в HTML-исходнике страницы. Присутствие grecaptcha.execute в коде страницы означает невидимую v3 — тогда в запрос добавляются version и action, а ожидание перед первым опросом увеличивается до 20 секунд, потому что v3 обычно решается дольше v2.
Из этого следует ограничение: метод возвращает управление сразу после первого найденного типа. Если на одном шаге чекаута одновременно висят и Turnstile, и reCAPTCHA (редко, но встречается на страницах с несколькими сторонними виджетами), придётся вызвать _solve_if_captcha() повторно после первого решения — сам метод второй проход не делает.
Как запустить сценарий
Данные ниже — тестовая карта Stripe и вымышленный адрес. Не подставляйте сюда реальные платёжные данные — только sandbox-номера платформы.
automator = CheckoutAutomator("YOUR_API_KEY")
try:
success = automator.run_checkout(
product_url="https://store.example.com/product/widget-pro",
address={
"first_name": "Jane",
"last_name": "Smith",
"address": "123 Test Street",
"city": "San Francisco",
"state": "CA",
"zip": "94102",
"phone": "415-555-0100",
},
payment={
"card_number": "4111111111111111",
"expiry": "12/26",
"cvv": "123",
},
quantity=1,
)
print(f"Checkout {'succeeded' if success else 'failed'}")
finally:
automator.close()
Нестандартные ситуации при многошаговом чекауте
CAPTCHA появляется после ошибки валидации
Если сайт откатывает шаг из-за неверного значения, при повторной отправке формы часто всплывает новая CAPTCHA. Оборачивайте шаг в повтор с проверкой:
def retry_step_with_captcha(self, step_fn, max_retries=2):
for attempt in range(max_retries + 1):
try:
return step_fn()
except Exception:
if attempt < max_retries:
self._solve_if_captcha()
continue
raise
CAPTCHA, привязанная к сессии
Некоторые сайты показывают CAPTCHA только на первый запрос в сессии. Используйте один экземпляр драйвера с общими cookies на всех шагах — так CAPTCHA всплывёт максимум один раз за прогон.
Если логи сохраняют адрес и данные карты, обращайтесь с ними как с боевыми персональными данными — это касается и 152-ФЗ для РФ, и GDPR-дисциплины у трансграничных команд.
Динамические поля формы
Если поля шага подгружаются через JS уже после рендера, _solve_if_captcha() может сработать раньше, чем появится сам виджет. Дождитесь индикатора загрузки шага:
def wait_for_step(self, indicator_selector, timeout=15):
"""Wait for a step to fully load before proceeding."""
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((By.CSS_SELECTOR, indicator_selector))
)
GeeTest v3 вместо reCAPTCHA на отдельных шагах
Часть площадок ставит GeeTest v3 на шаг входа или на форму доставки вместо reCAPTCHA — CaptchaAI решает и его через тот же in.php/res.php цикл, только с другим набором параметров задачи (challenge, gt, api_server вместо sitekey/googlekey). Если это ваш случай, добавьте отдельную ветку в _solve_if_captcha() по аналогии с веткой Turnstile — общий каркас CaptchaSolver менять не придётся.
Частые проблемы при автоматизации чекаута
Ниже — симптомы, которые чаще всего ловят в логах QA-прогонов, и что за ними обычно стоит.
| Проблема | Причина | Как исправить |
|---|---|---|
| Токен CAPTCHA отклонён на оплате | Токен истёк | Решайте CAPTCHA прямо перед кликом «Отправить» |
| Поля карты не заполняются | Форма в iframe (Stripe/Braintree) | Переключитесь в iframe перед вводом |
| Чекаут кидает на страницу входа | Истекла сессия | Добавьте шаг входа перед оформлением |
| Заказ не оформляется без ошибки | Не заполнены обязательные поля | Проверяйте ошибки после каждого шага |
_solve_if_captcha() ничего не находит |
Виджет ещё не отрендерился | Добавьте wait_for_step() перед вызовом решения |
Частые вопросы об автоматизации чекаута
Сколько потоков нужно, чтобы тестировать чекаут параллельно?
При 3–5 CAPTCHA за прогон и нескольких браузерах параллельно берите тариф с запасом: ADVANCE ($90/мес, 50 потоков) закрывает 10–15 сессий без очереди. Для разовых прогонов хватит BASIC ($15/мес, 5 потоков).
Можно ли гонять чекаут в песочнице, не тратя реальные карты?
Да. У большинства платформ есть sandbox-режим с тестовыми номерами карт, как в примере выше. CaptchaAI решает CAPTCHA одинаково и в тестовой, и в боевой среде.
Что делать, если CAPTCHA появляется на шаге, где её не было в прошлый раз?
Не привязывайте проверку к конкретному шагу — вызывайте _solve_if_captcha() перед каждым действием, способным вызвать редирект. reCAPTCHA v3 срабатывает по поведенческому скорингу, и порог может сместиться.
Как не столкнуться с истёкшим токеном на длинной форме оплаты?
Решайте CAPTCHA прямо перед отправкой формы — пока пользователь вводит данные карты, ранее полученный токен успевает протухнуть.
Нужна ли отдельная логика, если вместо reCAPTCHA попадается GeeTest v3?
Да, но каркас CaptchaSolver не меняется. Добавьте в _solve_if_captcha() третью ветку с проверкой на GeeTest-виджет и параметрами challenge/gt/api_server вместо sitekey — очередь in.php/res.php и опрос результата остаются теми же.
Похожие материалы
Автоматизация тестирования чекаута — решайте CAPTCHA вместе с CaptchaAI.