先把不稳定测试按症状分层
拿到一个时好时坏的端到端测试,第一件事不是改代码,而是先给它定层。大多数不稳定都落在四类里,把层定对,后面的排查才有方向。
环境层:依赖服务没起来、测试账号额度用尽、缓存被上一轮污染。时序层:等待条件写得过松或过紧、异步任务尚未完成就断言、加载动画遮住了可点击元素。数据层:用例之间共用账号、上一轮残留订单、随机输入撞上边界值。断言层:把当前时间、随机标识、未排序的结果当成固定值比对。
- 只在某一台机器上失败,多半指向环境层。
- 重跑立刻通过,换台机器又失败,多半指向时序层或数据层。
- 白天稳定、夜里失败,多半与时间、时区或并发有关。
这四层之间也会互相伪装,比如环境里的慢启动看起来像时序问题,所以定层时一定要拿证据说话,而不是凭感觉。
分层之后把结论写进测试注释或工单,下一位接手的人不必从零猜起。
用最小可复现集合钉死变量
不稳定最耗人的地方是复现,所以要先把变量钉死。没有固定变量的排查,等于在沙子上盖房子。
- 关闭并行执行,用单线程跑一遍完整套件,确认失败是否依然出现。
- 固定随机种子与时间输入,排除输入漂移带来的干扰。
- 每一轮跑完都清理账号、订单与缓存数据。
- 把浏览器控制台、网络请求与服务端日志一并落盘。
- 记录失败发生的轮次、用例顺序与当时的并发情况。
复现时还要注意顺序依赖:如果失败只在完整套件里出现,而单跑通过,多半是前序用例留下的数据或登录状态在作怪。把可疑用例前后各抽两个,缩小到最小可复现集合,比整套重跑要高效得多。
一旦某次失败可以稳定复现,它就不再是一个不稳定问题,而是一个普通的缺陷,普通的缺陷是可以被修复、被验证、被写进回归清单的。
如果连跑很多轮仍无法复现,就把它标记为观察项,转而去收紧等待条件与断言口径,而不是无限期地追下去。
重跑优先与确定性优先的取舍
面对一条不稳定的用例,团队通常会走出两条路线。重跑优先是失败就再跑一次,通过就先合并,短期成本最低,也不需要动脑。
但重跑优先的问题会像滚雪球一样留在流水线里,绿灯的含义逐渐变成碰运气,最后没人敢真正相信自动化结果。它还会掩盖真实缺陷,偶发的竞态或空指针被重跑盖住之后,可能一直潜伏到线上才爆发。
确定性优先是每次失败都定位到根因,把等待条件、数据隔离、断言口径一一改到位。前期投入更大,但每修一个,套件就稳定一分。
更实用的做法是把两者拼起来:用重跑做保护网,用确定性优先做修复标准。合并前允许一次受控重跑,但同一条用例在一周内第二次失败,就必须进入根因分析。
把这条规则写进仓库的贡献说明,让编码代理和人类工程师按同一套标准执行,避免每个人各有一套判断。
给编码代理一份固定的排查流程
编码代理很适合当第一响应人:它不怕重复执行,也擅长把散落的日志整理成结构化报告。真正决定效果的,是给它一份固定的例行流程,而不是一句模糊的帮我修好。
- 先运行失败用例,保存原始输出与环境信息。
- 按环境、时序、数据、断言四层分类,并给出对应证据。
- 列出三个最可能的根因,以及各自的验证方式。
- 每次只改动一个假设,重跑验证后再进入下一个。
- 输出改动清单、验证结果与剩余风险。
把这份流程写成一段可复用的提示词模板,放进仓库文档里,每次开新会话直接引用,代理就不会因为措辞不同而给出质量参差的报告。
工具链上,SwiftRelay 这种按 token 计费、用多少付多少的中转,很适合这种反复试错的节奏。它支持 Claude Code、Codex、Gemini CLI 以及任何 OpenAI 兼容 SDK,Claude Code 与 Gemini CLI 填主机地址即可,OpenAI 兼容 SDK 在其后追加 /v1,主机是 https://api.fastapirelay.com。
模型选择上,日常整理日志、改写测试用 DeepSeek V4 Flash 已经足够;碰到跨服务的时序问题,再换 Claude Sonnet 5.5 这类擅长长程推理的模型,把预算花在真正难啃的地方。
新账号会得到一笔小额赠金,邀请好友还能拿到对方充值金额 14% 的返佣。现在就可以注册 SwiftRelay,把第一圈排查跑起来。
绿灯亮起前的合并检查清单
收尾是一张短清单,全部通过再点合并。
- 同一条用例连续多轮通过,且全程没有依赖重跑。
- 测试数据在每一轮开始前已被清理干净。
- 等待条件挂在元素真正可交互上,而不是固定睡眠。
- 断言中不包含时间、随机标识与未排序集合。
- 新增日志足够定位下一次失败,而不是只输出一句超时。
- 相关文档、注释与排查清单同步更新。
清单的价值在于可执行,每一项都要能用是或否回答,模糊的条目等于没有条目。
最后给出明确推荐:以确定性优先为主、受控重跑为辅,把编码代理当作第一响应人,人类工程师负责审核根因并决定是否合并。
一套稳定的端到端测试靠的从来不是运气,而是一份每次都照做的例行流程。绿灯亮起的时候,你可以解释它为什么是绿的。
把每次排查沉淀成防复发清单
修完一个不稳定用例,工作并没有结束。真正让套件变稳的,是把这次的教训固化下来。
- 在用例旁边补一条注释,说明它曾经在哪一层失败。
- 把可复用的等待与清理工具抽成公共方法,避免下一个人再写一遍。
- 为这次根因补一条最小回归用例,锁住修复本身。
- 把高频失败模式整理进团队的排查清单,下次先照着跑。
当这类沉淀积累到一定数量,编码代理的例行流程会越来越快,因为它可以先对照历史清单做排除,而不是每次都从头猜测。
这一步常常被跳过,但它决定了三个月后这套流程是资产还是负担。