mcpField
E
enabling-accessibility-checks

enabling-accessibility-checks

github.com/skydoves测试与质量33967 次安装22 周前更新
代码仓库

关于 enabling-accessibility-checks

此技能指南介绍如何在 Jetpack Compose 测试中启用 Espresso AccessibilityValidator,覆盖 UI 操作并检查无障碍问题。适用于编写 Android 无障碍回归测试的开发者,也说明 API 34+ 设备要求及 Robolectric 的局限。

主要功能

  1. 01为 Compose 测试启用无障碍检查
  2. 02配置警告或错误阈值
  3. 03覆盖 UI 变更操作
  4. 04手动触发检查
  5. 05与 Espresso 共用验证器

适用场景

  1. 01为 Compose 屏幕添加无障碍回归测试
  2. 02让无障碍违规导致测试失败
  3. 03检查 Robolectric 测试结果为何无效

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/skydoves/android-testing-skills --skill enabling-accessibility-checks

安装后在 Claude Code 中输入/enabling-accessibility-checks确认已加载。

详细介绍

启用无障碍检查——在 Compose 树上使用 Espresso 的 AccessibilityValidator

Compose 无障碍测试制品会将 Espresso 的 AccessibilityValidator(来自 com.google.android.apps.common.testing.accessibility.framework.integrations.espresso)接入 Compose 操作流水线。启用后,每个会修改 UI 的操作(performClick、performScrollTo*、performTextInput 等)都会先针对底层 View 运行验证器。有两种不同的制品分别覆盖两个测试入口(ComposeUiTest 和 ComposeTestRule),其中一个稳定,另一个为实验性。若要获得有意义的结果,需使用真实设备和 API 34+;在 Robolectric 上,验证器仍会安装,但会发出 Log.w 警告,任何通过结果都不能作为定论(Robolectric 无法忠实地驱动无障碍服务)。

何时使用此技能

  • 开发者正在为仅使用 Compose 的屏幕添加无障碍回归测试。
  • 审查者要求添加无障碍测试,以便在对比度/触控目标/标签规则回退时使构建失败。
  • 开发者提到“AccessibilityChecks.enable”“AccessibilityValidator”或“Compose accessibility validator”。
  • 混合使用 Compose 和 Views 的屏幕需要在 Espresso 与 Compose 之间共享同一个 AccessibilityValidator。
  • 开发者的无障碍检查似乎在 Robolectric 上不起作用(Log.w 警告说明了原因)。

何时不要使用此技能

  • 检查失败是因为查找器有误,而非无障碍违规。请使用 ../printing-the-semantics-tree/SKILL.md。
  • 测试运行在 Robolectric(宿主 JVM)上。此限制见 compose/ui/ui-test-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/accessibility/ComposeUiTestExt.android.kt:50-53。请将测试移至 androidDeviceTest,并使用 API 34+ 的真实设备——参见 ../../setup/setting-up-host-vs-device-tests/SKILL.md。
  • 测试目标 API 低于 34。注解 @RequiresApi(34) 会导致调用在较低的 minSdk 上无法编译。请在测试方法上使用 @SdkSuppress(minSdkVersion = 34),并在较旧设备上跳过测试。
  • 用户想要的是语义测试覆盖(角色、内容描述、点击操作)。请使用 ../../assertions/asserting-node-state-and-text/SKILL.md。

前置条件

  • 运行 API 34+(Android U)的真实 Android 设备(或模拟器)。
  • 以下两者之一:
    • 在 androidTestImplementation 中添加 androidx.compose.ui:ui-test-accessibility——扩展 ComposeUiTest。仅支持 @RequiresApi(34) @ExperimentalTestApi。需要 @OptIn(ExperimentalTestApi::class)。
    • 在 androidTestImplementation 中添加 androidx.compose.ui:ui-test-junit4-accessibility——扩展 AndroidComposeTestRule 和 ComposeTestRule。仅需 @RequiresApi(34)——非实验性。
  • 来自 ../../patterns/structuring-a-compose-test/SKILL.md 的测试类骨架。

