Cypress ile CAPTCHA korumalı bir formu test etmenin en temiz yolu, korumayı kapatmak değil; gerçek bir token'ı CaptchaAI API'siyle üretip forma enjekte etmektir. Böylece testiniz kullanıcının gördüğü akışın aynısını çalıştırır: aynı reCAPTCHA v2 alanı, aynı Turnstile widget'ı, aynı gönderim isteği. Bu yazıda Cypress görev tanımından özel komutlara ve CI/CD adımına kadar tam çalışan bir kurulum kuruyoruz; kod bloklarının hiçbiri sahte değil, doğrudan kopyalayıp kullanabilirsiniz.
CAPTCHA'yı yalnızca test ortamında devre dışı bırakan ekipler bir tuzağa düşer: uygulama üretimde CAPTCHA'lı çalışır, testler ise CAPTCHA'sız. Bu fark — ortam kayması — token enjeksiyonu, callback tetikleme ve form gönderimi gibi tam da en kırılgan yerlerde gizli hatalar bırakır. CaptchaAI, Cypress testlerinizin bu adımların gerçeğini çalıştırmasını sağlar.
Cypress testlerinde CAPTCHA neden gerçek token ile çözülmeli?
CAPTCHA'yı test için kapatmanın üç yaygın yolu var ve üçü de üretim davranışını gizler. Aşağıdaki tablo neyi kaybettiğinizi özetliyor:
| Yaklaşım | Risk |
|---|---|
| Hazırlamada CAPTCHA'yı devre dışı bırakın | Entegrasyon hatalarını ve form akışı farklılıklarını gözden kaçırır |
| Test anahtarlarını kullanın (her zaman başarılı) | Token enjeksiyonunu ve callback işlemlerini test etmez |
| CaptchaAI ile çözün | Tam üretim eşitliği testi |
Google'ın "always-pass" test sitekey'i CAPTCHA'yı gösterir ama hiçbir zaman gerçek bir token doğrulaması yapmaz; yani g-recaptcha-response alanının backend tarafında nasıl işlendiğini test etmez. CaptchaAI ile çözdüğünüzde ise gerçek token'ı alır, DOM'a enjekte eder ve sunucunuzun onu kabul edip etmediğini gerçekten doğrularsınız.
Kurulum: Cypress ve CaptchaAI görev tanımı
Önce Cypress'i geliştirme bağımlılığı olarak ekleyin:
npm install cypress --save-dev
Cypress yapılandırması
Cypress'te ağ çağrıları içeren işleri tarayıcıda değil, Node tarafında task olarak çalıştırırsınız. CAPTCHA çözümü bir HTTP isteği olduğu için solveCaptcha görevini burada tanımlıyoruz. Zaman aşımlarını 120 saniyeye çekmemizin nedeni, çözümün tipik olarak 15–30 saniye sürmesi ve varsayılan Cypress zaman aşımının bunun için fazla kısa olmasıdır. API anahtarınızı sabit yazmak yerine env üzerinden okuyun.
// cypress.config.js
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
baseUrl: "https://your-app.com",
defaultCommandTimeout: 120000,
responseTimeout: 120000,
setupNodeEvents(on, config) {
on("task", {
solveCaptcha({ siteUrl, sitekey, type }) {
return solveCaptchaTask(siteUrl, sitekey, type);
},
});
return config;
},
},
env: {
CAPTCHAAI_KEY: "YOUR_API_KEY",
},
});
CaptchaAI çözüm görevini yazma
Görev işleyicisi CaptchaAI'nin çözüm akışını uygular: görevi in.php uç noktasına gönderir, sonra res.php'yi CAPCHA_NOT_READY yanıtı bitene kadar periyodik olarak sorgular. reCAPTCHA için method userrecaptcha ve parametre googlekey; Turnstile için method turnstile ve parametre sitekey olur. Bu ayrım kritiktir — iki tür farklı alan adı bekler ve karıştırırsanız gönderim reddedilir.
// cypress/plugins/captcha-solver.js
const https = require("https");
function httpPost(url, data) {
return new Promise((resolve, reject) => {
const params = new URLSearchParams(data).toString();
const options = {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
};
const req = https.request(url, options, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
});
req.on("error", reject);
req.write(params);
req.end();
});
}
function httpGet(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
}).on("error", reject);
});
}
async function solveCaptchaTask(siteUrl, sitekey, type = "recaptcha_v2") {
const API = "https://ocr.captchaai.com";
const key = process.env.CAPTCHAAI_KEY || "YOUR_API_KEY";
const submitData = {
key,
pageurl: siteUrl,
json: "1",
};
if (type === "turnstile") {
submitData.method = "turnstile";
submitData.sitekey = sitekey;
} else {
submitData.method = "userrecaptcha";
submitData.googlekey = sitekey;
}
const submitResp = await httpPost(`${API}/in.php`, submitData);
if (submitResp.status !== 1) {
throw new Error(`Submit failed: ${submitResp.request}`);
}
const taskId = submitResp.request;
// Poll for result
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const params = new URLSearchParams({
key,
action: "get",
id: taskId,
json: "1",
});
const result = await httpGet(`${API}/res.php?${params}`);
if (result.request === "CAPCHA_NOT_READY") continue;
if (result.status !== 1) throw new Error(`Solve failed: ${result.request}`);
return result.request; // The CAPTCHA token
}
throw new Error("CAPTCHA solve timeout");
}
module.exports = { solveCaptchaTask };
cypress.config.js'ye bağlama
Görev işleyicisini içe aktarıp setupNodeEvents içindeki task kaydına bağlayın. Bu adımdan sonra cy.task("solveCaptcha", ...) her test dosyasından çağrılabilir hale gelir.
// cypress.config.js
const { solveCaptchaTask } = require("./cypress/plugins/captcha-solver");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on("task", {
solveCaptcha({ siteUrl, sitekey, type }) {
return solveCaptchaTask(siteUrl, sitekey, type);
},
});
},
},
});
reCAPTCHA ve Turnstile için özel Cypress komutları
Token'ı alıp DOM'a enjekte etme mantığını her testte tekrarlamak yerine, tek seferlik özel komutlara taşıyın. solveCaptcha komutu sayfadaki data-sitekey değerini okur, görevi çağırır, dönen token'ı #g-recaptcha-response alanına ve tüm gizli yanıt alanlarına yazar, ardından varsa ___grecaptcha_cfg üzerinden callback'i tetikler. Bu son adım önemlidir: birçok form token'ı yalnızca callback çalıştığında geçerli sayar.
// cypress/support/commands.js
Cypress.Commands.add("solveCaptcha", (options = {}) => {
cy.get("[data-sitekey]", { timeout: 10000 }).then(($el) => {
const sitekey = options.sitekey || $el.attr("data-sitekey");
const siteUrl = options.siteUrl || cy.url();
cy.url().then((url) => {
cy.task("solveCaptcha", {
siteUrl: url,
sitekey,
type: options.type || "recaptcha_v2",
}).then((token) => {
// Inject token
cy.window().then((win) => {
const responseEl = win.document.querySelector(
"#g-recaptcha-response"
);
if (responseEl) {
responseEl.value = token;
}
// Set all hidden response fields
win.document
.querySelectorAll('[name="g-recaptcha-response"]')
.forEach((el) => {
el.value = token;
});
// Trigger callback if exists
if (win.___grecaptcha_cfg) {
const clients = win.___grecaptcha_cfg.clients;
for (const key in clients) {
const client = clients[key];
if (client && typeof client.callback === "function") {
client.callback(token);
}
}
}
});
});
});
});
});
Cypress.Commands.add("solveTurnstile", (options = {}) => {
cy.get("[data-sitekey]", { timeout: 10000 }).then(($el) => {
const sitekey = options.sitekey || $el.attr("data-sitekey");
cy.url().then((url) => {
cy.task("solveCaptcha", {
siteUrl: url,
sitekey,
type: "turnstile",
}).then((token) => {
cy.window().then((win) => {
const input = win.document.querySelector(
'input[name="cf-turnstile-response"]'
);
if (input) input.value = token;
});
});
});
});
});
Turnstile için ayrı bir komut kullanıyoruz çünkü token farklı bir alana yazılır: cf-turnstile-response. Alan adlarını türe göre sabit tutun; reCAPTCHA alanına Turnstile token'ı yazmak sessizce başarısız olur.
E2E test örnekleri: giriş, kayıt ve ödeme akışları
Aşağıdaki üç senaryo, gerçek bir uygulamada en sık CAPTCHA ile korunan akışları temsil eder. Her testte önce alanlar doldurulur, sonra cy.solveCaptcha() veya cy.solveTurnstile() çağrılır, en son gönderim yapılır — sıralama önemlidir, çünkü token gönderimden hemen önce üretilmelidir.
reCAPTCHA korumalı giriş akışı
// cypress/e2e/login.cy.js
describe("Login with reCAPTCHA", () => {
it("should log in through a CAPTCHA-protected form", () => {
cy.visit("/login");
cy.get("#username").type("testuser");
cy.get("#password").type("securepassword123");
// Solve the CAPTCHA
cy.solveCaptcha();
// Submit
cy.get('button[type="submit"]').click();
// Verify login success
cy.url().should("include", "/dashboard");
cy.get(".welcome-message").should("contain", "Welcome, testuser");
});
});
Kayıt formu akışı
// cypress/e2e/register.cy.js
describe("Registration with CAPTCHA", () => {
it("completes registration with all fields + CAPTCHA", () => {
cy.visit("/register");
cy.get("#first-name").type("Test");
cy.get("#last-name").type("User");
cy.get("#email").type("test@example.com");
cy.get("#password").type("StrongPass!123");
cy.get("#confirm-password").type("StrongPass!123");
cy.solveCaptcha();
cy.get("#register-btn").click();
cy.url().should("include", "/verify-email");
});
});
Turnstile korumalı ödeme akışı
describe("Checkout with Turnstile", () => {
it("processes payment through Turnstile-protected checkout", () => {
cy.visit("/cart");
cy.get(".checkout-btn").click();
cy.get("#card-number").type("4242424242424242");
cy.get("#expiry").type("12/26");
cy.get("#cvc").type("123");
cy.solveTurnstile();
cy.get("#pay-now").click();
cy.get(".confirmation").should("contain", "Order confirmed");
});
});
Türkiye'deki e-ticaret ve fintech ekipleri için en kritik regresyon noktası genellikle bu ödeme akışıdır: Turnstile ile korunan bir ödeme sayfasında token doğru enjekte edilmezse sipariş oluşmaz ve hata yalnızca canlıda görünür. Test verisi olarak test@example.com, +90 formatlı sahte telefon ve tr-TR locale gibi sentetik değerler kullanın — gerçek müşteri verisi asla test paketine girmemelidir. Bu, KVKK açısından da doğru yaklaşımdır: kişisel veri barındıran senaryoları yalnızca yetkili QA ortamınızda ve uydurma verilerle çalıştırın.
Yeniden deneme ve hata yönetimi
Ağ dalgalanmaları veya geçici zaman aşımları nedeniyle tek bir çözüm denemesi bazen boş dönebilir. Aşağıdaki komut, token gelene kadar sınırlı sayıda yeniden deneme yapar ve her denemeyi loglar; böylece CI çıktısında hangi denemede başarılı olduğunuzu görürsünüz.
// cypress/support/commands.js
Cypress.Commands.add("solveCaptchaWithRetry", (options = {}) => {
const maxRetries = options.retries || 3;
function attempt(retryCount) {
return cy.task("solveCaptcha", {
siteUrl: options.siteUrl,
sitekey: options.sitekey,
type: options.type || "recaptcha_v2",
}).then((token) => {
if (!token && retryCount < maxRetries) {
cy.log(`CAPTCHA retry ${retryCount + 1}/${maxRetries}`);
cy.wait(2000);
return attempt(retryCount + 1);
}
return token;
});
}
return attempt(0);
});
CI/CD entegrasyonu: GitHub Actions ve Jest
Testleri yerelde geçirmek yeterli değil; asıl değer, her push ve pull request'te otomatik çalışmalarıdır. API anahtarını depoya yazmayın — CI gizli dizisi (secrets.CAPTCHAAI_KEY) olarak saklayın ve ortam değişkeni olarak geçirin.
GitHub Actions iş akışı
name: E2E Tests
on: [push, pull_request]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Run Cypress tests
uses: cypress-io/github-action@v6
env:
CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
with:
wait-on: "http://localhost:3000"
start: npm start
Jest ile API seviyesinde doğrulama
Cypress dışında API katmanında hızlı bir sağlık kontrolü isteyen ekipler, aynı görev işleyicisini Jest ile de çağırabilir. Bu test, çözüm görevinin geçerli bir token döndürdüğünü doğrular.
// For teams that also use Jest for API-level CAPTCHA tests
const { solveCaptchaTask } = require("../cypress/plugins/captcha-solver");
test("CaptchaAI solves reCAPTCHA v2", async () => {
const token = await solveCaptchaTask(
"https://www.google.com/recaptcha/api2/demo",
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"recaptcha_v2"
);
expect(token).toBeDefined();
expect(token.length).toBeGreaterThan(50);
}, 120000);
CI'da eşzamanlılık planlıyorsanız, CaptchaAI thread tabanlı çalışır: her aktif thread bir çözümü paralel yürütür. Küçük bir test paketi için BASIC ($15/ay, 5 thread) genellikle yeterlidir; USD üzerinden sabit aylık fiyat, TL dalgalanmasından etkilenmeyen öngörülebilir bir CI maliyeti anlamına gelir. Cypress Cloud ile paralelleştirdiğinizde her makine aynı API anahtarını çağırır ve thread havuzunuzu paylaşır.
Sık karşılaşılan sorunlar
| Sorun | Sebep | Düzeltme |
|---|---|---|
cy.task timed out |
CAPTCHA çözümü çok uzun sürdü | Yapılandırmada taskTimeout değerini artırın |
| Token reddedildi | Enjeksiyondan önce süresi doldu | Çözme ve gönderme arasındaki gecikmeyi azaltın |
data-sitekey bulunamadı |
CAPTCHA dinamik olarak yükleniyor | Açıkça cy.wait() ekleyin veya isteği yakalayın |
| callback tetiklenmedi | Özel callback adı kullanılıyor | DevTools'ta ___grecaptcha_cfg'yi inceleyin |
| CI başarısız, yerelde geçiyor | Ortam değişkeni eksik | CI gizli dizilerine CAPTCHAAI_KEY ekleyin |
Sık sorulan sorular
Cypress testinde bir CAPTCHA çözmek ne kadar sürer?
reCAPTCHA v2 ve Turnstile için tipik olarak 15–30 saniye. Bu süre test paketini yavaşlatabileceğinden, CAPTCHA içeren testleri ayrı bir pakette toplayın veya Cypress Cloud ile paralelleştirin.
Test anahtarı (always-pass sitekey) kullanmak yeterli değil mi?
Hayır. Test anahtarı CAPTCHA'yı gösterir ama gerçek bir token doğrulaması yapmaz; backend'in token'ı nasıl işlediğini test etmez. Üretim eşitliği için gerçek token gerekir.
CaptchaAI hangi CAPTCHA türlerini Cypress ile çözebilir?
reCAPTCHA v2/v3, Cloudflare Turnstile ve Challenge, GeeTest v3 ile image/OCR türleri desteklenir. hCaptcha ve FunCaptcha desteklenmez; bu türleri koruyan formları bu akışla test edemezsiniz.
CI'da CaptchaAI için kaç thread gerekir?
Eşzamanlı çözüm sayısı kadar. Sıralı çalışan küçük bir pakette 5 thread (BASIC, $15/ay) yeterlidir; Cypress Cloud ile birçok makineyi paralel çalıştırıyorsanız thread sayısını buna göre planlayın.
reCAPTCHA v3 skorlu formları da test edebilir miyim?
Evet, reCAPTCHA v3 desteklenir. Ancak v3 bir kutucuk göstermez; token'ı görünmez alana enjekte edip gönderim isteğinde iletmeniz gerekir. Skor eşiği backend'de belirlendiği için düşük skorlu senaryoları ayrıca test edin.