Scarnox · Blog

让 CI 保持通过:应用中有验证码时的端到端测试

你的注册表单带有 PopularCaptcha,而你的端到端测试套件需要完成注册。于是 CI 挂了。此时最容易想到的办法是在测试构建中关闭验证码,但这意味着你测试的流程并不是用户实际使用的流程。

有几种方法可以解决这个问题。它们在多大程度上覆盖真实流程各不相同,因此请根据你的测试所要回答的问题来选择。下面的选项大致按从最低成本到最接近真实的顺序排列。

Scarnox 目前支持 PopularCaptcha 的求解。其他验证码类型即将推出,因此下面的求解方案仅适用于 PopularCaptcha。

1. 用于单元测试和本地测试的测试密钥

许多验证码服务商都会公开始终通过校验的测试密钥。当你控制测试环境,并且只需要确认表单能够渲染和提交时,可以使用它们。测试密钥不会执行真实的校验,一旦你测试的是使用真实密钥的已部署环境,它们就无济于事了。

2. 为预发布环境设置的绕过机制

一些团队会添加一个密钥请求头,后端接受它来代替验证码 token。这种方式快速且免费,但它是一条仅为测试而存在的代码路径,距离出现在生产环境中只差一次配置错误。如果你使用它,请通过环境检查来限制其启用范围,使用足够长且随机的密钥,并测试它在生产环境中会被拒绝。

3. 模拟校验调用

在集成测试中桩化服务端的校验请求。这种方式适合检查后端逻辑,例如校验失败时会发生什么。但它无法告诉你前端是否正确发送了 token,因此应当与其他选项之一搭配使用。

4. 在预发布环境和冒烟测试中真实求解

当你需要证明真实流程端到端都能正常工作时,即使用真实密钥、真实组件和真实校验,也可以在测试中从验证码求解 API 获取 token。这是唯一能够发现组件集成损坏的方法。

下面是一个 Playwright 辅助函数示例:

// Playwright 测试辅助函数
export async function solveCaptcha(page) {
  const sitekey = await page.getAttribute("[data-sitekey]", "data-sitekey");
  const r = await fetch("https://api.scarnox.com/api/tasks/create", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SCARNOX_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      captcha_type: "PopularCaptchaTaskProxyless",
      site_key: sitekey,
      site_url: page.url(),
    }),
  });
  const { errorId, solution, errorCode, errorDescription } = await r.json();
  if (errorId !== 0) throw new Error(`${errorCode}: ${errorDescription}`);

  await page.evaluate((t) => {
    document.querySelectorAll('[name="h-captcha-response"],[name="g-recaptcha-response"]')
      .forEach((el) => { el.value = t; });
  }, solution.token);
}

请在测试中表单提交之前立即调用它,而不是在测试开始时调用。token 会在约两分钟内过期,测试中较早的一个缓慢步骤就可能耗尽这段时间。

尽量减少求解次数

每次运行都注册的测试套件,一周下来可能会产生大量求解。请控制求解数量:

关于辅助函数内部的重试规则,可以沿用错误处理与退避中的策略。

将密钥保存在代码仓库之外

将密钥保存为 CI 的 secret,例如命名为 SCARNOX_KEY,并且不要把它放进测试代码或测试夹具中。如果密钥泄露,请在控制台中重新生成。旧密钥会立即失效,因此泄露的密钥无法长时间用于执行求解。

为 CI 使用单独的密钥。如果某条流水线出现异常,你可以轮换该密钥,而不影响其他任何环境。

在你自己的预发布环境中试用

注册账户,即可获得免费试用额度,无需信用卡。先将辅助函数接入一个冒烟测试,确认它能够顺利完成注册,然后再把它加入套件的其余部分。如需了解大规模求解的费用,请查看当前定价。

提交之后应断言什么

验证码成功求解,只能证明组件这一步是成功的。提交之后,应当断言对用户真正重要的内容:下一个页面加载成功,显示成功提示,并且数据库中存在对应的账户或订单。只检查“表单没有报错”的测试,可能会在后端注册悄悄失败的情况下依然通过。

断言应尽量贴近业务结果:检查 URL、可见的提示信息,以及测试创建的那一条记录。在运行结束时清理该记录,使下次运行从已知的状态开始。

按计划运行冒烟测试

将使用真实求解的冒烟测试安排为定时任务,例如每天针对预发布环境运行一次,而不是在每个 Pull Request 上运行。定时运行可以发现网站在夜间做出的变更,例如新的组件版本或变动后的 sitekey,同时又不必在每次提交时都消耗求解次数。当定时运行失败时通知团队,同时把每次提交运行的测试套件保持在更快的选项上。

下一步