验证码会阻止自动化 E2E 测试。在暂存阶段禁用它们会导致环境漂移 - 仅出现在验证码处于活动状态的生产中的错误。CaptchaAI 让您的赛普拉斯测试与真实的验证码交互,保持测试环境与生产相同。
为什么不在测试中禁用验证码?
| 方法 | 风险 |
|---|---|
| 在暂存中禁用验证码 | 错过集成错误、形成流程差异 |
| 使用测试键(始终通过) | 不测试token 提交、回调处理 |
| 用CaptchaAI解决 | 全面的生产平价测试 |
设置
npm install cypress --save-dev
赛普拉斯配置
// 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",
},
});
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 };
连接到 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);
},
});
},
},
});
自定义命令
// 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;
});
});
});
});
});
端到端测试示例
使用 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");
});
});
注册流程
// 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");
});
});
旋转栅门保护结账
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");
});
});
重试和错误处理
// 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);
});
CI/CD 集成
GitHub 操作
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
玩笑集成测试
// For teams that also use Jest for API-level CAPTCHA tests
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);
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
cy.task timed out |
验证码解决时间太长 | 配置中增加taskTimeout |
| 令牌被拒绝 | 注射前已过期 | 减少解决和提交之间的延迟 |
data-sitekey 未找到 |
验证码动态加载 | 添加显式 cy.wait() 或拦截 |
| 回调未触发 | 自定义回调名称 | 在 DevTools 中检查 ___grecaptcha_cfg |
| CI 失败,本地通过 | 缺少环境变量 | 将 CAPTCHAAI_KEY 添加到 CI 机密 |
常问问题
这会减慢我的测试套件的速度吗?
每个验证码解决都会增加 15-30 秒。在单独的套件中运行 CAPTCHA 测试或与 Cypress Cloud 并行运行。
我可以将其用于赛普拉斯组件测试吗?
否 – 组件测试不加载真实页面。仅将此用于使用真实验证码命中完整页面 URL 的 E2E 测试。
我应该使用真实的验证码进行测试还是模拟它们?
在分阶段 E2E 中使用真实的验证码进行测试。在单元测试中使用模拟。这确保了完全的生产平价。
CaptchaAI 是否可以与 Cypress Cloud 并行化配合使用?
是的。每个并行机器调用相同的 API 密钥。 CaptchaAI 处理并发请求。
相关指南
- Puppeteer隐身 + CaptchaAI
- Node.js 验证码解决 API
后续阅读
- CaptchaAI 快速上手:5 分钟解决你的第一个验证码
- 使用 API 解决 reCAPTCHA v2:分步实战指南
- 如何使用 API 解决 Cloudflare Turnstile
- 如何使用 API 解决 GeeTest v3 问题