第 20 章 · 无障碍测试与 Aria Snapshot
本章目标:理解 aria snapshot 的数据来源与 YAML 语法,学会用
to_match_aria_snapshot()断言页面的可访问结构,并把无障碍检查纳入日常测试与 CI。
20.1 无障碍测试为什么值得自动化
无障碍(Accessibility,a11y)不是"锦上添花":它直接影响屏幕阅读器用户能否使用产品,也在越来越多地区成为合规要求。更重要的是,对机器友好的页面往往对人也更友好——一个没有正确 role 和可访问名称的按钮,对测试自动化同样是灾难。
传统 a11y 审计工具(如 axe)扫描的是"有没有违规";Playwright 提供了另一个互补的视角:把页面的可访问性树快照下来,当作结构契约来断言——语义结构错了,测试就红。
20.2 认识 Aria Snapshot:YAML 形式的可访问树
Aria snapshot 是 Playwright 对元素/页面可访问性树的 YAML 表示,自 v1.49 起可用。每个节点描述一个可访问元素:
- role "name" [attribute=value]role:ARIA 或 HTML 隐含角色,如heading、button、listitem;"name":可访问名称(精确字符串;/正则/表示模式匹配);[attribute=value]:checked、disabled、expanded、level、pressed、selected等状态属性。
用代码直接查看当前页面的快照:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://playwright.dev/python/docs/intro")
# 打印整个侧边栏的可访问结构(YAML 字符串)
print(page.get_by_role("navigation").aria_snapshot())
browser.close()输出形如:
- navigation "Docs":
- list:
- listitem:
- link "Intro"
- listitem:
- link "Writing tests"缩进表达层级嵌套——这就是屏幕阅读器"看到"的世界。
20.3 to_match_aria_snapshot():结构断言
核心断言是 expect(page).to_match_aria_snapshot()(整页)或 expect(locator).to_match_aria_snapshot()(局部区域):
from playwright.sync_api import expect
def test_heading_structure(page):
page.goto("https://example.com")
# 整页断言:标题层级正确
expect(page).to_match_aria_snapshot("""
- heading "Example Domain"
- paragraph
""")
def test_nav_structure(page):
page.goto("https://example.com")
# 局部断言:只关心导航区,带 [level] 属性校验
expect(page.get_by_role("contentinfo")).to_match_aria_snapshot("""
- contentinfo:
- link /更多/
""")匹配规则有四个要点,全部来自官方文档:
- 大小写敏感,但空白会被折叠(缩进和换行不影响比较);
- 顺序敏感:模板中节点的顺序必须与实际树一致;
- 支持部分匹配:省略名称或属性即可只校验关心的部分;
/pattern/形式支持正则名称,适合动态文本。
20.4 用 level 属性守住文档大纲
heading 层级混乱是最常见的 a11y 问题之一(比如为了视觉效果从 h1 直接跳到 h4)。用 [level=N] 可以把它变成 CI 里的一条硬约束:
def test_article_outline(page):
page.goto("https://blog.example.com/post/1")
# 文章主体的大纲必须是 h2 → h3 两级结构
expect(page.get_by_role("article")).to_match_aria_snapshot("""
- article:
- heading "概述" [level=2]
- paragraph
- heading "安装步骤" [level=2]
- list:
- listitem: 安装 Python
- listitem: 安装依赖
- heading "常见问题" [level=3]
""")一旦有人为了样式把 h3 改成 div class="title" 或跳级到 h4,这条测试会立刻失败并给出 diff。
20.5 与 axe 配合:结构断言 + 违规扫描
to_match_aria_snapshot 擅长守护"结构意图",但不检查对比度、重复 id 等规则类问题——这正是 axe-core 的强项。两者组合才是完整的 a11y 策略:
pip install pytest-playwright axe-playwright-pythonfrom axe_playwright_python.sync_playwright import Axe
def test_no_axe_violations(page):
page.goto("https://example.com")
results = Axe().run(page)
# 规则扫描:严重违规必须为零
assert results.violations_count(0) == 0, results.report()分工建议:
| 工具 | 擅长 | 失败时的产物 |
|---|---|---|
to_match_aria_snapshot | 页面语义结构是否符合设计意图 | YAML diff,直观展示结构差异 |
| axe 扫描 | 规则库类违规(对比度、标签缺失等) | 违规清单及修复建议 |
20.6 纳入 CI 的策略
a11y 测试全量跑会很吵,建议分级:
- 关键页面结构断言(首页、表单、导航):随每次 PR 跑,用本章的 snapshot 断言;
- axe 全站扫描:每日定时任务跑一次,产出趋势报告而非阻塞 PR;
- 失败处理约定:snapshot diff 属于"破坏性变更",必须在 PR 里显式更新模板并说明理由——这本身就是一次 a11y review。
20.7 本章小结
- Aria snapshot 是可访问性树的 YAML 表示,v1.49+ 提供
locator.aria_snapshot; - 节点语法
- role "name" [attr=value],支持正则名称与部分匹配; expect(locator).to_match_aria_snapshot()大小写/顺序敏感、空白折叠;- 用
[level=N]守护 heading 大纲是最实用的落地场景; - 结构断言(Playwright)+ 规则扫描(axe)互补,CI 中分级执行。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. Aria snapshot 中节点 `- heading "标题" [level=3]` 的含义是?
2. 关于 to_match_aria_snapshot() 的匹配规则,错误的是?
3. locator.aria_snapshot 的作用是?
4. 关于 Playwright 结构断言与 axe 扫描的分工,正确的是?
🛠️ 动手实践
- 给你的项目首页写一条整页
to_match_aria_snapshot断言,先跑一遍拿到真实 YAML,再裁剪成只保留关键区域的"部分匹配"版本。 - 在团队博客或文档站上找出一个 heading 跳级问题,写一条
[level=N]断言证明它的存在,然后修复前端代码让测试变绿。 - 安装
axe-playwright-python,对登录页做一次扫描,把 severity 最高的违规项整理成 issue(附复现步骤与修复建议)。
下一章进入实时应用的测试世界:第 21 章 · WebSocket 与实时应用测试。