Skip to content

Commit 6bf2d04

Browse files
authored
Merge pull request #1708 from yschimke/introduce_screenshot_a11y_tests
Introduce screenshot accessibility tests
2 parents e36b7ab + a258794 commit 6bf2d04

File tree

4 files changed

+81
-2
lines changed

4 files changed

+81
-2
lines changed

core/screenshot-testing/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ android {
2626
dependencies {
2727
api(libs.bundles.androidx.compose.ui.test)
2828
api(libs.roborazzi)
29+
api(libs.roborazzi.accessibility.check)
2930
implementation(libs.androidx.compose.ui.test)
3031
implementation(libs.androidx.activity.compose)
3132
implementation(libs.robolectric)

core/screenshot-testing/src/main/kotlin/com/google/samples/apps/nowinandroid/core/testing/util/ScreenshotHelper.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17+
@file:OptIn(ExperimentalRoborazziApi::class)
18+
1719
package com.google.samples.apps.nowinandroid.core.testing.util
1820

21+
import android.graphics.Bitmap.CompressFormat.PNG
1922
import androidx.activity.ComponentActivity
2023
import androidx.activity.compose.setContent
2124
import androidx.compose.runtime.Composable
@@ -30,12 +33,25 @@ import androidx.compose.ui.test.DeviceConfigurationOverride
3033
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
3134
import androidx.compose.ui.test.onRoot
3235
import androidx.test.ext.junit.rules.ActivityScenarioRule
36+
import com.github.takahirom.roborazzi.ExperimentalRoborazziApi
37+
import com.github.takahirom.roborazzi.RoborazziATFAccessibilityCheckOptions
38+
import com.github.takahirom.roborazzi.RoborazziATFAccessibilityChecker
39+
import com.github.takahirom.roborazzi.RoborazziATFAccessibilityChecker.CheckLevel
3340
import com.github.takahirom.roborazzi.RoborazziOptions
3441
import com.github.takahirom.roborazzi.RoborazziOptions.CompareOptions
3542
import com.github.takahirom.roborazzi.RoborazziOptions.RecordOptions
3643
import com.github.takahirom.roborazzi.captureRoboImage
44+
import com.github.takahirom.roborazzi.checkRoboAccessibility
45+
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset
46+
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityViewCheckResult
47+
import com.google.android.apps.common.testing.accessibility.framework.integrations.espresso.AccessibilityViewCheckException
48+
import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.BitmapImage
3749
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
50+
import org.hamcrest.Matcher
51+
import org.hamcrest.Matchers
3852
import org.robolectric.RuntimeEnvironment
53+
import java.io.File
54+
import java.io.FileOutputStream
3955

4056
val DefaultRoborazziOptions =
4157
RoborazziOptions(
@@ -52,10 +68,17 @@ enum class DefaultTestDevices(val description: String, val spec: String) {
5268
}
5369
fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.captureMultiDevice(
5470
screenshotName: String,
71+
accessibilitySuppressions: Matcher<in AccessibilityViewCheckResult> = Matchers.not(Matchers.anything()),
5572
body: @Composable () -> Unit,
5673
) {
5774
DefaultTestDevices.entries.forEach {
58-
this.captureForDevice(it.description, it.spec, screenshotName, body = body)
75+
this.captureForDevice(
76+
deviceName = it.description,
77+
deviceSpec = it.spec,
78+
screenshotName = screenshotName,
79+
body = body,
80+
accessibilitySuppressions = accessibilitySuppressions,
81+
)
5982
}
6083
}
6184

@@ -64,6 +87,7 @@ fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.c
6487
deviceSpec: String,
6588
screenshotName: String,
6689
roborazziOptions: RoborazziOptions = DefaultRoborazziOptions,
90+
accessibilitySuppressions: Matcher<in AccessibilityViewCheckResult> = Matchers.not(Matchers.anything()),
6791
darkMode: Boolean = false,
6892
body: @Composable () -> Unit,
6993
) {
@@ -83,11 +107,46 @@ fun <A : ComponentActivity> AndroidComposeTestRule<ActivityScenarioRule<A>, A>.c
83107
}
84108
}
85109
}
110+
111+
// Run Accessibility checks first so logging is included
112+
val accessibilityException = try {
113+
this.onRoot().checkRoboAccessibility(
114+
roborazziATFAccessibilityCheckOptions = RoborazziATFAccessibilityCheckOptions(
115+
failureLevel = CheckLevel.Error,
116+
checker = RoborazziATFAccessibilityChecker(
117+
preset = AccessibilityCheckPreset.LATEST,
118+
suppressions = accessibilitySuppressions,
119+
),
120+
),
121+
)
122+
null
123+
} catch (e: AccessibilityViewCheckException) {
124+
e
125+
}
126+
86127
this.onRoot()
87128
.captureRoboImage(
88129
"src/test/screenshots/${screenshotName}_$deviceName.png",
89130
roborazziOptions = roborazziOptions,
90131
)
132+
133+
// Rethrow the Accessibility exception once screenshots have passed
134+
if (accessibilityException != null) {
135+
accessibilityException.results.forEachIndexed { index, check ->
136+
val viewImage = check.viewImage
137+
if (viewImage is BitmapImage) {
138+
val file = File("build/outputs/roborazzi/${screenshotName}_${deviceName}_$index.png")
139+
println("Writing check.viewImage to $file")
140+
FileOutputStream(
141+
file,
142+
).use {
143+
viewImage.bitmap.compress(PNG, 100, it)
144+
}
145+
}
146+
}
147+
148+
throw accessibilityException
149+
}
91150
}
92151

