Use Cases

Sürekli Entegrasyon Testinde CAPTCHA Kullanımı

Yeşil bir pipeline'ın kırmızıya dönmesinin en sinir bozucu sebeplerinden biri, testin gerçek bir hata bulması değil; login veya form sayfasındaki bir CAPTCHA'nın otomasyonu tamamen durdurmasıdır. Çözüm göründüğü kadar basit: CAPTCHA'yı test kodunuzun içinden bir API çağrısıyla çözün, API anahtarını CI sırrı olarak saklayın ve uçtan uca (E2E) test paketiniz hiçbir manuel müdahale olmadan sonuna kadar aksın.

Bu rehber, reCAPTCHA v2 ve Cloudflare Turnstile korumalı sayfaları pytest + Selenium ile test eden, GitHub Actions, GitLab CI ve Jenkins üzerinde çalışan bir kurulumu adım adım gösterir. Kod örnekleri kopyala-yapıştır ile çalışacak şekilde tamdır.


CI testlerini CAPTCHA neden durdurur?

CI/CD işlem hatları tanım gereği otomatik ve insansız çalışır. CAPTCHA ise tanım gereği insan etkileşimi bekler. Bu iki gerçek çakıştığı anda, E2E testiniz korumalı sayfaya her ulaştığında sessizce ya da gürültülü biçimde başarısız olur — ve ekip zamanla o testleri "flaky" diye devre dışı bırakır.

Kalıcı çözüm şu: CAPTCHA doğrulamasını test paketinizin içinden CaptchaAI API'sine devredin. API anahtarı sabit kodlanmaz; CI platformunuzun gizli dizi yönetiminde tutulur. Test çalışırken CAPTCHA'yı programatik olarak çözer, dönen token'ı sayfadaki gizli alana enjekte eder ve akışa kaldığı yerden devam eder. Böylece login, kayıt ve ödeme adımı gibi kritik akışları her gece gerçek koruma altında doğrulayabilirsiniz.

Türkiye'deki e-ticaret ve fintech ekiplerinde bu senaryo çok yaygındır: ödeme akışının (checkout) her deploy öncesi otomatik test edilmesi gerekir, ancak form CAPTCHA ile korunur. CI hattına CAPTCHA çözümü eklendiğinde, bu regresyon testleri manuel QA'ya bağımlı olmaktan çıkar.


Mimari: CAPTCHA çözümü işlem hattına nasıl oturur?

Akış tek yönlü ve öngörülebilirdir. Git push CI runner'ı tetikler, runner headless Chrome ile E2E testlerini başlatır, testler CAPTCHA'ya geldiğinde CaptchaAI API'sini çağırır ve sonuç rapora yazılır:

┌──────────────┐     ┌──────────────┐     ┌────────────┐     ┌──────────────┐
│ Git Push     │────▶│ CI Runner    │────▶│ E2E Tests  │────▶│ Test Report  │
│              │     │ (headless    │     │ + CAPTCHA  │     │              │
│              │     │  Chrome)     │     │ solving    │     │              │
└──────────────┘     └──────────────┘     └────────────┘     └──────────────┘
                                                │
                                                ▼
                                         ┌────────────┐
                                         │ CaptchaAI  │
                                         │ API        │
                                         └────────────┘

CI için CAPTCHA çözüm yardımcısı

Önce tekrar kullanılabilir bir yardımcı sınıf yazalım. in.php uç noktasına görevi gönderir, ardından res.php'yi sonuç hazır olana kadar periyodik olarak sorgular. API anahtarını yalnızca ortam değişkeninden okur — böylece testlerde asla sabit kodlanmaz:

import os
import time
import requests


class CICaptchaSolver:
    """CAPTCHA solver designed for CI environments."""
    BASE = "https://ocr.captchaai.com"

    def __init__(self):
        self.api_key = os.environ.get("CAPTCHAAI_API_KEY")
        if not self.api_key:
            raise EnvironmentError("CAPTCHAAI_API_KEY not set")

    def solve(self, params, initial_wait=10, timeout=120):
        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(f"CAPTCHA submit failed: {resp['request']}")

        task_id = resp["request"]
        time.sleep(initial_wait)
        deadline = time.time() + timeout

        while time.time() < deadline:
            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(f"CAPTCHA solve failed: {result['request']}")

        raise TimeoutError("CAPTCHA solve timed out in CI")

    def solve_recaptcha(self, sitekey, pageurl):
        return self.solve({
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
        })

    def solve_turnstile(self, sitekey, pageurl):
        return self.solve({
            "method": "turnstile",
            "sitekey": sitekey,
            "pageurl": pageurl,
        })

CaptchaAI'nin reCAPTCHA v2 ve Cloudflare Turnstile'ı aynı API üzerinden desteklediğini unutmayın; solve_recaptcha ve solve_turnstile yalnızca farklı method değerleriyle aynı akışı kullanır.


