Cara Memecahkan CAPTCHA di Nightwatch.js (Antrean Perintah)

Pemecahan captcha di Nightwatch harus berjalan di dalam antrean perintah, bukan di sampingnya. Nightwatch tidak mengeksekusi perintah browser di tempat Anda menuliskannya. Nightwatch memasukkannya ke antrean lalu menguras antrean itu setelah fungsi tes Anda selesai, sehingga panggilan pemecahan yang ditulis di antara dua perintah browser langsung dijalankan, sebelum halaman dimuat dan sebelum widget-nya ada. Solusinya adalah browser.perform, ditambah satu global yang harus Anda naikkan karena satu pemecahan butuh waktu lebih lama daripada sepuluh detik yang diizinkan Nightwatch untuk callback asinkron.
Apa yang Anda butuhkan
- Nightwatch 3 dengan driver yang berfungsi, entah chromedriver secara lokal atau endpoint WebDriver jarak jauh.
- CapSkip berjalan di mesin Windows, dengan client Node terinstal di project yang sama dengan tes Anda.
- Sitekey dan URL halaman. Baca sitekey langsung dari widget alih-alih menuliskannya secara hardcode, karena staging dan production jarang memakai sitekey yang sama.
- Mode Server setiap kali tes berjalan di tempat selain mesin solver itu sendiri, dan itu mencakup semua runner CI. Ini hanya satu pengaturan di bagian pengaturan koneksi.
# npm install capskip npm install --save-dev nightwatch npm install capskip
Langkah 1: mengapa panggilan pemecahan biasa berjalan terlalu awal
Setiap perintah browser dalam tes Nightwatch adalah instruksi yang ditambahkan ke sebuah antrean. Fungsi tes berjalan dari atas ke bawah lebih dulu untuk membangun antrean itu, dan baru setelah itu Nightwatch mulai mengeksekusinya. JavaScript biasa yang berada di antaranya bukan bagian dari antrean, jadi ia berjalan pada tahap pembangunan antrean. Itulah keseluruhan masalahnya dalam satu kalimat.
// WRONG. The solve starts while the queue is still being
// built, so it runs before browser.url() has navigated.
module.exports = {
"signup form": function (browser) {
browser.url("https://example.com/page-with-recaptcha");
solver.recaptcha(sitekey, pageUrl).then((r) => {
// fires first, against a page that is not open yet
});
browser.click("#submit");
},
};Gejalanya membingungkan karena tidak ada error yang dilempar. Pemecahan berhasil, tes kadang lulus, dan tokennya untuk pemuatan halaman yang tidak pernah terjadi. Nightwatch menyediakan cara yang terdokumentasi untuk memasukkan kode Anda sendiri ke dalam antrean: browser.perform, yang callback-nya dijelaskan sebagai fungsi yang dijalankan sebagai bagian dari antrean.
// RIGHT. perform() queues the callback, so it runs in
// sequence with the commands either side of it.
browser.url("https://example.com/page-with-recaptcha");
browser.perform(async function () {
const { code } = await solver.recaptcha(sitekey, pageUrl);
return code;
});
browser.click("#submit");Tes async adalah opsi satunya lagi. Mendeklarasikan fungsi tes sebagai async membuat perintah API mengembalikan promise, sehingga meng-await setiap perintah menjaga urutan semuanya tanpa perform. Keduanya sama-sama bisa dipakai. Kesalahannya adalah mencampur keduanya: promise eksternal yang di-await di tengah perintah browser yang tidak di-await akan mengembalikan Anda ke contoh pertama.
Langkah 2: naikkan asyncHookTimeout, atau pemecahan akan timeout di detik kesepuluh
Ini masalah yang bisa menghabiskan waktu sore Anda. Eksekusi asinkron di dalam perform dibatasi oleh global asyncHookTimeout, dan nilai default-nya 10000 milidetik. Pemecahan reCAPTCHA rutin memakan waktu lima belas sampai empat puluh lima detik. Jadi callback-nya dimatikan saat solver masih bekerja, dan error yang Anda dapat berbicara soal timeout, bukan soal CAPTCHA.
Naikkan nilainya di config Nightwatch Anda, secara global atau per environment.
// nightwatch.conf.js
module.exports = {
globals: {
// Default is 10000, which is shorter than most solves.
asyncHookTimeout: 120000,
// waitFor commands default to 5000. The widget is not
// the slow part, but give it room on a cold CI runner.
waitForConditionTimeout: 15000,
},
};Setel sisi client-nya agar berada di bawahnya, supaya jelas batas atas mana yang benar-benar berlaku. Client Node melakukan polling dengan jadwalnya sendiri, mulai dari 250 milidetik lalu melambat sampai pollingInterval, dan itulah sebabnya ia biasanya kembali lebih cepat daripada loop buatan tangan.
// npm install capskip
const { CapSkip } = require("capskip");
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST || "127.0.0.1",
port: 8080,
// Seconds. Keep this under the 120s asyncHookTimeout above.
recaptchaTimeout: 90,
pollingInterval: 3,
});Langkah 3: suntikkan token dengan execute, bukan setValue
Kolom response yang dibaca reCAPTCHA adalah textarea tersembunyi. WebDriver menolak berinteraksi dengan elemen yang dianggapnya tidak dapat diinteraksi, jadi setValue pada kolom itu gagal dengan error element not interactable. Menyuntikkan nilainya lewat halaman adalah jawaban yang lazim, dan Nightwatch menyediakannya sebagai execute, yang menerima badan fungsi, array argumen, dan callback opsional.
browser.perform(async function () {
const { code } = await solver.recaptcha(sitekey, pageUrl);
// The function is serialized and run in the page, so it
// cannot close over anything. Pass values in the array.
await browser.execute(
function (token) {
document.getElementById("g-recaptcha-response").value = token;
},
[code]
);
});Jika formulirnya menjalankan callback saat selesai alih-alih membaca kolom itu ketika dikirim, panggil callback tersebut di script yang sama. Invisible reCAPTCHA hampir selalu bekerja seperti itu, dan varian widget hanya mengubah opsi yang Anda kirim: invisible atau enterprise diisi 1, version diisi v3 beserta action, atau turnstile dan geetest alih-alih recaptcha. Seluruh cakupannya ada di halaman pemecah CAPTCHA Node.js.
Langkah 4: tes lengkapnya
Client dibangun sekali di scope modul, di luar tes, sehingga tidak dibangun ulang pada setiap test case. Sitekey dibaca dari halaman alih-alih ditulis hardcode, dan itulah yang membuat spec yang sama bisa dijalankan terhadap staging maupun production.
// npm install capskip
const { CapSkip } = require("capskip");
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST || "127.0.0.1",
port: 8080,
recaptchaTimeout: 90,
});
const PAGE = "https://example.com/page-with-recaptcha";
describe("signup", function () {
it("submits through the reCAPTCHA", async function (browser) {
await browser.url(PAGE);
await browser.waitForElementPresent(".g-recaptcha", 15000);
// Read the sitekey off the widget that is actually there.
const sitekey = await browser.getAttribute(
".g-recaptcha",
"data-sitekey"
);
await browser.perform(async function () {
const { code } = await solver.recaptcha(sitekey.value, PAGE);
await browser.execute(
function (token) {
document.getElementById("g-recaptcha-response").value = token;
},
[code]
);
});
// Submit straight after. The token is not a long-lived value.
await browser.click("#submit");
await browser.assert.textContains(".result", "Thanks");
});
});Pecahkan selambat mungkin lalu segera kirim. Token reCAPTCHA berlaku sekitar dua menit, dan suite yang memecahkan di hook before lalu mengirim tiga test case kemudian sebenarnya sedang mengirim token yang sudah kedaluwarsa. Jendela waktu itu layak dibaca sekali: berapa lama token reCAPTCHA bertahan.
Langkah 5: menjalankannya di CI, dan mode koneksi apa yang dibutuhkan
Tes Nightwatch jarang menetap di mesin tempat ia ditulis. Begitu tes pindah ke runner CI, sebuah container, atau node Selenium Grid, loopback tidak lagi berarti komputer di meja Anda. Ada dua mode koneksi. Mode Local mengikat ke 127.0.0.1 dan hanya melayani perangkat itu. Mode Server mengikat ke alamat jaringan atau IP publik Anda, sehingga sebuah runner, container, atau node grid bisa menjangkau mesin Windows yang sama lewat API. Keduanya ada di pengaturan koneksi, dan Mode Server hanya mengubah alamat mana yang didengarkan solver. Perangkat kerasnya tetap milik Anda dan tetap tanpa kuota.
| Di mana proses tes berjalan | Mode koneksi yang mana |
|---|---|
| Mesin Anda sendiri, chromedriver lokal | Mode Local. 127.0.0.1 memang benar |
| Build agent di jaringan Anda | Pakai mode Server dengan alamat LAN pemecah CAPTCHA |
| Runner CI yang di-hosting | Pakai mode Server dengan IP publik statis dan sebuah aturan firewall |
| Sebuah container, solver di host | Mode Server. Loopback di dalam container adalah container itu sendiri |
Satu pembedaan yang sering menjegal orang di Grid: panggilan ke solver dilakukan oleh proses tes, bukan oleh browser. Jadi alamat yang penting adalah alamat yang bisa dijangkau proses Node, dan jaringan node grid sama sekali tidak berpengaruh. Pemisahan itu dibahas tuntas di panduan Selenium Grid, dan lapisan driver di bawah keduanya dibahas di halaman pemecah CAPTCHA Selenium.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| Pemecahan tercatat di log sebelum halaman berpindah | Panggilannya berada di luar antrean, jadi ia berjalan saat antrean sedang dibangun | Bungkus di dalam browser.perform |
| Timeout yang terjadi nyaris tepat di detik kesepuluh | asyncHookTimeout masih di nilai default 10000 | Naikkan di globals, di atas timeout client |
| Element not interactable pada kolom response | Itu textarea tersembunyi dan WebDriver tidak mau mengetik ke dalamnya | Isi nilainya dengan browser.execute |
| Token ditolak meskipun tesnya lulus | Token kedaluwarsa di antara pemecahan dan pengiriman | Pecahkan tepat sebelum mengirim, bukan di dalam hook |
| NetworkException begitu suite pindah ke CI | Solver tidak ada di runner | Mode Server, lalu set CAPSKIP_HOST di runner |
| ERROR_GOOGLEKEY di dalam ApiException | Sitekey diambil dari widget yang salah atau dari URL iframe | Baca data-sitekey dari elemen yang sedang Anda pecahkan |
| CAPCHA_NOT_READY dari polling buatan sendiri | Hasilnya dibaca sebelum selesai | Biarkan client yang melakukan polling. Ia melambat dengan sendirinya |
Respons terakhir itu memang ditulis persis seperti kelihatannya, dan huruf yang hilang bukan salah ketik dari pihak kami, karena API memang benar-benar mengembalikannya seperti itu. Penjelasan lengkapnya ada di panduan CAPCHA_NOT_READY.
FAQ
Apakah saya butuh plugin Nightwatch untuk ini?
Tidak. Solver-nya adalah paket Node biasa yang Anda require di file spec, jadi tidak ada custom command yang perlu didaftarkan dan tidak ada yang perlu ditambahkan ke array plugins. Kalau Anda tetap ingin membuatnya, satu-satunya yang layak dibungkus adalah pasangan perform dan execute, yang panjangnya sekitar delapan baris dan menghemat pengulangan di banyak file spec.
Bisakah saya memecahkan sekali di hook before global lalu memakai ulang tokennya?
Tidak, karena dua alasan terpisah. Token kedaluwarsa dalam sekitar dua menit, dan suite sebesar apa pun akan berjalan lebih lama dari itu. Selain itu token terikat pada pemuatan halaman yang menghasilkannya, jadi test case kedua yang memuat halaman lagi butuh tokennya sendiri. Pecahkan per test case, selambat mungkin di dalamnya. Karena solver-nya tanpa kuota, melakukan itu tidak memakan biaya apa pun selain beberapa detik waktu berjalan.
Tes saya berjalan paralel. Apakah itu mengubah sesuatu?
Hanya hitung-hitungannya. Setiap worker punya antrean sendiri dan panggilan pemecahan sendiri, jadi empat worker berarti empat pemecahan berbarengan. Tidak ada yang perlu dikoordinasikan, dan tidak ada yang mengantre di belakang saldo bersama, karena batasnya adalah mesin yang menjalankan CapSkip, bukan jumlah kredit. Naikkan asyncHookTimeout sedikit kalau mesinnya menangani empat sekaligus pada hari yang lambat.
Apa bedanya dengan melakukannya di WebdriverIO?
WebdriverIO menyelesaikan perintahnya di tempat Anda menuliskannya, jadi panggilan pemecahan di antara dua perintah mendarat di tempat yang benar tanpa perlu cara khusus. Nightwatch memakai antrean, dan itulah sebabnya perform ada serta mengapa artikel ini memberi satu bagian khusus untuknya. Semua yang terjadi setelah token identik di keduanya. Versi WebdriverIO-nya dibahas tuntas di panduan WebdriverIO.
Versi singkatnya
Letakkan pemecahan di dalam browser.perform, karena apa pun yang berada di luar antrean berjalan saat antrean sedang dibangun, bukan saat Anda menginginkannya. Naikkan asyncHookTimeout di atas timeout milik client, karena nilai default 10000 lebih pendek daripada satu pemecahan. Suntikkan token dengan execute, karena kolom response-nya tersembunyi. Kirim formulir tepat setelah memecahkan. Set CAPSKIP_HOST dari environment dan jalankan CapSkip dalam Mode Server di mana pun tes tidak berada di mesin solver itu sendiri.
- Endpoint mentah di balik klien didokumentasikan di dokumentasi API CapSkip.
- Tantangan kotak centangnya sendiri dijelaskan di halaman pemecah reCAPTCHA v2.
Perlu Anda ketahui sebelum menambahkan pemecahan ke setiap spec di suite: CapSkip adalah pemecah captcha lokal, jadi satu run tes yang memecahkan dua ratus kali berbiaya persis sama dengan satu run tes yang memecahkan sekali.
