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——数字都对,但你不会把两张卡片的坐标拿出来人工比较是否重叠
缺陷是几何 / 相对性的:两个正确的元素,放在一起就爆了。这只有渲染像素才能暴露。
getComputedStyle、textContent、className、甚至 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——那等于放弃了验证。
一句话外卖
视觉主张需要视觉证据;属性读取再多,也只是在量每棵树,看不见森林有没有烧起来。