Cypress Testlerinde cy.task ile CAPTCHA Nasıl Çözülür

Bir Cypress captcha sorunu, bir çözme sorunu olmadan önce bir çalışma zamanı sorunudur. Spec kodunuz test edilen tarayıcının içinde çalışır; dolayısıyla çözücüyle konuşan Node istemcisi orada yaşayamaz. Bunun yerine onu cypress.config.js içinde bir task olarak kaydedin, bu task'ı spec'ten çağırın ve token'ı gizli alana kendiniz yazın. Üç şey bunu çalışır kılar: task, yükseltilmiş bir zaman aşımı ve Cypress tıklaması yerine doğrudan bir DOM yazımı. Bu kılavuz üçünü de ele alıyor.
Neye ihtiyacınız var
- Cypress 10 veya daha yenisi; cypress.config.js ve setupNodeEvents bu sürümle geldi. Daha eski sürümler eski plugins dosyasını kullanır ve aynı fikir yine geçerlidir
- Node.js 18 veya daha yenisi
- CapSkip'in çalışıyor ve erişilebilir olması. Local mod, aynı makinedeki bir test çalıştırması için 127.0.0.1 üzerinde 8080 portunu dinler; Server mod ise ağınızı veya genel IP'nizi dinler, böylece bir CI runner'ı ya da başka bir makine ona çağrı yapabilir. Her ikisi de şurada anlatılıyor: bağlantı ayarları
- Dev bağımlılığı olarak kurulmuş çözücü istemcisi
# The client only ever runs in the Node half of Cypress. npm install --save-dev capskip
Çözüm işlemi neden spec içinde olamaz
Cypress iki sürece ayrılır ve saf yaklaşımın başarısız olmasının bütün nedeni budur. Spec dosyanız paketlenir ve uygulamanın yanında, tarayıcının içinde çalıştırılır. cypress.config.js içindeki her şey ise onun dışında, Node'da çalışır.
Bu nedenle çözücü istemcisini bir spec'in en üstünde require etmek, bir Node HTTP istemcisini tarayıcı paketinin içine çeker. Paketleyici buna izin verse bile tarayıcı çağrıyı engeller: uygulamanızın kaynağından 127.0.0.1 üzerindeki 8080 portuna yapılan bir istek çapraz kaynaklıdır ve çözücü bunu meşru kılacak CORS başlıklarını göndermez.
Cypress size Node'a açılan iki kapı sunar ve ikisi de uygundur:
- cy.task yapılandırmada kaydettiğiniz herhangi bir fonksiyonu çalıştırır. SDK'nın yeri burasıdır, çünkü yoklama ve geri çekilme mantığı böylece tasarlandığı yerde, Node içinde çalışır.
- cy.request HTTP çağrısını tarayıcı yerine Cypress Node sürecinden yapar; Cypress dokümantasyonunun onun CORS'u tamamen atladığını söylemesinin nedeni budur. Ham API'ye gitmeyi ve bağımlılıktan kurtulmayı tercih ediyorsanız iyi bir seçenek.
Adım 1: çözümü bir task olarak kaydedin
Tek bir fonksiyon, bir kez kaydedilir, her spec'te kullanılabilir.
// npm install --save-dev capskip
const { defineConfig } = require('cypress');
const { CapSkip } = require('capskip');
// Local mode. Point host at a server IP to share one solver.
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
module.exports = defineConfig({
// 60000 is the default, and a v2 solve can outlast it.
taskTimeout: 180000,
e2e: {
setupNodeEvents(on) {
on('task', {
async solveRecaptcha({ sitekey, url }) {
const result = await solver.recaptcha(sitekey, url);
return result.code; // the token
},
});
},
},
});Task'lar hakkında ilk seferde herkese bir saate mal olan bir kural: bir task ya bir değer ya da null döndürmelidir, asla undefined değil. Return ifadesini unutun, Cypress komutu task'ın undefined döndürdüğüne dair bir mesajla başarısız kılar; bu da çözücü hiç çağrılmamışken çözücü bozulmuş gibi okunur.
Adım 2: task'ı çağırın ve token'ı enjekte edin
Sitekey'i sayfadan okuyun, task'a verin ve cevabı widget'ın koyacağı yere koyun.
// cypress/e2e/login.cy.js
it('logs in through the reCAPTCHA', () => {
cy.visit('/login');
cy.get('[data-sitekey]')
.invoke('attr', 'data-sitekey')
.then((sitekey) => {
const url = 'https://example.com/login';
cy.task('solveRecaptcha', { sitekey, url }).then((token) => {
// The widget writes into a hidden textarea. Do the same.
cy.document().then((doc) => {
doc.getElementById('g-recaptcha-response').value = token;
});
});
});
cy.get('button[type=submit]').click();
cy.contains('Welcome back');
});DOM yazımına dikkat edin. Yanıt alanı gizli bir textarea'dır ve Cypress görünmez saydığı bir öğeye yazmayı reddeder; bu yüzden get ve type çağrılarıyla yazılan bariz sürüm, token'a hiç yaklaşamadan görünürlükte başarısız olur. cy.document üzerinden gitmek bunu aşar, tıpkı widget’ın kendi JavaScript'inin yapacağı gibi.
Widget div'i bir data-callback özniteliği taşıyorsa, gönder düğmesine tıklamak yerine o fonksiyonu token ile çağırın. Bu şekilde kurulan sayfalar normal bir form gönderimi bağlamaz, dolayısıyla tıklama hiçbir şey yapmaz.
Asıl canınızı yakan zaman aşımı taskTimeout
En sık çözücüye yüklenen hata budur ve düzeltmesi yapılandırmada tek satırdır.
Cypress bir task'a varsayılan olarak 60 saniye verir. Bir reCAPTCHA v2 işi ilk 15 ila 20 saniye boyunca hazır olmaz, v3 ise 10 ila 15 saniye sürer ve meşgul bir makine ikisini de uzatabilir. Üst sınıra ulaşıldığında Cypress komutu sonlandırır ve test, CAPTCHA'yı değil sizin task'ınızı adlandıran bir zaman aşımıyla başarısız olur.
Yanı başındaki tuzak, yanlış sayıyı yükseltmektir. Cypress zaman aşımı önerilerinin çoğu, 4000 milisaniye olan ve DOM komutlarını yöneten defaultCommandTimeout değerini işaret eder. Bunun bir task üzerinde hiçbir etkisi yoktur. Burada üç değer önemlidir ve üçü de birbirinden ayrıdır:
| Seçenek | Varsayılan | Şunlar için geçerli |
|---|---|---|
| taskTimeout | 60000 ms | cy.task, yani çözüm işlemi |
| responseTimeout | 30000 ms | cy.request, yani ham bir API çağrısı |
| defaultCommandTimeout | 4000 ms | DOM komutları, yukarıdakilerin ikisi de değil |
Yukarıdaki yapılandırmada olduğu gibi genel olarak ayarlayın ya da yalnızca tek bir testin bu alana ihtiyacı varsa çağrı başına ayarlayın:
// Same task, a longer leash for this one call.
cy.task('solveRecaptcha', { sitekey, url }, { timeout: 180000 });180 saniye makul bir üst sınırdır. Normal bir çözümün kabaca on katıdır ve bilinçli olarak SDK’nin kendi 300 saniyelik reCAPTCHA yoklama sınırının altında durur; böylece Cypress, hâlâ bekleyen bir istemcinin arkasında asılı kalmak yerine gerçekten takılmış bir testi başarısız kılar. İstemcinin önce pes etmesini tercih ederseniz, recaptchaTimeout değerini task zaman aşımınızın altına indirin.
Ya da SDK'yı atlayıp cy.request kullanın
API 2captcha uyumludur, bu yüzden iki çağrı bütün işi görür. cy.request Node'da çalıştığı için tarayıcının kaynak kuralları hiç devreye girmez.
// No task registration needed. Both calls happen in Node.
function pollForToken(id, tries = 20) {
return cy.request({
method: 'POST',
url: 'http://127.0.0.1:8080/res.php',
form: true,
body: { key: 'capskip', action: 'get', id },
}).then((res) => {
const text = res.body.trim();
if (text !== 'CAPCHA_NOT_READY') return text.replace('OK|', '');
if (tries === 0) throw new Error('gave up waiting for ' + id);
return cy.wait(5000).then(() => pollForToken(id, tries - 1));
});
}Aklınızda tutmanız gereken iki ayrıntı. res.php’den gelen düz metin yanıtı, başarı durumunda OK|TOKEN olur; iş hâlâ çalışırken ise yalnızca CAPCHA_NOT_READY dizesidir, ki bu bir hata değil bir durumdur. Ayrıca bir sonuç yalnızca bir kez okunabilir, bu yüzden iki kez sormak yerine geldiği anda saklayın. Her parametre ve her hata dizesi şurada listelenmiştir: API dokümantasyonu.
Çözücü yerinde kalırken testleri CI'da çalıştırmak
Dizüstünüzde geçen bir test paketinin ilk push'ta başarısız olduğu yer burasıdır. Bir GitHub Actions runner'ı, bir GitLab işi ya da bir Jenkins agent'ı kendi loopback adresine sahiptir ve orada 8080 portunu dinleyen hiçbir şey yoktur. Local mod, tanımı gereği makineye özeldir.
Cevap Server mod. CapSkip, loopback yerine ağınızı veya genel IP'nizi dinler ve yapılandırma adresi ortamdan okur.
// npm install --save-dev capskip
const { CapSkip } = require('capskip');
// Same client, different address. The spec never changes.
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST || '127.0.0.1',
port: Number(process.env.CAPSKIP_PORT || 8080),
});SDK, CAPSKIP_HOST ve CAPSKIP_PORT değerlerini kendi başına okur; dolayısıyla yukarıdaki yedek değer, bunlar olmadan başlayan bir runner için fazladan bir önlemdir. Çözücü makinesi için statik bir genel IP önerilir ve adımlar şurada: bağlantı ayarları. Bu hâlâ sizin donanımınızdır ve kullanım hâlâ ölçülmez: değişen tek şey, sürecin nerede dinlediğidir.
Sık görülen hatalar ve anlamları
| Belirti | Neden | Düzeltme |
|---|---|---|
| solveRecaptcha task'ı kaydedilmedi | Yanlış blokta kaydedilmiş ya da yapılandırma hiç export edilmemiş | e2e anahtarı için setupNodeEvents içinde kaydedin |
| Task'ınız için 60000 ms beklendikten sonra zaman aşımı | taskTimeout hâlâ varsayılan değerinde | Genel olarak ya da çağrı başına 180000'e yükseltin |
| Task undefined döndürdü | İşleyicide return ifadesi yok | Token'ı döndürün, hiçbir şey yoksa null döndürün |
| Öğe görünür değil, bu yüzden Cypress yazamıyor | Yanıt alanı gizli bir textarea | Değeri bunun yerine cy.document üzerinden yazın |
ERROR_GOOGLEKEY | Sitekey boştu ya da bir Turnstile widget'ına ait | Çözmeden önce özniteliği loglayın; Turnstile'ın kendi yöntemi var |
| Her testte NetworkException | O host ve portta dinleyen hiçbir şey yok | Local mod yalnızca loopback'tir; CI'dan Server modu kullanın |
| Yerelde yeşil, CI'da kırmızı | Runner, makinenizin loopback adresine erişemez | CAPSKIP_HOST değerini erişilebilir bir adrese yöneltin |
Sık sorulan sorular
Test ortamımda CAPTCHA'yı kapatsam olmaz mı?
Widget sizinse, evet. Staging derlemesinde bir özellik bayrağı ya da bir test sitekey'i, çözmekten daha ucuz ve daha hızlıdır ve test paketini deterministik tutar. Çözmek üç durumda hak ettiği yeri bulur: CAPTCHA başkasına aitse, staging'in üretimi birebir yansıtması gerekiyorsa ya da test edilen şeyin kendisi doğrulama yolu ise. CAPTCHA demo sayfaları üçüncü durum için kullanışlıdır, çünkü bir spec'i gerçek gibi davranan bir widget'a yöneltebilirsiniz.
Bu, Cypress bileşen testlerinde çalışır mı?
İşe yarar biçimde değil. Bir bileşen testi, arkasında gerçek bir sayfa ve sunucu olmadan bir bileşeni mount eder; dolayısıyla token'ın karşısında doğrulanacağı bir şey yoktur. Erişilebilir olmasını istiyorsanız task'ı component anahtarı altında kaydedin, ancak doğrulama işini, gönderilecek gerçek bir isteğin bulunduğu uçtan uca spec'lerde tutun.
Cypress Cloud'a kaydedilen bir çalıştırma çözücüye erişebilir mi?
Cypress Cloud sonuçları kaydeder, testlerinizi çalıştırmaz; dolayısıyla soru aslında tarayıcıyı hangi makinenin çalıştırdığıyla ilgilidir. Dizüstünüzde bu Local moddur. Barındırılan bir runner'da Server mod ve çözücünün adresine giden bir yol gerekir; kayıt her iki durumda da aynı şekilde çalışır.
Paralel bir Cypress çalıştırması aynı anda kaç çözüm gönderebilir?
Kaç spec dosyanız çalışıyorsa o kadar. Her Cypress süreci kendi istemcisini tutar ve kendi task'ını bekler; dolayısıyla istemci tarafında yapılandırılacak bir şey yoktur. SDK 250 milisaniyede yoklamaya başlar ve pollingInterval üst sınırına doğru kademeli olarak açılır; bu da aynı anda birkaç iş varken bile hızlı bir çözümü hızlı tutar. İş, sahibi olduğunuz donanımda gerçekleştiği için makine eklemek bir faturalandırma değil, bir kapasite kararıdır.
Kısa özet
İstemciyi cypress.config.js içine koyun, onu bir task olarak açın, taskTimeout değerini 180000'e yükseltin ve token'ı cy.document üzerinden gizli alana yazın. Entegrasyonun tamamı budur; bozulan kısımlar ise altındaki iki Cypress kuralıdır: spec kodu tarayıcı kodudur ve bir task ya döndürür ya da başarısız olur.
Çözücüyü kendiniz çalıştırmak, kararsız bir doğrulamayı bütçelemek yerine yeniden denemeyi makul kılar. Bu gerekçe, onu nereye koyarsanız koyun geçerlidir: bir captcha çözücü, ister bir test paketinde ister üretimde olsun. İstemci seçenekleri ayrıntılı olarak şurada ele alınıyor: Node.js entegrasyon kılavuzu. Cypress'in diğer tüm runner'larla paylaştığı tarayıcı tarafındaki ayrıntılar ise şurada: Playwright kılavuzu.