工作流

  • 1. 每次测试启用一次检查(或在 @Before 中启用)。 AndroidComposeTestRule 的 API:
import androidx.compose.ui.test.junit4.accessibility.enableAccessibilityChecks
import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityValidator

@Before
fun before() {
    rule.enableAccessibilityChecks()      // default: AccessibilityValidator().setRunChecksFromRootView(true)
}

默认验证器的签名,引用自 compose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/AndroidComposeTestRuleExt.android.kt:44-49:

@RequiresApi(34)
public fun <R : TestRule, A : ComponentActivity> AndroidComposeTestRule<R, A>.enableAccessibilityChecks(
    accessibilityValidator: AccessibilityValidator =
        AccessibilityValidator().setRunChecksFromRootView(true),
)

对于 ComposeUiTest(例如在 runComposeUiTest { } 内),对应 API 位于 androidx.compose.ui.test.accessibility,并且需要 @OptIn(ExperimentalTestApi::class):

@OptIn(ExperimentalTestApi::class)
@Test
fun runs() = runAndroidComposeUiTest<ComponentActivity> {
    enableAccessibilityChecks()
    onNodeWithText("Submit").performClick()
}

引用自 compose/ui/ui-test-accessibility/samples/src/main/java/androidx/compose/ui/test/accessibility/samples/AccessibilityChecksSamples.kt:42-61。

  • 2. 通过 AccessibilityValidator 本身配置更严格的模式。 传入自定义验证器,将阈值从仅 WARNING 切换为 ERROR。
val validator = AccessibilityValidator().apply {
    setThrowExceptionFor(AccessibilityCheckResultType.ERROR)
}
rule.enableAccessibilityChecks(validator)

引用自 AccessibilityChecksSamples.kt:68-82。

  • 3. 每次修改 UI 的操作之前都会触发自动检查。 引用自 compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt:79, 160, 189, 236, 402, 451, 496, 535, 697, 780, 948 — tryPerformAccessibilityChecks() 会在 performClick、performScrollTo、performScrollToIndex/Key/Node、performTouchInput、performMouseInput、performKeyInput、performTrackpadInput、performRotaryScrollInput、performIndirectPointerInput、performFirstLinkClick 以及文本操作系列的开头调用。performMultiModalInput 不会自动检查(Actions.kt:582);开发者必须显式调用 tryPerformAccessibilityChecks()。requestFocus()(Actions.kt:600-601)会通过 performSemanticsAction 执行,同样不会自动检查 — 如果需要覆盖聚焦后的无障碍检查,请在聚焦后调用 tryPerformAccessibilityChecks()。

  • 4. 需要时手动运行检查(用于 performMultiModalInput,或用于设置阶段门控)。

rule.onRoot().tryPerformAccessibilityChecks()

引用自 AccessibilityChecksSamples.kt:53。

  • 5. 在 Compose + View 混合屏幕中与 Espresso 共享验证器。 AccessibilityChecks.enable() 会返回 Espresso 使用的同一验证器实例;将其传给 Compose,以便两个层使用同一配置报告结果。
import androidx.test.espresso.accessibility.AccessibilityChecks

@OptIn(ExperimentalTestApi::class)
@Test
fun hybrid() = runAndroidComposeUiTest<ComponentActivity> {
    val validator = AccessibilityChecks.enable()
    enableAccessibilityChecks(validator)
}

引用自 AccessibilityChecksSamples.kt:88-98。

  • 6. 离开独立阶段时禁用检查。
rule.disableAccessibilityChecks()

引用自 AndroidComposeTestRuleExt.android.kt:71-75。

  • 7. 仅当规则实际为 AndroidComposeTestRule 时,才使用 ComposeTestRule 重载。 引用自 compose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/ComposeTestRuleExt.android.kt:38-49。非 Android 规则会抛出 NotImplementedError:
