大模型应用实战

LLM 驱动的自动化测试系列(12):像素 diff 之外,谁真的在让 LLM「看懂」UI 变化

7.2k star 的 BackstopJS 处理噪声的全部手段就是四个数字配置项:misMatchThreshold、requireSameDimensions、ignoreAntialiasing、usePreciseMatching——本质上还是在跟字体渲染抖动死磕像素级容差。真正把 VLM/LLM 接进视觉回归判断链路的,是一个只有 23 star 的项目 vlmkit,而且它自己的内部评测数据直接写着某个模型会把红改成红——把这两个项目摆在一起,视觉语义回归到底解决了什么、又引入了什么新问题,答案比想象中更具体。

·约 14 分钟阅读·AI Engineering

像素 diff 的天花板,恰好是这篇要验证的起点

这个系列讨论过结构化输入(API 测试,文章 11)为什么让 LLM 落地更保守,视觉回归测试站在另一个极端:输出是一张图片,"这张图跟基线图哪里不一样"这件事,理论上传统算法就能算清楚——不需要理解语义,只需要比较像素矩阵。 但规划里提出的问题很尖锐:字体渲染抖动、动画时序差异、随机内容(比如时间戳、广告位)造成的误报,纯像素比较解决不了,因为它们造成的像素级差异跟真正的 UI bug 造成的像素级差异,在数值上没有本质区别。这篇要验证的就是:LLM/VLM 介入"这个视觉变化到底是不是真 bug"这个判断之后,到底解决了传统工具解决不了的哪部分问题,又引入了规划里提到的哪些新麻烦(主观性、幻觉、判断标准不稳定)。

结论提前说:star 数最高的传统像素 diff 工具(BackstopJS,7.2k star)处理噪声的手段,从头到尾都是数字阈值,没有任何语义判断;真正让 VLM 参与"这个变化算不算 bug"这一步判断的开源实现,只有一个 23 star 的实验性项目 vlmkit,而且它的架构做得相当克制——把 AI 判断严格限定在一个很窄的、可关闭的环节,剩下的部分照样是纯确定性代码。 这个"高星纯机械 vs 低星真 AI"的落差,跟文章 10(Healenium)、文章 11(Keploy vs AutoRestTest)是同一个模式的第三次重现。


BackstopJS:7.2k star,噪声处理的全部武器是四个数字

BackstopJS(garris/BackstopJS,7,182 star,MIT 协议,最近提交 2026-09-08,这个赛道里星数最高的开源项目)的核心依赖是 @mirzazeyrek/node-resemble-js——Resemble.js 的一个分支/封装,做的事情很直白:把两张截图逐像素比较,统计不同像素的比例,超过阈值就判定为回归。对整个代码库做 AI/LLM 关键词全文搜索,零命中——这不是隐藏得深,是这个项目从设计上就没有语义判断这一层。

它应对"字体渲染抖动、反锯齿噪声"这类问题的完整工具箱,就是 README 里列出的这几个配置项:

  • misMatchThreshold(默认 0.1)——"允许通过测试的不同像素百分比",本质是一个容差阈值,调大容易漏检真 bug,调小容易被渲染噪声误报
  • requireSameDimensions——尺寸不一致直接判定失败,不做任何智能对齐
  • ignoreAntialiasing——一个布尔开关,试图过滤掉反锯齿造成的边缘像素差异,但只是一种统计意义上的模糊匹配,不理解"这个像素为什么不一样"
  • usePreciseMatching——在精确匹配和模糊匹配之间切换的另一个开关

这四个配置项加在一起,就是 BackstopJS 面对规划里提到的"字体渲染抖动、动画时序、随机内容导致的误报"问题时的全部手段——本质上都是在用统计容差去逼近"这个差异是不是噪声",而不是真正判断"这个差异对用户来说意味着什么"。 阈值调得越松,动画时序、渲染抖动造成的误报越少,但同时真正的视觉 bug(比如一个按钮的颜色改变了 2% 的像素占比)也更容易被同一个阈值放过——这是纯像素比较的结构性天花板,不是 BackstopJS 实现得不好,而是这条路线能做到的极限就在这里。

同期检查的 Lost Pixel(1.7k star,自称"Percy/Chromatic/Applitools 的开源替代品")走的是同一条路线:底层比较引擎可以在 pixelmatch(默认)和 odiff-bin 之间切换(compareEngine: z.enum(['pixelmatch', 'odiff'])),对源码全文搜索 AI/LLM 关键词同样零命中。两个高星项目殊途同归——视觉回归测试这个赛道里,star 数和"是否用了语义判断"完全不相关,甚至可以说负相关。


vlmkit:23 star,但架构设计比大多数高星项目更克制

vlmkit(mizchi/vlmkit,23 star,MIT 协议,最近提交 2026-09-24,自我定位是"Deterministic verification toolkit for frontend work")是这次研究里最值得细看的项目——它不是简单地"把截图丢给 VLM 问它像不像",而是把整套流程拆成了三层,层与层之间的技术选型边界划得很清楚,跟文章 11 里 AutoRestTest 的三层分工是同一种设计哲学。

