返回博客

DOM eval 说「没问题」,截图说「完全爆了」——UI 布局验证的认知陷阱

做完一个卡片组件展示页,用 Playwright 的 browser_evaluate 跑一遍:boxShadow 有值、header/footer slot 都挂上了、文字内容全对。"属性都正确,应该没问题。"——然后截图一开,256px 宽的卡片溢出到了 128px 的格子里,相邻卡片互相叠压,还把左侧边栏盖了一半。

这类错误不是偶然。它是一种特定的认知盲区,有个名字:验证剧场

现象

在一个组件状态陈列页里,多张卡片(w-64,固定 256px)被放进密集 grid(grid-cols-auto + minmax(8rem,1fr),每格最窄 128px)。

browser_evaluate 检查结果:

  • getComputedStyle(card).boxShadow → 有阴影,符合「elevated」变体
  • slot 检查 → header、footer 都存在
  • textContent → 标题、描述、操作文字全部正确

断言通过,结论:「组件渲染正常」。

打开截图:卡片宽度溢出容器,相互叠压,左侧导航栏被遮住三分之一。

根因

browser_evaluate 读取的是元素自身的属性——每个属性都只属于它所在的那一个盒子。它无法感知元素之间的空间关系:谁在谁上面、谁溢出了谁的边界、两个兄弟节点的实际像素是否重叠。

这类 bug 在属性层面「完全正确」:

  • 卡片的 width: 256px 是正确的——它就是被设计成 256px
  • 容器的 grid-template-columns 计算值也是正确的
  • getBoundingClientRect() 会告诉你卡片的 left/top/width/height——数字都对,但你不会把两张卡片的坐标拿出来人工比较是否重叠

缺陷是几何 / 相对性的:两个正确的元素,放在一起就爆了。这只有渲染像素才能暴露。

坑:用属性读取替代截图,等于没有验证视觉

getComputedStyletextContentclassName、甚至 getBoundingClientRect 的数字,验证的是元素的个体属性,不是布局正确性

overflow、overlap、遮挡、错位、响应式断裂、z-index 层叠——这些缺陷在属性层面可以对应零个「错误」,只有截图才能看见。

「boxShadow 正确、slot 存在、文字对——looks fine」是典型的验证剧场,没有实际看到渲染结果。

正解

截图 + 真正看截图,这是 UI 布局验证的唯一主证据。

// 1. 拍截图
await browser_take_screenshot({ fullPage: true, filename: "check.png" });
 
// 2. 找到文件(Playwright MCP 写到 MCP server 启动时的 CWD,
//    通常是 workspace 父目录,不是 .playwright-mcp/)
// 如果 Read 报文件不存在:
//   find <workspace-parent> -name check.png
// 找到绝对路径后再 Read
 
// 3. 用 Read 工具打开图片,用眼睛确认布局

browser_evaluate 依然有价值——确认某个 CSS 变量的值、检查交互后的状态、断言元素数量——但它是补充,不是截图的替代品。

browser_snapshot(a11y tree)展示的是结构嵌套,同样不是几何信息,验证 overflow/overlap 时也不够用。

上面那个例子的修复:把展示画廊改成 flex flex-wrap,每格加 min-w-32,让卡片自然换行。修复后截图确认卡片整齐排布、不再叠压。

正解:截图是视觉主张的唯一有效证据

只有打开截图并看了,才算验证了视觉。

如果截图路径找不到,就 find 出绝对路径再 Read,不要因为路径麻烦就退回到 evaluate——那等于放弃了验证。

一句话外卖

视觉主张需要视觉证据;属性读取再多,也只是在量每棵树,看不见森林有没有烧起来。