diff --git a/core/screenshot-testing/build.gradle.kts b/core/screenshot-testing/build.gradle.kts index 794416ba9..57a43a200 100644 --- a/core/screenshot-testing/build.gradle.kts +++ b/core/screenshot-testing/build.gradle.kts @@ -26,6 +26,7 @@ android { dependencies { api(libs.bundles.androidx.compose.ui.test) api(libs.roborazzi) + api(libs.roborazzi.accessibility.check) implementation(libs.androidx.compose.ui.test) implementation(libs.androidx.activity.compose) implementation(libs.robolectric) diff --git a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt index f11651220..20b9a5deb 100644 --- a/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt +++ b/core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt @@ -14,8 +14,11 @@ * limitations under the License. */ +@file:OptIn(ExperimentalRoborazziApi::class) + package com.google.samples.apps.nowinandroid.core.testing.util +import android.graphics.Bitmap.CompressFormat.PNG import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable @@ -30,12 +33,25 @@ import androidx.compose.ui.test.DeviceConfigurationOverride import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.onRoot import androidx.test.ext.junit.rules.ActivityScenarioRule +import com.github.takahirom.roborazzi.ExperimentalRoborazziApi +import com.github.takahirom.roborazzi.RoborazziATFAccessibilityCheckOptions +import com.github.takahirom.roborazzi.RoborazziATFAccessibilityChecker +import com.github.takahirom.roborazzi.RoborazziATFAccessibilityChecker.CheckLevel import com.github.takahirom.roborazzi.RoborazziOptions import com.github.takahirom.roborazzi.RoborazziOptions.CompareOptions import com.github.takahirom.roborazzi.RoborazziOptions.RecordOptions import com.github.takahirom.roborazzi.captureRoboImage +import com.github.takahirom.roborazzi.checkRoboAccessibility +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult +import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityViewCheckException +import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.BitmapImage import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme +import org.hamcrest.Matcher +import org.hamcrest.Matchers import org.robolectric.RuntimeEnvironment +import java.io.File +import java.io.FileOutputStream val DefaultRoborazziOptions = RoborazziOptions( @@ -52,10 +68,17 @@ enum class DefaultTestDevices(val description: String, val spec: String) { } fun AndroidComposeTestRule, A>.captureMultiDevice( screenshotName: String, + accessibilitySuppressions: Matcher = Matchers.not(Matchers.anything()), body: @Composable () -> Unit, ) { DefaultTestDevices.entries.forEach { - this.captureForDevice(it.description, it.spec, screenshotName, body = body) + this.captureForDevice( + deviceName = it.description, + deviceSpec = it.spec, + screenshotName = screenshotName, + body = body, + accessibilitySuppressions = accessibilitySuppressions, + ) } } @@ -64,6 +87,7 @@ fun AndroidComposeTestRule, A>.c deviceSpec: String, screenshotName: String, roborazziOptions: RoborazziOptions = DefaultRoborazziOptions, + accessibilitySuppressions: Matcher = Matchers.not(Matchers.anything()), darkMode: Boolean = false, body: @Composable () -> Unit, ) { @@ -83,11 +107,46 @@ fun AndroidComposeTestRule, A>.c } } } + + // Run Accessibility checks first so logging is included + val accessibilityException = try { + this.onRoot().checkRoboAccessibility( + roborazziATFAccessibilityCheckOptions = RoborazziATFAccessibilityCheckOptions( + failureLevel = CheckLevel.Error, + checker = RoborazziATFAccessibilityChecker( + preset = AccessibilityCheckPreset.LATEST, + suppressions = accessibilitySuppressions, + ), + ), + ) + null + } catch (e: AccessibilityViewCheckException) { + e + } + this.onRoot() .captureRoboImage( "src/test/screenshots/${screenshotName}_$deviceName.png", roborazziOptions = roborazziOptions, ) + + // Rethrow the Accessibility exception once screenshots have passed + if (accessibilityException != null) { + accessibilityException.results.forEachIndexed { index, check -> + val viewImage = check.viewImage + if (viewImage is BitmapImage) { + val file = File("build/outputs/roborazzi/${screenshotName}_${deviceName}_$index.png") + println("Writing check.viewImage to $file") + FileOutputStream( + file, + ).use { + viewImage.bitmap.compress(PNG, 100, it) + } + } + } + + throw accessibilityException + } } /** diff --git a/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt b/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt index 14b67c64e..29fc6f536 100644 --- a/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt +++ b/feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt @@ -19,6 +19,10 @@ package com.google.samples.apps.nowinandroid.feature.foryou import androidx.activity.ComponentActivity import androidx.compose.runtime.Composable import androidx.compose.ui.test.junit4.createAndroidComposeRule +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils +import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesElements +import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck +import com.google.android.apps.common.testing.accessibility.framework.matcher.ElementMatchers.withText import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme import com.google.samples.apps.nowinandroid.core.testing.util.DefaultTestDevices @@ -31,6 +35,7 @@ import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Loa import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.NotShown import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Shown import dagger.hilt.android.testing.HiltTestApplication +import org.hamcrest.Matchers import org.junit.Before import org.junit.Rule import org.junit.Test @@ -108,7 +113,20 @@ class ForYouScreenScreenshotTests { @Test fun forYouScreenTopicSelection() { - composeTestRule.captureMultiDevice("ForYouScreenTopicSelection") { + composeTestRule.captureMultiDevice( + "ForYouScreenTopicSelection", + accessibilitySuppressions = Matchers.allOf( + AccessibilityCheckResultUtils.matchesCheck(TextContrastCheck::class.java), + Matchers.anyOf( + // Disabled Button + matchesElements(withText("Done")), + + // TODO investigate, seems a false positive + matchesElements(withText("What are you interested in?")), + matchesElements(withText("UI")), + ), + ), + ) { ForYouScreenTopicSelection() } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 676b99ee5..4e996177a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -141,6 +141,7 @@ retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.r retrofit-kotlin-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" } robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" } +roborazzi-accessibility-check = { group = "io.github.takahirom.roborazzi", name = "roborazzi-accessibility-check", version.ref = "roborazzi" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }