Bukan bug di kode Anda. Kalau end-to-end test hijau di laptop tapi merah terus di GitHub Actions, GitLab CI, atau Jenkins, penyebabnya biasanya CAPTCHA di halaman yang diuji — reCAPTCHA v2 atau Cloudflare Turnstile yang tidak bisa diselesaikan otomatis oleh Chrome headless.
Solusinya bukan mematikan CAPTCHA di staging — itu menghapus hal yang mau divalidasi — melainkan menyisipkan API CaptchaAI ke test suite sebagai satu langkah biasa: kirim sitekey, tunggu token, isi field tersembunyi, submit. Kunci API disimpan sebagai secret CI, dan prosesnya berjalan tanpa interaksi manusia.
Yang Anda dapat dari panduan ini:
- Class solver Python siap pakai, khusus untuk lingkungan CI
- Konfigurasi kerja untuk GitHub Actions, GitLab CI, dan Jenkins
- Strategi menekan biaya thread dan menghindari test yang gagal karena saldo habis
Kenapa CAPTCHA Jadi Titik Gagal di Pipeline CI/CD
Pipeline CI/CD berjalan otomatis tanpa operator yang mengawasi. CAPTCHA didesain untuk hal sebaliknya: menahan trafik sampai ada bukti interaksi manusia. Begitu Chrome headless di runner CI menyentuh reCAPTCHA atau Turnstile tanpa solver, test langsung timeout atau gagal — bukan karena logic-nya salah, tapi karena tidak ada yang menyelesaikan tantangannya.
Ini kerap dialami tim QA otomasi Indonesia yang mengelola pipeline untuk beberapa klien sekaligus — misalnya agensi freelance yang menjalankan regression test malam hari untuk beberapa toko e-commerce. Kalau setiap CAPTCHA bikin build merah, tim jadi terbiasa mengabaikan notifikasi CI, dan bug asli ikut lolos tanpa ketahuan. Solver di test suite mengembalikan arti build merah ke tempatnya: ada yang benar-benar rusak, bukan CAPTCHA yang menghalangi.
Alur Kerja: Di Mana CaptchaAI Masuk ke Pipeline
Posisi CaptchaAI ada di antara CI runner dan test suite — bukan menggantikan Selenium atau Playwright, tapi mengisi satu langkah yang browser headless tidak bisa lakukan sendiri:
┌──────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐
│ Git Push │────▶│ CI Runner │────▶│ E2E Tests │────▶│ Test Report │
│ │ │ (headless │ │ + CAPTCHA │ │ │
│ │ │ Chrome) │ │ solving │ │ │
└──────────────┘ └──────────────┘ └────────────┘ └──────────────┘
│
▼
┌────────────┐
│ CaptchaAI │
│ API │
└────────────┘
Git push memicu CI runner menjalankan Chrome headless, test suite menyentuh halaman berCAPTCHA, memanggil API CaptchaAI untuk mendapatkan token, lalu melanjutkan assertion seperti biasa. Hasilnya masuk ke test report seperti langkah lain — tidak ada percabangan proses baru yang perlu dipelihara terpisah.
Pola ini juga berlaku untuk Playwright atau Cypress, bukan cuma Selenium — API CaptchaAI tidak terikat ke framework testing tertentu, selama browser yang dipakai bisa menjalankan JavaScript untuk menyisipkan token ke field CAPTCHA.
Class Solver Python untuk Lingkungan CI
CICaptchaSolver di bawah ini dirancang khusus untuk CI: membaca API key dari environment variable (bukan file config yang bisa lupa ter-commit), mengirim CAPTCHA, lalu polling res.php sampai token siap atau timeout.
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,
})
Nilai default initial_wait=10 dan timeout=120 sengaja longgar. Turnstile biasanya selesai di bawah 10 detik, reCAPTCHA v2 di bawah 60 detik. Timeout 120 detik memberi ruang tanpa membuat runner CI menganggur lama kalau solve gagal.
Menghubungkan Solver ke pytest
Dua berkas saja yang dibutuhkan: fixture di conftest.py untuk instantiate solver dan browser, lalu test case yang memanggilnya seperti fixture pytest lainnya.
conftest.py
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()
Berkas Test Login dan Form Kontak
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.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"))
)
Pola yang sama berlaku untuk form apa pun yang dilindungi CAPTCHA — ganti SITEKEY, URL, dan method: userrecaptcha untuk reCAPTCHA v2/v3, turnstile untuk Cloudflare Turnstile.
Memilih Platform: GitHub Actions, GitLab CI, atau Jenkins
Ketiga konfigurasi menjalankan test suite yang sama — bedanya cara masing-masing platform mendefinisikan job, service, dan secret:
- GitHub Actions — paling langsung kalau repo sudah ada di GitHub; runner
ubuntu-latestbawaan sudah cukup untuk Chrome headless. - GitLab CI — cocok kalau pipeline Anda sudah terbiasa pakai service container, seperti
selenium/standalone-chromedi bawah. - Jenkins — pilihan tim yang menjalankan CI on-premise atau runner self-hosted, termasuk yang ditaruh di AWS
ap-southeast-1/ap-southeast-3atau GCPasia-southeast2untuk menekan latensi.
Konfigurasi di bawah tetap berlaku untuk runner self-hosted — cukup arahkan ke runner label yang sesuai.
Integrasi dengan GitHub Actions
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
Konfigurasi Pipeline di GitLab CI
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
Pipeline Jenkins
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'
}
}
}
Mengelola Biaya Solve CAPTCHA di CI
CaptchaAI menagih per thread aktif, bukan per CAPTCHA yang diselesaikan — biaya tidak naik walau test suite menyelesaikan ratusan CAPTCHA per run, selama concurrency tidak melebihi jatah thread paket. Untuk tim kecil, paket BASIC ($15/bulan, 5 thread) biasanya cukup untuk nightly run tanpa paralel besar-besaran. Kalau memakai pytest-xdist dengan banyak worker, samakan jumlah thread paket dengan jumlah worker supaya tidak saling antre.
Dua taktik di bawah paling efektif menekan biaya tanpa mengorbankan cakupan test:
- Batasi kapan CAPTCHA benar-benar diselesaikan (bukan di setiap PR)
- Hentikan suite lebih awal kalau saldo sudah menipis, bukan membiarkannya gagal satu per satu
Kontrol Kapan CAPTCHA Diuji
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
Aktifkan SKIP_CAPTCHA_TESTS=1 di pipeline pull request agar review tetap cepat, lalu matikan flag itu di job yang jalan saat merge ke main atau terjadwal tiap malam — biaya thread hanya keluar saat perlu.
Cek Saldo Sebelum Test Suite Jalan
@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}")
Fixture autouse=True ini menghentikan run lebih awal kalau saldo tinggal recehan — lebih baik daripada suite berjalan penuh lalu gagal satu per satu karena submit CAPTCHA ditolak.
Pertanyaan Umum
Berapa Biaya Menjalankan CAPTCHA Solving di CI Setiap Bulan?
Tergantung jumlah thread yang dipakai bersamaan, bukan jumlah CAPTCHA yang diselesaikan. Paket BASIC ($15/bulan, 5 thread) sudah mencakup solve tanpa batas per thread — naikkan paket hanya kalau pytest-xdist butuh lebih banyak worker paralel.
Jenis CAPTCHA Apa Saja yang Bisa Diuji CaptchaAI di CI?
reCAPTCHA v2/v3, Cloudflare Turnstile dan Challenge, GeeTest v3, CAPTCHA gambar/grid, dan BLS sudah tersedia penuh, plus CaptchaFox, Friendly Captcha, dan Lemin dalam status beta. hCaptcha dan FunCaptcha belum didukung. GeeTest v4 masih berstatus segera hadir.
Perlu Menguji CAPTCHA di Setiap Pull Request atau Cukup Saat Merge?
Cukup saat merge ke branch utama atau lewat jadwal malam. Setiap PR memperlambat feedback loop dan menaikkan biaya thread tanpa manfaat sebanding — gunakan flag SKIP_CAPTCHA_TESTS untuk build PR.
Bagaimana Cara Menyimpan API Key CaptchaAI dengan Aman di CI?
Simpan di secret manager platform CI: GitHub Secrets, GitLab CI Variables, atau Jenkins Credentials. Jangan hardcode API key di kode atau file yang ikut ter-commit ke repository.
Kenapa Test yang Lolos di Laptop Bisa Gagal di CI karena CAPTCHA?
Biasanya versi Chrome/ChromeDriver di CI berbeda dari lokal, atau jaringan runner lebih lambat hingga token kedaluwarsa sebelum dipakai. Pin versi Chrome dan naikkan parameter timeout di solve() sebagai langkah pertama debugging.
Error yang Sering Muncul di CI dan Cara Mengatasinya
Error yang paling sering dilaporkan tim QA saat pertama kali menyambungkan CaptchaAI ke CI:
| Masalah | Penyebab | Solusi |
|---|---|---|
CAPTCHAAI_API_KEY not set |
Secret tidak dikonfigurasi | Tambahkan kunci ke secret CI |
| Chrome crash di CI | Flag --no-sandbox tidak ada |
Tambahkan flag headless Chrome |
| Test lulus lokal, gagal di CI | Versi browser berbeda | Pin versi Chrome di CI |
| Timeout CAPTCHA | Jaringan CI lambat | Naikkan parameter timeout |
| Test terlalu mahal | Terlalu banyak penyelesaian CAPTCHA per run | Gunakan SKIP_CAPTCHA_TESTS untuk PR build |
| Saldo habis di tengah run | Solve CAPTCHA terus-menerus tanpa cek saldo | Tambahkan fixture cek saldo sebelum suite jalan |
| Token ditolak begitu dipakai | pageurl saat submit beda dengan URL saat token dipakai |
Pastikan pageurl sama persis dengan URL halaman yang diuji |
Panduan Terkait
Sambungkan CaptchaAI ke pipeline CI Anda — mulai di sini.