Files
2026-07-13 21:37:15 +08:00

539 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 + FirefoxWindows
2. VoiceOver + SafarimacOS
3. VoiceOver + SafariiOS
全面覆盖:
+ JAWS + ChromeWindows
+ TalkBack + ChromeAndroid
+ Narrator + EdgeWindows
```
### 3. 屏幕阅读器模式
| 模式 | 用途 | 使用场景 |
| ---------------------- | -------------- | ------------------ |
| **浏览/虚拟** | 阅读内容 | 默认阅读 |
| **焦点/表单** | 与控件交互 | 填写表单 |
| **应用程序** | 自定义微件 | ARIA 应用程序 |
## VoiceOvermacOS
### 设置
```
启用:系统设置 → 辅助功能 → VoiceOver
开关:Cmd + F5
快速开关:三按 Touch ID
```
### 常用命令
```
导航:
VO = Ctrl + OptionVoiceOver 修饰键)
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
<!-- 问题:按钮未播报用途 -->
<button><svg>...</svg></button>
<!-- 修复 -->
<button aria-label="关闭对话框"><svg aria-hidden="true">...</svg></button>
<!-- 问题:动态内容未播报 -->
<div id="results">新结果已加载</div>
<!-- 修复 -->
<div id="results" role="status" aria-live="polite">新结果已加载</div>
<!-- 问题:表单错误未被朗读 -->
<input type="email" />
<span class="error">无效的电子邮件</span>
<!-- 修复 -->
<input type="email" aria-invalid="true" aria-describedby="email-error" />
<span id="email-error" role="alert">无效的电子邮件</span>
```
## NVDAWindows
### 设置
```
下载: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. 检查:焦点是否返回?
```
## JAWSWindows
### 常用命令
```
启动:桌面快捷方式或 Ctrl + Alt + J
虚拟光标:浏览器中自动启用
导航:
箭头键 导航内容
Tab 下一个可聚焦元素
Insert + 下箭头键 朗读全部
Ctrl 停止语音
快速键:
H 下一个标题
T 下一个表格
F 下一个表单字段
B 下一个按钮
G 下一个图形
L 下一个列表
; 下一个地标
表单模式:
Enter 进入表单模式
Numpad + 退出表单模式
F5 列出表单字段
列表:
Insert + F7 链接列表
Insert + F6 标题列表
Insert + F5 表单字段列表
表格:
Ctrl + Alt + 箭头键 表格导航
```
## TalkBackAndroid
### 设置
```
启用:设置 → 辅助功能 → TalkBack
开关:同时按住两个音量键 3 秒
```
### 手势
```
探索:在屏幕上拖动手指
下一个:向右滑动
上一个:向左滑动
激活:双击
滚动:双指滑动
阅读控制(向上滑动再向右):
- 标题
- 链接
- 控件
- 字符
- 单词
- 行
- 段落
```
## 常见测试场景
### 1. 模态对话框
```html
<!-- 无障碍模态框结构 -->
<div
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc"
>
<h2 id="dialog-title">确认删除</h2>
<p id="dialog-desc">此操作不可撤销。</p>
<button>取消</button>
<button>删除</button>
</div>
```
```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
<!-- 状态消息(礼貌式) -->
<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="上传进度"
></div>
<!-- 日志(仅新增) -->
<div role="log" aria-live="polite" aria-relevant="additions">
<!-- 新消息播报,移除的不播报 -->
</div>
```
### 3. 标签页界面
```html
<div role="tablist" aria-label="产品信息">
<button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">
描述
</button>
<button
role="tab"
id="tab-2"
aria-selected="false"
aria-controls="panel-2"
tabindex="-1"
>
评论
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
产品描述内容...
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
评论内容...
</div>
```
```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 是补充手段
- **在浏览模式和焦点模式下分别测试**——体验不同
- **验证焦点管理**——尤其是单页应用
- **先测试纯键盘操作**——这是屏幕阅读器测试的基础
### 不应做事项
- **不要认为一个屏幕阅读器就够了**——请测试多个
- **不要忽略移动端**——用户群体在持续增长
- **不要只测试正常流程**——也要测试错误状态
- **不要跳过动态内容**——这是最常见的问题来源
- **不要依赖视觉测试**——用户体验完全不同