Ein CAPTCHA vor Ihrem Formular-Endpunkt darf Ihre Test-Suite nicht ausbremsen. Statt in jedem Testlauf einen kompletten Browser hochzufahren oder Tokens von Hand zu klicken, holen Sie das Token über die CaptchaAI-API, hängen es an die Payload und senden die Anfrage direkt an den Endpunkt. So prüfen Sie Backend-Validierung, Fehlerpfade und Rate-Limits reproduzierbar – headless, ohne UI-Automatisierung und schnell genug für CI.
Dieser Ansatz eignet sich für alle Fälle, in denen der eigentliche Prüfpunkt der Server ist und nicht das Rendering im Browser: reCAPTCHA v2, reCAPTCHA v3 und Cloudflare Turnstile decken den Großteil der Formulare ab, die deutschsprachige Teams testen.
Architektur des Testablaufs
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ Solve │────▶│ Build │────▶│ POST to │────▶│ Validate │
│ CAPTCHA │ │ Request │ │ Endpoint │ │ Response │
│ (API) │ │ Payload │ │ │ │ │
└──────────┘ └────────────┘ └──────────────┘ └──────────────┘
Für die meisten Endpunkttests ist kein Browser erforderlich: Der Token-Anbieter kümmert sich um das CAPTCHA, der Tester baut die Anfrage und wertet die Antwort aus.
Wann sich dieser Ansatz lohnt
- Backend-Validierung: Prüfen Sie, ob der Server CAPTCHA-Tokens tatsächlich verifiziert und nicht blind akzeptiert.
- Lasttests: Senden Sie viele Anfragen an einen CAPTCHA-geschützten Endpunkt, ohne Browser-Instanzen zu skalieren.
- Integrationstests in CI: Binden Sie die Formular-APIs in Ihre GitLab-CI- oder GitHub-Actions-Pipeline ein.
- Fehlerpfade: Verifizieren Sie die korrekten Antworten auf ungültige und abgelaufene Tokens.
Voraussetzungen
Bevor Sie loslegen, brauchen Sie nur wenige Bausteine – kein Browser-Treiber, keine UI-Automatisierung:
- CaptchaAI-API-Schlüssel – über das Dashboard auf
captchaai.comverfügbar. - Python mit
requests– die einzige externe Abhängigkeit im Beispiel. sitekeyundpageurldes zu testenden Formulars – beide stehen im HTML der Zielseite.- Eine Staging-Umgebung – testen Sie gegen eigene oder freigegebene Endpunkte, nicht gegen fremde Produktivsysteme.
Implementierung
CAPTCHA-Token über die API beziehen
Die Klasse TokenProvider kapselt die beiden Schritte des CaptchaAI-Workflows: Aufgabe an in.php übermitteln, anschließend das Ergebnis an res.php abfragen (Polling). Für reCAPTCHA v3 wird eine etwas längere Anfangswartezeit gesetzt, weil der Score-Mechanismus mehr Zeit braucht.
import time
import requests
class TokenProvider:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def get_recaptcha_token(self, sitekey, pageurl, version="v2"):
params = {
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
}
if version == "v3":
params["version"] = "v3"
params["action"] = "submit"
return self._solve(params, initial_wait=15 if version == "v3" else 10)
def get_turnstile_token(self, sitekey, pageurl):
return self._solve({
"method": "turnstile",
"sitekey": sitekey,
"pageurl": pageurl,
})
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")
Endpunkt-Tester aufbauen
Der EndpointTester löst das Token, hängt es unter dem passenden Feldnamen an die Payload und sendet die Anfrage. Neben dem Positivfall prüfen zwei eigene Methoden gezielt die Fehlerpfade – ungültiges Token und fehlendes Token –, denn genau dort scheitern reale Backends am häufigsten. Welcher Feldname zu welchem Typ gehört, fasst diese Tabelle zusammen:
| CAPTCHA-Typ | Wert für captcha_type |
Feldname (captcha_field) |
|---|---|---|
| reCAPTCHA v2 | recaptcha_v2 |
g-recaptcha-response |
| reCAPTCHA v3 | recaptcha_v3 |
g-recaptcha-response |
| Cloudflare Turnstile | turnstile |
cf-turnstile-response |
import json
import time
class EndpointTester:
def __init__(self, api_key):
self.token_provider = TokenProvider(api_key)
self.session = requests.Session()
self.results = []
def test_endpoint(self, config):
"""
config: {
"name": "test name",
"url": "endpoint URL",
"method": "POST",
"captcha_type": "recaptcha_v2" | "recaptcha_v3" | "turnstile",
"sitekey": "...",
"pageurl": "...",
"captcha_field": "g-recaptcha-response",
"payload": { ... form data ... },
"expected_status": 200,
"expected_contains": "success",
}
"""
start = time.time()
result = {"name": config["name"], "passed": False}
try:
# Get CAPTCHA token
captcha_type = config.get("captcha_type", "recaptcha_v2")
if captcha_type == "recaptcha_v2":
token = self.token_provider.get_recaptcha_token(
config["sitekey"], config["pageurl"]
)
elif captcha_type == "recaptcha_v3":
token = self.token_provider.get_recaptcha_token(
config["sitekey"], config["pageurl"], version="v3"
)
elif captcha_type == "turnstile":
token = self.token_provider.get_turnstile_token(
config["sitekey"], config["pageurl"]
)
else:
raise ValueError(f"Unknown captcha type: {captcha_type}")
# Build payload
payload = {**config.get("payload", {})}
captcha_field = config.get("captcha_field", "g-recaptcha-response")
payload[captcha_field] = token
# Submit request
method = config.get("method", "POST").upper()
headers = config.get("headers", {})
if config.get("json_body"):
resp = self.session.request(
method, config["url"], json=payload, headers=headers
)
else:
resp = self.session.request(
method, config["url"], data=payload, headers=headers
)
# Validate response
result["status_code"] = resp.status_code
result["response_length"] = len(resp.text)
result["elapsed"] = round(time.time() - start, 2)
# Check expected status
expected_status = config.get("expected_status", 200)
if resp.status_code != expected_status:
result["error"] = f"Expected {expected_status}, got {resp.status_code}"
self.results.append(result)
return result
# Check expected content
expected = config.get("expected_contains")
if expected and expected.lower() not in resp.text.lower():
result["error"] = f"Response missing: '{expected}'"
self.results.append(result)
return result
result["passed"] = True
except Exception as e:
result["error"] = str(e)
result["elapsed"] = round(time.time() - start, 2)
self.results.append(result)
return result
def test_invalid_token(self, config):
"""Test that endpoint rejects invalid CAPTCHA tokens."""
invalid_config = {**config}
invalid_config["name"] = f"{config['name']} (invalid token)"
# Override with fake token
payload = {**config.get("payload", {})}
captcha_field = config.get("captcha_field", "g-recaptcha-response")
payload[captcha_field] = "INVALID_TOKEN_12345"
start = time.time()
result = {"name": invalid_config["name"], "passed": False}
try:
resp = self.session.post(config["url"], data=payload)
result["status_code"] = resp.status_code
result["elapsed"] = round(time.time() - start, 2)
# Should reject — 4xx or error message
if resp.status_code >= 400 or "error" in resp.text.lower() or "invalid" in resp.text.lower():
result["passed"] = True
else:
result["error"] = "Endpoint accepted invalid CAPTCHA token"
except Exception as e:
result["error"] = str(e)
result["elapsed"] = round(time.time() - start, 2)
self.results.append(result)
return result
def test_missing_token(self, config):
"""Test that endpoint rejects missing CAPTCHA token."""
start = time.time()
result = {"name": f"{config['name']} (missing token)", "passed": False}
try:
payload = config.get("payload", {})
resp = self.session.post(config["url"], data=payload)
result["status_code"] = resp.status_code
result["elapsed"] = round(time.time() - start, 2)
if resp.status_code >= 400 or "captcha" in resp.text.lower():
result["passed"] = True
else:
result["error"] = "Endpoint accepted request without CAPTCHA"
except Exception as e:
result["error"] = str(e)
result["elapsed"] = round(time.time() - start, 2)
self.results.append(result)
return result
def run_suite(self, configs):
"""Run a full test suite against multiple endpoints."""
for config in configs:
self.test_endpoint(config)
self.test_invalid_token(config)
self.test_missing_token(config)
return self.report()
def report(self):
passed = sum(1 for r in self.results if r["passed"])
total = len(self.results)
lines = [f"Endpoint Tests: {passed}/{total} passed", "=" * 50]
for r in self.results:
status = "PASS" if r["passed"] else "FAIL"
elapsed = r.get("elapsed", "?")
lines.append(f" [{status}] {r['name']} ({elapsed}s)")
if r.get("error"):
lines.append(f" Error: {r['error']}")
return "\n".join(lines)
Drei Prüfungen pro Endpunkt
Ein aussagekräftiger Endpunkt-Test besteht nicht aus einem einzelnen Aufruf, sondern aus drei ergänzenden Prüfungen. Genau diese fährt run_suite für jeden Eintrag automatisch:
- Gültige Übermittlung: echtes Token von CaptchaAI, korrekte Payload – der Endpunkt soll
200und die Erfolgsmeldung liefern. - Ungültiges Token: ein bewusst falscher Wert im CAPTCHA-Feld – der Endpunkt muss ablehnen, sonst validiert das Backend gar nicht.
- Fehlendes Token: Anfrage komplett ohne CAPTCHA-Feld – der Endpunkt muss ebenfalls mit einem
4xxantworten.
Erst wenn alle drei Fälle das erwartete Verhalten zeigen, ist die CAPTCHA-Absicherung des Endpunkts belastbar getestet. Ein grüner Positivtest allein sagt nichts über die Sicherheit aus.
Anwendungsbeispiel
Ein typisches DACH-Szenario: Sie betreiben einen Shopware- oder JTL-Shop und wollen die Kontakt- und Newsletter-Endpunkte im Staging absichern, bevor eine Release-Pipeline sie live schaltet. Das folgende Beispiel testet zwei Endpunkte – ein Kontaktformular mit reCAPTCHA v2 und eine Newsletter-Anmeldung mit Cloudflare Turnstile. Ersetzen Sie die Beispiel-URLs durch Ihre eigenen Staging-Adressen; YOUR_API_KEY ist Ihr CaptchaAI-Schlüssel.
tester = EndpointTester("YOUR_API_KEY")
configs = [
{
"name": "Contact form submission",
"url": "https://example.com/api/contact",
"captcha_type": "recaptcha_v2",
"sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"pageurl": "https://example.com/contact",
"captcha_field": "g-recaptcha-response",
"payload": {
"name": "Test User",
"email": "test@example.com",
"message": "Automated test message",
},
"expected_status": 200,
"expected_contains": "success",
},
{
"name": "Newsletter signup",
"url": "https://example.com/api/subscribe",
"captcha_type": "turnstile",
"sitekey": "0x4AAAA...",
"pageurl": "https://example.com/newsletter",
"captcha_field": "cf-turnstile-response",
"payload": {
"email": "test@example.com",
},
"expected_status": 200,
},
]
report = tester.run_suite(configs)
print(report)
Die run_suite-Methode fährt für jeden Endpunkt drei Prüfungen: gültige Übermittlung, ungültiges Token und fehlendes Token. Das Ergebnis zeigt kompakt, wo die Backend-Validierung greift – und wo nicht:
Ausgabe:
Endpoint Tests: 5/6 passed
==================================================
[PASS] Contact form submission (18.5s)
[PASS] Contact form submission (invalid token) (0.3s)
[PASS] Contact form submission (missing token) (0.2s)
[PASS] Newsletter signup (14.2s)
[FAIL] Newsletter signup (invalid token) (0.3s)
Error: Endpoint accepted invalid CAPTCHA token
[PASS] Newsletter signup (missing token) (0.2s)
Der FAIL beim Newsletter-Endpunkt ist genau der Befund, für den solche Tests gebaut werden: Das Backend akzeptiert ein offensichtlich ungültiges Token – ein Sicherheitsproblem, das ein reiner Positivtest niemals aufdecken würde.
DSGVO-Hinweis: Sobald Ihr Test-Traffic personenbezogene Daten wie E-Mail- oder IP-Adressen erzeugt, greift die DSGVO. Verwenden Sie in Staging-Läufen synthetische Testdaten und keine echten Kundendatensätze.
Fehlerbehebung
| Problem | Ursache | Lösung |
|---|---|---|
| Gültiges Token abgelehnt | Token ist vor der Übermittlung abgelaufen | Verzögerung zwischen Lösung und Absenden verkürzen |
| Ungültiges Token akzeptiert | Backend validiert das CAPTCHA nicht | Als Bug melden – ernstes Sicherheitsproblem |
| 403 auf alle Anfragen | CSRF-Token oder Cookies fehlen | Session-Cookies oder CSRF-Header ergänzen |
| JSON-Endpunkt lehnt Formulardaten ab | Falscher Content-Type | json_body: True in der Konfiguration setzen |
Häufige Fragen
Welche CAPTCHA-Typen deckt dieser Ansatz ab?
reCAPTCHA v2, reCAPTCHA v3 und Cloudflare Turnstile über die gezeigten Methoden. CaptchaAI löst darüber hinaus reCAPTCHA Enterprise, GeeTest v3, Cloudflare Challenge sowie Bild- und Raster-CAPTCHAs; hCaptcha und FunCaptcha werden nicht unterstützt.
Wie viel Zeit entfällt auf das Lösen des Tokens?
Den Großteil der Laufzeit macht das CAPTCHA aus – die HTTP-Anfrage und die Auswertung der Antwort dauern nur Millisekunden. Das zeigen die elapsed-Werte im Bericht: Die Positivtests mit echtem Token liegen deutlich über den Fehlerpfad-Tests ohne Lösung.
Wie integriere ich die Tests in GitLab CI oder GitHub Actions?
Hinterlegen Sie den API-Schlüssel als geschütztes CI-Secret und rufen Sie die Test-Suite in einem Job auf. Da kein Browser nötig ist, läuft alles in einem schlanken Python-Container – passend zu den in DACH verbreiteten GitLab-CI-Pipelines.
Welcher CaptchaAI-Tarif reicht für parallele Endpunkt-Tests?
Das richtet sich nach der Zahl gleichzeitiger Tests, denn CaptchaAI rechnet pro Thread ab – nicht pro Lösung. BASIC (15 $/Monat, 5 Threads) genügt für kleine Suites; für viele parallele Endpunkte bietet ADVANCE (90 $/Monat, 50 Threads) mehr Spielraum. Jeder Tarif enthält unbegrenzte Lösungen pro Thread.
Verwandte Leitfäden
- Formularübermittlung mit CAPTCHA automatisieren
- CaptchaAI API-Kurzreferenz
Sichern Sie jeden CAPTCHA-geschützten Endpunkt in Ihrer Pipeline ab – mit CaptchaAI.