Tutorials

CaptchaAI ile Otomatik Test Hattı Oluşturun

Uçtan uca test paketiniz giriş ya da ödeme adımında bir reCAPTCHA v2 gördüğü an kırmızıya mı dönüyor? Çözüm, doğrulamayı testin içinde otomatik çözmek: CaptchaAI'yi pytest ve Selenium tabanlı bir hatta bağladığınızda CI, CAPTCHA ekranında takılmadan akışı tamamlar. Bu rehber, yardımcı sınıftan GitHub Actions iş akışına kadar çalışan kodla uçtan uca bir kurulum verir.

Senaryo Türkiye'deki çoğu QA ekibine tanıdık gelir: bir e-ticaret ya da fintech uygulamasının giriş ve ödeme akışını otomatik test edersiniz, ama bot korumasındaki reCAPTCHA v2 gece çalışan (nightly) paketi durdurur. Freelance otomasyon geliştiricisiyseniz aynı duvarla genellikle müşteri teslimatından bir gün önce karşılaşırsınız. Aşağıdaki hat bu boşluğu kapatır ve gideri öngörülebilir tutar — CaptchaAI thread bazlı fiyatlandığı için aylık maliyetiniz çözüm hacminden bağımsız, USD olarak sabit kalır.

Kuracağımız yapı üç parçadan oluşur:

  • CAPTCHA çözümünü kapsayan tek bir yardımcı sınıf,
  • bu sınıfı testlere sunan pytest fikstürleri,
  • tüm paketi CI'da tetikleyen bir GitHub Actions iş akışı.

Her parçayı sırayla, çalışan kodla kuruyoruz.


Test hattının klasör yapısı

CAPTCHA çözüm mantığını ayrı bir modülde tutup testlere fikstür olarak veriyoruz; böylece çözüm kodu tek yerde durur, testler sade kalır ve ileride başka bir çözüme geçmeniz gerekirse yalnızca tek dosyayı düzenlersiniz. Aşağıdaki düz yapı, pytest'in helpers/ paketini ve conftest.py içindeki paylaşılan fikstürleri sorunsuz bulmasını sağlar.

tests/
├── conftest.py          # Shared fixtures
├── helpers/
│   ├── captcha.py       # CaptchaAI integration
│   └── browser.py       # Selenium helpers
├── test_login.py        # Login flow tests
├── test_checkout.py     # Checkout flow tests
└── pytest.ini           # Config

CaptchaAI çözüm yardımcısı

CaptchaTestHelper sınıfı işin kalbidir ve çözüm akışını üç adımda yürütür:

  1. Sayfadan okunan sitekey ve pageurl bilgisini method=userrecaptcha ile in.php uç noktasına gönderir, dönen görev kimliğini alır.
  2. Sonucu res.php üzerinden periyodik olarak sorgular; ilk 15 saniyelik bekleme reCAPTCHA v2'nin tipik çözüm süresine denk gelir, sonrasında beşer saniye aralıklarla en fazla 24 kez sorgulama yapar.
  3. CAPCHA_NOT_READY yanıtı "çözüm henüz hazır değil" demektir ve döngü devam eder; başka bir hata kodu gelirse test anında düşer.

Token hazır olduğunda inject_token onu tarayıcıya yerleştirir: önce gizli g-recaptcha-response alanına yazar, sonra reCAPTCHA'nın callback fonksiyonunu tetikleyerek sayfanın token'ı gerçekten teslim alınmış gibi işlemesini sağlar. Yalnızca alana değer yazmak çoğu sayfada yetmez — callback tetiklenmezse form gönderimi başarısız olur. API anahtarını koda gömmeyin; sınıf onu CAPTCHAAI_API_KEY ortam değişkeninden okur, böylece anahtar CI gizli değişkeni olarak kalır.

# tests/helpers/captcha.py
import requests
import time
import os