Enabling accessibility checks is currently only supported for AndroidComposeTestRule.
If you have a custom ComposeTestRule implementation that wraps an AndroidComposeTestRule,
directly call enableAccessibilityChecks on the AndroidComposeTestRule instead

模式

模式:在 Robolectric 上启用 — 结果不确定
// WRONG (assertion is meaningless under Robolectric)
@Config(sdk = [34])
@RunWith(RobolectricTestRunner::class)
class MyA11yTest {
    @get:Rule val rule = createComposeRule(StandardTestDispatcher())

    @Test fun checks() {
        rule.enableAccessibilityChecks()                      // logs Log.w; validator IS installed
        rule.setContent { /* contrast violation */ }
        rule.onNodeWithTag("submit").performClick()           // may PASS, may FAIL — inconclusive
    }
}
// WRONG because: HasRobolectricFingerprint = (Build.FINGERPRINT.lowercase() == "robolectric")
// triggers a Log.w but the validator is STILL installed (ComposeUiTestExt.android.kt:50-61
// AND AndroidComposeTestRuleExt.android.kt:50-64). Robolectric does not faithfully drive
// the accessibility services, so any result is inconclusive. Tracking bug: b/332778271.
// Run accessibility checks ONLY on a real device API 34+ for trustworthy results.
// RIGHT — move the test to androidDeviceTest and run on a real device API 34+
@MediumTest
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = 34)
class MyA11yTest {
    @get:Rule val rule = createComposeRule(StandardTestDispatcher())

    @Before fun before() { rule.enableAccessibilityChecks() }

    @Test fun submitPassesA11y() {
        rule.setContent { MyScreen() }
        rule.onNodeWithTag("submit").performClick()
    }
}
模式:performMultiModalInput 会跳过自动检查
// WRONG
@Test
fun multiModal() {
    rule.enableAccessibilityChecks()
    rule.setContent { MyScreen() }
    rule.onNodeWithTag("canvas").performMultiModalInput {
        touch { down(center); up() }
        key  { pressKey(Key.Enter) }
    }
    // No a11y check ran for this action — Actions.kt:582 omits tryPerformAccessibilityChecks().
}
// RIGHT
@Test
fun multiModal() {
    rule.enableAccessibilityChecks()
    rule.setContent { MyScreen() }
    rule.onNodeWithTag("canvas").performMultiModalInput { … }
    rule.onRoot().tryPerformAccessibilityChecks()             // explicit
}
模式:ComposeTestRule(非 Android)重载会抛出异常
// WRONG
val rule: ComposeTestRule = MyCustomRuleThatWrapsAndroid()
rule.enableAccessibilityChecks()                              // throws NotImplementedError
// WRONG because: only AndroidComposeTestRule is supported. Cited at
// ComposeTestRuleExt.android.kt:38-49.
// RIGHT — call on the underlying AndroidComposeTestRule
val rule = AndroidComposeTestRule(/* … */)
rule.enableAccessibilityChecks()
模式:与 Espresso 共享验证器
// RIGHT
@OptIn(ExperimentalTestApi::class)
@Test
fun shared() = runAndroidComposeUiTest<ComponentActivity> {
    val validator = AccessibilityChecks.enable().apply {
        setThrowExceptionFor(AccessibilityCheckResultType.ERROR)
    }
    enableAccessibilityChecks(validator)
    onNodeWithText("Submit").performClick()
}

