Tích Hợp

Cypress + CaptchaAI: Thử nghiệm E2E bằng cách xử lý CAPTCHA

CAPTCHA chặn các bài kiểm tra E2E tự động. Việc vô hiệu hóa chúng trong quá trình chạy sẽ gây ra hiện tượng lệch môi trường — các lỗi chỉ xuất hiện trong quá trình sản xuất khi CAPTCHA đang hoạt động. CaptchaAI cho phép các bài kiểm tra Cypress của bạn tương tác với CAPTCHA thực, giữ cho môi trường kiểm tra giống hệt với môi trường sản xuất.


Tại sao không tắt CAPTCHA trong các bài kiểm tra?

Cách tiếp cận Rủi ro
Vô hiệu hóa CAPTCHA trong giai đoạn Thiếu lỗi tích hợp, sự khác biệt về dòng chảy biểu mẫu
Sử dụng các phím kiểm tra (luôn vượt qua) Không kiểm tra việc chèn mã thông báo, xử lý gọi lại
Giải bằng CaptchaAI Thử nghiệm tương đương sản xuất đầy đủ

thiết lập

npm install cypress --save-dev

Cấu hình cây bách

// 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",
  },
});

Trình xử lý tác vụ 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 };

Nối dây vào 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);
        },
      });
    },
  },
});

Lệnh tùy chỉnh

// 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;
        });
      });
    });
  });
});

Ví dụ về thử nghiệm E2E

Luồng đăng nhập bằng 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");
  });
});

Luồng đăng ký

// 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");
  });
});

Thanh toán được bảo vệ bằng cửa quay

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");
  });
});

Thử lại và xử lý lỗi

// 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);
});

Tích hợp CI/CD

Hành động 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

Kiểm tra tích hợp Jest

// 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);

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
cy.task timed out Quá trình giải CAPTCHA mất quá nhiều thời gian Tăng taskTimeout trong cấu hình
Mã thông báo bị từ chối Hết hạn trước khi tiêm Giảm độ trễ giữa giải quyết và gửi
Không tìm thấy data-sitekey CAPTCHA tải động Thêm cy.wait() rõ ràng hoặc chặn
Cuộc gọi lại không được kích hoạt Tên gọi lại tùy chỉnh Kiểm tra ___grecaptcha_cfg trong DevTools
CI không thành công, vượt qua cục bộ Thiếu biến env Thêm CAPTCHAAI_KEY vào bí mật CI

Câu hỏi thường gặp

Điều này có làm chậm bộ thử nghiệm của tôi không?

Mỗi lần giải CAPTCHA sẽ cộng thêm 15-30 giây. Chạy thử nghiệm CAPTCHA trong một bộ riêng biệt hoặc song song với Cypress Cloud.

Tôi có thể sử dụng tính năng này với thử nghiệm thành phần Cypress không?

Không - kiểm tra thành phần không tải các trang thực. Chỉ sử dụng tùy chọn này cho các thử nghiệm E2E đạt được URL toàn trang có CAPTCHA thực.

Tôi nên kiểm tra bằng CAPTCHA thật hay thử nghiệm chúng?

Kiểm tra bằng CAPTCHA thực trong giai đoạn E2E. Sử dụng mô phỏng trong các bài kiểm tra đơn vị. Điều này đảm bảo sự cân bằng sản xuất đầy đủ.

CaptchaAI có hoạt động song song với Cypress Cloud không?

Vâng. Mỗi máy song song gọi cùng một khóa API. CaptchaAI xử lý các yêu cầu đồng thời.


Hướng dẫn liên quan



Bước tiếp theo

Os comentários estão desativados para este artigo.