SuperAPI AI 工具集

screen-reader-testing

测试与质量

查看项目

更新于 10-16 07:31

技能介绍

用途

本技能是一份使用屏幕阅读器测试网页应用的实用指南,面向需要做全面可访问性验证的场景,帮助开发者与测试人员发现辅助技术下的可用性问题。

何时使用

  • 验证屏幕阅读器兼容性
  • 测试 ARIA 实现
  • 调试辅助技术问题
  • 验证表单可访问性
  • 测试动态内容播报
  • 确保导航可访问性

核心概念

主要屏幕阅读器

资料给出以下对照:

  • VoiceOver:macOS/iOS,浏览器 Safari,使用占比约 15%
  • NVDA:Windows,浏览器 Firefox/Chrome,使用占比约 31%
  • JAWS:Windows,浏览器 Chrome/IE,使用占比约 40%
  • TalkBack:Android,浏览器 Chrome,使用占比约 10%
  • Narrator:Windows,浏览器 Edge,使用占比约 4%

测试优先级

最低覆盖:

  1. NVDA + Firefox(Windows)
  2. VoiceOver + Safari(macOS)
  3. VoiceOver + Safari(iOS)

全面覆盖:

  • 加上 JAWS + Chrome(Windows)
  • 加上 TalkBack + Chrome(Android)
  • 加上 Narrator + Edge(Windows)

屏幕阅读器模式

  • 浏览/虚拟模式:用于阅读内容,默认阅读状态
  • 焦点/表单模式:用于与控件交互,填写表单时使用
  • 应用模式:用于自定义组件,ARIA 应用

各平台准备

  • 在 macOS 测试前,阅读 VoiceOver 的设置、命令和检查清单
  • 对于必需的 iOS Safari 覆盖,阅读 VoiceOver 触控设置和手势
  • 使用 NVDA 测试前,阅读 NVDA 设置、模式、命令和测试脚本
  • 使用 JAWS 或 TalkBack 测试前,阅读对应平台的设置和命令

常见测试场景

1. 模态对话框

资料给出可访问的模态结构示例:

<!-- 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);
  }
}

2. 实时区域

<!-- 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>

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)
  • 先做纯键盘测试,这是屏幕阅读器测试的基础

不应当做:

  • 不要以为一款屏幕阅读器就够了,要多款测试
  • 不要忽略移动端,用户群体在增长
  • 不要只测顺利路径,要测错误状态
  • 不要跳过动态内容,这是最常见的问题来源
  • 不要依赖视觉测试,屏幕阅读器体验不同

限制

资料未提供该技能的具体安装命令、运行环境要求、收费方式或与具体工具链的集成说明,这些信息暂未提供。