第一层:纯确定性的像素 diff 核心(src/vrt/snapshot/snapshot.ts,763 行,全文读完,没有一行 LLM 调用)。多视口截图(桌面 1280×900、移动端 375×812)、阈值 diff、位移检测(globalShift/compensatedDiffRatio/shiftOnly——专门用来区分"整块内容只是被挪动了几像素"和"内容真的变了",这是比 BackstopJS 的四个静态阈值更精细的一步,但依然是纯数学,不涉及语义),以及一个 stability 模式:对同一个基线反复截图运行 N 次,专门用来测量"什么都没变的情况下,纯渲染噪声本身能造成多高的误报率"(输出一个 overallFalsePositiveRate 指标)。这一层是这篇文章能找到的、对"渲染噪声到底有多大"这个问题做得最认真的量化——它没有回避这个问题,而是先把噪声本身的基线测出来。

第二层:无需基线图的"完整性门禁"(packages/vlmkit-markup/src/inspect/integrity-check.ts)。这一层同样完全是确定性代码——检测 JS 报错、图片/样式表/字体加载失败、文字重叠或被裁切、容器塌陷、横向溢出、不可见或低对比度文字、元素错位——文档里明确列出了 A1 到 A12 十二类缺陷,代码注释直接写着:"every probe is deterministic (DOM measurement + pixel math)... exemption is the tool's judgment, never the consuming agent's"(每一个探测器都是确定性的,豁免与否是工具自己的判断,不是交给下游 AI agent 决定)。这句注释本身就是这个项目的设计立场:能用 DOM 测量和像素数学解决的问题,就不交给模型去"感觉"。

第三层:真正的 VLM+LLM 两阶段推理管线(packages/vlmkit-ai/src/reasoning-pipeline.ts、vlm-client.ts)——这才是名副其实的"AI 判断",而且从设计上就是完全可选、按 API key 门控的(OPENROUTER_API_KEY/GEMINI_API_KEY/ANTHROPIC_API_KEY,对应 README 里的原则"Everything is key-free unless marked [key]")。

  • Stage 1(便宜的 VLM):输入热力图/截图,输出结构化的 CHANGE: [element] | [css-property] | [before] | [after] | [severity] 行。Prompt 里有一句专门写来对抗一个具体幻觉模式的提示:"Red in the heatmap means 'this area changed' — it does NOT mean the element is red. You must infer the actual CSS values from the element context, not from the heatmap color."——这句话的存在本身就说明,工程师在实测中真的观测到了模型会把"这里在热力图上是红色"误解成"这个元素本身是红色"这种具体的幻觉模式,才需要专门写规则去防。
  • Stage 2(更贵的 LLM):输入 Stage 1 的结构化报告加真实 CSS 源码,输出具体的 FIX: 修复行。Prompt 里有一条"权威来源"规则:"If 'CSS Diff from Baseline' is provided above, use those EXACT selectors and values... Do NOT guess CSS values"——只要有真实的 CSS diff 可用,就强制模型用真实值,不允许它凭视觉印象"猜"一个数值。这条规则的存在,直接对应规划里说的"语义判断引入幻觉"这个风险——vlmkit 的应对方式不是相信模型的视觉估计,而是在有确定性数据源的时候,直接把模型的自由度收窄到"抄"而不是"猜"。

这套两阶段设计还带自适应分辨率升级和优雅降级(adaptiveResolution、maxResolution、throwIfMissing: false 在没有 API key 时返回 null 而不是抛异常)——这意味着 vlmkit 的 AI 层从架构上就被设计成一个可以整体拔掉都不影响核心 VRT 功能的可选插件,而不是流程里必须经过的一环。


vlmkit 自己承认的幻觉,和自己发现的"这个方向没人做"

vlmkit 内部有两份文档,提供了这篇文章能直接引用的一手证据,不需要转述。

第一份是它自己的模型评测记录(docs/knowledge.md),里面一行写着:

google/gemini-2.5-flash-lite | 1937ms | 1640 tokens | ⚠ hallucinates `red → red` uniformly

这是项目作者自己跑评测时记录下来的失败模式——某个模型会把热力图里标红的区域,统一误判成"颜色从红变成了红",也就是 Stage 1 prompt 里专门警告过的那个幻觉模式,即便有警告,某些模型依然会犯。 这行记录直接对应规划文档提出的"语义判断引入的新问题:主观性、幻觉、判断标准不稳定"——不是理论推演,是有名有姓的模型、有具体数字的实测记录。

第二份是它自己的竞品调研笔记(docs/research.md),"Gaps(未被探索的领域)"一节的第一条写着:

LLM-based VRT reasoning: No OSS tool sends screenshot diffs to a Vision LLM for judgment.

这是 vlmkit 作者自己做调研后得出的结论:开源世界里,没有其他工具会把截图 diff 送进 Vision LLM 去做判断——这也是这篇研究独立验证后得到的同一个结论。 同一份文档里还记录了商业产品的对比数据作为参照:Percy 的 Visual Review Agent 宣称通过自然语言描述 diff 能减少 40% 的误报(但仍需人工审核确认),Chromatic 的 TurboSnap 通过依赖树分析将快照数量减少 60-90%(这是"缩小对比范围"而不是"语义判断变化",跟这篇讨论的问题不是同一个技术路线,仅作对比)。

