TestCafe Testlerinde CAPTCHA Nasıl Çözülür (Node.js SDK)

testcafe captcha - How to Solve a CAPTCHA in TestCafe Tests (Node.js SDK)

TestCafe'de bir captcha adımı çoğu framework'e göre daha kısadır, çünkü TestCafe test kodu zaten Node içinde çalışır. Çözücüyü doğrudan test dosyasından çağırır, ardından token'ı bir ClientFunction ile sayfaya yazarsınız. Ancak bütün bunlardan önce kontrol edilmesi gereken tek bir şey var ve bunu yanlış yapmak bir öğleden sonrayı boşa harcatır: çalıştırmanızın native automation mı yoksa eski URL yeniden yazma proxy'sini mi kullandığı. Proxy üzerinde reCAPTCHA, bir çözücü daha yanına bile yaklaşmadan bozuktur.

Neye ihtiyacınız var

  • TestCafe 3.0 veya daha yenisi ve Node.js 18 veya daha yenisi, ayrıca CapSkip Node.js SDK'sı.
  • Bir Chromium tarayıcısı, yani Chrome veya Edge. Native automation, Firefox veya Safari'yi kapsamaz.
  • Test edilen formun sayfa URL'si ve sitekey'i.
  • Test koşucusu ile çözücü aynı makineyi paylaşıyorsa Local mode, paylaşmıyorsa Server mode ile çalışan CapSkip. Her iki mod da şurada anlatılır: bağlantı ayarları.
# npm install capskip
npm install --save-dev testcafe
npm install capskip

Önce kontrol edin: native automation mı, proxy mi?

TestCafe'nin bir tarayıcıyı sürmek için iki yolu vardır ve bunlar CAPTCHA söz konusu olduğunda tamamen farklı davranır. Orijinal olanı hammerhead adlı bir web proxy'sidir. Tarayıcı ile site arasında durur, otomasyon scriptlerini her sayfaya enjekte eder ve kaynaktaki her URL'yi proxy'ye geri işaret edecek şekilde yeniden yazar. TestCafe'nin sürücü olmadan her tarayıcıyı desteklemesini sağlayan şey budur ve reCAPTCHA'yı bozan da yine budur.

Proxy üzerinde iki hata ortaya çıkar; ikisi de hammerhead'e karşı raporlanmıştır ve hiçbiri testinizden düzeltilemez. reCAPTCHA, Google’ın origin'inden bir web worker başlatmaya çalışır ve tarayıcı bunu reddeder, çünkü dokümanın origin'i artık proxy'nin kendi host'u ve portudur. Ayrıca proxy üzerinden sunulan sayfalar her seferinde 0.1'lik bir reCAPTCHA v3 skoruyla döner ve çoğu site bunu doğrudan bot olarak değerlendirir.

Native automation bunların tamamının yerini aldı. TestCafe bunun yerine Chromium'u DevTools protokolü üzerinden sürer, dolayısıyla yolda proxy yoktur ve hiç URL yeniden yazma olmaz. v2.5.0'da deneysel olarak geldi ve v3.0.0'dan beri varsayılan durumda. Test paketiniz güncel bir TestCafe üzerindeyse ve Chrome'da çalışıyorsa, ona zaten sahipsiniz.

Bu yüzden ilk hata ayıklama adımı, hiçbir şeyin bunu kapatmadığını doğrulamaktır. TestCafe, Firefox ve Safari'de native automation'ı otomatik olarak devre dışı bırakır; disable-native-automation adlı CLI bayrağı ve onun yapılandırma dosyasındaki karşılığı da bunu Chromium'da kapatır. Yıllar önce başka bir sorunu aşmak için bu bayrağı eklemiş olan test paketleri yaygın durumdur. Herhangi bir çözücü kodu yazmadan önce onu arayın.

# Run in Chrome, which uses native automation by default.
npx testcafe chrome tests/checkout.js

# This flag puts you back on the proxy and breaks reCAPTCHA.
# npx testcafe chrome tests/checkout.js --disable-native-automation