class CaptchaTestHelper:
    """Solve CAPTCHAs during automated tests."""

    def __init__(self):
        self.api_key = os.environ.get("CAPTCHAAI_API_KEY")
        if not self.api_key:
            raise EnvironmentError("CAPTCHAAI_API_KEY required for CAPTCHA tests")

    def solve_recaptcha(self, sitekey, pageurl):
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": 1,
        }, timeout=30)
        result = resp.json()
        if result.get("status") != 1:
            raise RuntimeError(f"Submit failed: {result.get('request')}")

        task_id = result["request"]
        time.sleep(15)

        for _ in range(24):
            resp = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            }, timeout=15)
            data = resp.json()
            if data.get("status") == 1:
                return data["request"]
            if data["request"] != "CAPCHA_NOT_READY":
                raise RuntimeError(data["request"])
            time.sleep(5)

        raise TimeoutError("CAPTCHA solve timeout in test")

    def inject_token(self, driver, token):
        """Inject solved token into Selenium browser."""
        driver.execute_script(
            'document.getElementById("g-recaptcha-response").value = arguments[0];',
            token,
        )
        # Trigger callback if available
        driver.execute_script("""
            if (typeof ___grecaptcha_cfg !== 'undefined') {
                var clients = ___grecaptcha_cfg.clients;
                for (var key in clients) {
                    var client = clients[key];
                    for (var prop in client) {
                        var val = client[prop];
                        if (val && typeof val === 'object') {
                            for (var inner in val) {
                                if (typeof val[inner] === 'function') {
                                    val[inner](arguments[0]);
                                    return;
                                }
                            }
                        }
                    }
                }
            }
        """, token)

Pytest fikstürleri: tarayıcı ve çözücü

Fikstür katmanı üç sorumluluğu ayırır:

  • Çözücü oturum kapsamında (scope="session") bir kez oluşturulur — tekrar tekrar kurmanın anlamı yoktur.
  • Tarayıcı her test için (scope="function") yeniden başlatılır, böylece testler birbirinin oturum durumundan etkilenmez.
  • base_url fikstürünü kendi staging ortamınıza yöneltin; testleri üretimde değil, yalnızca yetkili QA ortamında çalıştırın.

CI için eklenen --no-sandbox ve --disable-dev-shm-usage bayrakları, headless Chrome'un konteyner içinde kararlı çalışması için gereklidir.

# tests/conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from helpers.captcha import CaptchaTestHelper


@pytest.fixture(scope="session")
def captcha_solver():
    return CaptchaTestHelper()


@pytest.fixture(scope="function")
def browser():
    options = Options()
    options.add_argument("--headless")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    driver = webdriver.Chrome(options=options)
    driver.implicitly_wait(10)
    yield driver
    driver.quit()


@pytest.fixture(scope="session")
def base_url():
    return "https://staging.example.com"

reCAPTCHA v2'li giriş testi

İlk senaryo iki durumu birlikte doğrular:

  • Geçerli kimlik bilgileri: CAPTCHA çözülünce giriş başarılı olmalı ve panele yönlenmeli.
  • Hatalı kimlik bilgileri: CAPTCHA doğru çözülmüş olsa bile giriş reddedilmeli.

İkinci durum önemlidir, çünkü çözümün uygulamanın kimlik doğrulama mantığına dokunmadığını kanıtlar — yalnızca doğrulama katmanını yerine getirir. Test, sayfadaki data-sitekey değerini okur, token'ı çözer, g-recaptcha-response alanına enjekte eder ve formu gönderir; ardından yönlendirmeyi ve karşılama mesajını doğrular.

# tests/test_login.py
import pytest
from selenium.webdriver.common.by import By


