关于 screen-reader-testing
这是一项用于通过屏幕阅读器验证 Web 无障碍性的实用技能,面向开发者和测试人员,涵盖平台覆盖、交互模式及常见组件场景。
主要功能
- 01主流屏幕阅读器覆盖建议
- 02浏览、焦点和应用模式说明
- 03对话框、实时区域与标签页场景
- 04可访问名称调试建议
- 05键盘与焦点测试实践
适用场景
- 01检查不同屏幕阅读器中的 ARIA 行为
- 02使用辅助技术测试表单和导航
- 03排查动态内容播报问题
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wshobson/ agents --skill screen-reader-testing安装后在 Claude Code 中输入/screen-reader-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/jaws-talkback.mdnvda.mdvoiceover-ios.mdvoiceover.md详细介绍
屏幕阅读器测试
使用屏幕阅读器测试 Web 应用的实用指南,助你全面验证无障碍性。
何时使用此技能
- 验证屏幕阅读器兼容性
- 测试 ARIA 实现
- 调试辅助技术问题
- 验证表单的无障碍性
- 测试动态内容播报
- 确保导航无障碍
核心概念
1. 主流屏幕阅读器
| 屏幕阅读器 | 平台 | 浏览器 | 使用率 |
|---|---|---|---|
| VoiceOver | macOS/iOS | Safari | ~15% |
| NVDA | Windows | Firefox/Chrome | ~31% |
| JAWS | Windows | Chrome/IE | ~40% |
| TalkBack | Android | Chrome | ~10% |
| Narrator | Windows | Edge | ~4% |
2. 测试优先级
Minimum Coverage:
1. NVDA + Firefox (Windows)
2. VoiceOver + Safari (macOS)
3. VoiceOver + Safari (iOS)
Comprehensive Coverage:
+ JAWS + Chrome (Windows)
+ TalkBack + Chrome (Android)
+ Narrator + Edge (Windows)
3. 屏幕阅读器模式
| 模式 | 用途 | 使用场景 |
|---|---|---|
| 浏览/虚拟 | 阅读内容 | 默认阅读模式 |
| 焦点/表单 | 与控件交互 | 填写表单 |
| 应用程序 | 自定义小组件 | ARIA 应用程序 |
在 macOS 上测试前,请阅读 VoiceOver 设置、命令和检查清单。
如需覆盖 iOS Safari,请阅读 VoiceOver 触控设置和手势。
使用 NVDA 测试前,请阅读 NVDA 设置、模式、命令和测试脚本。
使用 JAWS 或 TalkBack 测试前,请阅读 平台设置和命令。
常见测试场景
1. 模态对话框
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
>
<h2 id="dialog-title">Confirm Delete</h2>
<p id="dialog-desc">This action cannot be undone.</p>
<button>Cancel</button>
<button>Delete</button>
</div>
// Focus management
function openModal(modal) {
// Store last focused element
lastFocus = document.activeElement;
// Move focus to modal
modal.querySelector("h2").focus();
// Trap focus
modal.addEventListener("keydown", trapFocus);
}
function closeModal(modal) {
// Return focus
lastFocus.focus();
}
function trapFocus(e) {
if (e.key === "Tab") {
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey && document.activeElement === first) {
last.focus();
e.preventDefault();
} else if (!e.shiftKey && document.activeElement === last) {
first.focus();
e.preventDefault();
}
}
if (e.key === "Escape") {
closeModal(modal);
}
}
2. 实时区域
<div role="status" aria-live="polite" aria-atomic="true">
</div>
<div role="alert" aria-live="assertive">
</div>
<div
role="progressbar"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Upload progress"
></div>
<div role="log" aria-live="polite" aria-relevant="additions">
</div>
3. 标签页界面
<div role="tablist" aria-label="Product information">
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">
Description
</button>
<button
role="tab"
id="tab-2"
aria-selected="false"
aria-controls="panel-2"
tabindex="-1"
>
Reviews
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
Product description content...
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
Reviews content...
</div>
// Tab keyboard navigation
tablist.addEventListener("keydown", (e) => {
const tabs = [...tablist.querySelectorAll('[role="tab"]')];
const index = tabs.indexOf(document.activeElement);
let newIndex;
switch (e.key) {
case "ArrowRight":
newIndex = (index + 1) % tabs.length;
break;
case "ArrowLeft":
newIndex = (index - 1 + tabs.length) % tabs.length;
break;
case "Home":
newIndex = 0;
break;
case "End":
newIndex = tabs.length - 1;
break;
default:
return;
}
tabs[newIndex].focus();
activateTab(tabs[newIndex]);
e.preventDefault();
});
调试技巧
// Log what screen reader sees
function logAccessibleName(element) {
const computed = window.getComputedStyle(element);
console.log({
role: element.getAttribute("role") || element.tagName,
name:
element.getAttribute("aria-label") ||
element.getAttribute("aria-labelledby") ||
element.textContent,
state: {
expanded: element.getAttribute("aria-expanded"),
selected: element.getAttribute("aria-selected"),
checked: element.getAttribute("aria-checked"),
disabled: element.disabled,
},
visible: computed.display !== "none" && computed.visibility !== "hidden",
});
}
最佳实践
应做事项
- 使用实际的屏幕阅读器进行测试 - 不要只用模拟器
- 优先使用语义化 HTML - ARIA 是补充手段
- 在浏览和焦点模式下测试 - 两种模式的体验不同
- 验证焦点管理 - 尤其是针对 SPA
- 先只用键盘进行测试 - 这是屏幕阅读器测试的基础
不应做事项
- 不要以为一种屏幕阅读器就够了 - 应测试多种屏幕阅读器
- 不要忽视移动设备 - 用户群体正在不断扩大
- 不要只测试顺利流程 - 也要测试错误状态
- 不要跳过动态内容 - 这是最常见的问题
- 不要依赖视觉测试 - 两者的体验不同
评论
支持 Markdown