Bir şeyi açıkça söylemekte fayda var, çünkü TestCafe de aynısını söylüyor: test edilen sitenin sahibi sizseniz, en iyi cevap hiçbir şey çözmemektir. Google, her zaman geçen bir v2 test sitekey'i yayınlar; gevşetilmiş eşiğe sahip ayrı bir v3 anahtarı kurmak ise reCAPTCHA konsolunda beş dakikalık bir değişikliktir. TestCafe’nin kendi reCAPTCHA tarifi ikisini de adım adım anlatır. Çözme, o kapının kapalı olduğu durumlar içindir: akışın içindeki üçüncü taraf bir ödeme adımı, üretim anahtarlarını paylaşan bir staging ortamı veya gerçek siteye karşı çalışması gereken bir smoke testi.

Adım 1: sitekey'i sayfadan okuyun

TestCafe'de seçiciler tembeldir ve yeniden dener, bu yüzden widget render edilmeden önce yazılmış bir seçici, widget göründüğünde yine de çözümlenir. Teste sabit bir değer yapıştırmak yerine sitekey'i widget elementinden çekin; böylece aynı test bir anahtar rotasyonundan sağ çıkar.

// npm install capskip
import { Selector } from 'testcafe';

const PAGE_URL = 'https://example.com/page-with-recaptcha';

fixture('Checkout').page(PAGE_URL);

test('submits behind reCAPTCHA', async t => {
    const widget = Selector('.g-recaptcha');
    const sitekey = await widget.getAttribute('data-sitekey');
});

Adım 2: test dosyasından çözün

TestCafe'nin tarayıcı tarafında çalışan koşuculardan daha kolay olduğu yer burasıdır. Test fonksiyonunuz sıradan Node'dur, dolayısıyla SDK düz bir import, çağrı da düz bir await'tir. Kurulacak bir köprü ve kaydedilecek bir task yoktur; Cypress'ten gelenlerin ihtiyaç duyacağını sandığı kısım tam da budur.

// npm install capskip
import { CapSkip } from 'capskip';

// Local mode. Change only the host to talk to a solver
// running on another machine.
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const result = await solver.recaptcha(sitekey, PAGE_URL);
const token = result.code;

Bu tek metot reCAPTCHA v2, Invisible, Enterprise ve v3'ü kapsar. Varyantlar ayrı çağrılar değil, üçüncü argümandaki seçeneklerdir: invisible 1 olarak ayarlanır, enterprise 1 olarak ayarlanır veya version bir action dizesiyle birlikte v3 olarak ayarlanır. Turnstile ve GeeTest'in aynı yapıda kendi metotları vardır. Her parametre şurada listelenmiştir: CapSkip API dokümantasyonu.

Adım 3: token'ı bir ClientFunction ile yazın

Yanıt alanı gizli bir textarea'dır, bu yüzden normal yazma eylemi ona dokunmaz. TestCafe eylemleri yalnızca görünür elementler üzerinde çalışır ve bu bilinçli bir tercihtir. Bunun yerine bir ClientFunction kodunuzu sayfanın içinde çalıştırır; bu da gerçek bir kullanıcının asla yazmadığı bir alan için doğru araçtır.

Buradaki tuzak neredeyse herkesi bir kez yakalar. Bir ClientFunction, çevresindeki testteki değişkenleri göremez. Fonksiyon gövdesi serileştirilip tarayıcıya gönderilir, bu yüzden dış kapsamdan yakalanan bir token çalışma zamanında tanımsız bir tanımlayıcı olarak ulaşır. Onu bir argüman olarak veya bildirilmiş bir bağımlılık olarak geçirin.

// The token is a parameter, not a closure variable.
import { ClientFunction } from 'testcafe';

const injectToken = ClientFunction(value => {
    const field = document.getElementById('g-recaptcha-response');
    field.value = value;
    field.dispatchEvent(new Event('change', { bubbles: true }));
});

await injectToken(token);

TestCafe’nin tavsiyesi, bir sitenin davranışını kalıcı olarak değiştirmek için client function kullanmamaktır ve bu tavsiyeye uymakta fayda vardır. Tek bir çalıştırma için tek bir form alanına tek bir değer yazmak bu değildir. Sayfanın davranışını yamalamıyor, bir alanı dolduruyorsunuz ve çalıştırma biter bitmez o değer yok olur.

Bazı formlar textarea'yı okumak yerine bir callback bekler. Widget bir data-callback özniteliği bildiriyorsa, o fonksiyonu aynı ClientFunction içinde token ile çağırın; sayfa tam olarak bir insan için ilerlediği gibi ilerler.

Tam çalışan örnek

Testin tamamı. Sitekey'i okuyun, çözün, enjekte edin, gönderin, doğrulayın.

