--- name: screen-reader-testing description: 使用屏幕阅读器(包括 VoiceOver、NVDA 和 JAWS)测试 Web 应用程序。适用于验证屏幕阅读器兼容性、调试无障碍问题或确保辅助技术支持。 --- # 屏幕阅读器测试 使用屏幕阅读器测试 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. 测试优先级 ``` 最低覆盖: 1. NVDA + Firefox(Windows) 2. VoiceOver + Safari(macOS) 3. VoiceOver + Safari(iOS) 全面覆盖: + JAWS + Chrome(Windows) + TalkBack + Chrome(Android) + Narrator + Edge(Windows) ``` ### 3. 屏幕阅读器模式 | 模式 | 用途 | 使用场景 | | ---------------------- | -------------- | ------------------ | | **浏览/虚拟** | 阅读内容 | 默认阅读 | | **焦点/表单** | 与控件交互 | 填写表单 | | **应用程序** | 自定义微件 | ARIA 应用程序 | ## VoiceOver(macOS) ### 设置 ``` 启用:系统设置 → 辅助功能 → VoiceOver 开关:Cmd + F5 快速开关:三按 Touch ID ``` ### 常用命令 ``` 导航: VO = Ctrl + Option(VoiceOver 修饰键) VO + 右箭头键 下一个元素 VO + 左箭头键 上一个元素 VO + Shift + 下箭头键 进入群组 VO + Shift + 上箭头键 退出群组 阅读: VO + A 从光标处开始朗读所有内容 Ctrl 停止朗读 VO + B 朗读当前段落 交互: VO + Space 激活元素 VO + Shift + M 打开菜单 Tab 下一个可聚焦元素 Shift + Tab 上一个可聚焦元素 转子(VO + U): 按以下类别导航:标题、链接、表单、地标 左/右箭头键 切换转子类别 上/下箭头键 在类别内导航 Enter 跳转到项目 网页专用: VO + Cmd + H 下一个标题 VO + Cmd + J 下一个表单控件 VO + Cmd + L 下一个链接 VO + Cmd + T 下一个表格 ``` ### 测试清单 ```markdown ## VoiceOver 测试清单 ### 页面加载 - [ ] 页面标题已播报 - [ ] 找到主要地标 - [ ] 跳过链接可用 ### 导航 - [ ] 所有标题可通过转子发现 - [ ] 标题层级合理(H1 → H2 → H3) - [ ] 地标已正确标注 - [ ] 跳过链接功能正常 ### 链接与按钮 - [ ] 链接用途清晰 - [ ] 按钮操作已描述 - [ ] 新窗口/标签页已播报 ### 表单 - [ ] 所有标签随输入框一并朗读 - [ ] 必填字段已播报 - [ ] 错误信息已朗读 - [ ] 操作指引可用 - [ ] 焦点移至错误处 ### 动态内容 - [ ] 弹窗立即播报 - [ ] 加载状态已传达 - [ ] 内容更新已播报 - [ ] 模态框正确锁定焦点 ### 表格 - [ ] 表头与单元格关联 - [ ] 表格导航正常 - [ ] 复杂表格有标题 ``` ### 常见问题与修复 ```html
新结果已加载
新结果已加载
无效的电子邮件 无效的电子邮件 ``` ## NVDA(Windows) ### 设置 ``` 下载:nvaccess.org 启动:Ctrl + Alt + N 停止:Insert + Q ``` ### 常用命令 ``` 导航: Insert = NVDA 修饰键 下箭头键 下一行 上箭头键 上一行 Tab 下一个可聚焦元素 Shift + Tab 上一个可聚焦元素 阅读: NVDA + 下箭头键 朗读全部 Ctrl 停止语音 NVDA + 上箭头键 当前行 标题: H 下一个标题 Shift + H 上一个标题 1-6 标题级别 1-6 表单: F 下一个表单字段 B 下一个按钮 E 下一个编辑框 X 下一个复选框 C 下一个组合框 链接: K 下一个链接 U 下一个未访问链接 V 下一个已访问链接 地标: D 下一个地标 Shift + D 上一个地标 表格: T 下一个表格 Ctrl + Alt + 箭头键 导航单元格 元素列表(NVDA + F7): 显示所有链接、标题、表单字段、地标 ``` ### 浏览模式与焦点模式 ``` NVDA 自动切换模式: - 浏览模式:箭头键导航内容 - 焦点模式:箭头键控制交互元素 手动切换:NVDA + Space 注意观察: - 导航时听到「浏览模式」提示 - 进入表单字段时听到「焦点模式」 - application 角色强制进入表单模式 ``` ### 测试脚本 ```markdown ## NVDA 测试脚本 ### 初始加载 1. 导航到页面 2. 等待页面加载完成 3. 按 Insert + 下箭头键朗读全部内容 4. 注意:页面标题、主要内容是否已识别? ### 地标导航 1. 反复按 D 2. 检查:所有主要区域是否可达? 3. 检查:地标是否已正确标注? ### 标题导航 1. 按 Insert + F7 → 标题 2. 检查:标题结构是否合理? 3. 按 H 导航标题 4. 检查:所有章节是否可发现? ### 表单测试 1. 按 F 找到第一个表单字段 2. 检查:标签是否朗读? 3. 输入无效数据 4. 提交表单 5. 检查:错误是否已播报? 6. 检查:焦点是否移至错误处? ### 交互元素 1. 用 Tab 遍历所有交互元素 2. 检查:每个元素是否播报角色和状态 3. 用 Enter/Space 激活按钮 4. 检查:结果是否已播报? ### 动态内容 1. 触发内容更新 2. 检查:变化是否已播报? 3. 打开模态框 4. 检查:焦点是否锁定? 5. 关闭模态框 6. 检查:焦点是否返回? ``` ## JAWS(Windows) ### 常用命令 ``` 启动:桌面快捷方式或 Ctrl + Alt + J 虚拟光标:浏览器中自动启用 导航: 箭头键 导航内容 Tab 下一个可聚焦元素 Insert + 下箭头键 朗读全部 Ctrl 停止语音 快速键: H 下一个标题 T 下一个表格 F 下一个表单字段 B 下一个按钮 G 下一个图形 L 下一个列表 ; 下一个地标 表单模式: Enter 进入表单模式 Numpad + 退出表单模式 F5 列出表单字段 列表: Insert + F7 链接列表 Insert + F6 标题列表 Insert + F5 表单字段列表 表格: Ctrl + Alt + 箭头键 表格导航 ``` ## TalkBack(Android) ### 设置 ``` 启用:设置 → 辅助功能 → TalkBack 开关:同时按住两个音量键 3 秒 ``` ### 手势 ``` 探索:在屏幕上拖动手指 下一个:向右滑动 上一个:向左滑动 激活:双击 滚动:双指滑动 阅读控制(向上滑动再向右): - 标题 - 链接 - 控件 - 字符 - 单词 - 行 - 段落 ``` ## 常见测试场景 ### 1. 模态对话框 ```html

确认删除

此操作不可撤销。

``` ```javascript // 焦点管理 function openModal(modal) { // 保存最后聚焦的元素 lastFocus = document.activeElement; // 将焦点移到模态框 modal.querySelector("h2").focus(); // 锁定焦点 modal.addEventListener("keydown", trapFocus); } function closeModal(modal) { // 恢复焦点 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. 动态区域 ```html
``` ### 3. 标签页界面 ```html
产品描述内容...
``` ```javascript // 标签页键盘导航 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(); }); ``` ## 调试技巧 ```javascript // 记录屏幕阅读器所看到的内容 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 是补充手段 - **在浏览模式和焦点模式下分别测试**——体验不同 - **验证焦点管理**——尤其是单页应用 - **先测试纯键盘操作**——这是屏幕阅读器测试的基础 ### 不应做事项 - **不要认为一个屏幕阅读器就够了**——请测试多个 - **不要忽略移动端**——用户群体在持续增长 - **不要只测试正常流程**——也要测试错误状态 - **不要跳过动态内容**——这是最常见的问题来源 - **不要依赖视觉测试**——用户体验完全不同