class TestLogin:
    def test_valid_login_with_captcha(self, browser, captcha_solver, base_url):
        """Test that login succeeds when CAPTCHA is solved correctly."""
        browser.get(f"{base_url}/login")

        # Fill form
        browser.find_element(By.ID, "email").send_keys("test@example.com")
        browser.find_element(By.ID, "password").send_keys("testpassword123")

        # Solve CAPTCHA
        sitekey = browser.find_element(
            By.CLASS_NAME, "g-recaptcha"
        ).get_attribute("data-sitekey")

        token = captcha_solver.solve_recaptcha(sitekey, browser.current_url)
        captcha_solver.inject_token(browser, token)

        # Submit
        browser.find_element(By.ID, "login-btn").click()

        # Assert redirect to dashboard
        assert "/dashboard" in browser.current_url
        assert browser.find_element(By.CLASS_NAME, "welcome-message")

    def test_invalid_credentials_with_captcha(self, browser, captcha_solver, base_url):
        """Test that wrong credentials show error even with valid CAPTCHA."""
        browser.get(f"{base_url}/login")

        browser.find_element(By.ID, "email").send_keys("wrong@example.com")
        browser.find_element(By.ID, "password").send_keys("wrongpass")

        sitekey = browser.find_element(
            By.CLASS_NAME, "g-recaptcha"
        ).get_attribute("data-sitekey")

        token = captcha_solver.solve_recaptcha(sitekey, browser.current_url)
        captcha_solver.inject_token(browser, token)

        browser.find_element(By.ID, "login-btn").click()

        error = browser.find_element(By.CLASS_NAME, "error-message")
        assert "Invalid" in error.text

Ödeme akışı testi

Türkiye'de QA ekiplerinin en sık otomatikleştirdiği akış ödeme adımıdır: sepete ürün ekleme, kargo bilgisi girme ve sipariş onayı arasında bir CAPTCHA belirebilir. Aşağıdaki test tüm akışı uçtan uca yürütür:

  1. Ürünü sepete ekler ve ödeme sayfasına geçer.
  2. Adres formunu doldurur.
  3. Ödeme sayfasındaki CAPTCHA'yı çözer ve token'ı enjekte eder.
  4. Siparişi gönderir ve onay mesajını bekler.

Test verisi olarak yalnızca yapay değerler kullanın (örneğin test@example.com); KVKK kapsamındaki gerçek kişisel verileri test fikstürlerine koymayın, çünkü test kayıtları sürüm kontrolünde ve CI günlüklerinde açıkta kalır.

# tests/test_checkout.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class TestCheckout:
    def test_checkout_flow_with_captcha(self, browser, captcha_solver, base_url):
        """Full checkout flow: add item, fill form, solve CAPTCHA, confirm."""
        # Add item to cart
        browser.get(f"{base_url}/products/test-item")
        browser.find_element(By.ID, "add-to-cart").click()

        # Go to checkout
        browser.get(f"{base_url}/checkout")

        # Fill shipping
        browser.find_element(By.ID, "address").send_keys("123 Test St")
        browser.find_element(By.ID, "city").send_keys("Test City")
        browser.find_element(By.ID, "zip").send_keys("12345")

        # Solve CAPTCHA on checkout page
        captcha_el = browser.find_element(By.CLASS_NAME, "g-recaptcha")
        sitekey = captcha_el.get_attribute("data-sitekey")

        token = captcha_solver.solve_recaptcha(sitekey, browser.current_url)
        captcha_solver.inject_token(browser, token)

        # Submit order
        browser.find_element(By.ID, "place-order").click()

        # Wait for confirmation
        wait = WebDriverWait(browser, 15)
        confirmation = wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "order-confirmation"))
        )
        assert "Thank you" in confirmation.text

Pytest yapılandırması ve maliyet işaretçisi

Her çözüm gerçek bir API çağrısıdır, dolayısıyla CAPTCHA testlerini yalnızca gerektiğinde çalıştırmak istersiniz. captcha işaretçisi (marker) bu testleri diğerlerinden ayırır:

  • geliştirme sırasındaki hızlı koşularda dışarıda bırakılır;
  • gece çalışan ya da sürüm öncesi paketlerde etkinleştirilir.

Bu ayrım hem çalışma süresini hem de gideri kontrol altında tutar.

# tests/pytest.ini
[pytest]
markers =
    captcha: tests requiring CAPTCHA solving (cost per run)
addopts = -v --tb=short

GitHub Actions ile CI iş akışı

İş akışı iki tetikleyiciyle çalışır:

  • her main push'unda, değişikliğin akışı bozmadığını doğrulamak için;
  • haftada bir zamanlanmış koşuda, koddan bağımsız olarak hedef sitedeki CAPTCHA davranışının değişmediğini erken yakalamak için.

