像素级稳定的截图
确定性渲染,等待字体加载、固定动画、遮罩动态内容 — 让 diff 反映真实回归,而不是噪声。
ScanU 是什么
ScanU 把发布质量的最后一公里 — 也就是用户真正看到的部分 — 变成团队可以据此行动的信号。它会对您指定的页面生成像素级稳定的截图,记录一份显式 baseline,此后每一次运行都会在那些真正重要的浏览器与设备上,与该 baseline 做比对。
大多数团队已经跑着 linter、类型检查、单元测试和端到端流程。 但这些都可能保持绿色 — 而与此同时,一个按钮却悄悄下移了 十二个像素,一个弹层丢了 padding,一个菜单在 Safari 上 错乱,或者一个移动端断点让 hero 区无声地重排。功能测试 验证的是行为,它们不会验证 页面长成什么样。这正是截图测试工具存在的意义, 也正是 ScanU 在没有任何仪式感的情况下所要做的事。
在底层,ScanU 驱动真实的渲染引擎 — Chromium、Gecko 和 WebKit — 访问您指定的 URL 或组件预览。每一次运行都会在 每对(浏览器,视口)配置上产生一张确定性的截图。第一次被 接受的运行会成为 baseline。从此,每一个 pull request、 每一次部署、每一次定时检查,都是针对这个 baseline 的 diff: 任何一处视觉回归都会以可审阅的变更形式被突显出来 — 就像 code review 会把一行代码的改动凸显出来一样。
完整的 比对流水线 负责处理那些让截图 diff 在生产中真正可用的细节: anti-aliasing 容差、动画稳定、字体加载、动态内容遮罩、 滚动截取以及感知 diff 阈值 — 一句话,不能让 sub-pixel 级 的 font hinting 变化淹没掉一处真正的布局回归。
ScanU 面向那些把 UI 视为与用户之间契约的团队。它既是视觉 回归测试工具,也是跨浏览器截图比对工具,又是 diff 审阅 界面 — 这些都集中在一个平台里 — 坐落于您的 build 与 release gate 之间。
确定性渲染,等待字体加载、固定动画、遮罩动态内容 — 让 diff 反映真实回归,而不是噪声。
Chromium、Gecko 与 WebKit 并行驱动,覆盖您的用户真正使用的桌面、平板与移动端视口。
baseline、当前截图、高亮 diff 三者并排。接受、拒绝或更新 baseline — 始终是显式动作,从不静默发生。
与您现有的流水线同跑。Status check、PR 评论与产物链接,出现在工程师本来就在做 code review 的地方。
把 ScanU 对准 Storybook、组件沙箱或线上 URL。Token 变动与组件重构在发布前就能获得一次诚实的视觉审计。
截图与元数据保存在欧洲基础设施上,数据处理遵循 GDPR — 适合对数据驻留有严格要求的团队。
为什么这件事重要
视觉 bug 的成本很少用开发时间来衡量。它衡量的是一种信任 — 当用户打开您的产品,看到某处微妙的不对劲,心里默默判定:这个团队不在意细节。这种信任很难再建,而几乎总是预防 bug 的成本更低。
功能测试能很好地回答一个问题:「点击按钮时,它做了正确的事吗?」 但对于点击之前发生的一切 — 按钮颜色是否正确、尺寸是否正确、 位置是否正确、在 360 像素屏幕上是否仍然可读、在最近一次 CSS 重构之后是否还真的可见 — 它们是沉默的。绝大多数发布 当天的事故,就住在这条缝隙里。
我们观察到的、也是 ScanU 专门为之设计的真实回归,会围绕 少数几种可预测的触发源聚集:
gap 塌陷。桌面端看起来一切如常;到了 768 像素 的平板上,两张卡片已经开始重叠。这些变化没有一个会让 assertion 变红。但每一个都在折损用户 体验。ScanU 的工作,就是让这第二类故障和一个失败的单元 测试一样容易被看见、被审阅、被拦下。当 diff 出现在 pull request 上,设计师就能像 reviewer 在函数签名上发表意见 一样介入 — 在合入之前,而不是等到它上线。
这正是关键的一步转变 — 从「希望有人在 staging 上会注意到」 走向「像审阅代码变更那样审阅视觉变更」— 也正是视觉回归 测试工具在现代发布流水线中的核心价值。
跨浏览器测试
跨浏览器测试并非 IE6 时代遗留下来的一道怀旧税。现代引擎 — Chromium、Gecko 与 WebKit — 仍然在细节上存在分歧,而您的布局正默默地依赖这些细节;许多线上事故,归根结底都能追溯到一处没人审计过的渲染差异。
当一处回归只在某一个引擎上出现,几乎总是意味着以下三种 之一:用到了某个特性,它在部分引擎中上线得比其他晚;某块 布局依赖于一个按平台变化的度量(scrollbar 宽度、字体 baseline、平滑算法);或者某个依赖 user-agent 的 polyfill 在线上的表现和在开发机上略有不同。三种情况对一套与引擎 无关的测试套件都是不可见的。
跨浏览器审计中常见的例子:
date、datetime-local、select — 在 WebKit 上渲染出的控件外观与 Chromium 上明显不同,偶尔会把相邻元素 挤开几像素。line-height 在 Chromium 上很平衡,到了 Gecko 可能就把 CTA 顶到首屏之下。gap、aspect-ratio 以及 container queries,在各引擎中落地的时间略有 差异。如果您的 build 以较旧的 baseline 为目标,fallback 路径的渲染效果可能与主路径并不相同。prefers-color-scheme、accent-color 以及 Windows 上的 forced-colors — 会以不同方式在各平台上透到 DOM,在本地开发环境中很容易被忽略。ScanU 会在您配置的所有引擎与视口上,用同样的 fixture 和 timing hook 在同一次运行中捕获每一个页面,从而让您看到的 diff 是真实的布局差异 — 而不是「每个浏览器跑在不同 harness 里」的副作用。完整的 跨浏览器能力 可以在产品页查看:您配置的是哪张矩阵,每次运行捕获的 就是哪张矩阵。
对于交付设计系统的团队,或任何把视觉识别视为品牌一部分 的产品,这张矩阵都不是锦上添花。它是唯一能够保证您的客户 在 Safari 上看到的就是您在 Chrome 上签收的那个状态的方法。
| 浏览器 | 视口 | 状态 |
|---|---|---|
| Chromium 124 | 1440 × 900 | 与 baseline 一致 |
| Firefox 126 | 1440 × 900 | 与 baseline 一致 |
| WebKit (Safari 17) | 1440 × 900 | 视觉 diff · 待审阅 |
| Chromium 124 | 768 × 1024 | 与 baseline 一致 |
| WebKit (Safari 17) | 768 × 1024 | 检测到布局偏移 |
| Chromium 124 | 390 × 844 | 新 baseline 待确认 |
截图比对是如何进行的
一个视觉 diff 工具有多有用,取决于它产出的 diff 在信号与噪声之间的比值。ScanU 的捕获流水线刻意如此设计:两次运行之间唯一改变的,就是您真正改过的东西 — 而不是它周围那一圈字体、动画、时间戳和广告位布景。
每一次运行都是一条三步流水线。首先,ScanU 会在每一个引擎中 打开待测页面或组件,等待一组已配置的 readiness 信号 — 字体已加载、图片已解码、网络已安静、存在一个 data-ready 属性,或者自定义的 JavaScript 探针 成功 — 然后把所有 CSS 动画固定在最终帧。第二步,它在同一 次运行中按每一个已配置的视口捕获页面;对长页面使用 scroll-stitching,这样您拿到的是完整的产物,而不是首屏。 最后一步,它把捕获结果和已接受的 baseline 做比对,并为每 一对(浏览器,视口)给出结果:完全一致、在容差范围内, 或存在显著差异。
比对本身并不是一个朴素的像素 diff。ScanU 采用了一种感知 模型,它理解 anti-aliasing、sub-pixel 渲染以及色彩空间的 细微漂移,因此 WebKit 里的一处 kerning 小怪癖不会被当成 bug。它也支持对那些您明知每次加载都会变化的区域设置显式 遮罩 — 实时数据、相对时间戳、随机化内容、走马灯 — 这些 区域对 diff 的贡献会是零噪声。
当真正的改动出现时,平台会呈现三个同步的面板:baseline、 当前截图和一个高亮出位移的 diff overlay。每一次改动都会 记录一个可审阅的决定 — 接受、拒绝或更新 baseline — 这个 决定随后会成为项目历史的一部分。没有静默的漂移,也没有 自动接受:baseline 只有在有权限的人明确表态时才会移动。
同一条流水线可以从三种入口驱动:针对线上 URL(生产、 staging、PR 预览)、针对一份静态站点或构建产物包、针对 Storybook 风格沙箱中的组件预览。多数团队会用第一种作为 release gate,第二种用于部署产物,第三种用于设计系统工作 — 它们共同喂养同一份 baseline 存储。
该页面在这个浏览器、这个视口上最近一次被接受的渲染。在 reviewer 显式更新之前保持冻结。
本次运行捕获到的截图。相同的 fixture、相同的 timing hook、相同的视口 — 只有被测代码发生了变化。
以琥珀色高亮的区域标示出当前渲染在哪些地方超出了配置的容差,与 baseline 不一致。
ScanU 适合谁
ScanU 为那些以「用户看到了什么」为评判标准的团队而生。这个群体比听起来更广 — 不止是设计师与前端工程师,还包括任何发布决策依赖于 UI 在特定浏览器、特定视口、在特定一天正确呈现的人。
前端与全栈工程师把 ScanU 用作 pull request 流水线的最后一道:build 是绿的,单元测试通过,端到端套件 也满意 — 然后由一次视觉 diff 要么确认改动与设计师意图一致, 要么暴露出一条光看 patch 根本发现不了的回归。结果是: 上线后救火的时间变少,用在真正推动产品前进的工作上的时间 更多。
QA 工程师得到的是一层无需重写选择器就能 扩展的测试。把一个新页面纳入视觉套件,只需要一个 URL 加 一份 baseline — 不再是一周脆弱的 XPath。捕获 UI 回归的 那一层,成为测试金字塔的一等成员,而不再是上线之后由用户 通过 Slack 发来的一条事后消息。
设计系统与平台团队对库里每一个组件沙箱 跑 ScanU。一旦某个 token 变更,依赖它的每一个组件都会自动 得到一次视觉审计。一旦某个组件被重构,下游消费者会看到 一份可审阅的 diff — 早于新版本的落地。这正是一个设计系统 避免在岁月里悄悄积累回归的方式。
产品与工程负责人把审阅界面当作证据来使用。 Release notes 可以附上每次发布对应的已接受视觉 diff 链接。 事故回顾可以引用某条回归到底是被拦住、被漏过,还是被 明确接受。久而久之,平台变成了一份记录 — 记下 UI 如何 演进,以及谁在每一步签了字。
使用场景
同一套平台可以服务形态非常不同的工作流。共同的那条线索是:这些团队都按照他们的客户真正在意的节奏在交付 UI,也都需要一种方式 — 一眼就能看出 UI 是否依旧是它应有的样子。
不管团队形态如何,ScanU 通常会因为下面五种原因之一被引入: 一次最近发生的线上事故,而视觉回归本能拦下;一次设计系统 迁移,其影响面无法靠人工审计;一条 CI/CD 工作流需要比 「测试通过」更丰富的 release gate;一份面向客户的交付物 需要「前后对比」的证据;又或是一种合规立场,倾向于使用 欧盟托管的测试工具。共用的一套 CI/CD 集成 承载着这五种场景 — 无论触发来自 PR、merge 还是夜间定时, 同一条流水线都会完成工作。
每周向客户交付一份按站点的视觉差异报告。在每一个 sprint、每一个合同约定的浏览器上,一份报告就能给出「前后对比」证据。
为测试金字塔补上视觉这一层。新页面通过一个 URL 加一份 baseline 加入套件 — 没有脆弱的选择器,也没有需要维护的自制截图脚本。
截图 diff 内联出现在 pull request 上。布局回归在 code review 里被拦下,不再落到线上,也不再变成客户在 Slack 上的一条反馈。
在不弄坏首页的前提下快速迭代。一张确定性的安全网,在改版、A/B 测试和 token 迁移之中保住营销站点的像素稳定。
对每一个组件沙箱进行视觉覆盖。token 变更、主题切换与组件重构,都会在库发布之前自动过一轮审计。
在同一次运行中验证移动端、平板与桌面端的断点。响应式测试不再是抓截图的苦差,而成为一个审阅步骤。
CI/CD 与发布信心
发布信心并不是一种感觉,它是一条如实讲述即将上线之物的流水线 — 包括那些您的 test runner 根本无从置喙的部分。ScanU 以 status check、PR 评论和可存档产物三种身份,接入这条流水线,任何一次事故回顾都能随手引用。
集成模型刻意做得无聊:ScanU 的捕获步骤,和您现有的测试跑 在同一个 job 里;它把 pass、review-needed 或 fail 报到 PR 上;这个状态被当作任何其他必需检查来对待。团队本来就知道 如何从流水线里读出绿、黄、红 — 把一个视觉回归检查织进 这套节奏,在不要求新的思维模型的前提下,补上一层安全感。
在底层,ScanU 针对现代 CI/CD 的现实而设计:
分步的 CI/CD 集成指南 给出了 GitHub Actions、GitLab CI、Bitbucket Pipelines 以及 自建 runner 的具体接线方式。常规场景是多加一个 job;进阶 场景则是一套环境矩阵,让 baseline 在分阶段发布中逐步 向前推进。
GDPR · 欧盟托管 · 隐私
测试产物本身就是数据。截图可能在不经意间把页面上的个人信息一并保留下来 — 导航栏里的姓名、个人菜单里的邮箱、确认页面上的订单号。一个负责任的视觉测试平台,会用对待生产数据同等的谨慎对待这些产物。
ScanU 在欧盟构建并托管,存储与处理都位于欧盟运营的基础 设施之上。对那些在数据驻留方面有承诺的团队 — 受监管行业、 公共部门客户、对隐私敏感的 B2B 客户 — 这通常是采购的硬 要求,而不是偏好。ScanU 的设计目标,正是在不牺牲产品 能力的前提下满足这一要求。
由这一立场出发,衍生出几条刻意的设计选择:
完整的隐私立场 — 分包处理方、区域托管、DPA、保留时间窗 — 请参阅 GDPR 与数据处理文档。对大多数团队而言,简短版已经够用:您的截图留在欧盟, 平台正是为了让它们留在那里而构建的。
差异所在
视觉测试是一个拥挤的品类。让 ScanU 与众不同的,有相当一部分是它拒绝去做的事 — 它不抄的近路、它不愿意往审阅队列里塞的噪声、以及它对一个 diff 应当如何被审阅所持的立场。
通用截图工具往往以「贴在 test runner 上的一个 library」的 形态发布,然后把 timing、字体、动画,以及「一像素的偏移 到底是 bug 还是舍入误差」这道永恒之问,统统留给您去搏斗。 ScanU 的态度更硬气:如果平台无法产出确定性的捕获,那是 平台自己的问题 — 不是您的。所以 readiness 信号、字体加载、 动画固定、scroll-stitching、感知 diff 与遮罩区域,都是 内建能力,而不是事后挂上去的。
定价遵循同样的逻辑。对一个体面的团队 — 几十个页面、三种 引擎、三种视口、一条流水线 — 视觉覆盖的成本不该是要在 预算会上特别辩护的一个条目。完整的 定价页 以清楚的数字写明:每一档包含什么、什么算一次 run、上限 在哪里。对于只想发布出他们设计的那版 UI 的团队,我们 没有定制报价。
稳定化、字体 readiness、动画固定、scroll-stitching 都是平台能力 — 不是要您在自家代码库里维护的零碎片段。
diff 引擎理解 anti-aliasing 与 sub-pixel 渲染,因此那些不可见的微小偏移不会淹没掉真正重要的回归。
只有有权限的人接受了变更,baseline 才会移动。每一次更新都可被审计。没有静默的漂移。
截图、元数据与审阅历史都生活在欧盟基础设施上。对那些必须坚守这一立场的团队,无需额外开关。
ScanU 能拦下什么
视觉回归平台所拦下的多数 bug 并不奇异。它们是那些细小的、不戏剧的、容易被忽略的破损 — 因为没有任何 assertion 在专门盯着它们,才会从 reviewer 眼前溜过去。下面是 ScanU 专门为之设计要让其浮出水面的若干类别的样本。
视觉 bug 会聚集在少数几种反复出现的模式中。每一种孤立 来看都显得不起眼;每一种都可能在没有一个红色测试的情况下 上到生产。所有这些,都会在下一次 ScanU 运行中以 diff 的 形式浮现 — 早于 PR 被合入。
backdrop-filter 自动加前缀的 PostCSS 插件丢掉了。Safari 上,一条原本带模糊的导航栏 突然变得不透明。text-wrap: balance 的设计,在 Chrome 上 干净利落,在尚未发布该特性的引擎上仍然不平衡。没有 跨浏览器检查时,这在开发机上是不可见的。功能概览 更详细地讲述检测原语 — 感知容差、遮罩区域、scroll 捕获、baseline 分支 — 并展示每一项原语对应上面哪一类问题。 对大多数团队而言,简短版本是这样:只要视觉上有变化, ScanU 就会注意到;若是预期内,一键接受;若不是,您已经 比其他人都更早地拦下了它。
FAQ