// npm install capskip
import { Selector, ClientFunction } from 'testcafe';
import { CapSkip, NetworkException } from 'capskip';

const PAGE_URL = 'https://example.com/page-with-recaptcha';
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const injectToken = ClientFunction(value => {
    const field = document.getElementById('g-recaptcha-response');
    field.value = value;
    field.dispatchEvent(new Event('change', { bubbles: true }));
});

fixture('Checkout').page(PAGE_URL);

test('submits the protected form', async t => {
    const sitekey = await Selector('.g-recaptcha').getAttribute('data-sitekey');

    let token;
    try {
        token = (await solver.recaptcha(sitekey, PAGE_URL)).code;
    } catch (err) {
        if (err instanceof NetworkException) {
            throw new Error('CapSkip is not reachable on 127.0.0.1:8080.');
        }
        throw err;
    }

    await injectToken(token);
    await t.click(Selector('button[type=submit]'));
    await t.expect(Selector('.thank-you').exists).ok();
});

Mümkün olduğunca geç çözün. Bir token tek kullanımlıktır ve yaklaşık iki dakikada sona erer, bu yüzden başka üç testten önce çalışan bir fixture hook'unda çözülen bir token, dördüncü test onu gönderdiğinde çoktan ölmüş olur. Çağrıyı ona ihtiyaç duyan testin içine koyun.

Zaman aşımları ve gerçekten canınızı yakan tanesi

Bir reCAPTCHA çözümü onlarca saniye sürer ve bu, birkaç TestCafe varsayılanından daha uzundur. İyi haber şu ki insanların ilk baktığı zaman aşımları bu işin içinde değil. Çözücü çağrınız bir sayfa eylemi değil, test fonksiyonunun içindeki bir await'tir; bu yüzden 10 saniyelik seçici zaman aşımı ve 3 saniyelik doğrulama zaman aşımı onu hiç görmez.

Önemli olan sınır, tek bir testin ne kadar süre çalışabileceğini kısıtlayan test yürütme zaman aşımıdır. Varsayılanı yoktur, bu yüzden ancak biri onu ayarladığında canınızı yakar. CI yapılandırmanız bir test yürütme zaman aşımı veriyorsa, değerin testin yaptığı her şeyin üstüne yavaş bir çözüme de yer bıraktığından emin olun. SDK'nın da kendi tavanı vardır: recaptchaTimeout varsayılan olarak 300 saniyedir ve bir çözüm bunu aştığında TimeoutException fırlatır.

Çözücüyü başka bir yerde çalıştırmak

Testler CI'ya taşınır ve bir CI koşucusu sizin masanız değildir. Yukarıdaki kodda host dizesi dışında hiçbir şey değişmez.

CapSkip'in iki bağlantı modu vardır. Local, 127.0.0.1'e bağlanır ve yalnızca o cihaza yanıt verir; testi yazarken isteyeceğiniz şey budur. Server ise ağ IP'nize veya genel IP'nize bağlanır, böylece bir derleme ajanı, bir konteyner ya da bir VM aynı Windows makinesini API üzerinden çağırır. Statik bir genel IP bu adresi sabit tutar. Donanım sizindir ve her iki modda da sayaçsız kalır, bu yüzden gecede beş yüz çözüm yapan bir test paketi, beş çözüm yapan bir paketle tam olarak aynı maliyettedir.

// Same SDK, same call. Only the host moves.
const solver = new CapSkip({
    host: process.env.CAPSKIP_HOST || '127.0.0.1',
    port: 8080,
    apiKey: process.env.CAPSKIP_API_KEY,
});

