让 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,同时又不必在每次提交时都消耗求解次数。当定时运行失败时通知团队,同时把每次提交运行的测试套件保持在更快的选项上。