vlmkit 文档里还提到了第三种技术路线,不算这篇的主角但值得一提:一个叫 crater 的绘制树(paint tree)diff 预扫描器——不比较像素也不调用 LLM,靠比较浏览器绘制树结构变化,能在完整 Chromium 渲染之前,确定性地捕获约 60% 的视觉变化,实测误报率 0%;文档明确写着"Chromium pixel comparison can produce false positives from anti-aliasing and font rendering noise, but crater paint tree diff doesn't have this issue"——这提示了在"像素 diff"和"LLM 语义判断"之间,其实还存在第三条路:用更结构化的中间表示(绘制树、DOM 计算样式)代替像素本身做比较,能不靠语言模型就规避掉一部分渲染噪声问题,只是覆盖率不如完整方案。


视觉语义回归测试给出的答案

把 BackstopJS 和 vlmkit 并排看,规划里提出的两个问题现在都有具体依据了:

LLM/VLM 语义判断解决的问题:纯像素比较无法区分"渲染噪声造成的差异"和"真实 UI 变化造成的差异",因为它们在像素矩阵层面看起来是同一种东西——一堆数值不同的像素点。VLM 之所以能帮上忙,是因为它可以从截图里识别出"这是一个元素的颜色变了"还是"这只是抗锯齿边缘的抖动",这是像素数值比较原则上做不到、需要视觉语义理解才能做的判断。vlmkit 的 Stage 1 就是把这个判断产出为结构化的 CHANGE 记录,而不是一句笼统的"有变化"。

LLM/VLM 语义判断引入的新问题:gemini-2.5-flash-lite 会把"热力图标红的区域"误判成"元素本身是红色",这是一个具体、可复现、项目自己记录在案的幻觉模式,即便工程师已经在 prompt 里专门写了反幻觉规则。vlmkit 应对这个问题的架构选择,不是"相信模型、多测几次取共识",而是尽量把能用确定性方法解决的部分(像素 diff、位移检测、完整性检查、绘制树 diff)都留在确定性层里做,把 LLM 的自由裁量权压缩到最窄的一个环节("这个已经被确定性方法框定好范围的变化,用自然语言描述成什么样、该怎么修"),并且在有权威数据源(真实 CSS diff)时强制模型服从数据而不是自己"猜"。 这跟文章 11 里 AutoRestTest 把 LLM 限定在 SmartValueGenerator 这一个窄环节、api-automation-agent 让 TypeScript 编译器而不是 LLM 自己判断代码对不对,是同一种工程纪律:LLM 的输出不是终点,是需要被下游确定性机制约束、校验、甚至否决的中间产物。


总结

  1. BackstopJS(7.2k star)代表纯像素 diff 路线的天花板——底层依赖 Resemble.js 分支,全部噪声处理手段是 misMatchThreshold/requireSameDimensions/ignoreAntialiasing/usePreciseMatching 四个数字/布尔配置项,本质是用统计容差逼近"这是不是噪声",无法真正区分渲染抖动和真实 bug;Lost Pixel(1.7k star)走的是同一条路线(pixelmatch/odiff-bin),同样零 AI 代码。
  2. vlmkit(23 star)是这次研究找到的唯一一个真正让 VLM/LLM 参与"这个视觉变化是不是 bug"判断的开源实现,架构上分三层:确定性像素 diff 核心(含专门测量渲染噪声本身误报率的 stability 模式)、确定性的十二类缺陷完整性门禁、完全可选且按 API key 门控的两阶段 VLM+LLM 推理管线。
  3. vlmkit 的 Stage 1 VLM prompt 里专门写了一条反幻觉规则(热力图红色不等于元素本身是红色),但它自己的内部评测记录显示 gemini-2.5-flash-lite 仍然会犯这个错误——这是项目自己记录在案、可直接引用的幻觉证据。
  4. vlmkit 的 Stage 2 LLM prompt 里规定"有真实 CSS diff 时必须使用其中的精确值,不允许凭视觉猜测",这是应对语义判断主观性/不稳定性问题的具体工程手段:把 LLM 的自由裁量权压缩到确定性数据无法覆盖的最窄环节。
  5. vlmkit 自己的竞品调研得出结论"没有开源工具把截图 diff 送进 Vision LLM 做判断",与本文独立验证的结果一致——视觉回归测试领域,高星项目(BackstopJS、Lost Pixel)依然清一色是纯像素路线,真正的语义判断只存在于个位数 star 的实验性项目里,而这些实验性项目在架构上反而比高星项目更谨慎,把 AI 层做成了可以整体拔掉的可选环节。

欢迎访问 PrimeSkills —— 一个精心策划的 AI Agent 与技能市场,所有内容均经过真实企业级工作流验证。没有噱头,只有真正有效的东西。

更多实用知识和有趣产品,欢迎访问我的个人主页