pytest ile entegrasyon

Yardımcıyı bir fixture haline getirin ki her test aynı çözücüyü ve temiz bir headless tarayıcı örneğini paylaşabilsin.

conftest.py

captcha_solver oturum kapsamında bir kez oluşturulur; browser fixture'ı ise her test için yeni bir headless Chrome açar ve sonunda kapatır:

import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


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


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

Test dosyası

Aşağıdaki test sınıfları iki tipik akışı doğrular: reCAPTCHA v2 ile korunan bir login sayfası ve Turnstile ile korunan bir iletişim formu. Çözülen token, sayfadaki g-recaptcha-response ya da cf-turnstile-response alanına enjekte edilir:

import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class TestLoginFlow:
    SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
    LOGIN_URL = "https://staging.example.com/qa-login"

    def test_login_with_captcha(self, browser, captcha_solver):
        browser.get(self.LOGIN_URL)

        # Fill credentials
        browser.find_element(By.ID, "username").send_keys("testuser")
        browser.find_element(By.ID, "password").send_keys("testpass123")

        # Solve CAPTCHA
        token = captcha_solver.solve_recaptcha(self.SITEKEY, self.LOGIN_URL)
        browser.execute_script(
            f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
        )

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

        # Verify login success
        assert "dashboard" in browser.current_url.lower()

    def test_login_wrong_password(self, browser, captcha_solver):
        browser.get(self.LOGIN_URL)
        browser.find_element(By.ID, "username").send_keys("testuser")
        browser.find_element(By.ID, "password").send_keys("wrongpass")

        token = captcha_solver.solve_recaptcha(self.SITEKEY, self.LOGIN_URL)
        browser.execute_script(
            f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
        )

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

        error = browser.find_element(By.CSS_SELECTOR, ".error-message")
        assert error.is_displayed()


class TestContactForm:
    SITEKEY = "0x4AAAA..."
    FORM_URL = "https://staging.example.com/contact"

    def test_contact_form_submission(self, browser, captcha_solver):
        browser.get(self.FORM_URL)

        browser.find_element(By.ID, "name").send_keys("CI Test")
        browser.find_element(By.ID, "email").send_keys("ci@test.com")
        browser.find_element(By.ID, "message").send_keys("Automated CI test")

        token = captcha_solver.solve_turnstile(self.SITEKEY, self.FORM_URL)
        browser.execute_script(
            f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
        )

        browser.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".success-message"))
        )

GitHub Actions iş akışı

Bu iş akışı main ve staging dallarına push ile pull request'lerde tetiklenir. Python ve Chrome kurar, testleri çalıştırır ve HTML raporunu her durumda artefakt olarak yükler. Kritik satır CAPTCHAAI_API_KEY değerini bir GitHub Secret'tan okuyan env bloğudur:

name: E2E Tests with CAPTCHA

on:
  push:
    branches: [main, staging]
  pull_request:
    branches: [main]

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

    steps:

      - uses: actions/checkout@v4

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

      - name: Install Chrome
        uses: browser-actions/setup-chrome@v1
        with:
          chrome-version: stable

      - name: Install ChromeDriver
        uses: nanasess/setup-chromedriver@v2

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

      - name: Run E2E tests
        env:
          CAPTCHAAI_API_KEY: ${{ secrets.CAPTCHAAI_API_KEY }}
        run: |
          pytest tests/e2e/ -v --html=report.html --self-contained-html

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: e2e-report
          path: report.html

GitLab CI yapılandırması

GitLab tarafında aynı mantığı selenium/standalone-chrome servisiyle kurarsınız. API anahtarını proje Settings → CI/CD → Variables altında tanımlayın; anahtar otomatik olarak ortam değişkeni olarak enjekte edilir:

e2e_tests:
  stage: test
  image: python:3.11
  services:

    - selenium/standalone-chrome:latest
  variables:
    SELENIUM_REMOTE_URL: "http://selenium__standalone-chrome:4444/wd/hub"
  script:

    - pip install selenium requests pytest
    - pytest tests/e2e/ -v
  artifacts:
    when: always
    reports:
      junit: report.xml

Jenkins pipeline

Jenkins kullanan ekiplerde API anahtarını Credentials deposunda saklayıp credentials() yardımıyla ortam değişkenine bağlayın. Böylece anahtar log'larda görünmez ve pipeline betiğinde açık yazılmaz:

pipeline {
    agent any
    environment {
        CAPTCHAAI_API_KEY = credentials('captchaai-api-key')
    }
    stages {
        stage('Setup') {
            steps {
                sh 'pip install selenium requests pytest'
            }
        }
        stage('E2E Tests') {
            steps {
                sh 'pytest tests/e2e/ -v --junitxml=results.xml'
            }
        }
    }
    post {
        always {
            junit 'results.xml'
        }
    }
}