93152
/**

feature/foryou/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/ForYouScreenScreenshotTests.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ package com.google.samples.apps.nowinandroid.feature.foryou
1919
import androidx.activity.ComponentActivity
2020
import androidx.compose.runtime.Composable
2121
import androidx.compose.ui.test.junit4.createAndroidComposeRule
22+
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils
23+
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesElements
24+
import com.google.android.apps.common.testing.accessibility.framework.checks.TextContrastCheck
25+
import com.google.android.apps.common.testing.accessibility.framework.matcher.ElementMatchers.withText
2226
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
2327
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
2428
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultTestDevices
@@ -31,6 +35,7 @@ import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Loa
3135
import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.NotShown
3236
import com.google.samples.apps.nowinandroid.feature.foryou.OnboardingUiState.Shown
3337
import dagger.hilt.android.testing.HiltTestApplication
38+
import org.hamcrest.Matchers
3439
import org.junit.Before
3540
import org.junit.Rule
3641
import org.junit.Test
@@ -108,7 +113,20 @@ class ForYouScreenScreenshotTests {
108113

109114
@Test
110115
fun forYouScreenTopicSelection() {
111-
composeTestRule.captureMultiDevice("ForYouScreenTopicSelection") {
116+
composeTestRule.captureMultiDevice(
117+
"ForYouScreenTopicSelection",
118+
accessibilitySuppressions = Matchers.allOf(
119+
AccessibilityCheckResultUtils.matchesCheck(TextContrastCheck::class.java),
120+
Matchers.anyOf(
121+
// Disabled Button
122+
matchesElements(withText("Done")),
123+
124+
// TODO investigate, seems a false positive
125+
matchesElements(withText("What are you interested in?")),
126+
matchesElements(withText("UI")),
127+
),
128+
),
129+
) {
112130
ForYouScreenTopicSelection()
113131
}
114132
}

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.r
141141
retrofit-kotlin-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" }
142142
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
143143
roborazzi = { group = "io.github.takahirom.roborazzi", name = "roborazzi", version.ref = "roborazzi" }
144+
roborazzi-accessibility-check = { group = "io.github.takahirom.roborazzi", name = "roborazzi-accessibility-check", version.ref = "roborazzi" }
144145
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
145146
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
146147
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }

0 commit comments

Comments
 (0)