Cara Memecahkan CAPTCHA di Pengujian TestCafe (SDK Node.js)

Langkah captcha di TestCafe lebih singkat daripada di kebanyakan framework, karena kode pengujian TestCafe sudah berjalan di Node. Anda memanggil pemecah langsung dari file pengujian, lalu menulis token ke halaman dengan sebuah ClientFunction. Namun ada satu hal yang harus diperiksa sebelum semua itu, dan salah menilainya bisa menghabiskan satu sore penuh: apakah run Anda memakai native automation atau proxy penulisan ulang URL yang lama. Di atas proxy, reCAPTCHA sudah rusak bahkan sebelum pemecah mendekat.
Apa yang Anda butuhkan
- TestCafe 3.0 atau lebih baru dan Node.js 18 atau lebih baru, ditambah SDK Node.js CapSkip.
- Browser berbasis Chromium, jadi Chrome atau Edge. Native automation tidak mencakup Firefox atau Safari.
- URL halaman formulir yang diuji, beserta sitekey-nya.
- CapSkip berjalan dalam Local mode ketika test runner dan pemecah berbagi satu mesin, atau dalam Server mode ketika tidak. Kedua mode dijelaskan di bagian pengaturan koneksi.
# npm install capskip npm install --save-dev testcafe npm install capskip
Periksa dulu: native automation atau proxy?
TestCafe punya dua cara menggerakkan browser dan keduanya berperilaku sangat berbeda terhadap CAPTCHA. Cara yang asli adalah sebuah web proxy bernama hammerhead. Proxy itu duduk di antara browser dan situs, menyuntikkan skrip otomatisasinya ke setiap halaman, dan menulis ulang setiap URL pada resource agar mengarah kembali ke proxy. Itulah yang membuat TestCafe bisa mendukung browser apa pun tanpa driver, dan itu pula yang merusak reCAPTCHA.
Dua kegagalan muncul saat memakai proxy, keduanya dilaporkan terhadap hammerhead dan tidak satu pun bisa diperbaiki dari pengujian Anda. reCAPTCHA mencoba memulai sebuah web worker dari origin Google dan browser menolaknya, karena origin dokumen kini adalah host dan port milik proxy itu sendiri. Lalu halaman yang disajikan lewat proxy selalu kembali dengan skor reCAPTCHA v3 sebesar 0,1, yang oleh sebagian besar situs langsung dianggap bot.
Native automation menggantikan semua itu. Sebagai gantinya TestCafe menggerakkan Chromium lewat protokol DevTools, jadi tidak ada proxy di jalurnya dan tidak ada penulisan ulang URL sama sekali. Fitur ini hadir sebagai eksperimen di v2.5.0 dan menjadi default sejak v3.0.0. Jika suite Anda memakai TestCafe versi terkini dan berjalan di Chrome, Anda sudah memilikinya.
Jadi langkah debugging pertama adalah memastikan tidak ada yang mematikannya. TestCafe menonaktifkan native automation secara otomatis di Firefox dan Safari, dan flag CLI bernama disable-native-automation beserta kembarannya di file konfigurasi juga mematikannya di Chromium. Kasus yang umum adalah suite yang menambahkan flag itu bertahun-tahun lalu untuk mengakali hal lain. Cari flag tersebut sebelum Anda menulis kode pemecah apa pun.
# 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
Satu hal perlu dikatakan terus terang, karena TestCafe juga mengatakannya: jika situs yang diuji adalah milik Anda, jawaban terbaiknya adalah tidak memecahkan apa pun. Google menerbitkan sitekey uji v2 yang selalu lolos, dan menyiapkan key v3 terpisah dengan ambang yang longgar hanya butuh perubahan lima menit di konsol reCAPTCHA. TestCafe sendiri punya resep reCAPTCHA yang membahas keduanya. Pemecahan diperlukan untuk kasus ketika pintu itu tertutup: checkout pihak ketiga di dalam alur, lingkungan staging yang memakai key produksi yang sama, atau smoke test yang harus berjalan terhadap situs asli.
Langkah 1: baca sitekey dari halaman
Selector di TestCafe bersifat lazy dan melakukan percobaan ulang, jadi selector yang ditulis sebelum widget dirender tetap terselesaikan begitu widget muncul. Ambil sitekey dari elemen widget alih-alih menempelkan nilai literal ke dalam pengujian, dan pengujian yang sama akan tetap bekerja setelah rotasi key.
// 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');
});Langkah 2: pecahkan dari file pengujian
Di sinilah TestCafe lebih mudah daripada runner yang berjalan di sisi browser. Fungsi pengujian Anda adalah Node biasa, jadi SDK cukup diimpor biasa dan pemanggilannya cukup await biasa. Tidak ada bridge yang perlu dibangun dan tidak ada task yang perlu didaftarkan, dan justru bagian itulah yang diperkirakan perlu oleh orang yang datang dari Cypress.
// 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;Satu metode itu mencakup reCAPTCHA v2, Invisible, Enterprise dan v3. Variannya berupa opsi pada argumen ketiga alih-alih pemanggilan terpisah: invisible diatur ke 1, enterprise diatur ke 1, atau version diatur ke v3 dengan sebuah string action. Turnstile dan GeeTest punya metodenya sendiri dengan bentuk yang sama. Setiap parameter tercantum di dokumentasi API CapSkip.
Langkah 3: tulis token menggunakan ClientFunction
Field respons berupa textarea tersembunyi, jadi aksi pengetikan biasa tidak akan menyentuhnya. Aksi TestCafe hanya bekerja pada elemen yang terlihat, dan itu disengaja. Sebagai gantinya, ClientFunction menjalankan kode Anda di dalam halaman, dan itulah alat yang tepat untuk sebuah field yang tidak pernah diketik oleh pengguna sungguhan.
Jebakan di sini menjerat hampir semua orang sekali. Sebuah ClientFunction tidak dapat melihat variabel dari pengujian di sekitarnya. Badan fungsinya diserialisasi dan dikirim ke browser, sehingga token yang diambil dari scope pembungkusnya tiba sebagai identifier undefined saat run time. Kirimkan token itu sebagai argumen atau sebagai dependency yang dideklarasikan.
// 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);Panduan TestCafe adalah jangan menggunakan client function untuk mengubah perilaku sebuah situs secara permanen, dan panduan itu layak dipatuhi. Menulis satu nilai ke satu field formulir untuk satu kali run bukan termasuk hal itu. Anda mengisi sebuah field, bukan menambal perilaku halaman, dan nilainya hilang begitu run berakhir.
Sebagian formulir menunggu sebuah callback alih-alih membaca textarea. Jika widget mendeklarasikan atribut data-callback, panggil fungsi itu dengan token di dalam ClientFunction yang sama dan halaman akan berjalan persis seperti untuk manusia.
Contoh lengkap yang berfungsi
Pengujian selengkapnya. Baca sitekey, pecahkan, suntikkan, kirim, lalu lakukan assert.
// 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();
});Pecahkan selambat mungkin. Token hanya sekali pakai dan kedaluwarsa dalam sekitar dua menit, jadi token yang dipecahkan di dalam fixture hook yang berjalan sebelum tiga pengujian lain sudah mati saat pengujian keempat mengirimkannya. Letakkan pemanggilannya di dalam pengujian yang membutuhkannya.
Timeout, dan satu yang benar-benar jadi masalah
Pemecahan reCAPTCHA memakan puluhan detik, lebih lama daripada beberapa nilai default TestCafe. Kabar baiknya, timeout yang pertama kali terpikirkan orang justru tidak terlibat. Pemanggilan pemecah Anda adalah await di dalam fungsi pengujian, bukan aksi halaman, jadi timeout selector 10 detik dan timeout assertion 3 detik tidak pernah melihatnya.
Batas yang benar-benar penting adalah test execution timeout, yang membatasi berapa lama satu pengujian boleh berjalan. Batas ini tidak punya nilai default, jadi baru terasa setelah ada yang mengaturnya. Jika konfigurasi CI Anda memberikan test execution timeout, pastikan nilainya menyisakan ruang untuk pemecahan yang lambat di atas semua hal lain yang dilakukan pengujian. SDK juga punya batas atasnya sendiri: recaptchaTimeout secara default 300 detik dan memunculkan TimeoutException ketika pemecahan melampauinya.
Menjalankan pemecah di tempat lain
Pengujian berpindah ke CI, dan runner CI bukanlah komputer Anda. Tidak ada yang berubah pada kode di atas kecuali string host.
CapSkip punya dua mode koneksi. Local mengikat ke 127.0.0.1 dan hanya melayani perangkat itu, dan itulah yang Anda inginkan saat sedang menulis pengujian. Server mengikat ke IP jaringan atau IP publik Anda, sehingga sebuah build agent, container atau VM memanggil mesin Windows yang sama melalui API. IP publik statis menjaga alamat itu tetap stabil. Perangkat kerasnya milik Anda dan tetap tanpa batas pemakaian di kedua mode, jadi suite yang memecahkan lima ratus per malam berbiaya persis sama dengan suite yang memecahkan lima.
// 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 membaca CAPSKIP_HOST, CAPSKIP_PORT dan CAPSKIP_API_KEY dari environment dengan sendirinya, jadi sebuah job CI dapat mengarahkan file pengujian yang sama ke pemecah jarak jauh hanya dengan dua variabel dan tanpa perubahan kode. Aktifkan validasi key begitu pemecah mendengarkan di alamat jaringan, dan beri setiap runner key-nya sendiri agar satu key bisa dicabut tanpa mengganggu yang lain. Kedua mode dibahas lengkap di panduan penyiapan CapSkip.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| Gagal membuat Worker: skrip tidak dapat diakses dari origin | Proxy hammerhead ada di jalurnya, sehingga origin halaman bukanlah situs tersebut | Hapus flag disable-native-automation dan jalankan di Chrome atau Edge |
| Setiap skor v3 kembali sebagai 0,1 | Penyebab yang sama. Proxy mematok skornya apa pun yang dilakukan pengujian | Perbaikan yang sama. Native automation menghilangkan proxy sepenuhnya |
| ReferenceError yang menyatakan token tidak terdefinisi | Badan ClientFunction tidak dapat membaca variabel dari scope luar | Kirimkan token sebagai argumen atau sebagai dependency yang dideklarasikan |
| Aksi pengetikan gagal pada field respons | Textarea-nya tersembunyi, dan aksi membutuhkan elemen yang terlihat | Sebagai gantinya, atur nilainya di dalam sebuah ClientFunction |
| Formulir menolak token yang tampak baik-baik saja | Pemecahan dilakukan di dalam hook, beberapa menit sebelum pengiriman | Pecahkan di dalam pengujian, tepat sebelum mengirim |
| NetworkException | CapSkip tidak berjalan, atau host-nya salah | Jalankan aplikasinya, atau arahkan host ke alamat server |
| TimeoutException | Proses pemecahan melampaui recaptchaTimeout | Naikkan di atas nilai default 300 detik |
| ValidationException | Sitekey atau URL halaman yang hilang atau salah bentuk | Catat keduanya sebelum pemanggilan dan pastikan sitekey-nya adalah yang aktif |
FAQ
Apakah saya perlu task atau plugin seperti pada Cypress?
Tidak. Cypress menjalankan kode pengujian Anda di dalam browser, jadi apa pun yang membutuhkan Node harus melewati sebuah bridge. TestCafe menjalankan kode pengujian Anda di Node sejak awal dan hanya badan ClientFunction yang dikirim ke browser, jadi pemanggilan pemecah cukup berupa impor biasa. Versi Cypress dari pekerjaan ini ditulis di panduan CAPTCHA Cypress.
Bisakah saya melakukan ini di Firefox atau Safari?
Anda bisa menjalankan pengujiannya, tetapi bersiaplah widget-nya sendiri berperilaku aneh, karena TestCafe kembali memakai proxy di browser tersebut dan pada konfigurasi itulah reCAPTCHA tidak bertahan. Jalankan pengujian yang memuat CAPTCHA di Chrome atau Edge, dan biarkan matriks lintas browser mencakup halaman yang tidak punya widget.
Apakah ini juga bekerja untuk Turnstile?
Ya, dengan dua perbedaan. Metodenya adalah turnstile, bukan recaptcha, dan field yang harus diisi adalah input tersembunyi bernama cf-turnstile-response. Halaman challenge penuh juga membutuhkan nilai data dan pagedata ditambah user agent yang dikembalikan bersama token, yang dibahas di halaman pemecah Cloudflare Turnstile.
Haruskah pengujian CAPTCHA dijalankan pada setiap commit?
Biasanya tidak, dan alasannya kecepatan, bukan biaya. Pemecahan di sini tanpa batas, tetapi puluhan detik per pengujian membuat pemeriksaan pull request menjadi lambat. Beri tag pada pengujian tersebut dan jalankan pada job nightly atau pra rilis, lalu arahkan suite cepat Anda ke build yang memakai test key.
Versi singkatnya
Pastikan native automation aktif, karena proxy lama merusak reCAPTCHA dengan sendirinya. Baca sitekey dengan sebuah Selector, panggil pemecah dari file pengujian karena file itu sudah berjalan di Node, dan tulis token melalui sebuah ClientFunction dengan nilainya dikirim sebagai argumen. Pecahkan tepat sebelum Anda mengirim.
Sisa dari cakupan Node.js dibahas di halaman pemecah CAPTCHA Node.js. Semua yang khusus untuk tipe CAPTCHA tersebut ada di halaman pemecah reCAPTCHA v2. Pekerjaan yang sama pada runner berbasis WebDriver ditulis di panduan WebdriverIO.
Satu hal terakhir sebelum Anda memasang ini ke CI. CapSkip adalah pemecah captcha lokal yang berjalan di perangkat keras yang sudah Anda miliki, jadi suite nightly yang memecahkan seribu berbiaya sama dengan suite yang memecahkan sepuluh.
