Flaky 测试(或不稳定测试)是指在相同代码和相同配置下产生不同结果——通过或失败——而被测系统未做任何修改的测试。
这里有一个可能让你不舒服的观点:一个 flaky 的视觉测试比没有测试更糟糕。这不是故意的挑衅。一个缺失的测试日常不花费任何成本。它不阻塞你的 pipeline。它不消耗你的团队时间。它不破坏对测试基础设施的信心。而 flaky 测试每天都在做这些事,而且是悄无声息地——因为每次假失败看起来都足够像真失败,以至于需要进行调查。
Google 关于自身测试系统的数据很说明问题:在任何给定时刻,大约 1.5% 的测试是 flaky 的,但这些测试消耗了不成比例的工程时间。如果一家像 Google 这样规模和技术实力的公司都没有消除这个问题,那这就不是一个简单的问题。而在视觉测试这个特定领域,由于被比较对象本身的性质,这个问题被进一步放大。
为什么视觉测试特别容易不稳定
自动化视觉测试引入了单元测试或功能测试所没有的非确定性层。单元测试验证一个函数返回正确的值。功能测试验证一个按钮触发正确的动作。这些结果是二元的、确定性的。
视觉测试验证的是你的界面渲染是否与参考图像匹配。而网页的渲染是一系列复杂过程的结果,每个过程都引入了自己的变异性:HTML 解析、CSS 应用、JavaScript 执行、外部资源加载、布局计算、光栅化、合成。
Flaky 视觉测试的四个主要原因
时序:无法忽视的问题
Web 本质上是异步的。当你要求浏览器截取一张截图时,页面真的准备好了吗?答案几乎永远是:看情况。
页面加载不是一个单一事件——它是一连串事件的级联。HTML 被解析,CSS 被应用,脚本被执行,图片被加载,Web 字体被应用,API 请求返回数据。每一步都有可变的持续时间。等待"就绪"的经典策略——DOMContentLoaded、load 事件或 network idle——都不能保证视觉渲染已经完成。
结果:你的截图有时捕获的是一个完整的页面,有时是一个正在渲染中的页面。
动画:静态媒介中的运动
截图是一张固定图像。动画是持续的变化。两者对自动化比较来说根本不兼容。如果你的页面包含一个在加载时启动的 300ms 动画,相对于动画开始的精确捕获时刻在不同运行之间会有所不同。
无限循环动画(加载旋转器、骨架屏加载器)更糟:不存在一个"稳定"的时刻来截取截图。
动态内容:所有不受你控制的变化
日期、时间、广告、随机生成的头像、实时通知、访客计数器——这些在测试运行之间全部不同。每一个变化都被检测为视觉差异。每一个差异都会导致测试失败。
网络和基础设施:你无法控制的变量
你的测试运行在一个依赖外部资源的环境中:API 服务器、图片和字体的 CDN、第三方服务。延迟在运行之间有所不同。在 CI/CD pipeline 中,这个问题被进一步放大——你的 CI runner 与其他作业共享资源。
Flaky 测试的真实成本
最显眼的成本是分类调查时间。每次 flaky 测试失败都需要调查:有人需要查看结果、手动比较、判断是否为真实问题,并在需要时重新运行。
但最具破坏性的成本是不可见的:信心的丧失。当团队得知视觉测试"总是"无故失败时,他们会养成自动重新运行的条件反射。而当某天测试因为一个真实的 bug 而失败时,条件反射是一样的:重新运行。于是 bug 就进入了生产环境。
这个现象有一个名字:"狼来了效应"。一旦形成,就很难逆转。
有效的稳定化策略
控制渲染环境
在受控容器中使用 headless 浏览器,固定分辨率,预装字体,配置确定性网络。锁定浏览器版本,禁用 GPU 渲染,配置固定的视口尺寸。
中和动画
注入一个样式表,强制所有动画和过渡的持续时间为零。这会立即将所有动画元素冻结在它们的最终状态。
稳定动态内容
固定日期和时间,禁用第三方小部件,mock API 数据,在测试固件中将生成的头像替换为静态图片。目标是创造一个唯一变量就是你界面代码的环境。
智能等待
不要使用固定延迟(等待 3 秒),而是使用基于状态的等待策略。等待关键元素可见、图片加载完成、字体应用完毕、网络请求全部完成。
采用能容忍噪音的比较方式
逐像素比较对渲染非确定性最为敏感。感知算法(SSIM、pHash)更为宽容。结构化方法——比较 DOM 和计算后的 CSS 属性而非像素——对渲染噪音最具抵抗力,因为它原生忽略了导致大多数间歇性失败的亚像素变化。
No-code 视觉测试作为维护问题的解决方案
基于代码的视觉测试(Playwright、Cypress、Selenium)需要编写脚本来导航、交互和截图。这些脚本本身就是一个不稳定性来源:一个不再能找到元素的 CSS 选择器、一个错过目标的点击时序。
像 Delta-QA 这样的 no-code 工具消除了这层脆弱性。你不编写脚本——而是可视化地配置测试。工具负责加载、等待、稳定化和比较。当元素的选择器发生变化时,工具会自动适应而无需人工干预。
何时删除一个 flaky 测试
如果一个视觉测试在所有稳定化尝试后仍然间歇性失败,最勇敢——也往往最明智——的决定是删除它。一个没有人修复的 flaky 测试正在主动恶化你的 pipeline。它在训练你的团队忽视失败。
删除它,记录原因,然后替换为一个更有针对性的检查。目标不是拥有最多的测试——而是拥有你的团队信任的测试。
常见问题
Flaky 测试和误报有什么区别?
误报信号一个不存在的问题——单次发生就足够了。Flaky 测试在相同代码的不同运行之间产生不一致的结果。Flaky 测试会间歇性地产生误报。
如何测量视觉测试的 flakiness 率?
在不更改代码的情况下多次运行同一视觉测试套件,统计结果在运行之间发生变化的测试。连续五次运行足以识别最不稳定的测试。
No-code 视觉测试是否比编码测试更少 flaky?
它们消除了一类 flakiness——测试脚本脆弱性(脆弱的选择器、导航时序、状态管理)。但它们仍然受制于相同的浏览器渲染约束。
应该自动重试失败的视觉测试吗?
重试是创可贴,不是解决方案。它掩盖了问题。如果你必须启用重试,限制为一次重试,并标记需要重试的测试以供调查。
CI/CD 中可接受的 flakiness 阈值是多少?
目标应低于测试套件总数的 1%。超过 3%,生产力影响就会变得可衡量。超过 5%,你的团队几乎肯定会养成系统性重新运行失败 pipeline 的习惯。
Delta-QA 能帮助稳定视觉测试吗?
Delta-QA 通过使用结构化方法而非逐像素比较,从源头减少 flakiness。导致大多数间歇性失败的亚像素渲染变化、抗锯齿问题和时序问题被原生忽略。结合消除脆弱测试脚本的 no-code 方法,Delta-QA 无需复杂配置即可产生可靠且可重现的测试结果。
延伸阅读
视觉测试只有在你的团队信任它时才有价值。 Flaky 测试日复一日地摧毁这种信任。与其花时间稳定脆弱的脚本和甄别假失败,不如采用一个从一开始就为可靠性而设计的工具。