Composable Preview Functions using Roborazzi for Visual Regression Tests (VRT). Will also explain the roles of related tools and mechanisms to make it easier for everyone to implement.
the screenshots taken Timing for taking the screenshots Selection of image comparison tools for VRT Building and operating CI (Continuous Integration) See https://github.com/DeNA/android-modern-architecture-test-handson/blob/main/docs/handson/VisualRegressionTest_CI.md for CI operation know-how.
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
the results of screenshot tests to each other to ensure that there are no unintended differences in the screenshot images A visual check by a human is still required to see if the difference is intentional or not Screenshot testing tools often also provide diff detection Definitions of Terms
Write test code to interact with the UI and show the desired screen Take the screenshots by using Instrumented Test Hard to keep it operational ◆ Hard to write test code ◆ Tests tend to flaky due to async processing ◆ Very slow to execute on real devices and/or emulators and difficult to incorporate into CI
the Preview function has led to the habit of checking screen layouts on the fly Saving a screenshot of the Preview function has also become popular It's now possible to take screenshots in Local Test The foundation for easy operation has been established! ◆ No test code is required with the Preview function ◆ Easily integrated into CI since it can be executed quickly in the JVM History of screenshot testing
Implement screenshot test based on the screenshot of the Preview function Take a screenshot of the various variations such as: Night mode and Light mode Tablet and Smartphone Various locales Write as little test code as possible
testing and Visual Regression Tests With Jetpack Compose now in use, the foundation for easy operation of screenshot testing and VRT has been established Clarified what we want to achieve in this session
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
@Preview ・ ・ ・ @Test fun takeScreenShot() { ... } ①Collect the Preview Functions ②Take a screenshot of each Preview function collected ③And even take screenshots of various variations. (e.g., night mode, tablet screen, German locale, etc.)
{ ... } ①Collect the Preview Functions ②Take a screenshot of each Preview function collected ③And even take screenshots of various variations. (e.g., night mode, tablet screen, German locale, etc.)
that generates a UI catalog browser (ShowkaseBrowser) with Preview functions, fonts, colors, etc., and allows browsing on Android devices Provides API to retrieve the Preview functions (@Composable () -> Unit) collected by Showkase Only some properties of @Preview can be obtained Realized by using an annotation processor
@ShowkaseRoot class MyShowkaseRootModule : ShowkaseRootModule // Retrieve the list of Preview composable functions val componentList: List<ShowkaseBrowserComponent> = Showkase.getMetadata().componentList val previewFunctionList: List<@Composable () -> Unit> = componentList.map { it.component }
group componentName String name componentKDoc String component @Composable () -> Unit styleName String? isDefaultStyle Boolean widthDp Int? widthDp heightDp Int? heightDp tags List<String> extraMetaData List<String> Of the properties @Preview has, only these 4 can be handled by Showkase Note: Properties of ShowkaseBrowserComponent
released on 2024-06-10) Library dedicated to collecting Preview functions and their metadata (e.g. properties of @Preview) Allows to access all @Preview properties Also allows to access other annotations attached to the @Preview function Realized by using the JVM's reflection functionality ①Libs for Collecting Preview Functions
list of Preview composable functions val componentList: List<ComposablePreview<AndroidPreviewInfo>> = AndroidComposablePreviewScanner() .scanPackageTrees("com.example.droidkaigi", ...) .getPreviews() // ComposablePreview<T> implements a @Composable invoke(), so // you can call the preview function by calling invoke(). // If you intentionally store them in a List<@Composable () -> Unit> type, it would look like the following val previewFunctionList: List<@Composable () -> Unit> = composablePreviewList.map { { it() } }
name String name group String group apiLevel Int apiLevel widthDp Int widthDp heightDp Int heightDp locale String locale fontScale Float fontScale showSystemUi Boolean showSystemUi showBackground Boolean showBackground backgroundColor Long backgroundColor uiMode Int uiMode device String device wallpaper Int wallpaper
name String name group String group apiLevel Int apiLevel widthDp Int widthDp heightDp Int heightDp locale String locale fontScale Float fontScale showSystemUi Boolean showSystemUi showBackground Boolean showBackground backgroundColor Long backgroundColor uiMode Int uiMode device String device wallpaper Int wallpaper All properteis of @Preview can be handled
2 libraries Showkase Composable Preview Scanner Basic functionality is the same for both Differ in the extent to which @Preview properties can be accessed
{ ... } ①Collect the Preview Functions ②Take a screenshot of each Preview function collected ③And even take screenshots of various variations. (e.g., night mode, tablet screen, German locale, etc.)
Local Tests (on the JVM of your development PC) Executes Android Framework code as-is (as much as possible) in the JVM Also works with Jetpack Compose Recently, Graphics related classes are also executed as-is (Robolectric Native Graphics (RNG)) Hardware rendering is also supported to get more realistic rendering results ②Libs for Taking Screenshot
Obtains screenshots closer to the actual device due to improved Robolectric functionality Supports Jetpack Compose (work with ComposeTestRule) ②Libs for Taking Screenshot
@RunWith(AndroidJUnit4::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) class MyScreenshotTest { @get:Rule val composeTestRule = createComposeRule() @Test fun test() { composeTestRule.setContent { MyComposable() } composeTestRule.onRoot().captureRoboImage() }} Declaration to use Robolectric with Native Graphics enabled
createComposeRule() @Test fun test() { composeTestRule.setContent { MyComposable() } composeTestRule.onRoot().captureRoboImage() }} Declaration of ComposeTestRule Code Snippet for Screenshot Testing a Single Composable Func.
createComposeRule() @Test fun test() { composeTestRule.setContent { MyComposable() } composeTestRule.onRoot().captureRoboImage() }} Composable function under test Code Snippet for Screenshot Testing a Single Composable Func.
createComposeRule() @Test fun test() { composeTestRule.setContent { MyComposable() } composeTestRule.onRoot().captureRoboImage() }} Take screenshot of MyComposable() Code Snippet for Screenshot Testing a Single Composable Func.
testCase: TestCase) { @Test fun test() { doSomeTest(testCase.text) } companion object { data class TestCase(private val text: String) @ParameterizedRobolectricTestRunner.Parameters @JvmStatic fun params() = listOf(TestCase("Hello"), TestCase("こんにちは")) }} ①Declare Runner for Parameterized Test
testCase: TestCase) { @Test fun test() { doSomeTest(testCase.text) } companion object { data class TestCase(private val text: String) @ParameterizedRobolectricTestRunner.Parameters @JvmStatic fun params() = listOf(TestCase("Hello"), TestCase("こんにちは")) }} ②Declare a list of parameters to be passed to the constructor of the test class
testCase: TestCase) { @Test fun test() { doSomeTest(testCase.text) } companion object { data class TestCase(private val text: String) @ParameterizedRobolectricTestRunner.Parameters @JvmStatic fun params() = listOf(TestCase("Hello"), TestCase("こんにちは")) }} ④Test with the parameter passed
combination of ComposeTestRule, Robolectric, and Roborazzi, a screenshot of the Composable function can be taken ParameterizedRobolectricTestRunner allows to run test for each element in the list of parameters used for testing
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
prepare a list of preview functions to take screenshots, we should be able to take screenshots of each preview function by using ParameterizedRobolectricTestRunner
{ ... } ①Collect the Preview Functions ②Take a screenshot of each Preview function collected ③And even take screenshots of various variations. (e.g., night mode, tablet screen, German locale, etc.)
version "1.26.0" apply false } Top-level build.gradle.kts # Fix the screenshot output directory roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDi rectory # Save screenshots when running tests in Android Studio roborazzi.test.record=true gradle.properties
testImplementation("io.github.takahirom.roborazzi:roborazzi- compose:1.26.0") testImplementation("org.robolectric:robolectric:4.13") } roborazzi { outputDir.set(rootProject.file("screenshots")) } build.gradle.kts file in the app module (cont.) Specify the output directory for screenshots here as well
"1.9.20-1.0.14" apply false } Top-level build.gradle.kts plugins { id("com.google.devtools.ksp") } dependencies { implementation("com.airbnb.android:showkase:1.0.3") ksp("com.airbnb.android:showkase-processor:1.0.3") } ksp { arg("skipPrivatePreviews", "true") } build.gradle.kts file in all modules in which @Preview function is used
"1.9.20-1.0.14" apply false } Top-level build.gradle.kt plugins { id("com.google.devtools.ksp") } dependencies { implementation("com.airbnb.android:showkase:1.0.3") ksp("com.airbnb.android:showkase-processor:1.0.3") } ksp { arg("skipPrivatePreviews", "true") } build.gradle.kts file in all modules in which @Preview function is used Configuration to ignore private @Preview function without error
= RobolectricDeviceQualifiers.Pixel7) class ShowkasePreviewTest( private val component: ShowkaseBrowserComponent ) { @get:Rule val composeTestRule = createComposeRule() ... Use Parameterized Test
= RobolectricDeviceQualifiers.Pixel7) class ShowkasePreviewTest( private val component: ShowkaseBrowserComponent ) { @get:Rule val composeTestRule = createComposeRule() ... Test under Pixel7 screen
= RobolectricDeviceQualifiers.Pixel7) class ShowkasePreviewTest( private val component: ShowkaseBrowserComponent ) { @get:Rule val composeTestRule = createComposeRule() ... Passed data collected by Showkase as parameters
{ val filePath = "${component.componentKey}.png" composeTestRule.setContent { CompositionLocalProvider( LocalInspectionMode provides true ) { component.component() } } composeTestRule.onRoot().captureRoboImage(filePath) } ... Ensure the same result as displaying @Preview in Android Studio
@ParameterizedRobolectricTestRunner.Parameters @JvmStatic fun params(): Iterable<ShowkaseBrowserComponent> = Showkase.getMetadata().componentList } } } Returns a list of ShowkaseBrowserComponent collected by Showkase
Robolectric's function to specify screen size, etc. filePath = "${component.componentKey}.png" componentKey: A value that uniquely distinguishes ShowkaseBrowserComponent Prevents screenshots of different Preview functions from being overwritten LocalInspectionMode This value is set to true when previewing in Android Studio Set to true if you want the result to be the same as the Android Studio preview
same as Showkase, but Different test parameter acquisition for Composable Preview Scanner Need to use AndroidPreviewScreenshotIdBuilder to specify screenshot file name Roborazzi Gradle Plugin provides a mechanism to simplify these, so use it as-is or customize it to your needs
build will generate a class called RoborazziPreviewParameterizedTests, which is used to run tests The following @Preview properties are supported by default uiMode (only night mode / light mode) locale fontScale
the ParameterizedRobolectricTestRunner, you can test screenshots of all collected Preview functions If you use Composable Preview Scanner, you can automatically generate screenshot tests just by setting up a build scripts Summary So Far
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
{ ... } ①Collect the Preview Functions ②Take a screenshot of each Preview function collected ③And even take screenshots of various variations. (e.g., night mode, tablet screen, German locale, etc.)
The screenshot is from Now in Android App licensed under Apache License (Version 2.0). https://github.com/android/nowinandroid Various variations of the preview image can be checked by adding optional parameters to @Preview
@Preview(fontScale = 2f) @Composable fun PreviewMyComposable() { ... } Custom annotations can be defined to group multiple @Previews @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) annotation class NightModeVariationPreview @NightModeVariationPreview @Composable fun PreviewMyComposable() { ... } Review of @Preview annotations
indicated in @Preview's property If the custom annotation shown on the previous slide is used, take 2 screenshots, night mode and light mode Can be achieved by changing the configs for taking screenshot based on the @Preview's property just before calling each captureRoboImage() @NightModeVariationPreview @Composable fun PreviewMyComposable() { ... }
patterns of screenshots uniformly for all @Preview functions If you want to take screenshots in night mode for all Preview functions, simply add the @NightModeVariationPreview to all Preview functions instead of the normal @Preview Strategy for taking various screenshots
are implemented Access to the @Preview properties of the collected functions Take screenshots in various configuration (night mode, font scale, etc.) Strategy for taking various screenshots
name, widthDp, heightDp can be accessed To pass other optional information, it is necessary to encode it into a group property, etc. Example: @Preview(group="ja-night", locale = "ja", uiMode = UI_MODE_NIGHT_YES) Composable Preview Scanner All properties can be accessed If you want to use the full @Preview information, this library is recommended
CompositionLocalProvider @Test fun test() { composeTestRule.setContent { val density = LocalDensity.current val newDensity = Density( density = density.density, fontScale = density.fontScale * fontScale ) CompositionLocalProvider(LocalDensity provides newDensity) { myComposable() } } composeTestRule.onRoot().captureRoboImage() } Assume this fontScale is given externally
val newDensity = Density( density = density.density, fontScale = density.fontScale * fontScale ) CompositionLocalProvider(LocalDensity provides newDensity) { myComposable() } } composeTestRule.onRoot().captureRoboImage() } 82 Override LocalDensity to change font scale Changing config using Compose's original functionality Use of CompositionLocalProvider
Configuration specified in @Config can be changed dynamically @RunWith(ParameterizedRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(qualifiers = RobolectricDeviceQualifiers.Pixel7) class MyTest(private val testCase: TestCase) { ...
TestCase) { ... "w411dp-h914dp-normal-long-notround-any-420dpi-keyshidden-nonav" Changing config using Robolectric's functionality Use of RuntimeEnvironment.setQualifiers() Configuration specified in @Config can be changed dynamically
(Example) Description Locale en, en-US, fr-rCA, ... language code / region code Layout Direction ldltr, ldrtl left-to-right / right-to-left Screen Orientation port, land portrait / landscape orientation Night Mode night, notnight Night Mode (Dark Mode) Same as Android Resource Directory naming rule(e.g., values-ja) https://developer.android.com/guide/topics/resources/providing-resources.html#QualifierRules Specify from the top of the table referenced from the URL above Example: land-night (Landscape and Night Mode)
A qualifier with a leading + can be used to override a part of configuration declared in @Config @Config(qualifiers = "xlarge-port") class MyTest { @get:Rule val composeTestRule = createAndroidComposeRule<ComponentActivity>() ... @Test fun testLandscape() { RuntimeEnvironment.setQualifiers("+land") composeTestRule.activityRule.scenario.recreate() // become xlarge-land (Landscape mode) here }}
MyTest { @get:Rule val composeTestRule = createAndroidComposeRule<ComponentActivity>() ... @Test fun testLandscape() { RuntimeEnvironment.setQualifiers("+land") composeTestRule.activityRule.scenario.recreate() // become xlarge-land (Landscape mode) here }} Changed from xlarge-port to xlarge-land Use of RuntimeEnvironment.setQualifiers() A qualifier with a leading + can be used to override a part of configuration declared in @Config
= createAndroidComposeRule<ComponentActivity>() ... @Test fun testLandscape() { RuntimeEnvironment.setQualifiers("+land") composeTestRule.activityRule.scenario.recreate() // become xlarge-land (Landscape mode) here }} Remember to recreate the Activity to apply changes Changing config using Robolectric's functionality Use of RuntimeEnvironment.setQualifiers() A qualifier with a leading + can be used to override a part of configuration declared in @Config
= createAndroidComposeRule<ComponentActivity>() ... @Test fun testLandscape() { RuntimeEnvironment.setQualifiers("+land") composeTestRule.activityRule.scenario.recreate() // become xlarge-land (Landscape mode) here }} Changing config using Robolectric's functionality Use this instead of createComposeRule to allow access to activityRule Use of RuntimeEnvironment.setQualifiers() A qualifier with a leading + can be used to override a part of configuration declared in @Config
{ // Assume that widthDp and heightDp are given externally setDisplaySize(widthDp, heightDp) composeTestRule.activityRule.scenario.recreate() composeTestRule.setContent { ... } composeTestRule.onRoot().captureRoboImage() } Use of ShadowDisplay for changing screen size Changing config using Robolectric's functionality
{ // Assume that widthDp and heightDp are given externally setDisplaySize(widthDp, heightDp) composeTestRule.activityRule.scenario.recreate() composeTestRule.setContent { ... } composeTestRule.onRoot().captureRoboImage() } will be explained in later slides Use of ShadowDisplay for changing screen size Changing config using Robolectric's functionality
{ // Assume widthDp and heightDp are given externally setDisplaySize(widthDp, heightDp) composeTestRule.activityRule.scenario.recreate() composeTestRule.setContent { ... } composeTestRule.onRoot().captureRoboImage() } Recreate the Activity to apply changes Use of ShadowDisplay for changing screen size Changing config using Robolectric's functionality
= ShadowDisplay.getDefaultDisplay() val density = context.resources.displayMetrics.density widthDp?.let { val widthPx = (widthDp * density).roundToInt() Shadows.shadowOf(display).setWidth(widthPx) } heightDp?.let { val heightPx = (heightDp * density).roundToInt() Shadows.shadowOf(display).setHeight(heightPx) } } Use of ShadowDisplay for changing screen size Changing config using Robolectric's functionality
= ShadowDisplay.getDefaultDisplay() val density = context.resources.displayMetrics.density widthDp?.let { val widthPx = (widthDp * density).roundToInt() Shadows.shadowOf(display).setWidth(widthPx) } heightDp?.let { val heightPx = (heightDp * density).roundToInt() Shadows.shadowOf(display).setHeight(heightPx) } } Specify new screen size (in pixels) Use of ShadowDisplay for changing screen size Changing config using Robolectric's functionality
time at which a screenshot is taken Useful for taking a screenshot when the automatically started animation is completed @Test fun test() { composeTestRule.apply { mainClock.autoAdvance = false setContent { ... } mainClock.advanceTimeBy(1_000) // animation playback duration onRoot().captureRoboImage() mainClock.autoAdvance = true } }
setContent { ... } mainClock.advanceTimeBy(1_000) // animation playback duration onRoot().captureRoboImage() mainClock.autoAdvance = true } } Declaration to manually control the time governed by composeTestRule Changing config using ComposeTestRule's functionality Can control the time at which a screenshot is taken Useful for taking a screenshot when the automatically started animation is completed
setContent { ... } mainClock.advanceTimeBy(1_000) // animation playback duration onRoot().captureRoboImage() mainClock.autoAdvance = true } } Advance the clock to the point where you want to take a screenshot Changing config using ComposeTestRule's functionality Can control the time at which a screenshot is taken Useful for taking a screenshot when the automatically started animation is completed
setContent { ... } mainClock.advanceTimeBy(1_000) // animation playback duration onRoot().captureRoboImage() mainClock.autoAdvance = true } } Restore the clock to automatic mode Changing config using ComposeTestRule's functionality Can control the time at which a screenshot is taken Useful for taking a screenshot when the automatically started animation is completed
how to take a screenshot as indicated in the properties below Night mode or light mode (uiMode in @Preview) Screen size (widthDp, heightDp in @Preview) Font scale (fontScale in @Preview) Screenshot after a specific time has elapsed If you want to take a screenshot after a specific time has elapsed, add @DelayedPreview as follows @Preview @DelayedPreview(delay = 2000) @Composable fun PreviewMyAnimationComposable() { ... }
defaultOptionsFromPlugin fun previews(): List<ComposablePreview<T>> fun test(preview: ComposablePreview<T>) } Used to customize options passed from build.gradle.kts You can also specify JUnit4 Rule to inject ComposePreviewTester Roborazzi provides a ComposePreviewTester interface, which wraps ParameterizedRobolectricTestRunner We'll implement the interface
defaultOptionsFromPlugin fun previews(): List<ComposablePreview<T>> fun test(preview: ComposablePreview<T>) } Implement to return a list of preview functions collected by Composable Preview Scanner ComposePreviewTester Roborazzi provides a ComposePreviewTester interface, which wraps ParameterizedRobolectricTestRunner We'll implement the interface
defaultOptionsFromPlugin fun previews(): List<ComposablePreview<T>> fun test(preview: ComposablePreview<T>) } Write test code for each preview function passwd as an argument ComposePreviewTester Roborazzi provides a ComposePreviewTester interface, which wraps ParameterizedRobolectricTestRunner We'll implement the interface
automatically generated with the name RoborazziPreviewParameterizedTests @RunWith(ParameterizedRobolectricTestRunner::class) class RoborazziPreviewParameterizedTests( private val preview: ComposablePreview<Any> ) { // ↓ Instance of the impl class of ComposePreviewTester private val tester = ... ... @Test fun test() { tester.test(preview) } companion object { val previews by lazy { tester.previews() } // lazy for performance @JvmStatic @ParameterizedRobolectricTestRunner.Parameters fun values(): List<ComposablePreview<Any>> = previews } } Excerpts from main part only
// ↓ Instance of the impl class of ComposePreviewTester private val tester = ... ... @Test fun test() { tester.test(preview) } companion object { val previews by lazy { tester.previews() } // lazy for performance @JvmStatic @ParameterizedRobolectricTestRunner.Parameters fun values(): List<ComposablePreview<Any>> = previews } } Calling tester.test() for each element of tester.previews() Excerpts from main part only Note: Caller of ComposePreviewTester Caller of ComposePreviewTester is automatically generated with the name RoborazziPreviewParameterizedTests
fun options(): ComposePreviewTester.Options { val testLifecycleOptions = ComposePreviewTester.Options.JUnit4TestLifecycleOptions( testRuleFactory = { composeTestRule } ) return super.options().copy( testLifecycleOptions = testLifecycleOptions ) } Specify the JUnit4 Rule to inject (in this case, composeTestRule) Implementation of ComposePreviewTester (1/6)
val options = options() return AndroidComposablePreviewScanner() .scanPackageTrees(*options.scanOptions.packages.toTypedArray()) .includeAnnotationInfoForAllOf(DelayedPreview::class.java) .getPreviews() } The package passed to be scanned is specified in build.gradle.kts
val options = options() return AndroidComposablePreviewScanner() .scanPackageTrees(*options.scanOptions.packages.toTypedArray()) .includeAnnotationInfoForAllOf(DelayedPreview::class.java) .getPreviews() } Get @DelayedPreview information as well
val delay = preview.getAnnotation<DelayedPreview>()?.delay ?: 0L val defaultFileName = AndroidPreviewScreenshotIdBuilder(preview) .build() val fileName = if (delay == 0L) defaultFileName else "${defaultFileName}_delay$delay" preview.myApplyToRobolectricConfiguration() composeTestRule.activityRule.scenario.recreate() composeTestRule.apply { ... } } Using Robolectric API to set up the configuration as indicated by the @Preview properties
generateComposePreviewRobolectricTests { enable = true // The class name of ComposePreviewTester implementation testerQualifiedClassName = "com.example.droidkaigi.MyPreviewTester" packages = listOf("com.example.droidkaigi") } }
screenshots with variations as specified in the @Preview properties There are 3 means of changing the configuration in which screenshots are taken CompositionLocalProvider Robolectric Time control by using ComposeTestRule Implement ComposePreviewTester interface provided by Roborazzi in case of using Compose Preview Scanner
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
are available by applying the Gradle Plugin provided by Google Take a screenshot of the preview function placed in src/screenshotTest (not src/main) The @Preview property is automatically reflected in the screenshot VRT feature to compare images
experimentalProperties["android.experimental.enableScreenshotTest"] = true } dependencies { screenshotTestImplementation("androidx.compose.ui:ui-tooling") } build.gradle.kts in all modules It is screenshotTestImplementation not testImplementation Compose Preview Screenshot Testing Setup Instructions
images Only save screenshots and don't create a test report ./gradlew updateDebugScreenshotTest Screenshots are saved in {module}/src/{variant}/screenshotTest/reference/ Generate test report Compare with the reference images and make a test report ./gradlew validateDebugScreenshotTest Test report are saved in {module}/build/reports/screenshotTest/preview/{variant}/index.html
of Google's tool is very simple All you have to do is put the preview functions in src/screenshotTest and it will automatically take a screenshot for you Also support @Preview properties No test code is needed Maybe the preview function needs to remain in src/main as well Sometimes preview is not shown in src/screenshotTest in Android Studio Not very customizable If your use case is a good fit, you may start with Google's Compose Preview Screenshot Testing
Roles of Related Tools 3. Taking Screenshots of Composable Preview Functions 4. Capturing Various Types of Screenshots 5. Differences from Google's Compose Preview Screenshot Testing Methodology 6. Conclusion
for taking screenshots can do and their roles Showed how to combine these libraries to take a screenshot of the preview functions Further explained how to take screenshots with different variations As additional information, gave an overview of Google's Compose Preview Screenshot Testing and explained how it differs from the libraries introduced in this session
the explanations and the code examples of this session (In Japanese) https://github.com/DeNA/android-modern-architecture-test- handson/blob/main/docs/handson/VisualRegressionTest_Previe w_ComposablePreviewScanner.md 「Composable Preview Scannerを使ってプレビュー画面のス クリーンショットを撮る」