In einem mehrstufigen Checkout scheitert ein automatisierter Test selten am Formular selbst, sondern an dem CAPTCHA, das an einer unvorhersehbaren Stelle zwischen Warenkorb und Bestellbestätigung auftaucht. Die zuverlässige Antwort darauf ist kein Sonderfall pro Seite, sondern ein einziger Prüf-und-Löse-Schritt, den Ihre Automatisierung vor jeder Aktion aufruft. Genau dieses Muster zeigt dieser Leitfaden: reCAPTCHA v2, das unsichtbare reCAPTCHA v3 und Cloudflare Turnstile werden über CaptchaAI gelöst, während Selenium das Ausfüllen und Absenden der Formulare übernimmt.
Zum Einsatzbereich: Der gesamte Ablauf ist als QA-Szenario gedacht – für Ihren eigenen Shop oder eine freigegebene Staging-Umgebung, in der Sie den Kaufabschluss end-to-end absichern wollen, nicht für fremde Produktivsysteme.
Wo im Checkout CAPTCHAs auftauchen
Kein Shop platziert seine Schutzmechanismen an denselben Stellen. Die folgende Übersicht zeigt die typischen Auslöser entlang eines Standard-Checkouts – von der unsichtbaren reCAPTCHA-v3-Prüfung beim Warenkorb bis zur finalen Validierung auf der Bestätigungsseite.
| Phase | Häufige CAPTCHA-Typen | Auslöser |
|---|---|---|
| In den Warenkorb legen | reCAPTCHA v3 (unsichtbar) | Bot-Prävention |
| Login / Gast-Checkout | reCAPTCHA v2 | Kontobestätigung |
| Lieferadresse | Cloudflare Turnstile | Rate-Limiting |
| Zahlungsseite | reCAPTCHA v2/v3 | Betrugsprävention |
| Bestellbestätigung | reCAPTCHA v2 | Finale Validierung |
Weil sich diese Verteilung von Shop zu Shop verschiebt, lohnt es sich nicht, jede Phase einzeln zu behandeln. Wesentlich robuster ist ein Automator, der vor jedem Klick prüft, ob überhaupt eine CAPTCHA-Abfrage vorliegt, und nur dann löst. Drei Gründe sprechen für diesen zentralen Schritt:
- Weniger Wartung: Verschiebt ein Shop die Abfrage von der Zahlungs- auf die Bestätigungsseite, bleibt der Test unverändert.
- Weniger Fehlerquellen: Es gibt genau eine Stelle, an der Sitekey-Erkennung und Token-Einbau passieren – nicht fünf Kopien davon.
- Bessere Lesbarkeit: Die eigentlichen Checkout-Schritte bleiben frei von CAPTCHA-Logik und beschreiben nur den fachlichen Ablauf.
Der Checkout-Ablauf im Überblick
Jede Phase kann eine Abfrage anzeigen – oder eben nicht. Deshalb sitzt der Prüf-und-Löse-Schritt zwischen den Übergängen, nicht fest an einer bestimmten Seite:
┌────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ Cart │───▶│ Shipping │───▶│ Payment │───▶│ Review │───▶│ Confirm │
│ │ │ Address │ │ Info │ │ Order │ │ │
└────────┘ └───────────┘ └──────────┘ └─────────┘ └──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?]
So bleibt der Ablauf stabil, auch wenn ein Shop eine Abfrage verschiebt oder eine neue hinzufügt – der Test muss nicht umgeschrieben werden.
Kernimplementierung: der Checkout-Automator
Das Herzstück sind zwei Klassen mit klar getrennten Aufgaben:
CaptchaSolverkapselt die Kommunikation mit dem CaptchaAI-Endpunkt: Aufgabe anin.phpübermitteln, danach das Ergebnis überres.phpabfragen, bis der Status stimmt.CheckoutAutomatorsteuert Selenium, füllt die Formulare und ruft an jedem Übergang_solve_if_captcha()auf.
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()
Zwei Details lohnen einen zweiten Blick. Erstens die Reihenfolge in _solve_if_captcha(): zuerst wird auf Turnstile geprüft, danach auf reCAPTCHA – so schreibt der Code nie versehentlich das cf-turnstile-response-Feld, wenn eigentlich ein g-recaptcha-response-Token gebraucht wird. Zweitens die v3-Erkennung: Findet der Code grecaptcha.execute im Seiten-Quelltext, behandelt er die Abfrage als unsichtbares reCAPTCHA v3 und wartet mit initial_wait=20 etwas länger auf das Ergebnis.
Anwendungsbeispiel: ein Testdurchlauf
Ein kompletter Durchlauf gegen Ihre Staging-Umgebung braucht nur einen Aufruf. Ersetzen Sie die product_url durch die Adresse Ihres eigenen Test-Shops und die Adress- sowie Zahlungsdaten durch die Sandbox-Werte Ihrer Plattform:
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()
Die Kartennummer 4111 1111 1111 1111 ist eine gängige Visa-Testnummer, die in keinem echten Zahlungsnetz belastet wird. Die im DACH-Raum verbreiteten Shop-Systeme bieten dafür jeweils einen eigenen Test- bzw. Sandbox-Modus mit hinterlegten Testkarten:
- Shopware – Test-Zahlungsart im Sandbox-Modus der jeweiligen Payment-Extension.
- JTL-Shop – Testmodus der angebundenen Zahlungsanbieter.
- Shopify – Bogus Gateway bzw. der Testmodus des Payment-Providers.
CaptchaAI löst dieselben CAPTCHA-Abfragen auch in diesen Testumgebungen, sodass Sie den Ablauf gefahrlos wiederholen können.
Grenzfälle sauber abfangen
In der Praxis verhält sich ein Checkout selten geradlinig. Drei Situationen tauchen besonders häufig auf.
CAPTCHA nach einem Validierungsfehler
Manche Shops blenden erst dann eine Abfrage ein, wenn ein Feld zunächst falsch ausgefüllt war. Ein Wiederholungs-Wrapper löst das CAPTCHA vor dem nächsten Versuch:
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
Sitzungsgebundene CAPTCHAs
Einige Websites zeigen eine Abfrage nur bei der ersten Anfrage pro Sitzung. Halten Sie die Browser-Cookies über alle Schritte hinweg konstant, damit die Sitzung nicht mitten im Checkout neu startet und eine zusätzliche Abfrage auslöst.
Dynamisch nachgeladene Formularfelder
Bei einem Single-Page-Checkout erscheinen Felder oft erst per JavaScript. Warten Sie explizit auf ein Element der jeweiligen Phase, bevor Sie fortfahren:
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))
)
Fehlerbehebung
| Problem | Ursache | Lösung |
|---|---|---|
| Token wird an der Zahlungsseite abgelehnt | Token bereits abgelaufen | Lösen Sie das CAPTCHA unmittelbar vor dem Klick auf „Senden“ |
| Kartenfelder bleiben leer | Stripe-/Braintree-Iframe | Vor dem Ausfüllen in den Iframe wechseln |
| Checkout springt zurück zum Login | Sitzung abgelaufen | Anmeldeschritt vor den Checkout setzen |
| Bestellung schlägt kommentarlos fehl | Pflichtfeld fehlt | Nach jedem Schritt auf Fehlermeldungen prüfen |
Ein abgelaufenes Token ist mit Abstand die häufigste Ursache. reCAPTCHA- und Turnstile-Tokens sind nur rund 120 Sekunden gültig – lösen Sie deshalb erst kurz vor dem Absenden, nicht vorab auf Vorrat.
Fazit
Ein stabiler Checkout-Test steht und fällt mit der Stelle, an der das CAPTCHA gelöst wird. Wenn Sie die Kernpunkte aus diesem Leitfaden umsetzen, übersteht Ihre Automatisierung auch Layout-Änderungen im Shop:
- Ein zentraler
_solve_if_captcha()-Aufruf vor jeder Aktion statt fünf verstreuter Sonderfälle. - Turnstile vor reCAPTCHA prüfen, damit nie das falsche Token-Feld beschrieben wird.
- Erst unmittelbar vor dem Absenden lösen – wegen der 120-Sekunden-Gültigkeit.
- Nur in eigenen oder freigegebenen Staging-Umgebungen mit synthetischen Testdaten arbeiten.
FAQ
Welche CAPTCHA-Typen deckt CaptchaAI im Checkout ab?
reCAPTCHA v2, reCAPTCHA v3 und Cloudflare Turnstile – genau die Typen, die in E-Commerce-Checkouts am häufigsten vorkommen. Bild- und Grid-CAPTCHAs sowie GeeTest v3 sind ebenfalls abgedeckt. hCaptcha und FunCaptcha werden derzeit nicht unterstützt; falls Ihr Shop diese einsetzt, prüfen Sie das vor dem Testaufbau.
Warum wird mein Token an der Zahlungsseite abgelehnt?
Weil es zwischen Erzeugung und Absenden abgelaufen ist. Die Tokens leben nur etwa 120 Sekunden. Rufen Sie _solve_if_captcha() direkt vor dem Absenden der Zahlung auf, nicht schon beim Betreten der Seite – so bleibt das Zeitfenster kurz und der Server akzeptiert das Token.
Wie viele Checkout-Flows kann ich parallel testen?
CaptchaAI rechnet pro gleichzeitigem Thread ab, nicht pro Lösung. Ein Thread bearbeitet eine Abfrage; sobald sie fertig ist, nimmt er die nächste. Der Tarif BASIC (15 $/Monat, 5 Threads) erlaubt also fünf parallele Test-Läufe mit unbegrenzten Lösungen – für größere Suiten skalieren Sie über die höheren Tarife.
Darf ich fremde Online-Shops automatisiert testen?
Nur mit ausdrücklicher Freigabe. Automatisieren Sie den Kaufabschluss ausschließlich in Ihrem eigenen Shop oder einer freigegebenen Staging-Umgebung. Beachten Sie zusätzlich, dass Adress- und Kundendaten in Testläufen unter die DSGVO fallen – nutzen Sie ausschließlich synthetische Testdaten.
Verwandte Leitfäden
- Formulare mit CAPTCHA automatisiert absenden
- CaptchaAI API-Kurzreferenz
Checkout-QA absichern – CAPTCHAs mit CaptchaAI lösen.