paparazzi icon indicating copy to clipboard operation
paparazzi copied to clipboard

Add compose accessibility support

Open yschimke opened this issue 2 years ago • 1 comments

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

image

yschimke avatar Aug 27 '22 18:08 yschimke

@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 avatar Oct 04 '22 14:10 yschimke

@yschimke Any chance you could get this merged? We are interested in using this at Netflix.

seanfreiburg avatar Nov 30 '22 22:11 seanfreiburg

@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?

yschimke avatar Dec 01 '22 06:12 yschimke

Added a test. Although it fails if Text is used.

yschimke avatar Dec 03 '22 11:12 yschimke

The Graphics2D code in A11ySnapshotHandler seems platform specific.

image
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)

yschimke avatar Dec 03 '22 16:12 yschimke

@geoff-powell you worked on the a11y testing for views, could you take a peek at this? thanks!

seanfreiburg avatar Dec 06 '22 04:12 seanfreiburg

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.

yschimke avatar Dec 07 '22 11:12 yschimke

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

yschimke avatar Jan 20 '23 16:01 yschimke

@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?

yschimke avatar Jan 21 '23 12:01 yschimke