CI maliyetini kontrol altında tutmak

CaptchaAI thread tabanlı çalışır: her plan belirli sayıda eşzamanlı thread verir ve thread başına sınırsız çözüm içerir; çözüm başına ücret veya günlük tavan yoktur. CI için bu ideal bir modeldir, çünkü maliyet toplam çözüm sayısına değil, aynı anda kaç testin CAPTCHA çözdüğüne bağlıdır. Küçük ve orta ölçekli test paketleri için BASIC ($15/ay, 5 thread) genellikle yeterlidir; TL yerine sabit USD fiyatlandırma, kur oynaklığından etkilenmeyen öngörülebilir bir bütçe anlamına gelir.

Yine de gereksiz çözümleri elemek maliyeti düşürür ve pipeline'ı hızlandırır.

Yalnızca gerektiğinde çözün

CAPTCHA testlerini her PR'da değil, yalnızca anahtar mevcutken ve açıkça devre dışı bırakılmamışken çalıştırın:

import os

def should_run_captcha_tests():
    """Skip CAPTCHA tests in certain environments."""
    if os.environ.get("SKIP_CAPTCHA_TESTS"):
        return False
    if not os.environ.get("CAPTCHAAI_API_KEY"):
        return False
    return True


# In test
import pytest

@pytest.mark.skipif(
    not should_run_captcha_tests(),
    reason="CAPTCHA tests disabled or API key not set"
)
class TestWithCaptcha:
    def test_login(self, browser, captcha_solver):
        pass

Paketi çalıştırmadan önce bakiye kontrolü

Paket başında bakiyeyi bir kez sorgulayın; düşükse testleri baştan atlayın ki yarıda kalan bir çalıştırmayla vakit kaybetmeyin:

@pytest.fixture(scope="session", autouse=True)
def check_captcha_balance(captcha_solver):
    import requests
    resp = requests.get(
        f"{captcha_solver.BASE}/res.php",
        params={"key": captcha_solver.api_key, "action": "getbalance"},
    )
    balance = float(resp.text)
    if balance < 0.50:
        pytest.skip(f"CaptchaAI balance too low: ${balance:.2f}")

Sorun giderme

CI'da en sık karşılaşılan tökezleme noktaları ve hızlı çözümleri:

Sorun Sebep Düzeltme
CAPTCHAAI_API_KEY not set Gizli kod yapılandırılmadı CI gizli dizilerine anahtar ekleyin
Chrome CI'da çöküyor --no-sandbox bayrağı eksik Headless Chrome bayrakları ekleyin
Testler yerel olarak geçiyor, CI'da başarısız oluyor Farklı tarayıcı sürümü Chrome sürümünü CI'ya sabitleme
CAPTCHA zaman aşımına uğradı CI ağı yavaş timeout parametresini artırın
Testler pahalı Çalıştırma başına çok fazla CAPTCHA çözümü var PR yapıları için SKIP_CAPTCHA_TESTS kullanın

Sık sorulan sorular

CAPTCHA çözümü E2E testlerimi ne kadar yavaşlatır?

CAPTCHA çözümü, ağ üzerinden bir API çağrısı olduğu için testinize birkaç saniyelik ek süre ekler. Bu yüzden çözümü her adımda değil yalnızca gerçekten CAPTCHA'lı sayfalarda çağırın ve initial_wait ile timeout değerlerini CI ağınızın hızına göre ayarlayın.

Staging ve production için farklı sitekey kullanıyorsam ne yapmalıyım?

Sitekey'i test koduna sabit yazmak yerine ortam değişkeninden okuyun. Her ortamın kendi sitekey ve pageurl değerini CI değişkeni olarak tanımlayın; böylece aynı test paketi staging'de de production benzeri ortamda da değişmeden çalışır.

CI testlerim için hangi CaptchaAI planı yeterli olur?

Çoğu ekip için başlangıç noktası BASIC ($15/ay, 5 thread) planıdır. Belirleyici olan toplam test sayısı değil, aynı anda kaç işin CAPTCHA çözdüğüdür; paralel test yürütmeyi artırdıkça daha yüksek thread sayılı bir plana geçmeniz gerekebilir.

reCAPTCHA v2 ve Turnstile'ı aynı pakette çözebilir miyim?

Evet. Her ikisi de aynı API üzerinden desteklenir; kod örneğindeki solve_recaptcha ve solve_turnstile yalnızca method değeriyle ayrışır. Login akışını reCAPTCHA v2, iletişim formunu Turnstile ile aynı test paketinde doğrulayabilirsiniz.


İlgili kılavuzlar


E2E testlerinizdeki CAPTCHA'ları otomatik çözün — CaptchaAI ile başlayın.

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