Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce screenshot accessibility tests #1708

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/screenshot-testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -52,10 +68,17 @@ enum class DefaultTestDevices(val description: String, val spec: String) {
}
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.captureMultiDevice(
screenshotName: String,
accessibilitySuppressions: Matcher<in AccessibilityViewCheckResult> = 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,
)
}
}

Expand All @@ -64,6 +87,7 @@ fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.c
deviceSpec: String,
screenshotName: String,
roborazziOptions: RoborazziOptions = DefaultRoborazziOptions,
accessibilitySuppressions: Matcher<in AccessibilityViewCheckResult> = Matchers.not(Matchers.anything()),
darkMode: Boolean = false,
body: @Composable () -> Unit,
) {
Expand All @@ -83,11 +107,46 @@ fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, 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
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
}
}
Expand Down
5 changes: 3 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ protobuf = "4.26.1"
protobufPlugin = "0.9.4"
retrofit = "2.11.0"
retrofitKotlinxSerializationJson = "1.0.0"
robolectric = "4.14"
roborazzi = "1.32.2"
robolectric = "4.14.1"
roborazzi = "1.33.0"
room = "2.6.1"
secrets = "2.0.1"
truth = "1.4.2"
Expand Down Expand Up @@ -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" }
Expand Down
Loading