在可访问性测试中,一个常见的误区是只盯着 DOM 结构看——比如检查 标签是否存在,或者某个元素有没有 class="btn"。但实际用户(尤其是依赖辅助技术的用户)并不关心标签名或 class 值,他们关心的是:这个元素在可访问性树里是不是一个按钮、有没有 accessible name、能不能用键盘聚焦。而这些东西,只能靠语义属性和真实浏览器环境来验证。只用 querySelector 检查标签或 class,等于在测字符串,不是测用户真实操作。

为什么不能只测 HTML 结构而要测可访问性行为
HTML 标签本身不带交互逻辑。 和 在 DOM 树里可能长得一模一样,但在可访问性树里,前者天然就是按钮,后者则完全取决于有没有正确设置 role 和 name。即便你给 硬加上 role="button",如果缺少 aria-label 或可见文本,屏幕阅读器根本读不到它。所以,只测结构就像只检查包装袋上的字,却不管里面有没有东西。
如何用 getByRole 定位并验证真实可访问性状态
getByRole 是目前最贴近用户操作的定位方式。它会自动查找可访问性树中匹配 role + name 的元素,同时触发隐式等待(比如组件异步渲染后才出现)。用起来有几个关键点:
- 必须显式提供
name:比如screen.getByRole('checkbox', { name: '接收邮件通知' })。如果 DOM 存在但 name 不匹配,测试会报错——这恰恰是组件缺aria-label或可见文本的信号,不是 API 的锅。 - 禁用态不能只看
disabled属性:有些组件用aria-disabled="true"或 class 模拟禁用,但浏览器并不阻止焦点和点击。所以await expect(checkbox).toBeDisabled()才是真检查。 - 展开/收起控件必须同步
aria-expanded:expect(button).toHa veAttribute('aria-expanded', 'true')是必要断言,否则屏幕阅读器无法感知状态变化。
测试时禁用 CSS 后还能否维持语义结构
在 Chrome DevTools 中执行 Command Menu → "Disable styles",页面只剩语义骨架。这时如果 看起来像普通段落、 无法被键盘 Tab 到、 没有焦点环,说明结构依赖样式而非语义——这种组件在无障碍场景下就是不可用的。记住三个要点:
- 标题层级必须连续且有意义:
→→,不能跳级或用冒充。 - 所有交互元素必须有明确的 role 和 name,哪怕只是装饰性图标,也要用
aria-label=""占位。 - 用
Elements → Inspect Accessibility Properties直接查看可访问性树,比肉眼判断可靠得多。
jsdom 为什么不能替代真实浏览器做可访问性测试
jsdom 不解析 CSS、不触发 layout、不模拟焦点样式、不支持 IntersectionObserver,更不会构建可访问性树。它返回的 offsetHeight 恒为 0,focus() 不触发 :focus-visible,aria-expanded 属性变了但屏幕阅读器根本读不到。所以它的适用场景非常有限:
- 适合场景:纯函数生成 HTML 字符串,比如
generateCardHtml(data),断言输出是否含和aria-label。 - 不适合场景:任何涉及键盘导航、焦点管理、状态同步、动态渲染的组件,必须进 Playwright 或 Puppeteer。
- Shadow DOM 组件需显式穿透:
host.shadowRoot().locator('button'),而 jsdom 根本不支持 shadowRoot API。
真实可访问性测试最难的不是写断言,而是让组件从第一行 HTML 就带上可测锚点:role、name、state 缺一不可,且不能靠 class 或内联样式驱动行为。
