CAPTCHA memblokir pengujian E2E otomatis. Menonaktifkannya di staging menciptakan perbedaan lingkungan — bug yang hanya muncul di produksi saat CAPTCHA aktif. CaptchaAI memungkinkan tes Cypress Anda berinteraksi dengan CAPTCHA asli, menjaga lingkungan pengujian tetap identik dengan produksi.
Mengapa Tidak Menonaktifkan CAPTCHA Saja dalam Pengujian?
| Pendekatan | Risiko |
|---|---|
| Nonaktifkan CAPTCHA di staging | Melewatkan bug integrasi, membentuk perbedaan alur |
| Gunakan test key (selalu lulus) | Tidak menguji inject token, penanganan callback |
| Solve dengan CaptchaAI | Paritas penuh dengan produksi |
Pengaturan
npm install cypress --save-dev
Konfigurasi Cypress
// cypress.config.js
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
baseUrl: "https://your-app.com",
defaultCommandTimeout: 120000,
responseTimeout: 120000,
setupNodeEvents(on, config) {
on("task", {
solveCaptcha({ siteUrl, sitekey, type }) {
return solveCaptchaTask(siteUrl, sitekey, type);
},
});
return config;
},
},
env: {
CAPTCHAAI_KEY: "YOUR_API_KEY",
},
});
Task Handler CaptchaAI
// cypress/plugins/captcha-solver.js
const https = require("https");
function httpPost(url, data) {
return new Promise((resolve, reject) => {
const params = new URLSearchParams(data).toString();
const options = {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
};
const req = https.request(url, options, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
});
req.on("error", reject);
req.write(params);
req.end();
});
}
function httpGet(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve(JSON.parse(body)));
}).on("error", reject);
});
}
async function solveCaptchaTask(siteUrl, sitekey, type = "recaptcha_v2") {
const API = "https://ocr.captchaai.com";
const key = process.env.CAPTCHAAI_KEY || "YOUR_API_KEY";
const submitData = {
key,
pageurl: siteUrl,
json: "1",
};
if (type === "turnstile") {
submitData.method = "turnstile";
submitData.sitekey = sitekey;
} else {
submitData.method = "userrecaptcha";
submitData.googlekey = sitekey;
}
const submitResp = await httpPost(`${API}/in.php`, submitData);
if (submitResp.status !== 1) {
throw new Error(`Submit failed: ${submitResp.request}`);
}
const taskId = submitResp.request;
// Poll for result
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 5000));
const params = new URLSearchParams({
key,
action: "get",
id: taskId,
json: "1",
});
const result = await httpGet(`${API}/res.php?${params}`);
if (result.request === "CAPCHA_NOT_READY") continue;
if (result.status !== 1) throw new Error(`Solve failed: ${result.request}`);
return result.request; // The CAPTCHA token
}
throw new Error("CAPTCHA solve timeout");
}
module.exports = { solveCaptchaTask };
Hubungkan ke cypress.config.js
// cypress.config.js
const { solveCaptchaTask } = require("./cypress/plugins/captcha-solver");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on("task", {
solveCaptcha({ siteUrl, sitekey, type }) {
return solveCaptchaTask(siteUrl, sitekey, type);
},
});
},
},
});
Perintah Khusus
// cypress/support/commands.js
Cypress.Commands.add("solveCaptcha", (options = {}) => {
cy.get("[data-sitekey]", { timeout: 10000 }).then(($el) => {
const sitekey = options.sitekey || $el.attr("data-sitekey");
const siteUrl = options.siteUrl || cy.url();
cy.url().then((url) => {
cy.task("solveCaptcha", {
siteUrl: url,
sitekey,
type: options.type || "recaptcha_v2",
}).then((token) => {
// Inject token
cy.window().then((win) => {
const responseEl = win.document.querySelector(
"#g-recaptcha-response"
);
if (responseEl) {
responseEl.value = token;
}
// Set all hidden response fields
win.document
.querySelectorAll('[name="g-recaptcha-response"]')
.forEach((el) => {
el.value = token;
});
// Trigger callback if exists
if (win.___grecaptcha_cfg) {
const clients = win.___grecaptcha_cfg.clients;
for (const key in clients) {
const client = clients[key];
if (client && typeof client.callback === "function") {
client.callback(token);
}
}
}
});
});
});
});
});
Cypress.Commands.add("solveTurnstile", (options = {}) => {
cy.get("[data-sitekey]", { timeout: 10000 }).then(($el) => {
const sitekey = options.sitekey || $el.attr("data-sitekey");
cy.url().then((url) => {
cy.task("solveCaptcha", {
siteUrl: url,
sitekey,
type: "turnstile",
}).then((token) => {
cy.window().then((win) => {
const input = win.document.querySelector(
'input[name="cf-turnstile-response"]'
);
if (input) input.value = token;
});
});
});
});
});
Contoh Tes E2E
Alur Login dengan reCAPTCHA
// cypress/e2e/login.cy.js
describe("Login with reCAPTCHA", () => {
it("should log in through a CAPTCHA-protected form", () => {
cy.visit("/login");
cy.get("#username").type("testuser");
cy.get("#password").type("securepassword123");
// Solve the CAPTCHA
cy.solveCaptcha();
// Submit
cy.get('button[type="submit"]').click();
// Verify login success
cy.url().should("include", "/dashboard");
cy.get(".welcome-message").should("contain", "Welcome, testuser");
});
});
Alur Pendaftaran
// cypress/e2e/register.cy.js
describe("Registration with CAPTCHA", () => {
it("completes registration with all fields + CAPTCHA", () => {
cy.visit("/register");
cy.get("#first-name").type("Test");
cy.get("#last-name").type("User");
cy.get("#email").type("test@example.com");
cy.get("#password").type("StrongPass!123");
cy.get("#confirm-password").type("StrongPass!123");
cy.solveCaptcha();
cy.get("#register-btn").click();
cy.url().should("include", "/verify-email");
});
});
Alur Checkout dengan Turnstile
describe("Checkout with Turnstile", () => {
it("processes payment through Turnstile-protected checkout", () => {
cy.visit("/cart");
cy.get(".checkout-btn").click();
cy.get("#card-number").type("4242424242424242");
cy.get("#expiry").type("12/26");
cy.get("#cvc").type("123");
cy.solveTurnstile();
cy.get("#pay-now").click();
cy.get(".confirmation").should("contain", "Order confirmed");
});
});
Retry dan Error Handling
// cypress/support/commands.js
Cypress.Commands.add("solveCaptchaWithRetry", (options = {}) => {
const maxRetries = options.retries || 3;
function attempt(retryCount) {
return cy.task("solveCaptcha", {
siteUrl: options.siteUrl,
sitekey: options.sitekey,
type: options.type || "recaptcha_v2",
}).then((token) => {
if (!token && retryCount < maxRetries) {
cy.log(`CAPTCHA retry ${retryCount + 1}/${maxRetries}`);
cy.wait(2000);
return attempt(retryCount + 1);
}
return token;
});
}
return attempt(0);
});
Integrasi CI/CD
GitHub Actions
name: E2E Tests
on: [push, pull_request]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Run Cypress tests
uses: cypress-io/github-action@v6
env:
CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
with:
wait-on: "http://localhost:3000"
start: npm start
Tes Integrasi Jest
// Untuk tim yang juga menggunakan Jest untuk tes CAPTCHA di level API
const { solveCaptchaTask } = require("../cypress/plugins/captcha-solver");
test("CaptchaAI solves reCAPTCHA v2", async () => {
const token = await solveCaptchaTask(
"https://www.google.com/recaptcha/api2/demo",
"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
"recaptcha_v2"
);
expect(token).toBeDefined();
expect(token.length).toBeGreaterThan(50);
}, 120000);
Pemecahan Masalah
| Masalah | Penyebab | Perbaikan |
|---|---|---|
cy.task timed out |
Solve CAPTCHA terlalu lama | Tingkatkan taskTimeout di config |
| Token ditolak | Kedaluwarsa sebelum inject | Kurangi delay antara solve dan submit |
data-sitekey tidak ditemukan |
CAPTCHA dimuat secara dinamis | Tambahkan cy.wait() eksplisit atau intercept |
| Callback tidak terpicu | Nama callback kustom | Periksa ___grecaptcha_cfg di DevTools |
| CI gagal, lokal oke | Environment variable tidak ada | Tambahkan CAPTCHAAI_KEY ke secret CI |
Pertanyaan Umum
Apakah ini akan memperlambat test suite saya?
Setiap solve CAPTCHA menambah 15–30 detik. Jalankan tes CAPTCHA di suite terpisah atau paralelkan dengan Cypress Cloud.
Bisakah saya menggunakan ini dengan Cypress component testing?
Tidak — component testing tidak memuat halaman nyata. Gunakan ini hanya untuk E2E test yang mengakses URL halaman penuh dengan CAPTCHA asli.
Haruskah saya menguji dengan CAPTCHA asli atau mock?
Uji dengan CAPTCHA asli di staging E2E. Gunakan mock di unit test. Ini memastikan paritas penuh dengan produksi.
Apakah CaptchaAI berfungsi dengan paralelisasi Cypress Cloud?
Ya. Setiap mesin paralel memanggil API key yang sama. CaptchaAI menangani request concurrent.
Panduan Terkait
- Puppeteer + CaptchaAI untuk QA
- CaptchaAI Quickstart
- Cara Solve reCAPTCHA v2 dengan API
- Cara Solve Cloudflare Turnstile dengan API