لاختبار نقطة نهاية محمية بـ CAPTCHA لا تحتاج إلى تشغيل متصفح كامل ولا إلى أي نقر يدوي. تحصل على رمز حقيقي من CaptchaAI عبر API، تُرفقه ضمن حمولة الطلب، ثم ترسل الطلب مباشرة إلى الواجهة الخلفية وتقرأ استجابتها. بهذا يصبح اختبار النموذج خطوة آلية سريعة ضمن مجموعة اختباراتك بدل خطوة بشرية بطيئة تكسر خط التكامل المستمر.
الفكرة موجّهة إلى مهندسي الجودة (QA) الذين يملكون التطبيق أو مصرَّح لهم باختباره، ويريدون التأكد من أن الخادم يتعامل بشكل صحيح مع الرموز الصالحة والمنتهية والمفقودة. تعمل الأمثلة هنا مع reCAPTCHA v2 و reCAPTCHA v3 و Cloudflare Turnstile، وكلها مدعومة رسمياً في CaptchaAI.
لماذا تختبر عبر API بدل المتصفح
فتح متصفح آلي لكل حالة اختبار مكلف وهشّ: كل عملية تستهلك ذاكرة، وتضيف ثواني على زمن التشغيل، وتنكسر عند أول تغيير في الواجهة. أما إرسال الطلب مباشرة إلى نقطة النهاية فيعزل ما يهمّك فعلاً وهو منطق التحقق في الخادم. تحصل على تشغيل أسرع، وسجلّات أوضح، وقدرة على تكرار الاختبار آلاف المرات داخل خط التكامل المستمر دون بيئة رسومية.
متى تحتاج هذا النمط
- اختبار التحقق في الواجهة الخلفية: التأكد من أن الخادم يتحقق فعلاً من صحة رمز CAPTCHA ولا يقبل أي قيمة.
- اختبار التحميل: إرسال عدد كبير من الطلبات إلى نقطة نهاية محمية لقياس سلوكها تحت الضغط.
- اختبار التكامل: تشغيل واجهات إرسال النماذج ضمن مسار CI/CD قبل كل إصدار.
- اختبار مسار الخطأ: التأكد من ظهور رسائل خطأ مناسبة عند إرسال رمز غير صالح أو منتهي الصلاحية.
مسار العمل من الحل إلى التحقق
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────────┐
│ Solve │────▶│ Build │────▶│ POST to │────▶│ Validate │
│ CAPTCHA │ │ Request │ │ Endpoint │ │ Response │
│ (API) │ │ Payload │ │ │ │ │
└──────────┘ └────────────┘ └──────────────┘ └──────────────┘
- احصل على رمز حقيقي من CaptchaAI عبر API.
- ابنِ حمولة الطلب وضع الرمز في حقله الصحيح.
- أرسل الحمولة بطلب POST إلى نقطة النهاية.
- تحقق من رمز الحالة ومحتوى الاستجابة مقابل المتوقّع.
التنفيذ
مزوّد رموز CAPTCHA
الصنف التالي يغلّف نمط التشغيل بالكامل: يرسل الطلب إلى in.php، ثم يستطلع النتيجة دورياً من res.php حتى يجهز الرمز. تدعم دالة get_recaptcha_token كلاً من الإصدار v2 و v3، بينما تتكفّل get_turnstile_token بـ Cloudflare Turnstile.
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")
مُختبِر نقطة النهاية
يستقبل الصنف EndpointTester تكويناً لكل حالة اختبار، يجلب الرمز المناسب حسب نوع CAPTCHA، يضعه في الحقل الصحيح (g-recaptcha-response لـ reCAPTCHA أو cf-turnstile-response لـ Turnstile)، ثم يرسل الطلب ويقارن الاستجابة بالمتوقّع. لاحظ دالتَي test_invalid_token وtest_missing_token: هما جوهر التحقق الأمني، إذ تتأكدان من أن الخادم يرفض الرموز المزيّفة والطلبات الخالية من رمز.
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)
مثال تشغيلي كامل
يجمع المثال التالي حالتَي اختبار: نموذج تواصل يستخدم reCAPTCHA v2، ونموذج اشتراك في نشرة بريدية يستخدم Cloudflare Turnstile. تشغّل run_suite لكل نقطة نهاية ثلاثة اختبارات — رمز صالح، ورمز غير صالح، ورمز مفقود — ثم تطبع تقريراً موجزاً.
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)
الإخراج:
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)
اقرأ التقرير من الأسفل إلى الأعلى: السطر [FAIL] هنا ليس عطلاً في الاختبار بل اكتشاف حقيقي — نقطة نهاية النشرة قبلت رمزاً مزيّفاً، أي أنها لا تتحقق من CAPTCHA على الخادم. هذا بالضبط نوع الخلل الأمني الذي يوجد هذا النمط لالتقاطه قبل الوصول إلى الإنتاج.
سيناريو محلي: فريق جودة في متجر إلكتروني
اختبر فقط نقاط النهاية التي تملكها أو المصرَّح لك رسمياً باختبارها؛ هذا النمط أداة لضمان الجودة داخل بيئتك، لا وسيلة للتعامل مع حماية مواقع لا تملكها.
تخيّل فريق QA في متجر إلكتروني بالقاهرة أو الرياض يطلق صفحة تواصل ونموذج اشتراك جديدين قبل موسم التخفيضات. بدل فتح المتصفح يدوياً في كل مرة، يضيف الفريق مجموعة EndpointTester إلى خط CI/CD لتعمل مع كل طلب دمج: تتأكد أن الرمز الصالح يمرّ، وأن الرمز المزيّف والطلب الخالي من رمز يُرفضان بـ 4xx.
من حيث التكلفة، تُحاسب CaptchaAI على أساس المسارات المتزامنة (threads) مع عدد حلول غير محدود لكل مسار خلال الشهر، لا على أساس كل عملية حل. لذلك تكفي خطة BASIC ($15 شهرياً، 5 مسارات متزامنة) مجموعةَ اختبارات صغيرة تعمل عدة مرات يومياً؛ وعند توسّع سلسلة المتاجر إلى بيئات متعددة يمكن الترقية إلى STANDARD ($30 شهرياً، 15 مساراً) دون أي رسوم إضافية على نوع CAPTCHA. الأسعار بالدولار الأمريكي كما هي معلنة على صفحة التسعير.
انتبه إلى أن reCAPTCHA v2 و v3 و Cloudflare Turnstile مدعومة بالكامل، بينما لا تدعم CaptchaAI حالياً hCaptcha ولا FunCaptcha؛ إن كانت نقطة نهايتك محمية بأحدهما فلن يفلح هذا النمط معها بعد.
جدول تشخيص الأعطال
| المشكلة | السبب المحتمل | الإجراء |
|---|---|---|
| رُفض رمز صالح | انتهت صلاحية الرمز قبل الإرسال | قلّل التأخير بين الحل والإرسال |
| قُبل رمز غير صالح | الواجهة الخلفية لا تتحقق من صحة CAPTCHA | سجّل الخلل — إنه ثغرة أمنية |
| الرمز 403 على كل الطلبات | رمز CSRF أو ملفات تعريف الجلسة مفقودة | أضف ملفات تعريف الجلسة أو رأس CSRF |
| نقطة نهاية JSON ترفض بيانات النموذج | نوع محتوى خاطئ | اضبط json_body: True في التكوين |
الأسئلة الشائعة
أي أنواع CAPTCHA يمكن اختبارها بهذه الطريقة؟
تعمل الأمثلة مع reCAPTCHA v2 و reCAPTCHA v3 و Cloudflare Turnstile، وهي أنواع مدعومة رسمياً في CaptchaAI. أما hCaptcha و FunCaptcha فغير مدعومين حالياً، لذا لا يمكن توليد رمز حقيقي لهما عبر هذا المسار.
كم تكلّف تشغيل هذه الاختبارات؟
الفوترة على أساس المسارات المتزامنة مع حلول غير محدودة لكل مسار شهرياً، لا على كل عملية حل. تكفي خطة BASIC ($15 شهرياً، 5 مسارات) مجموعةَ اختبارات نموذجية، ويمكن الترقية عند الحاجة إلى تزامن أعلى.
هل يمكن دمج هذه المجموعة داخل CI/CD؟
نعم، وهذا استخدامها الأمثل. خزّن YOUR_API_KEY كسرّ (secret) في خط الإصدار، وشغّل run_suite كخطوة اختبار تكامل تفشل البناء إذا قبلت نقطة النهاية رمزاً مزيّفاً أو مفقوداً.
لماذا يُرفض أحياناً رمز صالح تماماً؟
غالباً لأن الرمز انتهت صلاحيته في الفترة بين حلّه وإرساله. قلّل التأخير بين استلام الرمز واستدعاء نقطة النهاية، وتجنّب تخزين الرموز لإعادة استخدامها.
هل أحتاج إلى حل CAPTCHA حقيقي لاختبار الرمز المفقود؟
لا. اختبارات الرمز المفقود أو غير الصالح لا تتطلب حلاً حقيقياً؛ أرسل الطلب بلا رمز أو برمز مزيّف وتأكد من الرفض. الرمز الحقيقي مطلوب فقط لاختبار مسار الإرسال الناجح.
أدلة ذات صلة
اختبر كل نقطة نهاية محمية بـ CAPTCHA ضمن بيئة تملكها — ابدأ مع CaptchaAI.