SDK, CAPSKIP_HOST, CAPSKIP_PORT ve CAPSKIP_API_KEY değerlerini ortamdan kendisi okur; böylece bir CI işi, iki değişkenle ve hiç kod değişikliği olmadan aynı test dosyasını uzak bir çözücüye yönlendirebilir. Çözücü bir ağ adresini dinlemeye başladığında anahtar doğrulamasını açın ve her koşucuya kendi anahtarını verin ki biri diğerlerine dokunmadan iptal edilebilsin. Her iki mod da şurada adım adım anlatılıyor: CapSkip kurulum kılavuzu.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
Worker oluşturulamadı: script'e origin'den erişilemiyorYolda hammerhead proxy'si var, bu yüzden sayfanın origin'i site değildisable-native-automation bayrağını kaldırın ve Chrome veya Edge'de çalıştırın
Her v3 skoru 0.1 olarak dönüyorAynı neden. Test ne yaparsa yapsın proxy skoru sabitliyorAynı çözüm. Native automation proxy'yi tamamen ortadan kaldırır
Token'ın tanımlı olmadığını söyleyen bir ReferenceErrorClientFunction gövdesi dış kapsamdaki değişkenleri okuyamazToken'ı bir argüman veya bildirilmiş bir bağımlılık olarak geçirin
Yazma eylemi yanıt alanında başarısız oluyorTextarea gizli ve eylemler görünür bir element gerektirirBunun yerine değeri bir ClientFunction içinde ayarlayın
Form, sorunsuz görünen bir token'ı reddediyorGönderimden dakikalar önce bir hook içinde çözülmüşTestin içinde, göndermeden hemen önce çözün
NetworkExceptionCapSkip çalışmıyor ya da ana makine adresi yanlışUygulamayı başlatın veya host değerini sunucu adresine yöneltin
TimeoutExceptionÇözüm recaptchaTimeout süresini aştıVarsayılan 300 saniyenin üzerine çıkarın
ValidationExceptionEksik veya hatalı biçimlendirilmiş bir sitekey ya da sayfa URL'siÇağrıdan önce ikisini de loglayın ve sitekey'in canlı olan olduğunu doğrulayın

FAQ

Cypress'te olduğu gibi bir task ya da eklentiye ihtiyacım var mı?

Hayır. Cypress test kodunuzu tarayıcının içinde çalıştırır, bu yüzden Node gerektiren her şeyin bir köprüden geçmesi gerekir. TestCafe test kodunuzu en baştan Node içinde çalıştırır ve tarayıcıya yalnızca ClientFunction gövdeleri gider, dolayısıyla çözücü çağrısı sıradan bir import'tur. Bu işin Cypress sürümü şurada anlatılıyor: Cypress CAPTCHA rehberi.

Bunu Firefox veya Safari'de yapabilir miyim?

Testi çalıştırabilirsiniz ama widget'ın kendisinin hatalı davranmasını bekleyin, çünkü TestCafe bu tarayıcılarda proxy'ye geri döner ve reCAPTCHA'nın atlatamadığı yapılandırma tam olarak budur. CAPTCHA içeren testleri Chrome veya Edge'de tutun; tarayıcılar arası matris de üzerinde widget bulunmayan sayfaları kapsasın.

Bu, Turnstile için de çalışır mı?

Evet, iki farkla. Metot recaptcha değil turnstile'dır ve doldurulacak alan cf-turnstile-response adlı gizli input'tur. Tam bir challenge sayfası ayrıca data ve pagedata değerlerini ve token ile birlikte gelen user agent'ı da gerektirir; bu da şurada ele alınıyor: Cloudflare Turnstile çözücü sayfasını.

CAPTCHA testleri her commit'te çalışmalı mı?

Genellikle hayır ve bunun nedeni maliyet değil hızdır. Burada çözüm sayaçsızdır, ancak test başına onlarca saniye yavaş bir pull request kontrolü demektir. Onları etiketleyin ve gecelik ya da sürüm öncesi bir işte çalıştırın; hızlı test paketini de test anahtarları kullanan bir derlemeye yönlendirin.

Kısa özet

Native automation'ın açık olduğunu doğrulayın, çünkü eski proxy tek başına reCAPTCHA'yı bozar. Sitekey'i bir Selector ile okuyun, zaten Node olduğu için çözücüyü test dosyasından çağırın ve token'ı, değeri argüman olarak geçirilen bir ClientFunction aracılığıyla yazın. Göndermeden hemen önce çözün.

Node.js tarafının geri kalanı şurada ele alınıyor: Node.js CAPTCHA çözücü sayfası. O CAPTCHA türüne özgü her şey şurada yer alıyor: reCAPTCHA v2 çözücü sayfası. Aynı iş, WebDriver tabanlı bir koşucuda şurada anlatılıyor: WebdriverIO rehberi.

Bunu CI'ya bağlamadan önce son bir şey. CapSkip, bir yerel captcha çözücü olup zaten sahip olduğunuz donanımda çalışır, bu yüzden gecede bin çözüm yapan bir test paketi, on çözüm yapan bir paketle aynı maliyettedir.