paparazzi
paparazzi copied to clipboard
Add compose accessibility support
Modelled off the AccessibilityRenderExtension. But instead using the ViewRootForTest.onViewCreatedCallback
hook using in composeTestRule. And a separate SnapshotHandler to render instead of using views.
Derived from https://github.com/google/horologist/pull/524
@gmarques33 I won't pay much attention to this PR, until I've agreed that it's useful in Paparazzi 1.2 or so.
@yschimke Any chance you could get this merged? We are interested in using this at Netflix.
@jrodbx any appetite for this? We like it and use it for all screen.
But I guess it depends on the bigger plugin/interceptor strategy?
Added a test. Although it fails if Text is used.
The Graphics2D code in A11ySnapshotHandler seems platform specific.

app.cash.paparazzi.gradle.PaparazziPluginTest > composeA11yWear FAILED
java.lang.AssertionError: Expected % diff less than 0.01, but was: 0.1777610294117647
at org.junit.Assert.fail(Assert.java:89)
at app.cash.paparazzi.gradle.ImageSubject$ImageAssert.withThreshold(ImageSubject.kt:54)
at app.cash.paparazzi.gradle.ImageSubject$ImageAssert.withDefaultThreshold(ImageSubject.kt:40)
at app.cash.paparazzi.gradle.PaparazziPluginTest.composeA11yWear(PaparazziPluginTest.kt:984)
@geoff-powell you worked on the a11y testing for views, could you take a peek at this? thanks!
I'm very happy to follow up with this on looking at how to avoid platform differences. from fonts + Graphics2D. In practice this change as is, works for us since we have a consistent CI environment.
Changes required for 1.2 release
https://github.com/google/horologist/pull/938/files#diff-97977a5620e4bc79f81f77977b54e2c278325e531919ce7ea5b8668791914006
https://github.com/google/horologist/pull/938/files#diff-0b30e0de6183d1fe1fbcff8e9ffa085866c455593ac080cbffeba349e1b32e52
@geoff-powell @saket I'm stuck. The A11y approach we use for Views and Compose seem incompatible. I don't know how to bridge them.
Views
Works on enumerating important Views.
fun View.findAccessibilityViews(): List<View> {
val accessibilityViews = mutableListOf<View>()
if (isImportantForAccessibility && !iterableTextForAccessibility.isNullOrBlank()) {
accessibilityViews.add(this)
}
This happens synchronously in AccessibilityRenderExtension.renderView
Compose (this)
Registers the ViewRootForTest.onViewCreatedCallback (used for createComposeRule testing) during test setup. This let's us grab androidx.compose.ui.node.RootForTest during the test run.
ComposeA11yExtension.renderView can't access state synchronously, instead registers composeView.viewTreeObserver.addOnPreDrawListener
to grab rootForTest.semanticsOwner.rootSemanticsNode later.
Common Approaches
I don't have any representation of Semantic Nodes that is common for Views and Compose.
Maybe writing an a11y service?