airbnb / Showkase

🔦 Showkase is an annotation-processor based Android library that helps you organize, discover, search and visualize Jetpack Compose UI elements
https://medium.com/airbnb-engineering/introducing-showkase-a-library-to-organize-discover-and-visualize-your-jetpack-compose-elements-d5c34ef01095
Apache License 2.0
2.1k stars 107 forks source link

Implemented Showkase + Paparazzi artifact to automate screenshot testing #294

Closed vinaygaba closed 1 year ago

vinaygaba commented 1 year ago

This PR implements the showkase-screenshot-testing-paparazzi artifact that enables complete automation of screenshot testing for your codebase using Showkase + Paparazzi.

Assuming you were already using Showkase and had previews of your @Composable functions (by using either @Preview or @ShowkaseComposable), this is all the extra setup you will need to add screenshot testing to your codebase using Showkase + Paparazzi

@ShowkaseScreenshot(rootShowkaseClass = YourShowkaseRootModuleClass::class)
abstract class MyPaparazziShowkaseScreenshotTest: PaparazziShowkaseScreenshotTest {
    companion object: PaparazziShowkaseScreenshotTest.CompanionObject
}

With just 3 lines of code, all the previews from your codebase will be screenshot tested using Paparazzi with appropriate defaults 🤯💥

You might have a use case where you also want to screenshot test your components in dark mode or in RTL. Doing so is equally trivial. Just implement the appropriate functions to override the default behavior.

@ShowkaseScreenshot(rootShowkaseClass = YourShowkaseRootModuleClass::class)
abstract class MyPaparazziShowkaseScreenshotTest: PaparazziShowkaseScreenshotTest {
    companion object: PaparazziShowkaseScreenshotTest.CompanionObject {
        override fun layoutDirections(): List<LayoutDirection> = listOf(LayoutDirection.Ltr, LayoutDirection.Rtl)

        override fun uiModes(): List<PaparazziShowkaseUIMode> = listOf(PaparazziShowkaseUIMode.DEFAULT, PaparazziShowkaseUIMode.DARK)
    }
}

This will now ensure that all the previews are also screenshot tested in RTL and dark mode configurations. This creates a test matrix so all permutations are screenshot tested, just like you'd expect.

Generated File

// This is an auto-generated file. Please do not edit/modify this file.
package com.airbnb.android.showkase.screenshot.testing.paparazzi.sample

import androidx.compose.ui.unit.LayoutDirection
import app.cash.paparazzi.Paparazzi
import com.airbnb.android.showkase.models.Showkase
import com.airbnb.android.showkase.screenshot.testing.paparazzi.ColorPaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.ComponentPaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseDeviceConfig
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseUIMode
import com.airbnb.android.showkase.screenshot.testing.paparazzi.TypographyPaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.sample.getMetadata
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameter.TestParameterValuesProvider
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import kotlin.Unit
import kotlin.collections.List
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(TestParameterInjector::class)
public class MyPaparazziShowkaseScreenshotTest_PaparazziShowkaseTest :
    MyPaparazziShowkaseScreenshotTest() {
  @get:Rule
  public val paparazzi: Paparazzi = providePaparazzi()

  @Test
  public fun test_previews(
    @TestParameter(valuesProvider = PaparazziShowkasePreviewProvider::class)
        elementPreview: PaparazziShowkaseTestPreview,
    @TestParameter(valuesProvider = PaparazziShowkaseDeviceConfigProvider::class)
        config: PaparazziShowkaseDeviceConfig,
    @TestParameter(valuesProvider = PaparazziShowkaseLayoutDirectionProvider::class)
        direction: LayoutDirection,
    @TestParameter(valuesProvider = PaparazziShowkaseUIModeProvider::class)
        uiMode: PaparazziShowkaseUIMode,
  ): Unit {
    paparazzi.unsafeUpdateConfig(config.deviceConfig.copy(softButtons = false))
    takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode)
  }

  private object PaparazziShowkasePreviewProvider : TestParameter.TestParameterValuesProvider {
    public override fun provideValues(): List<PaparazziShowkaseTestPreview> {
      val metadata = Showkase.getMetadata()
      val components = metadata.componentList.map(::ComponentPaparazziShowkaseTestPreview)
      val colors = metadata.colorList.map(::ColorPaparazziShowkaseTestPreview)
      val typography = metadata.typographyList.map(::TypographyPaparazziShowkaseTestPreview)
      return components + colors + typography
    }
  }

  private object PaparazziShowkaseDeviceConfigProvider : TestParameter.TestParameterValuesProvider {
    public override fun provideValues(): List<PaparazziShowkaseDeviceConfig> = deviceConfigs()
  }

  private object PaparazziShowkaseLayoutDirectionProvider :
      TestParameter.TestParameterValuesProvider {
    public override fun provideValues(): List<LayoutDirection> = layoutDirections()
  }

  private object PaparazziShowkaseUIModeProvider : TestParameter.TestParameterValuesProvider {
    public override fun provideValues(): List<PaparazziShowkaseUIMode> = uiModes()
  }
}

@airbnb/showkase-maintainers