API anahtarı depoya değil, secrets.CAPTCHAAI_API_KEY altına konur ve çalışma anında ortam değişkeni olarak aktarılır. Aynı desen GitLab CI, Jenkins ya da CircleCI için de birebir taşınır — tek fark gizli değişkeni tanımlama söz dizimidir.

# .github/workflows/e2e-tests.yml
name: E2E Tests

on:
  push:
    branches: [main]
  schedule:

    - cron: "0 6 * * 1"  # Weekly Monday 6 AM

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install pytest selenium requests

      - name: Install Chrome
        uses: browser-actions/setup-chrome@latest

      - name: Run E2E tests
        env:
          CAPTCHAAI_API_KEY: ${{ secrets.CAPTCHAAI_API_KEY }}
        run: pytest tests/ -m captcha -v

Sık karşılaşılan sorunlar ve çözümleri

Hattı kurarken en çok karşılaşılan dört durum ve pratik çözümleri aşağıda. Çoğu hata, çözümün kendisinden değil, test ortamının yapılandırmasından kaynaklanır.

Sorun Neden Çözüm
Token enjeksiyonu başarısız oluyor g-recaptcha-response alanı bulunamadı Öğe kimliğini doğrulayın veya querySelector('[name="g-recaptcha-response"]') kullanın
Testler yerelde geçiyor, CI'da başarısız oluyor Chrome sürümü farklı CI kurulumunda Chrome sürümünü sabitleyin
Staging'de CAPTCHA görünmüyor Staging ortamı CAPTCHA'yı devre dışı bırakıyor QA ortamının yapılandırmasında CAPTCHA'yı etkinleştirin
Çözümü beklerken zaman aşımı CI ağı yavaş Sorgulama zaman aşımını 180 saniyeye çıkarın

Sık sorulan sorular

Bu test hattı hangi CAPTCHA türlerini çözer?

Örnek kod userrecaptcha yöntemiyle reCAPTCHA v2 çözer. Aynı yardımcı sınıf, yöntemi ve okunan alan adını değiştirerek reCAPTCHA v3, Cloudflare Turnstile ve GeeTest (v3) için de kullanılır. hCaptcha ve FunCaptcha desteklenmez; test hattınızı bu türlere dayandırmayın.

QA ekibi için hangi CaptchaAI planı yeterli?

Çoğu test paketi düşük hacimlidir; BASIC ($15/ay, 5 thread) genelde yeterli olur. Testleri paralel koşturuyorsanız STANDARD ($30/ay, 15 thread) eşzamanlılık payı bırakır. Fiyatlandırma thread bazlıdır: bir plan içinde çözüm sayısı sınırsızdır, yani aylık gideriniz USD olarak öngörülebilir kalır.

Birim testlerinde CAPTCHA çözümünü mock'layabilir miyim?

Evet. Birim testlerinde CaptchaTestHelper.solve_recaptcha metodunu mock'layın ve gerçek çözümleri yalnızca uçtan uca (E2E) entegrasyon testlerine bırakın. Böylece hem hızı hem de gideri kontrol edersiniz.

CAPTCHA testlerini yerelde çalıştırmadan geçmek istiyorum, nasıl yaparım?

pytest -m "not captcha" komutu @pytest.mark.captcha ile işaretlenmiş testleri hariç tutar. Hızlı yerel koşularda CAPTCHA adımlarını devre dışı bırakmanın en temiz yolu budur.

Aynı hattı reCAPTCHA v3 veya Turnstile için kullanabilir miyim?

Evet. İskelet aynı kalır — yalnızca çözüm çağrısının method parametresini ilgili türe göre değiştirir ve token'ı o türün beklediği alana enjekte edersiniz. Giriş testindeki reCAPTCHA v2 mantığını şablon olarak kullanabilirsiniz.


İlgili rehberler


CAPTCHA'lar test paketinizi durdurmasın — CaptchaAI ile başlayın.

Bu makale için yorumlar devre dışı bırakılmıştır.