Local Deep Research 无障碍测试指南:WCAG 2.1 AA 与屏幕阅读器兼容性的工程化落地
【免费下载链接】local-deep-research~95% on SimpleQA (e.g. Qwen3.6-27B on a 3090). Supports all local and cloud LLMs (llama.cpp, Ollama, Google, ...). 10+ search engines - arXiv, PubMed, your private documents. Everything Local & Encrypted.项目地址: https://gitcode.com/GitHub_Trending/lo/local-deep-research
本指南以 Local Deep Research(LDR)仓库中的 无障碍测试文档 为骨架,结合 后端测试、Playwright 合规测试、axe-core 辅助工具 等源码证据,系统讲解如何通过自动化手段保障 Web 界面符合 WCAG 2.1 AA 标准、支持屏幕阅读器与纯键盘操作。读者学完后,将能够独立运行整套无障碍测试、定位并修复典型的 ARIA/键盘导航缺陷,并把无障碍检查无缝接入 CI 流水线。
一、测试体系概览:双层自动化结构
LDR 的 Web 界面无障碍测试采用“JavaScript 端到端 + Python 后端结构”双层设计,位于 tests/accessibility_tests/ 目录,目标是确保研究页、设置页、历史页等核心页面满足 WCAG 2.1 AA 与屏幕阅读器兼容性要求。
| 层 | 入口文件 | 技术栈 | 验证维度 |
|---|---|---|---|
| 前端行为层 | wcag-compliance.spec.js | Playwright + @axe-core/playwright | 真实浏览器中的 DOM 结构、键盘导航、动态内容 ARIA |
| 后端结构层 | test_accessibility_backend.py | pytest + BeautifulSoup | 服务端渲染 HTML 的语义结构、配置与样式 |
两层各有分工:Python 层在无浏览器环境下快速校验 HTML 骨架,JavaScript 层则在真实浏览器中验证交互行为。辅助文件 axe-helper.js 封装了 axe-core 的公共配置,auth-setup.js 负责测试前的登录态准备(默认使用test_admin/testpass123测试凭据,可通过环境变量覆盖)。
1.1 JavaScript 测试覆盖范围
Playwright 测试重点覆盖四大类能力:
- 屏幕阅读器兼容:研究模式选择使用真正的
<input type="radio">结构(而非 div 模拟),校验 ARIA 属性与角色、.sr-only辅助元素、<fieldset>/<legend>分组; - 键盘导航:Tab 顺序与焦点管理、单选组的方向键(Arrow 键)切换、Enter/Space 激活、快捷键(Enter 提交、Shift+Enter 换行、Ctrl+Enter 备选提交);
- 表单无障碍:表单控件标签完整、键盘提示可见、纯键盘可完成表单提交、焦点指示器清晰;
- WCAG 合规:通过 axe-core 自动化扫描 color contrast、焦点可见性与语义标记校验。
1.2 Python 测试覆盖范围
Python 后端测试校验服务端 HTML:
- HTML 结构:表单标签、语义化标记、标题层级、必填字段标记;
- ARIA 实现:单选组结构、ARIA 角色与属性、屏幕阅读器支持元素;
- 配置与样式:
<html lang>属性、viewport meta 标签、CSS 焦点样式、跳转链接(skip link)、错误提示容器。
二、环境准备与运行命令
2.1 安装依赖
# Python 依赖(后端结构测试) pip install pytest beautifulsoup4 requests # Node.js 依赖(Playwright 前端测试) npm install playwright @playwright/test npx playwright install测试目录 package.json 声明了@axe-core/playwright ^4.13.0、@playwright/test ^1.63.0与@lhci/cli ^0.15.1,要求 Node.js>=22.19,并针对yauzl、tmp、lodash、ws等传递依赖配置了安全覆盖(overrides)。
2.2 启动被测应用
测试前必须先启动 Web 服务(默认地址http://localhost:5000):
# 方式一:仓库根目录启动 python app.py # 方式二:以模块方式启动 python -m src.local_deep_research.web.appTEST_BASE_URL环境变量可覆盖被测地址(默认http://localhost:5000),例如:
export TEST_BASE_URL=http://localhost:8080Playwright 测试则读取BASE_URL环境变量(wcag-compliance.spec.js 默认同样为http://localhost:5000)。
2.3 运行 JavaScript 测试
# 运行全部无障碍测试 npx playwright test test_accessibility.js # UI 模式调试(可视化查看每一步) npx playwright test test_accessibility.js --ui # 按用例名称过滤运行 npx playwright test test_accessibility.js -g "mode selection should have proper radio button structure"注意:README 中示例文件名为
test_accessibility.js,仓库实际实现为 wcag-compliance.spec.js,运行时可替换为实际文件名。
2.4 运行 Python 测试
# 从 tests 目录运行 cd tests python -m pytest ui_tests/test_accessibility_backend.py -v # 带覆盖率运行 python -m pytest ui_tests/test_accessibility_backend.py --cov=src # 运行单个测试用例 python -m pytest ui_tests/test_accessibility_backend.py::TestHTMLAccessibility::test_radio_button_structure -vREADME 中路径为ui_tests/test_accessibility_backend.py,仓库实际文件位于 tests/accessibility_tests/test_accessibility_backend.py,按实际路径调整即可。
2.5 CI 快速命令
# 快速无障碍检查 npm run test:accessibility:quick # 完整无障碍套件 npm run test:accessibility:full # Python 无障碍测试 python -m pytest tests/ui_tests/test_accessibility_backend.py三、axe-core 扫描与 WCAG 合规测试(源码级剖析)
3.1 axe 配置封装
axe-helper.js 提供了统一入口:
import AxeBuilder from '@axe-core/playwright'; // 默认启用 WCAG 2.1/2.2 的 A/AA 标签 const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']; export function createAxeBuilder(page, options = {}) { const { tags = WCAG_TAGS, exclude = [], disableRules = [] } = options; let builder = new AxeBuilder({ page }).withTags(tags); exclude.forEach(selector => { builder = builder.exclude(selector); }); if (disableRules.length > 0) { builder = builder.disableRules(disableRules); } return builder; }配套提供两个工具函数:
getCriticalViolations(violations):只筛选critical与serious两个等级的问题(axe-helper.js),避免被 minor 问题淹没;formatViolations(violations):把违规详情(影响等级、规则 ID、帮助链接、受影响元素选择器)格式化为可读的控制台输出。
3.2 全页面扫描用例
wcag-compliance.spec.js 对研究页/、历史页/history、设置页/settings三个页面做全量 WCAG 2.1 AA 扫描,以串行模式运行:
const results = await createAxeBuilder(page, { disableRules: AXE_DISABLE_RULES }).analyze(); const criticalViolations = getCriticalViolations(results.violations); expect(criticalViolations).toHaveLength(0);扫描结果通过test.info().annotations以accessibility-summary类型写入测试输出(violations 数量与 passes 数量),便于 CI 报表归档。
3.3 color-contrast 规则的工程取舍
源码中有一条值得注意的工程决策:全局禁用了color-contrast规则(wcag-compliance.spec.js):
// Color-contrast is excluded because the default theme (sepia/solarized-light) // has text colors that don't meet WCAG AA 4.5:1 contrast ratios. // This is a systemic theme design issue tracked separately. const AXE_DISABLE_RULES = ['color-contrast'];注释明确说明:默认主题(sepia/solarized-light)的文字颜色未达到 WCAG AA 4.5:1 对比度,属于系统性的主题设计问题,需单独跟踪,而非页面级的局部缺陷。因此测试策略是:先在自动化扫描中排除该规则以保证流水线可运行,同时将其作为独立议题跟踪修复。这一做法体现了"自动化兜底 + 人工跟进系统性问题"的务实分层。
3.4 单页面重点断言
除全量扫描外,研究页还有 7 个细粒度用例(wcag-compliance.spec.js):
- 标题结构:
main内恰好 1 个h1,标题层级不允许向上跳级(如h1后直接h3); - 页面地标(landmark):至少 1 个
main或role="main",至少 1 个nav或role="navigation"; - 表单标签:每个非 hidden 的 input/select/textarea 必须有
label[for]、包裹型<label>、aria-label或aria-labelledby之一; - 图片 alt:所有
<img>必须有alt或aria-label; - 按钮可访问名称:必须有
aria-label、aria-labelledby、文本内容或title; - 链接可辨别文本:文本、ARIA 名称或内嵌带 alt 的图片至少占其一;
- 设置页表单分组:每个
.form-group/.ldr-form-group内必须包含label或legend。
四、键盘导航与动态内容测试
4.1 Tab 顺序与焦点可见性
导航测试(wcag-compliance.spec.js)连续按下 10 次 Tab,记录每次聚焦元素的 tag/id/class,断言焦点至少移动过 2 个不同元素(验证 Tab 顺序存在且推进)。随后检查聚焦元素的outline、outlineWidth、boxShadow计算样式,确认存在可见焦点指示器。
这一断言与后端 CSS 相印证:仓库的 custom_dropdown.css 为.ldr-mode-option定义了:focus与:focus-visible的outline: 2px solid var(--accent-primary, #6e4ff6); outline-offset: 2px;,保证键盘聚焦有清晰可见的视觉反馈。
4.2 动态内容的 ARIA 规范
- 警报容器(wcag-compliance.spec.js):
[role="alert"]、.alert-container、.ldr-settings-alert-container必须具有role="alert"、role="status"或aria-live之一,且aria-live属性可来自祖先元素。这与研究页模板 research.html 中的<div id="research-alert" class="ldr-settings-alert-container" role="alert" aria-atomic="true">一一对应; - 进度条:
[role="progressbar"]必须同时具备aria-valuenow、aria-valuemin、aria-valuemax三个属性(无进度条时用例自动 skip)。
4.3 自定义下拉框 combobox 结构
组件测试(wcag-compliance.spec.js)验证:所有[role="combobox"]必须通过aria-controls指向一个role="listbox"的元素。若目标元素存在则校验其角色;不存在时虽不失败,但缺失aria-controls本身即视为违规。这正是 ARIA 组合组件(combobox → listbox → option)的推荐实践。
五、Python 后端测试:HTML 结构与 ARIA 实现(逐用例解析)
5.1 通用测试夹具
test_accessibility_backend.py 通过authenticated_clientfixture 请求/,将响应 HTML 交给 BeautifulSoup 解析后供各用例复用。
5.2 表单标签与单选组结构
test_form_has_proper_labels:遍历所有非 hidden/submit/button、非csrf_token的 input/textarea/select,凡带id的必须存在对应的label[for];test_radio_button_structure:若页面存在type="radio",断言其必有name属性,且带id时必须有对应<label>;test_fieldset_and_legend:若存在<fieldset>,每个都必须含<legend>或aria-label/aria-labelledby;若没有 fieldset,则要求存在 form-group 类分组或<form>,作为现代 UI 的替代分组方案。
这些断言与前端模板 research.html 的实现严格吻合——研究模式选择使用<fieldset>+<legend>+ 三个带sr-only类的原生 radio:
<fieldset> <legend>Research Mode</legend> <input type="radio" id="mode-quick" name="research_mode" value="quick" checked class="sr-only"> ... <input type="radio" id="mode-detailed" name="research_mode" value="detailed" class="sr-only"> ... <input type="radio" id="mode-chat" name="research_mode" value="chat" class="sr-only"> ... </fieldset>这正是 README 中 Issue #75(屏幕阅读器兼容)的核心修复:用原生 radio 取代 div 模拟的选择器,使屏幕阅读器能正确播报选中状态,同时保留自定义视觉样式(原生控件用.sr-only视觉隐藏)。
5.3 ARIA 属性与必填字段
test_aria_attributes:所有 button/a 必须有文本、aria-label或title(保证可访问名称);test_required_fields_marked:带required属性的输入,其标签文本须含*或 "required" 字样(或具备 required 类),或元素本身带aria-required="true"。研究页文本域 research.html 使用aria-required="true"即为此规范。
5.4 语义化与标题层级
test_semantic_markup要求页面至少出现 header/nav/main/footer/section/article 中的一个;test_heading_hierarchy要求至少有 1 个h1且标题级别不得跳级(h1 → h2 → h3…)。后者与前端用例中"main 内恰好一个 h1、向上不跳级"的规则形成前后端双重校验。
5.5 配置与样式类测试
TestAccessibilityConfiguration(test_accessibility_backend.py)验证:
- CSS 焦点样式:请求
/static/css/style.css,若返回 200 则必须包含:focus、focus-visible或outline(无法访问时自动 skip); - 响应式 viewport:
<meta name="viewport">的 content 必须含width=device-width; - lang 属性:
<html lang>必须为en/en-US/en-GB之一——仓库 base.html 使用<html lang="en">queryInput.addEventListener('keydown', function(event) { if (event.key === 'Enter') { if (event.shiftKey) { // Shift+Enter:允许默认行为(插入换行),不做拦截 } else if (event.ctrlKey || event.metaKey) { // Ctrl+Enter / Cmd+Enter:备选提交方式(通用习惯) event.preventDefault(); handleResearchSubmit(new Event('submit')); } else { // 单独 Enter:直接提交表单(既有行为) event.preventDefault(); handleResearchSubmit(new Event('submit')); } } });对应的键盘提示以
.sr-only文本形式渲染在 research.html,屏幕阅读器可读出提示,视觉用户不受干扰。模式切换的方向键导航(README 中"Arrow key navigation for radio button groups")同样在 research.js 实现:监听
keydown,ArrowLeft/ArrowUp选中上一个模式,ArrowRight/ArrowDown选中下一个模式,并调用selectMode()同步视觉状态与 ARIA 状态。七、浏览器兼容矩阵与测试前置
JavaScript 测试在Chromium(Chrome/Edge)、Firefox、WebKit(Safari)三种浏览器引擎上运行。不同浏览器对屏幕阅读器 API 的行为存在差异(尤其涉及 ARIA 动态播报),因此多引擎矩阵是必要的验证手段。
wcag-compliance.spec.js 还配置了:
test.use({ baseURL: BASE_URL, trace: 'on-first-retry', // 首次失败重试时记录 trace screenshot: 'only-on-failure', // 仅在失败时截图 });便于 CI 排障。
登录态处理(auth-setup.js)
由于多数页面需要登录,auth-setup.js 在测试前通过 project setup 任务完成一次登录并把
storageState保存到.auth/user.json,后续用例直接复用登录态,避免重复登录拖慢套件。脚本中值得注意的细节:- 登录超时放宽到 180 秒(
setup.setTimeout(180_000)),注释解释原因:冷启动 Docker 时首次登录会创建加密 SQLCipher 数据库、从密码派生密钥并导入 500+ 默认设置,实际等待由内部page.waitForURL('/', { timeout: 120_000 })兜底; - 登录后断言
.ldr-user-info可见,确保真正进入已登录状态再保存会话。
八、常见测试失败与修复方案
8.1 单选组结构问题
症状:找不到 radio 或标签不正确。修复:确保模板使用原生
<input type="radio">并配对应<label for>。参考 research.html 的 fieldset/legend 结构,避免用 div 模拟选择器。8.2 ARIA 属性不同步
症状:
aria-checked与实际选中状态不一致。修复:检查 research.js 的selectMode()函数——它必须同时更新视觉样式(active class)与 ARIA 状态(aria-checked/checked 属性)。8.3 键盘导航失效
症状:方向键无法切换模式。修复:确认事件监听器正确绑定在 research.js 的 mode 元素上,且
preventDefault()未被遗漏导致页面滚动干扰焦点。8.4 焦点可见性缺失
症状:Tab 聚焦后看不到焦点指示。修复:确认 custom_dropdown.css 中
:focus/:focus-visible的outline样式存在且未被outline: none覆盖(注意.ldr-custom-dropdown-input:focus { outline: none; }这类局部重置,需在组件级别单独补充可见焦点样式)。九、手动测试补充建议
自动化无法完全替代真实用户感知,README 建议至少覆盖以下场景:
- 屏幕阅读器实测:NVDA(Windows)、JAWS(Windows)、VoiceOver(macOS)各跑一遍核心流程;
- 纯键盘导航:断开鼠标,仅用 Tab、方向键、Enter/Space 完成一次完整的"输入问题 → 选择模式 → 提交研究"流程;
- 高对比模式:Windows 高对比模式下验证焦点指示器仍然可见;
- 200% 缩放下验证:所有内容在 200% 缩放下仍可访问、不丢功能。
十、为新增 UI 功能贡献无障碍测试
在添加新 UI 功能时,应遵循以下验收清单:
- 为功能补充对应的无障碍测试用例;
- 正确实现 ARIA 属性(角色、状态、labelledby 关联);
- 测试键盘导航路径(Tab 顺序、方向键、快捷键);
- 验证屏幕阅读器兼容性(可访问名称、播报内容);
- 提交 PR 前完整运行整套无障碍测试套件。
后端断言(如 test_accessibility_backend.py 中的语义化、标题层级、必填标记)可作为新页面模板的"结构红线";前端 axe 扫描与键盘用例则是"行为红线"。两层同时通过,才能保障新功能不回归 WCAG 2.1 AA 合规状态。
十一、最佳实践总结
- 原生优先:能用
<button>、<input type="radio">等原生控件就不用 div 模拟,原生控件自带键盘与屏幕阅读器语义; - 自动化分两级:后端结构断言负责快速反馈,axe-core 扫描负责行为级兜底,二者互补;
- 系统性问题单独跟踪:像 color-contrast 这类主题级的合规缺口,应在排除规则的同时登记独立议题,避免掩盖问题;
- 登录态复用:用 storageState 复用会话,显著缩短多页面测试耗时;
- 多浏览器矩阵:屏幕阅读器与键盘行为存在引擎差异,Chromium/Firefox/WebKit 三端都必须跑;
- 失败可观测:开启 trace 与失败截图,让 CI 中的无障碍失败可快速定位。
通过上述双层测试体系,Local Deep Research 得以在持续迭代中守住 WCAG 2.1 AA 与屏幕阅读器兼容性的底线——这正是 无障碍测试文档 及其配套 前端合规测试、后端结构测试 为项目提供的核心价值。
【免费下载链接】local-deep-research~95% on SimpleQA (e.g. Qwen3.6-27B on a 3090). Supports all local and cloud LLMs (llama.cpp, Ollama, Google, ...). 10+ search engines - arXiv, PubMed, your private documents. Everything Local & Encrypted.
项目地址: https://gitcode.com/GitHub_Trending/lo/local-deep-research
- 登录超时放宽到 180 秒(
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考