Node.js ile WebdriverIO testlerinde CAPTCHA nasıl çözülür

Bir WebdriverIO captcha adımı, bir tarayıcı çağrısı değil, bir Node.js çağrısıdır. Çözümü test sürecinde yaparsınız, token'ı browser.execute üzerinden sayfaya verirsiniz ve formu gönderirsiniz. İki şey insanları yanıltır ve hiçbiri çözücüyle ilgili değildir. Çözümü tarayıcı bağlamının içine koyarsanız, makinenize ulaşmasının hiçbir yolu kalmaz. Mocha'yı varsayılan otuz saniyelik zaman aşımında bırakırsanız, test bir çözümün ortasında hiçbir şey açıklamayan bir mesajla ölür. İşte özel komut, tek yapılandırma değişikliği ve çalışan bir spec.
Neye ihtiyacınız var
- Mocha framework'üyle birlikte WebdriverIO 8 veya daha yenisi. Aşağıdakilerin tamamı asenkrondur, çünkü eski senkron mod artık yok.
- Node.js 18 veya üzeri ve test projesine kurulmuş CapSkip paketi.
- Gerçekten bir doğrulama sunan bir sayfa. Her zaman geçen bir test sitekey'i bunların hiçbirini devreye sokmaz.
- Testler kendi makinenizde çalışıyorsa CapSkip Local modda, bir CI runner'ında veya bir grid'de çalışıyorsa Server modda çalışır. Ayrıntılı bilgi için bkz: bağlantı ayarları.
# Install into the project that runs wdio, not into the browser image. npm install capskip
Node içinde çözün, tarayıcıda değil
Önce bu hatayı bir kenara koymakta fayda var, çünkü WebdriverIO bu hatayı yapmayı kolaylaştırır. browser.execute komutu fonksiyonunuzu serileştirir, tarayıcıya gönderir ve sayfanın içinde çalıştırır. Orada bir modül yükleyici yoktur, dolayısıyla SDK basitçe kullanılamaz. Olsa bile, test ettiğiniz şey zaten sayfadır ve ona çözücünüzün adresini vermek yapmak isteyeceğiniz bir şey değildir.
Daha güçlü neden yönlendirmeyle ilgilidir. Tarayıcı kendi makineniz dışında herhangi bir yerdeyse, ki bir grid'de veya bulut cihaz sağlayıcısında asla kendi makineniz değildir, o tarayıcının içindeki loopback adresi tarayıcı sunucusuna aittir. Çözücünüz orada değildir. wdio'yu çalıştıran Node süreci, CapSkip'e nasıl ulaşılacağını bilen taraftır; bu yüzden çözüm orada kalır ve sayfaya yalnızca tamamlanmış token geçer.
Adım 1: özel bir komut kaydedin
SDK'yı her spec'e ayrı ayrı import etmek yerine, yapılandırmanızın before hook'una tek bir komut ekleyin. Bu komut her testte browser nesnesinde kullanılabilir hale gelir ve çözücünün adresi tam olarak tek bir yerde yaşar.
// npm install capskip
const { CapSkip } = require('capskip');
exports.config = {
framework: 'mocha',
before: function () {
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST || '127.0.0.1',
port: Number(process.env.CAPSKIP_PORT || 8080),
});
browser.addCommand('solveRecaptcha', async function (sitekey) {
const result = await solver.recaptcha(sitekey, await this.getUrl());
return result.code; // the token, ready to inject
});
},
};addCommand içinde this değeri browser kapsamıdır; getUrl'nin orada çalışmasının nedeni budur. Bu ayrıntı gerçek bir iş görür: API, widget'ın bulunduğu sayfanın URL'sini ister ve bunu tarayıcıdan istemek, spec'in zaten gitmiş olduğu bir URL'yi tekrar yazmak zorunda kalmaması anlamına gelir.
Host'u sabit kodlamak yerine ortamdan okuyun. Böylece aynı suite, kod değişikliği olmadan hem dizüstü bilgisayarınızdaki bir çözücüye karşı hem de CI'dan paylaşılan bir çözücüye karşı çalışır.
Adım 2: token'ı enjekte edin ve gönderin
Google, yanıtı g-recaptcha-response id'sine sahip gizli bir textarea'ya koyar. Gizli olduğu için hiçbir miktarda setValue ona dokunmaz. Değeri doğrudan ayarlayın ve ardından formu bir insanın yapacağı gibi gönderin.
const token = await browser.solveRecaptcha('YOUR_SITEKEY');
// Put the token where the page already expects to find it.
await browser.execute((value) => {
document.getElementById('g-recaptcha-response').value = value;
}, token);
await $('button[type="submit"]').click();Bu, yaygın durumu kapsar: gönder'e bastığınızda textarea'yı okuyan bir form. Bazı sayfalar bunun yerine widget üzerinde bir data-callback tanımlar ve textarea'ya hiç bakmaz. Test ettiğiniz şey buysa, değeri ayarladıktan sonra aynı browser.execute bloğundan callback'i çağırın; çünkü kimsenin okumadığı bir alanı doldurmak hiçbir şeyi değiştirmez.
Adım 3: Mocha zaman aşımını yükseltin
WebdriverIO, Mocha'yı varsayılan olarak 30000 milisaniyelik bir zaman aşımına ayarlar; bu, bir şeylere tıklamak için cömert ama bir doğrulamayı çözmek için çok kısadır. Aksi halde geçecek bir test burada başarısız olur ve hata, çözümle ilgili herhangi bir şey yerine Mocha'yı işaret eder; bu da insanların bir öğleden sonra yanlış yerde aramasına neden olur.
exports.config = {
framework: 'mocha',
mochaOpts: {
// The 30000 default expires mid solve. Give it room.
timeout: 120000,
},
};İki sınırı doğru sırayla ayarlayın. SDK, varsayılan olarak 300 saniye olan recaptchaTimeout'tan sonra pes eder; Mocha ise kendi zaman aşımından sonra pes eder. SDK'yı Mocha'nın altında tutarsanız çözümü adlandıran bir TimeoutException alırsınız. Üstünde tutarsanız Mocha testi önce öldürür ve öğrendiğiniz tek şey bir şeylerin çok uzun sürdüğüdür. Hızlı kalması gereken bir suite için 120000 milisaniyelik bir Mocha zaman aşımının altında 90 saniyelik bir çözücü zaman aşımı makul bir çifttir.
CI'da veya bir grid'de çalıştırma
Hangi makinenin çözücüye ulaşması gerektiğini belirleyin, çünkü bu bariz olan makine değildir. Tarayıcı CapSkip ile asla konuşmaz. wdio'yu çalıştıran süreç konuşur. Yani bir GitHub Actions runner'ında, bir container'da veya bir bulut tarayıcısını yöneten bir dizüstü bilgisayarda, çağıran taraf o runner'dır; onu kendi loopback adresine yöneltmek hiçbir şey bulmaz.
| Mod | Dinlediği adres | Ne zaman kullanılır |
|---|---|---|
| Yerel | 127.0.0.1, yalnızca o cihaz | wdio'yu çözücüyle aynı makinede çalıştırıyorsunuz |
| Sunucu | Ağ adresiniz veya genel IP'niz | CI runner'ları, container'lar, paylaşılan bir suite, bir grid |
Server modu ikinci satırdaki her şeyi kapsar. Uygulamadaki dinleme adresini değiştirin, runner üzerinde CAPSKIP_HOST'u ayarlayın ve her iş tek bir çözücüyü paylaşsın. Çağıranlar ağınızın dışındaysa sabit bir genel IP'ye sahip olmak işe yarar. Bunların hiçbiri ürünün ne olduğunu değiştirmez: hâlâ sizin donanımınızdır ve hâlâ ölçüm bazlı ücretlendirilmez; dolayısıyla loopback adresinden uzaklaşmak yalnızca çalıştığı yeri değiştirir, başka bir şeyi değil. CapSkip bir Windows uygulamasıdır; bu yüzden pratikte bu, runner'larınızın çağırdığı tek bir Windows makinesidir.
Tam spec
describe('protected signup form', () => {
it('submits with a solved challenge', async () => {
await browser.url('https://example.com/page-with-recaptcha');
await $('#email').setValue('[email protected]');
// Solve here, submit two lines later. The token is short lived.
const token = await browser.solveRecaptcha('YOUR_SITEKEY');
await browser.execute((value) => {
document.getElementById('g-recaptcha-response').value = value;
}, token);
await $('button[type="submit"]').click();
await expect($('.signup-success')).toBeDisplayed();
});
});Bunun ne kadar azının CAPTCHA ile ilgili olduğuna dikkat edin. Üç satır tüm entegrasyonu taşır ve geri kalanı zaten yazacak olduğunuz testtir. Çözümü ve gönderimi aynı test gövdesinde tutun ki form onu okuduğunda token yalnızca birkaç saniyelik olsun.
Yaygın hatalar
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| 30000ms'lik Mocha zaman aşımı aşıldı | Çözüm varsayılandan daha uzun sürdü | mochaOpts.timeout'u yükseltin ve recaptchaTimeout'u onun altında kalacak şekilde düşürün |
| browser.execute içinde solver tanımlı değil | Fonksiyon Node'da değil, sayfada çalıştı | execute çağrısından önce çözün ve yalnızca token'ı içeri geçirin |
| CI'dan NetworkException, yerelde sorunsuz | Runner çözücüye ulaşamıyor | Server moduna geçin ve runner üzerinde CAPSKIP_HOST'u ayarlayın |
| Form, sorunsuzca çözülmüş bir token'ı reddediyor | Sayfa bir callback kullanıyor ve textarea'yı yok sayıyor | Değeri ayarladıktan sonra widget callback'ini çağırın |
| ERROR_GOOGLEKEY | Bir Turnstile sitekey'i reCAPTCHA metoduna gönderildi | Turnstile widget'ları için turnstile metodunu kullanın |
Kodların tam listesi ve her birini neyin tetiklediği şurada yer alır: CapSkip API dokümantasyonu.
FAQ
Çözücüyü browser.execute içinde çağırabilir miyim?
Hayır; bu iki ayrı nedenle başarısız olur. Geçirdiğiniz fonksiyon serileştirilir ve modül yükleyicinin ve SDK'nın olmadığı sayfada çalıştırılır. Bir tane olsa bile, tarayıcı çoğu zaman tamamen başka bir sunucudadır; dolayısıyla aradığınız adres size ait değildir. Node sürecinde çözün ve tamamlanmış token'ı bir argüman olarak geçirin.
Bu, uzak bir grid veya bulut tarayıcısıyla çalışır mı?
Evet; bu kurulum, çözümün neden Node'a ait olduğunun nedenidir. Test süreciniz yerel olarak veya bir runner üzerinde çalışır ve çözücüye doğrudan ulaşır; tarayıcı ise başka bir yerdedir ve yalnızca bir token alır. Suite'i hangi makine çalıştırırsa çalıştırsın ona ulaşabilmesi için CapSkip'i Server modunda çalıştırın; spec ile ilgili hiçbir şey değişmez.
Bir before hook'ta bir kez çözüp token'ı yeniden mi kullanmalıyım?
Hayır. Bir reCAPTCHA token'ı tek kullanımlıktır ve yaklaşık iki dakika geçerli kalır; bu yüzden onu kullanan ikinci test reddedilir, yavaş bir spec'ten sonra çalışan ilk test de reddedilir. İhtiyacı olan her testin içinde çözün. Paylaşılan bir çözücü, çözüm başına bir maliyet olmadan ekstra çağrıları karşılar; dolayısıyla token biriktirerek kazanılacak bir şey yoktur.
Mocha yerine Cucumber veya Jasmine kullanıyorum. Ne değişir?
Yalnızca zaman aşımı ayarının adı. Özel komut, enjeksiyon ve Server modu sorusu tamamen aynıdır. mochaOpts.timeout yerine cucumberOpts.timeout veya jasmineOpts.defaultTimeoutInterval'ı yükseltin ve çözücü zaman aşımını hangisini ayarladıysanız onun altında tutun.
Kısa özet
Before hook'ta tek bir özel komut kaydedin, framework zaman aşımını çözümün üzerine çıkarın ve token'ı yazmaya çalışmak yerine browser.execute ile enjekte edin. İstemci tarafı için bkz: Node.js CAPTCHA çözücü sayfası, WebDriver tarafı için bkz: Selenium CAPTCHA çözücü sayfası, token'ın gerçekte ne olduğu için bkz: reCAPTCHA v2 çözücü sayfası. Test suite'leri, ücret bazlı fiyatlandırmanın en çok canını yaktığı yerdir; çünkü her pull request'te çalışan bir suite, aynı formu haftada yüzlerce kez çözer. Sahiplikle değişen de budur: sınırsız captcha çözücü kapasitesi, zaten sahip olduğunuz donanım üzerinde, CI günde iki kez de çalışsa saatte iki kez de çalışsa aynı maliyete sahiptir.
