name: accessibility-testing description: 用于WCAG合规性的移动端无障碍测试技能,包含VoiceOver/TalkBack验证、动态类型支持、颜色对比度分析以及跨iOS和Android平台的无障碍审计。 allowed-tools: Read, Grep, Write, Bash, Edit, Glob, WebFetch
无障碍测试技能
为iOS和Android平台提供全面的移动应用无障碍测试和验证,确保符合WCAG 2.1/2.2标准并提供最佳的屏幕阅读器兼容性。
概述
本技能提供测试移动应用无障碍性的能力,包括屏幕阅读器兼容性、动态类型支持、颜色对比度验证以及针对移动平台适配的Web内容无障碍指南(WCAG)合规性检查。
能力
屏幕阅读器测试
- 验证VoiceOver兼容性(iOS)
- 测试TalkBack交互(Android)
- 验证无障碍标签和提示
- 检查阅读顺序和焦点导航
- 测试自定义无障碍操作
动态类型支持
- 验证iOS动态类型缩放
- 测试Android字体缩放偏好
- 检查极端尺寸下的布局适应性
- 验证文本截断处理
- 测试多行文本换行
颜色对比度分析
- 测量对比度比率(WCAG AA/AAA)
- 识别低对比度文本和UI元素
- 针对浅色/深色模式主题进行验证
- 检查色盲无障碍性
- 建议合规的颜色替代方案
无障碍审计
- 运行iOS无障碍检查器审计
- 执行Android无障碍扫描器
- 生成合规性报告
- 识别WCAG违规
- 优先处理修复工作
触摸目标验证
- 测量触摸目标尺寸(iOS最小44x44pt,Android最小48x48dp)
- 检查交互元素之间的间距
- 验证基于手势的交互
- 为复杂手势测试单点触控替代方案
先决条件
iOS开发
# 无障碍测试工具
xcode-select --install
# 带无障碍焦点的UI测试
pod 'ViewInspector' # SwiftUI测试
Android开发
// build.gradle
dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.5.1'
}
测试工具
# 无障碍测试CLI工具
npm install -g @axe-core/cli
pip install accessibility-checker
使用模式
iOS无障碍标签(SwiftUI)
import SwiftUI
struct AccessibleButton: View {
var body: some View {
Button(action: { /* 操作 */ }) {
Image(systemName: "heart.fill")
}
.accessibilityLabel("添加到收藏")
.accessibilityHint("双击将此项目添加到您的收藏列表")
.accessibilityAddTraits(.isButton)
}
}
struct AccessibleList: View {
var body: some View {
List {
ForEach(items) { item in
ItemRow(item: item)
.accessibilityElement(children: .combine)
.accessibilityLabel("\(item.title), \(item.subtitle)")
.accessibilityValue(item.isSelected ? "已选中" : "未选中")
}
}
.accessibilityIdentifier("items_list")
}
}
iOS无障碍标签(UIKit)
import UIKit
class AccessibleViewController: UIViewController {
func configureAccessibility() {
// 基础标签
button.accessibilityLabel = "提交订单"
button.accessibilityHint = "双击提交您的订单"
// 分组元素
containerView.isAccessibilityElement = true
containerView.accessibilityLabel = "订单摘要:3件商品,总计$45.99"
// 自定义操作
cell.accessibilityCustomActions = [
UIAccessibilityCustomAction(name: "删除", target: self, selector: #selector(deleteItem)),
UIAccessibilityCustomAction(name: "编辑", target: self, selector: #selector(editItem))
]
}
}
Android无障碍(Jetpack Compose)
import androidx.compose.ui.semantics.*
@Composable
fun AccessibleButton() {
IconButton(
onClick = { /* 操作 */ },
modifier = Modifier.semantics {
contentDescription = "添加到收藏"
role = Role.Button
}
) {
Icon(Icons.Filled.Favorite, contentDescription = null)
}
}
@Composable
fun AccessibleCard(item: Item) {
Card(
modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = "${item.title}, ${item.subtitle}"
stateDescription = if (item.isSelected) "已选中" else "未选中"
}
) {
// 卡片内容
}
}
Android无障碍(XML视图)
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
class AccessibleActivity : AppCompatActivity() {
fun configureAccessibility() {
// 基础内容描述
imageButton.contentDescription = "添加到收藏"
// 对无障碍不重要
decorativeImage.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
// 动态内容的实时区域
statusTextView.accessibilityLiveRegion = View.ACCESSIBILITY_LIVE_REGION_POLITE
// 自定义无障碍代理
customView.accessibilityDelegate = object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(host, info)
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)
info.contentDescription = "自定义描述"
}
}
}
}
颜色对比度验证
// iOS - 检查对比度比率
import UIKit
func calculateContrastRatio(foreground: UIColor, background: UIColor) -> Double {
let fgLuminance = relativeLuminance(foreground)
let bgLuminance = relativeLuminance(background)
let lighter = max(fgLuminance, bgLuminance)
let darker = min(fgLuminance, bgLuminance)
return (lighter + 0.05) / (darker + 0.05)
}
func relativeLuminance(_ color: UIColor) -> Double {
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: nil)
let transform: (CGFloat) -> Double = { value in
let v = Double(value)
return v <= 0.03928 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4)
}
return 0.2126 * transform(r) + 0.7152 * transform(g) + 0.0722 * transform(b)
}
// 使用
let ratio = calculateContrastRatio(foreground: .label, background: .systemBackground)
let meetsWCAGAA = ratio >= 4.5 // 普通文本
let meetsWCAGAAA = ratio >= 7.0 // 增强
无障碍测试(XCTest)
import XCTest
class AccessibilityTests: XCTestCase {
func testVoiceOverNavigation() {
let app = XCUIApplication()
app.launch()
// 验证无障碍元素存在
XCTAssertTrue(app.buttons["提交订单"].exists)
XCTAssertTrue(app.staticTexts["订单总计"].exists)
// 检查无障碍特性
let submitButton = app.buttons["提交订单"]
XCTAssertTrue(submitButton.isEnabled)
// 使用VoiceOver手势导航(模拟)
let elements = app.descendants(matching: .any).allElementsBoundByAccessibilityElement
XCTAssertGreaterThan(elements.count, 0)
}
func testDynamicTypeSupport() {
let app = XCUIApplication()
app.launchArguments = ["-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXXL"]
app.launch()
// 验证布局在大文本尺寸下不会破坏
XCTAssertTrue(app.staticTexts["标题"].exists)
XCTAssertFalse(app.staticTexts["标题"].frame.isEmpty)
}
}
无障碍测试(Espresso)
import androidx.test.espresso.accessibility.AccessibilityChecks
import org.junit.BeforeClass
class AccessibilityTest {
companion object {
@BeforeClass
@JvmStatic
fun enableAccessibilityChecks() {
AccessibilityChecks.enable()
.setRunChecksFromRootView(true)
}
}
@Test
fun testScreenAccessibility() {
onView(withId(R.id.main_layout))
.check(matches(isDisplayed()))
// 每次视图交互都会自动运行无障碍检查
onView(withId(R.id.submit_button))
.perform(click())
}
}
与Babysitter SDK集成
任务定义示例
const accessibilityTestTask = defineTask({
name: 'accessibility-testing',
description: '测试移动应用无障碍合规性',
inputs: {
platform: { type: 'string', required: true, enum: ['ios', 'android', 'both'] },
wcagLevel: { type: 'string', required: true, enum: ['A', 'AA', 'AAA'] },
projectPath: { type: 'string', required: true },
testScreens: { type: 'array', items: { type: 'string' } }
},
outputs: {
complianceReport: { type: 'object' },
violations: { type: 'array' },
recommendations: { type: 'array' },
score: { type: 'number' }
},
async run(inputs, taskCtx) {
return {
kind: 'skill',
title: `测试${inputs.platform}的${inputs.wcagLevel}级无障碍性`,
skill: {
name: 'accessibility-testing',
context: {
operation: 'audit',
platform: inputs.platform,
wcagLevel: inputs.wcagLevel,
projectPath: inputs.projectPath,
screens: inputs.testScreens
}
},
io: {
inputJsonPath: `tasks/${taskCtx.effectId}/input.json`,
outputJsonPath: `tasks/${taskCtx.effectId}/result.json`
}
};
}
});
MCP服务器集成
使用Axiom无障碍(iOS)
{
"mcpServers": {
"axiom": {
"command": "npx",
"args": ["axiom-mcp"],
"env": {
"XCODE_PROJECT": "/path/to/project.xcodeproj"
}
}
}
}
可用的MCP工具
a11y_audit_ios- 运行iOS无障碍检查器审计a11y_audit_android- 运行Android无障碍扫描器check_contrast_ratio- 验证颜色对比度validate_touch_targets- 检查触摸目标尺寸test_screen_reader- 模拟屏幕阅读器导航generate_a11y_report- 创建合规性报告
WCAG 2.1移动端检查清单
可感知性
- [ ] 为非文本内容提供文本替代(1.1.1)
- [ ] 音频内容提供字幕(1.2.2)
- [ ] 颜色不是传达信息的唯一方式(1.4.1)
- [ ] 普通文本对比度比率≥4.5:1(1.4.3)
- [ ] 文本可调整至200%(1.4.4)
- [ ] 无需水平滚动即可重排(1.4.10)
可操作性
- [ ] 触摸目标≥44x44pt/48x48dp(2.5.5)
- [ ] 提供单指针手势(2.5.1)
- [ ] 操作不需要运动(2.5.4)
- [ ] 方向未锁定(1.3.4)
- [ ] 键盘用户的焦点可见(2.4.7)
可理解性
- [ ] 页面语言可编程确定(3.1.1)
- [ ] 一致的导航模式(3.2.3)
- [ ] 错误识别清晰(3.3.1)
- [ ] 用户输入的标签或说明(3.3.2)
健壮性
- [ ] 辅助技术的有效标记(4.1.1)
- [ ] 名称、角色、值可用(4.1.2)
- [ ] 状态消息向屏幕阅读器宣布(4.1.3)
最佳实践
- 与真实用户测试:在测试中包括残障用户
- 使用原生控件:利用平台无障碍功能
- 在极端情况下测试:检查最大的动态类型和字体缩放
- 验证阅读顺序:确保逻辑焦点导航
- 提供文本替代:标记所有图像和图标
- 支持多种输入方法:触摸、语音、开关控制