Enabling Accessibility Checks — Espresso's AccessibilityValidator on the Compose Tree
The Compose accessibility test artifacts wire Espresso's AccessibilityValidator (from com.google.android.apps.common.testing.accessibility.framework.integrations.espresso) into the Compose action pipeline. Once enabled, every action that mutates the UI (performClick, performScrollTo*, performTextInput, …) runs the validator against the underlying View first. Two distinct artifacts cover two test entry points (ComposeUiTest and ComposeTestRule), with one stable and one experimental. Real device + API 34+ for meaningful results — on Robolectric the validator is still installed but a Log.w warning is emitted and any pass is inconclusive (Robolectric does not faithfully drive the accessibility services).
When to use this skill
- The developer is adding accessibility regression coverage to a Compose-only screen.
- A reviewer asks for an a11y test that fails the build when contrast / touch-target / label rules regress.
- The developer mentions "AccessibilityChecks.enable", "AccessibilityValidator", or "Compose accessibility validator".
- A hybrid Compose + Views screen needs a single shared
AccessibilityValidatorbetween Espresso and Compose. - The developer's a11y check appears to do nothing on Robolectric (the
Log.wwarning explains why).
When NOT to use this skill
- The check fails because of a bad finder, not an a11y violation. Use
../printing-the-semantics-tree/SKILL.md. - The test runs on Robolectric (host JVM). Cited limitation at
compose/ui/ui-test-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/accessibility/ComposeUiTestExt.android.kt:50-53. Move the test toandroidDeviceTestand a real device API 34+ — see../../setup/setting-up-host-vs-device-tests/SKILL.md. - The test target API is below 34. The annotation
@RequiresApi(34)makes calls fail to compile on lower minSdk. Use@SdkSuppress(minSdkVersion = 34)on the test method and skip on older devices. - The user wants semantics test coverage (roles, content descriptions, click actions). Use
../../assertions/asserting-node-state-and-text/SKILL.md.
Prerequisites
- Real Android device (or emulator) running API 34+ (Android U).
- One of:
androidx.compose.ui:ui-test-accessibilityonandroidTestImplementation— extendsComposeUiTest.@RequiresApi(34) @ExperimentalTestApi. Needs@OptIn(ExperimentalTestApi::class).androidx.compose.ui:ui-test-junit4-accessibilityonandroidTestImplementation— extendsAndroidComposeTestRuleandComposeTestRule.@RequiresApi(34)only — NOT experimental.
- The test class skeleton from
../../patterns/structuring-a-compose-test/SKILL.md.
Workflow
- 1. Enable checks once per test (or in
@Before). API forAndroidComposeTestRule:
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)
}
Default-validator signature, cited at 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),
)
For ComposeUiTest (e.g. inside runComposeUiTest { }), the equivalent lives in androidx.compose.ui.test.accessibility and requires @OptIn(ExperimentalTestApi::class):
@OptIn(ExperimentalTestApi::class)
@Test
fun runs() = runAndroidComposeUiTest<ComponentActivity> {
enableAccessibilityChecks()
onNodeWithText("Submit").performClick()
}
Cited from compose/ui/ui-test-accessibility/samples/src/main/java/androidx/compose/ui/test/accessibility/samples/AccessibilityChecksSamples.kt:42-61.
- 2. Configure stricter modes via the
AccessibilityValidatoritself. Pass a custom validator to flip the threshold from WARNING-only to ERROR.
val validator = AccessibilityValidator().apply {
setThrowExceptionFor(AccessibilityCheckResultType.ERROR)
}
rule.enableAccessibilityChecks(validator)
Cited from AccessibilityChecksSamples.kt:68-82.
3. Auto-checks fire before every UI-mutating action. Cited at
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()is invoked at the top ofperformClick,performScrollTo,performScrollToIndex/Key/Node,performTouchInput,performMouseInput,performKeyInput,performTrackpadInput,performRotaryScrollInput,performIndirectPointerInput,performFirstLinkClick, and the text-action family.performMultiModalInputdoes NOT auto-check (Actions.kt:582); the developer must calltryPerformAccessibilityChecks()explicitly.requestFocus()(Actions.kt:600-601) goes throughperformSemanticsActionand likewise does NOT auto-check — calltryPerformAccessibilityChecks()after focusing if accessibility coverage matters there.4. Run a check manually when needed (for
performMultiModalInput, or to gate a phase).
rule.onRoot().tryPerformAccessibilityChecks()
Cited from AccessibilityChecksSamples.kt:53.
- 5. Share the validator with Espresso for hybrid Compose + View screens.
AccessibilityChecks.enable()returns the same validator instance Espresso uses; pass it to Compose so both layers report against one configuration.
import androidx.test.espresso.accessibility.AccessibilityChecks
@OptIn(ExperimentalTestApi::class)
@Test
fun hybrid() = runAndroidComposeUiTest<ComponentActivity> {
val validator = AccessibilityChecks.enable()
enableAccessibilityChecks(validator)
}
Cited from AccessibilityChecksSamples.kt:88-98.
- 6. Disable checks when leaving an isolated phase.
rule.disableAccessibilityChecks()
Cited at AndroidComposeTestRuleExt.android.kt:71-75.
- 7. Use the
ComposeTestRuleoverload only when the rule is concretely anAndroidComposeTestRule. Cited atcompose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/ComposeTestRuleExt.android.kt:38-49. Non-Android rules throwNotImplementedError:
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
Patterns
Pattern: enabling on Robolectric — inconclusive results
// 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()
}
}
Pattern: performMultiModalInput skips the auto-check
// 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
}
Pattern: ComposeTestRule (non-Android) overload throws
// 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()
Pattern: shared validator with Espresso
// RIGHT
@OptIn(ExperimentalTestApi::class)
@Test
fun shared() = runAndroidComposeUiTest<ComponentActivity> {
val validator = AccessibilityChecks.enable().apply {
setThrowExceptionFor(AccessibilityCheckResultType.ERROR)
}
enableAccessibilityChecks(validator)
onNodeWithText("Submit").performClick()
}
Mandatory rules
- MUST target real device API 34+ for any test that calls
enableAccessibilityChecks. MUST NOT rely on the check producing meaningful results under Robolectric —Build.FINGERPRINT.lowercase() == "robolectric"triggersLog.w("...", "Accessibility checks are currently not supported by Robolectric")(ComposeUiTestExt.android.kt:50-53,AndroidComposeTestRuleExt.android.kt:50-56), and the validator is still installed but cannot rely on the accessibility services Robolectric does not faithfully emulate. Treat any pass under Robolectric as inconclusive. Tracking bug: b/332778271. - MUST prefer
androidx.compose.ui:ui-test-junit4-accessibility(stable) when the rule entry point is used; MUST apply@OptIn(ExperimentalTestApi::class)when usingandroidx.compose.ui:ui-test-accessibilityagainstComposeUiTest. - MUST call
tryPerformAccessibilityChecks()manually afterperformMultiModalInput { … }and afterrequestFocus()if the test wants validation for those actions —Actions.kt:582omits the auto-check forperformMultiModalInput, andrequestFocus()(Actions.kt:600-601) routes throughperformSemanticsActionwhich likewise does not auto-check. - MUST NOT call
enableAccessibilityCheckson aComposeTestRulethat is not also anAndroidComposeTestRule. The non-Android overload throwsNotImplementedError(ComposeTestRuleExt.android.kt:38-49). - PREFERRED: raise the threshold to
AccessibilityCheckResultType.ERRORviaAccessibilityValidator().setThrowExceptionFor(...)so violations fail the test instead of merely logging. - PREFERRED: in hybrid Compose + Views suites, share one
AccessibilityValidatorviaAccessibilityChecks.enable()and pass it to both Espresso and Compose. Cited atAccessibilityChecksSamples.kt:88-98.
Verification
- The test runs on real device API 34+.
@SdkSuppress(minSdkVersion = 34)is present if the module's minSdk is lower. - No Robolectric
Log.wwarning "Accessibility checks are currently not supported by Robolectric" appears in test output. - The relevant artifact is on
androidTestImplementation:androidx.compose.ui:ui-test-junit4-accessibility(rule path) orandroidx.compose.ui:ui-test-accessibility+@OptIn(ExperimentalTestApi::class)(ComposeUiTestpath). -
enableAccessibilityChecks(...)is called once per test (or in@Before);disableAccessibilityChecks()is called only when intentionally suppressing checks. - Any
performMultiModalInput { … }site is followed by an explicitrule.onRoot().tryPerformAccessibilityChecks(). - Hybrid Compose+View suites obtain the validator via
AccessibilityChecks.enable()and pass it toenableAccessibilityChecks(validator). - No
enableAccessibilityCheckscall is made on a non-AndroidComposeTestRuleinstance.
References
enableAccessibilityChecks(rule path):compose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/AndroidComposeTestRuleExt.android.kt:44-75ComposeTestRuleoverload — NotImplementedError:compose/ui/ui-test-junit4-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/junit4/accessibility/ComposeTestRuleExt.android.kt:38-66enableAccessibilityChecks(ComposeUiTest path):compose/ui/ui-test-accessibility/src/androidMain/kotlin/androidx/compose/ui/test/accessibility/ComposeUiTestExt.android.kt:44-77- Robolectric inconclusive behavior (warns + still installs validator):
ComposeUiTestExt.android.kt:50-61,AndroidComposeTestRuleExt.android.kt:50-64, b/332778271 - Auto-check call sites in
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 - Samples:
compose/ui/ui-test-accessibility/samples/src/main/java/androidx/compose/ui/test/accessibility/samples/AccessibilityChecksSamples.kt:42-98 - Espresso accessibility checks: https://developer.android.com/training/testing/espresso/accessibility-checking
- Test for accessibility (Compose): https://developer.android.com/develop/ui/compose/accessibility/testing
- Compose UI testing release notes: https://developer.android.com/jetpack/androidx/releases/compose-ui