强制规则

  • 必须使用真实设备 API 34+,运行任何调用 enableAccessibilityChecks 的测试。不得依赖 Robolectric 环境下的检查结果有实际意义——Build.FINGERPRINT.lowercase() == "robolectric" 会触发 Log.w("...", "Accessibility checks are currently not supported by Robolectric")(ComposeUiTestExt.android.kt:50-53、AndroidComposeTestRuleExt.android.kt:50-56);验证器仍会安装,但不能依赖 Robolectric 无法忠实模拟的无障碍服务。将 Robolectric 下的任何通过结果视为无法确定。跟踪问题:b/332778271。
  • 必须在使用规则入口点时,优先选择稳定版 androidx.compose.ui:ui-test-junit4-accessibility;在 ComposeUiTest 上使用 androidx.compose.ui:ui-test-accessibility 时,必须应用 @OptIn(ExperimentalTestApi::class)。
  • 必须在 performMultiModalInput { … } 之后以及 requestFocus() 之后手动调用 tryPerformAccessibilityChecks(),以便测试验证这些操作——Actions.kt:582 中,performMultiModalInput 未触发自动检查;而 requestFocus()(Actions.kt:600-601)通过 performSemanticsAction 执行,同样不会触发自动检查。
  • 不得在并非 AndroidComposeTestRule 的 ComposeTestRule 上调用 enableAccessibilityChecks。非 Android 版本的重载会抛出 NotImplementedError(ComposeTestRuleExt.android.kt:38-49)。
  • **建议:**通过 AccessibilityValidator().setThrowExceptionFor(...) 将阈值提高到 AccessibilityCheckResultType.ERROR,让违规导致测试失败,而不仅仅是记录日志。
  • **建议:**在 Compose 与 Views 混合的测试套件中,通过 AccessibilityChecks.enable() 共享一个 AccessibilityValidator,并将其同时传递给 Espresso 和 Compose。引用位置:AccessibilityChecksSamples.kt:88-98。

验证

  • 测试在真实设备 API 34+ 上运行。如果模块的 minSdk 较低,则存在 @SdkSuppress(minSdkVersion = 34)。
  • 测试输出中不会出现 Robolectric 日志警告 "Accessibility checks are currently not supported by Robolectric"。
  • 相关依赖已添加到 androidTestImplementation:androidx.compose.ui:ui-test-junit4-accessibility(规则路径),或 androidx.compose.ui:ui-test-accessibility + @OptIn(ExperimentalTestApi::class)(ComposeUiTest 路径)。
  • 每个测试中(或在 @Before 中)调用一次 enableAccessibilityChecks(...);仅在有意抑制检查时调用 disableAccessibilityChecks()。
  • 每处 performMultiModalInput { … } 调用后,都显式调用 rule.onRoot().tryPerformAccessibilityChecks()。
  • Compose+View 混合测试套件通过 AccessibilityChecks.enable() 获取验证器,并将其传递给 enableAccessibilityChecks(validator)。
  • 不会在非 AndroidComposeTestRule 实例上调用 enableAccessibilityChecks。

参考资料

  • enableAccessibilityChecks(规则路径):compose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/AndroidComposeTestRuleExt.android.kt:44-75
  • ComposeTestRule 重载 — NotImplementedError:compose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/ComposeTestRuleExt.android.kt:38-66
  • enableAccessibilityChecks(ComposeUiTest 路径):compose/ui/ui-test-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/accessibility/ComposeUiTestExt.android.kt:44-77
  • Robolectric 的结果不确定行为(发出警告并仍然安装验证器):ComposeUiTestExt.android.kt:50-61、AndroidComposeTestRuleExt.android.kt:50-64、b/332778271
  • Actions.kt 中自动检查的调用位置:compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Actions.kt:79, 160, 189, 236, 402, 451, 496, 535, 582 (omitted), 697, 780, 948
  • 示例:compose/ui/ui-test-accessibility/samples/src/main/java/androidx/compose/ui/test/accessibility/samples/AccessibilityChecksSamples.kt:42-98
  • Espresso 无障碍检查:https://developer.android.com/training/testing/espresso/accessibility-checking
  • 无障碍测试(Compose):https://developer.android.com/develop/ui/compose/accessibility/testing
  • Compose UI 测试发行说明:https://developer.android.com/jetpack/androidx/releases/compose-ui

评论

支持 Markdown