Skip to content

Instantly share code, notes, and snippets.

@alexvanyo
Last active April 11, 2024 08:34
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexvanyo/c4d2b6530f860f3c89a33324d4152ae8 to your computer and use it in GitHub Desktop.
Save alexvanyo/c4d2b6530f860f3c89a33324d4152ae8 to your computer and use it in GitHub Desktop.
Automatic @Preview screenshot tests with Showkase and Paparazzi
// Copyright 2022 Google LLC.
// SPDX-License-Identifier: Apache-2.0
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalInspectionMode
import app.cash.paparazzi.DeviceConfig
import app.cash.paparazzi.Paparazzi
import com.airbnb.android.showkase.models.Showkase
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameter.TestParameterValuesProvider
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(TestParameterInjector::class) // Parameterization via https://github.com/google/TestParameterInjector
class ScreenshotTests {
object PreviewProvider : TestParameterValuesProvider {
override fun provideValues(): List<ComponentPreview> =
Showkase.getMetadata().componentList.map(::ComponentPreview)
}
enum class BaseDeviceConfig(
val deviceConfig: DeviceConfig,
) {
NEXUS_5(DeviceConfig.NEXUS_5),
PIXEL_5(DeviceConfig.PIXEL_5),
PIXEL_C(DeviceConfig.PIXEL_C),
}
@get:Rule
val paparazzi = Paparazzi(
maxPercentDifference = 0.0,
)
@Test
fun preview_tests(
@TestParameter(valuesProvider = PreviewProvider::class) componentPreview: ComponentPreview,
@TestParameter baseDeviceConfig: BaseDeviceConfig,
) {
paparazzi.snapshot(
deviceConfig = baseDeviceConfig.deviceConfig.copy(
softButtons = false,
)
) {
CompositionLocalProvider(LocalInspectionMode provides true) {
Box {
componentPreview.content()
}
}
}
}
}
/**
* A helper class primarily to use the component key as the parameterized test name which uses `toString()`.
*/
private class ComponentPreview(
private val showkaseBrowserComponent: ShowkaseBrowserComponent
) {
val content: @Composable () -> Unit = showkaseBrowserComponent.component
override fun toString(): String = showkaseBrowserComponent.componentKey
}
@alexvanyo
Copy link
Author

A couple additional notes about this setup:

The list of components is pulled from Showkase.getMetadata(), and Paparazzi only works in library modules currently, so this test should be a unit test in a library module where a ShowkaseRootModule is configured. The ShowkaseRootModule could only be defined in tests, if using Showkase only to aggregate all previews.

@yschimke
Copy link

yschimke commented May 2, 2022

That's impressively simple and effective.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment