快照测试 Snapshot Testing
本教程共 59 篇 · 第 49 篇 · 更新于 2026-08-04 · 约 8 分钟阅读
49. 快照测试 Snapshot Testing
本节目标:学完你能用一份「无障碍树快照」锁住页面结构,以后结构一变测试就报警。
上一章比的是「像素」。这章比的是「结构」。
做法是用 Aria 快照(aria snapshot)把页面的无障碍树(accessibility tree)存成一份模板。之后每次跑测试都拿它对一遍,结构乱了就报错。
Aria 快照是什么
无障碍树是浏览器给辅助技术(比如读屏软件)看的那棵树。它把页面抽象成「角色 + 名称 + 状态」。
Aria 快照就是这棵树的 YAML 文本表示。长这样:
await page.goto('https://playwright.dev/');
await expect(page).toMatchAriaSnapshot(`
- banner:
- heading /Playwright enables reliable end-to-end/ [level=1]
- link "Get started":
- /url: /docs/intro
`);
页面级用 expect(page).toMatchAriaSnapshot(),只想比某一块就用 expect(locator).toMatchAriaSnapshot()。
断言测试 vs 快照测试
两种思路不一样,别混为一谈。
断言测试像点名:你逐条查某个值。toHaveText() 查文字,toHaveValue() 查输入框值。它精准、好定位,但结构复杂时写起来啰嗦。
快照测试像拍全景:把整块结构一次存下来,以后整体比。适合整页、整组件这种「大体不变」的东西。
代价是粒度粗。快照一大,报错时你得自己在 diff 里找哪一行变了。
Tip实战里常搭配用:结构用快照锁住,关键数值用断言查。粗中有细。
快照长啥样
每个节点写法固定:角色 "名称" [属性=值]。
- heading "title"
- button "Submit"
- checkbox [checked]
- role:元素角色,如
heading、list、listitem、button。 - “name”:可访问名称。引号里是精确值,
/正则/是模糊匹配。 - [属性]:
checked、disabled、expanded、invalid、level、pressed、selected这些状态。
想看真实的无障碍树,打开 Chrome DevTools 的 Accessibility 面板,比自己猜快得多。
匹配的三条规则
新手最容易在这三条上翻车,先记住:
- 大小写敏感。
"Submit"和"submit"不是一回事。 - 空白会被折叠。缩进和换行不影响结果,放心排版。
- 顺序敏感。模板里节点的先后,必须跟页面无障碍树里的先后一致。
完全匹配与部分匹配
最省心的是部分匹配:只写你关心的节点,其余忽略。
<button>Submit</button>
- button
只写 button,名字无所谓,测试照样过。这对会变的文案很友好。
属性也能省。<input type="checkbox" checked> 写成 - checkbox,勾没勾都能过。
列表同理,可以只盯某一项:
- list
- listitem: Feature B
上面的写法只要求列表里「有 Feature B」这一项,前后还有啥不管。
控制子节点的匹配强度
用 /children 决定子节点要匹配多严:
contain(默认):模板里的子节点按顺序都在就行。equal:子节点要完全对上,不能多不能少。deep-equal:连嵌套子节点也要完全对上。
- list
- /children: equal
- listitem: Feature A
- listitem: Feature B
页面里要是还有个 Feature C,上面这份模板就会失败——因为你要求了 equal。
想全项目默认用 equal,写进配置:
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toMatchAriaSnapshot: { children: 'equal' },
},
});
单个快照仍可以用 /children 覆盖全局设置。
用正则对付动态文字
数字、时间戳这类用正则兜住:
- heading /Issues \d+/
链接地址也能用正则,写在 /url 里:
- link:
- /url: /https:\/\/example\.com\/.*/
怎么生成快照
三条路,从懒到勤。
第一条,空模板现场生成。 传空字符串,Playwright 会把当前结构填回你的源码里。
await expect(locator).toMatchAriaSnapshot('');
第二条,跑测试时整体刷新。 -u 是 --update-snapshots 的简写。
npx playwright test --update-snapshots
只有不匹配的快照会被更新,已经对上的不动。更新时 Playwright 会先等到 expect 超时,确保页面稳定;页面慢的话适当调大 --timeout。
默认直接覆盖基准快照文件。改版后先跑一次,用 git diff 确认差异是自己想要的再提交:
npx playwright test --update-snapshots
第三条,用 codegen 录。 录制工具栏里有「Assert snapshot」动作,点一下就把当前选中元素的快照断言写进来;旁边的「Aria snapshot」标签页还能实时看某个定位器的树长什么样。
快照存成独立文件
模板长了塞在代码里很难读,可以存成 .aria.yml:
await expect(page.getByRole('main')).toMatchAriaSnapshot({ name: 'main.aria.yml' });
默认放在 example.spec.ts-snapshots/ 目录下。结构在各浏览器里是一样的,所以多浏览器跑也只存一份。
想在代码里拿到快照文本
page.ariaSnapshot() 和 locator.ariaSnapshot() 直接返回 YAML 字符串,适合自己做处理:
const snapshot = await page.ariaSnapshot();
console.log(snapshot);
调试「为什么模板对不上」时,把它打出来跟模板肉眼比一遍,最快。
我的用法
我一般拿它守住「页面骨架」——导航在、主区域在、关键按钮在。具体文字和数值再用断言查。
这样既防结构被改坏,又不会因为一句文案变动就误报。
一句话:Aria 快照比的是结构不是像素,适合锁整页整组件的骨架,动态内容用正则和部分匹配兜底。
小结
Aria 快照比的是结构不是像素,适合锁整页整组件的骨架。动态文字用正则兜住,children 设 equal 能严格控子节点。生成快照三条路——空模板现场填、-u 整体刷、codegen 录。调试时把 ariaSnapshot() 打出来跟模板肉眼比,最快定位问题。