VoiceOver Testing Checklist
VoiceOver Testing Checklist
Page Load
- [ ] Page title announced
- [ ] Main landmark found
- [ ] Skip link works
Navigation
- [ ] All headings discoverable via rotor
- [ ] Heading levels logical (H1 → H2 → H3)
- [ ] Landmarks properly labeled
- [ ] Skip links functional
Links & Buttons
- [ ] Link purpose clear
- [ ] Button actions described
- [ ] New window/tab announced
Forms
- [ ] All labels read with inputs
- [ ] Required fields announced
- [ ] Error messages read
- [ ] Instructions available
- [ ] Focus moves to errors
Dynamic Content
- [ ] Alerts announced immediately
- [ ] Loading states communicated
- [ ] Content updates announced
- [ ] Modals trap focus correctly
Tables
- [ ] Headers associated with cells
- [ ] Table navigation works
- [ ] Complex tables have captions
### 常见问题与修复
技能文档给出了三组典型“问题代码 → 修复代码”对照:
```html
<!-- Issue: Button not announcing purpose -->
<button><svg>...</svg></button>
<!-- Fix -->
<button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
<!-- Issue: Dynamic content not announced -->
<div id="results">New results loaded</div>
<!-- Fix -->
<div id="results" role="status" aria-live="polite">New results loaded</div>
<!-- Issue: Form error not read -->
<input type="email" />
<span class="error">Invalid email</span>
<!-- Fix -->
<input type="email" aria-invalid="true" aria-describedby="email-error" />
<span id="email-error" role="alert">Invalid email</span>
三处修复分别对应屏幕阅读器领域的三类经典缺陷:
- 图标按钮无名称:纯 SVG 的
<button>没有可访问名称,屏幕阅读器只会播报 “button”。修复方式为按钮加aria-label声明用途,同时给 SVG 加aria-hidden="true"防止其内部文本被重复播报。 - 动态内容静默更新:JS 直接改写
<div>内容不会触发播报,需将该区域标记为role="status"(等价于aria-live="polite"的隐式声明)或显式加aria-live。 - 表单错误不播报:错误提示须通过
aria-describedby与输入控件关联(使播报名称中包含错误),用aria-invalid="true"标记无效状态,并给提示元素role="alert"使其作为断言型实时区域即时播报。
NVDA(Windows)完整测试手册
环境配置
Download: nvaccess.org
Start: Ctrl + Alt + N
Stop: Insert + Q
常用命令全集
NVDA 修饰键为 Insert,常用命令如下:
Navigation:
Insert = NVDA modifier
Down Arrow Next line
Up Arrow Previous line
Tab Next focusable
Shift + Tab Previous focusable
Reading:
NVDA + Down Arrow Say all
Ctrl Stop speech
NVDA + Up Arrow Current line
Headings:
H Next heading
Shift + H Previous heading
1-6 Heading level 1-6
Forms:
F Next form field
B Next button
E Next edit field
X Next checkbox
C Next combo box
Links:
K Next link
U Next unvisited link
V Next visited link
Landmarks:
D Next landmark
Shift + D Previous landmark
Tables:
T Next table
Ctrl + Alt + Arrows Navigate cells
Elements List (NVDA + F7):
Shows all links, headings, form fields, landmarks
几个值得注意的命令:NVDA + F7 打开元素列表(Elements List),可一次性列出页面全部链接、标题、表单字段、地标,适合快速核对页面结构;Ctrl + Alt + 方向键 用于表格单元格间导航,是检验表头关联是否正确的直接手段。
Browse 模式与 Focus 模式
NVDA 会在两种模式间自动切换,这是 Windows 侧测试必须观察的行为:
NVDA automatically switches modes:
- Browse Mode: Arrow keys navigate content
- Focus Mode: Arrow keys control interactive elements
Manual switch: NVDA + Space
Watch for:
- "Browse mode" announcement when navigating
- "Focus mode" when entering form fields
- Application role forces forms mode
排查“方向键行为突然变了”这类问题,先确认播报里出现的是 “Browse mode” 还是 “Focus mode”;特别地,role="application" 会强制进入表单模式,这在自绘 Canvas/富文本组件中影响很大。
NVDA 测试脚本
技能文档提供了一套按步骤执行的 NVDA 测试脚本,从初始加载到动态内容共六个环节:
## NVDA Test Script
### Initial Load
1. Navigate to page
2. Let page finish loading
3. Press Insert + Down to read all
4. Note: Page title, main content identified?
### Landmark Navigation
1. Press D repeatedly
2. Check: All main areas reachable?
3. Check: Landmarks properly labeled?
### Heading Navigation
1. Press Insert + F7 → Headings
2. Check: Logical heading structure?
3. Press H to navigate headings
4. Check: All sections discoverable?
### Form Testing
1. Press F to find first form field
2. Check: Label read?
3. Fill in invalid data
4. Submit form
5. Check: Errors announced?
6. Check: Focus moved to error?
### Interactive Elements
1. Tab through all interactive elements
2. Check: Each announces role and state
3. Activate buttons with Enter/Space
4. Check: Result announced?
### Dynamic Content
1. Trigger content update
2. Check: Change announced?
3. Open modal
4. Check: Focus trapped?
5. Close modal
6. Check: Focus returns?
该脚本的验证顺序暗含一条依赖链:地标 → 标题 → 表单 → 交互元素 → 动态内容,前一级通过是后一级有效测试的前提(例如标题结构混乱时,Insert + F7 的结果就不可信)。
JAWS(Windows)与 TalkBack(Android)要点
JAWS 常用命令
Start: Desktop shortcut or Ctrl + Alt + J
Virtual Cursor: Auto-enabled in browsers
Navigation:
Arrow keys Navigate content
Tab Next focusable
Insert + Down Read all
Ctrl Stop speech
Quick Keys:
H Next heading
T Next table
F Next form field
B Next button
G Next graphic
L Next list
; Next landmark
Forms Mode:
Enter Enter forms mode
Numpad + Exit forms mode
F5 List form fields
Lists:
Insert + F7 Link list
Insert + F6 Heading list
Insert + F5 Form field list
Tables:
Ctrl + Alt + Arrows Table navigation
JAWS 在浏览器中默认启用虚拟光标(Virtual Cursor),其“元素列表”三件套(Insert + F7 链接列表、Insert + F6 标题列表、Insert + F5 表单字段列表)与 NVDA 的元素列表功能对应,是两份环境共用的结构核对手段。
TalkBack(Android)
Enable: Settings → Accessibility → TalkBack
Toggle: Hold both volume buttons 3 seconds
移动端全部依赖手势完成:
Explore: Drag finger across screen
Next: Swipe right
Previous: Swipe left
Activate: Double tap
Scroll: Two finger swipe
Reading Controls (swipe up then right):
- Headings
- Links
- Controls
- Characters
- Words
- Lines
- Paragraphs
TalkBack 的 “Reading Controls”(上滑再右滑唤出)提供按标题/链接/控件等粒度的导航,其作用等价于 VoiceOver 的 Rotor 和 NVDA/JAWS 的元素列表——移动端测试时应确认页面在按标题导航时呈现的层级与桌面端一致。
三大典型场景的参考实现
以下三组“HTML + JavaScript”参考实现均出自技能文档,是屏幕阅读器验证中最常出问题的三个组件类型。
场景 1:模态对话框(Modal Dialog)
模态框必须满足三件事:角色与标题关联(role="dialog" + aria-modal="true" + aria-labelledby)、焦点被约束在框内(focus trap)、关闭后焦点归还触发元素。
<!-- Accessible modal structure -->
<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);
}
}
用屏幕阅读器验证时对应检查点:打开后 NVDA 是否播报对话框标题(VoiceOver 会播报 “Confirm Delete, dialog”)、Tab 循环是否困在框内、关闭后焦点是否回到触发按钮——这正是 NVDA 测试脚本 “Dynamic Content” 环节的 3~6 步。
场景 2:实时区域(Live Regions)
动态内容的播报策略由 aria-live 的礼貌等级与角色决定:
<!-- Status messages (polite) -->
<div role="status" aria-live="polite" aria-atomic="true">
<!-- Content updates will be announced after current speech -->
</div>
<!-- Alerts (assertive) -->
<div role="alert" aria-live="assertive">
<!-- Content updates interrupt current speech -->
</div>
<!-- Progress updates -->
<div
role="progressbar"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Upload progress"
></div>
<!-- Log (additions only) -->
<div role="log" aria-live="polite" aria-relevant="additions">
<!-- New messages announced, removals not -->
</div>
选择原则:role="status"(polite)用于“结果已加载”这类常规状态更新,不打断当前播报;role="alert"(assertive)用于表单校验失败、系统错误这类必须立刻感知的事件;role="log" 配合 aria-relevant="additions" 只播报追加内容,适合消息流、通知面板;进度类内容用 role="progressbar" 并维护 aria-valuenow。
场景 3:标签页界面(Tab Interface)
标签页需要完整的 tablist / tab / tabpanel 角色链,配合 aria-selected、aria-controls、tabindex 管理,并实现方向键 / Home / End 键盘导航:
<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();
});
实现要点:非选中的 tab 使用 tabindex="-1" 使 Tab 键只进出 tablist 一次、方向键在内部切换(roving tabindex 模式);隐藏的 panel 用 hidden 属性从可访问性树中移除,避免屏幕阅读器读到已隐藏内容。用 NVDA 验证时,焦点停在 tab 上应听到 “Tab, 2 of 2, Description, unselected” 之类的角色、序号与状态播报。
调试技巧:查看屏幕阅读器“看到”的内容
当播报不符合预期时,技能文档提供了一个控制台辅助函数,用于打印元素的等效角色、可访问名称与状态,帮助对齐 DOM 与屏幕阅读器认知:
// 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",
});
}
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00