Paparazzi

repository·master·Indexed 25 days ago

https://github.com/cashapp/paparazzi

An Android library for rendering application screens (Views and Composables) for snapshot testing without requiring a physical device or emulator. It supports JUnit 4 and JUnit 5, provides Gradle tasks for recording and verifying snapshots, and includes the AccessibilityRenderExtension for visually verifying accessibility properties.

Tokens
5.1K
Snippets
14
Records
18
Agent score
83%

What's inside Paparazzi

  1. Publish an internal release

    master

    To publish an internal release (e.g., 2.0.0-internal01) to an internal repository, follow these steps:

    1. Update VERSION_NAME in gradle.properties to the internal release version. Ensure this version is unique and does not collide with existing releases.
    2. Set RELEASE_SIGNING_ENABLED to false in gradle.properties.
    3. Verify that your local ~/.gradle/gradle.properties contains the following correctly configured variables:
      • internalUrl: The internal repository URL.
      • internalUsername: Internal repository credentials.
      • internalPassword: Internal repository credentials.
    4. Execute the publish command: ./gradlew publishMavenPublicationToInternalRepository paparazzi-gradle-plugin:publishAllPublicationsToInternalRepository --no-parallel.

    Troubleshooting: If the Gradle publish task fails with a 403 error, verify that the VERSION_NAME is unique and has not been previously published to the internal repository.

    ./gradlew publishMavenPublicationToInternalRepository paparazzi-gradle-plugin:publishAllPublicationsToInternalRepository --no-parallel
  2. Report unacceptable behavior

    master

    If you experience or witness unacceptable behavior or have concerns regarding the community, report it by emailing codeofconduct@squareup.com. All reports are handled with discretion.

    To ensure an effective review, please include the following in your report:

    • Your contact information.
    • Names (real, nicknames, or pseudonyms) of individuals involved and any witnesses.
    • A detailed account of what occurred and whether the incident is ongoing.
    • Links to any publicly available records (e.g., mailing list archives or public IRC logs).
    • Any other helpful additional information.
  3. Set up Accessibility Snapshots with AccessibilityRenderExtension

    master

    To enable accessibility property testing, add AccessibilityRenderExtension to the renderExtensions set in your Paparazzi configuration. This allows you to visually verify accessibility properties (like content descriptions, roles, and states) alongside your UI snapshots.

    Recording and verifying works using standard Paparazzi commands:

    • Record: ./gradlew recordPaparazzi
    • Verify: ./gradlew verifyPaparazzi

    Supported content types:

    • Jetpack Compose composables
    • Android Views
    • Mixed hierarchies (e.g., a View containing a ComposeView)
    @get:Rule
    val paparazzi = Paparazzi(
      renderExtensions = setOf(AccessibilityRenderExtension()),
    )
    
    // Example usage for Compose
    @Test
    fun composableTest() {
      paparazzi.snapshot {
        MyComposable()
      }
    }
    
    // Example usage for Android View
    @Test
    fun viewTest() {
      val view = MyCustomView(paparazzi.context)
      paparazzi.snapshot(view)
    }
    
    // Example usage for Mixed hierarchies
    @Test
    fun mixedTest() {
      val mixedView = MixedView(paparazzi.context)
      paparazzi.snapshot(mixedView)
    }
  4. Configure Git LFS for snapshots

    master

    It is recommended to use Git LFS to store snapshots. Follow these steps to set it up locally:

    1. Install Git LFS: brew install git-lfs
    2. Initialize: git lfs install --local
    3. Track snapshots: git lfs track "**/snapshots/**/*.png"
    4. Commit attributes: git add .gitattributes

    To improve git checkout performance, you can run: git config lfs.setlockablereadonly false.

    brew install git-lfs
    git config core.hooksPath
    git lfs install --local
    git lfs track "**/snapshots/**/*.png"
    git add .gitattributes
    git config lfs.setlockablereadonly false
  5. Install Paparazzi via Gradle

    master

    To use Paparazzi in your Android project, apply the plugin using either the buildscript block or the plugins DSL. Ensure mavenCentral() and google() are in your repositories.

    // Using plugins DSL
    plugins {
      id 'app.cash.paparazzi' version '2.0.0-alpha05'
    }
    
    // OR using buildscript
    buildscript {
      repositories {
        mavenCentral()
        google()
      }
      dependencies {
        classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:2.0.0-alpha05'
      }
    }
    
    apply plugin: 'app.cash.paparazzi'
  6. Interpret Accessibility Snapshot Outputs

    master

    Accessibility snapshots are rendered as a split view:

    • Left Pane: The original UI.
    • Right Pane: A color-coded legend.

    Key characteristics:

    • Legend Order: Matches the order a screen reader (TalkBack) surfaces elements (typically start-to-end, top-to-bottom).
    • Color Coding: Each legend entry has a color badge that matches a colored rectangle overlaid on the UI element.
    • Deterministic Colors: Colors are derived from a hash of the element's accessibility text, ensuring stability across runs.
    • Font Scaling: Legend text does not scale with fontScale, ensuring consistent snapshot output.
  7. Workaround for LocalInspectionMode in Composables

    master

    Paparazzi does not set LocalInspectionMode globally to ensure snapshots represent true production output. If your Composable (like GoogleMap()) relies on LocalInspectionMode to show a preview-safe version, wrap it in a CompositionLocalProvider during your test.

     @Test
      fun inspectionModeView() {
        paparazzi.snapshot(
          CompositionLocalProvider(LocalInspectionMode provides true) {
            YourComposable()
          }
        )
      }
  8. Release a new version to Maven Central

    master

    To release a non-SNAPSHOT version of Paparazzi to Maven Central, follow these steps to prepare the versioning, update documentation, and trigger the GitHub Action workflow:

    1. Update VERSION_NAME in gradle.properties to the new release version (e.g., 1.2.3).
    2. Update CHANGELOG.md:
      • Change the Unreleased header to the new version and append today's date.
      • Add a new Unreleased section at the top.
      • Add a link URL at the bottom to maintain the header link.
      • Update the Unreleased link URL to compare the new version against HEAD.
    3. Update README.md with the new version number.
    4. Commit the version changes: git commit -am "Prepare version X.Y.Z".
    5. Tag the release: git tag -a X.Y.Z -m "X.Y.Z".
    6. Reset VERSION_NAME in gradle.properties to the next SNAPSHOT version.
    7. Commit the development version reset: git commit -am "Prepare next development version".
    8. Push changes and tags: git push && git push --tags.

    Pushing these changes triggers a GitHub Action that creates a GitHub release and uploads artifacts to Maven Central.

    git commit -am "Prepare version X.Y.Z"
    git tag -a X.Y.Z -m "X.Y.Z"
    # Update gradle.properties to next SNAPSHOT
    git commit -am "Prepare next development version"
    git push && git push --tags
  9. Configure Jetifier to ignore bundled dependencies

    master

    If you use Jetifier to migrate off Support libraries, add the following to your gradle.properties to prevent issues with bundled Android dependencies:

    android.jetifier.ignorelist=android-base-common,common

  10. Accessibility Snapshot Limitations

    master

    When using AccessibilityRenderExtension, be aware of the following constraints:

    • RenderingMode.SHRINK is not supported: Using this mode with the extension will throw an IllegalStateException.
    • validateAccessibility is deprecated: The old validateAccessibility: Boolean parameter on Paparazzi (which used Android's LayoutValidator for contrast) cannot be used simultaneously with AccessibilityRenderExtension.
  11. Handle Lottie animations in snapshots

    master

    When taking screenshots of Lottie animations, you must force Lottie to run on the same thread to avoid exceptions. Set LottieTask.EXECUTOR to an executor that runs the Runnable immediately.

    @Before
    fun setup() {
        LottieTask.EXECUTOR = Executor(Runnable::run)
    }
  12. Use Paparazzi with JUnit 5

    master

    For JUnit 5, you must manually manage the Paparazzi lifecycle using @BeforeEach and @AfterEach. You need to call setup() with a TestName containing the package, class, and method names to ensure correct context.

    lateinit var paparazzi: Paparazzi
    
    @BeforeEach
    fun setup(testInfo: TestInfo) {
      paparazzi = Paparazzi().apply {
        setup(
          testName = TestName(
            packageName = testInfo.testClass.get().`package`?.name.orEmpty(),
            className = testInfo.testClass.get().simpleName,
            methodName = testInfo.testMethod.get().name
          )
        )
      }
    }
    
    @AfterEach
    fun tearDown() {
      paparazzi.teardown()
    }
    
    @Test
    fun snapshot_example() {
      val view = paparazzi.inflate<TextView>(android.R.layout.simple_list_item_1).apply {
        text = "Hello Paparazzi"
        textSize = 24f
        gravity = Gravity.CENTER
      }
    
      paparazzi.snapshot(view)
    }