Paparazzi is one of the most popular screenshot testing libraries for Android. It renders your Views and Composables on the JVM with layoutlib, so there's no emulator or device to manage, and your screenshot tests run as fast as your unit tests.
But Paparazzi has to compare each screenshot against a golden image, and those golden images have to live somewhere. The Paparazzi README recommends storing them in your repository with Git LFS. For a handful of screenshots that works fine. As your team writes more screenshot tests, it starts to hurt.
In this post we'll look at why Git LFS gets painful as Paparazzi test suites grow, and how to run the same Paparazzi tests with Screenshotbot without storing a single PNG in your repository.
The standard Paparazzi workflow looks like this:
To use LFS, you add a line like this to .gitattributes:
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
This is a reasonable default. It keeps screenshots next to the code that produced them, and it doesn't need any extra infrastructure. But every one of those four steps gets worse as the number of screenshots grows.
Every time a screenshot changes, a new copy of the image is stored forever. Change a color in your design system and you may re-record hundreds or thousands of screenshots in one commit. LFS keeps the large files out of the Git packfiles, but they still count against your storage and bandwidth quota on GitHub, GitLab or Bitbucket, and they still have to be downloaded.
We wrote about this in more detail in Can Git LFS scale?
A CI job that runs verifyPaparazziDebug needs the golden images, so it has to fetch them from LFS. That's extra time and bandwidth on every build, including the builds that didn't touch any UI. CI caches help, but they're one more thing to tune and keep warm.
Paparazzi is much more deterministic than emulator-based screenshot tests, but the golden images are still recorded on whatever machine the developer happens to be using. Different JDK versions, operating systems and font rendering can produce pixel-level differences, which turn into failures on CI that nobody can reproduce locally. Teams usually end up with a "record in Docker" script, or a CI job that records and pushes a commit back to the branch.
Two branches that change the same screen will both change the same golden images. Git can't merge PNGs, so whoever merges second has to re-record and push again. On a busy repository with a shared design system, this happens a lot.
When a pull request changes fifty screenshots, the reviewer has to click through fifty image diffs in the code review tool, one file at a time, mixed in with the code changes. It's easy to miss the one screenshot that changed for the wrong reason.
Golden images are build artifacts. They're generated from your code, so they don't need to be stored next to it in version control: they only need to be stored somewhere you can compare against, and tied to the commit that produced them.
That's what Screenshotbot does. Instead of recording on a developer's machine and committing the result:
Nothing is committed, so the repository stops growing, there's nothing to fetch from LFS on CI, and there are no merge conflicts on images. And because every screenshot is recorded on CI, they're all rendered in the same environment.
This scales to tens of thousands of screenshots, which is where the LFS approach tends to fall over.
We talked about this approach, and what it takes to scale up screenshot testing, at next app devCon 2026:
You don't need to change your Paparazzi tests at all. The Screenshotbot Gradle plugin detects Paparazzi and adds the tasks you need.
First, add the plugin to your root build.gradle.kts:
plugins {
id("io.screenshotbot.plugin") version "1.29.13"
}
Then apply it in each module that has Paparazzi tests:
apply(plugin = "io.screenshotbot.plugin")
Create an API key from your Screenshotbot account, and add it to your CI as the SCREENSHOTBOT_API_KEY and SCREENSHOTBOT_API_SECRET environment variables. Then replace verifyPaparazziDebug in your CI with:
./gradlew recordAndVerifyPaparazziDebugScreenshotbotCI
That's it. You'll see the screenshot changes on your next pull request. You may need to adjust the task name for your build flavor. The Android documentation has the full details, and there's a complete example in screenshotbot/paparazzi-example.
Once Screenshotbot is running on CI, you can delete the golden images from your repository. Remove the LFS rule for snapshots from .gitattributes, then:
git rm -r --cached '*/src/test/snapshots/*'
echo '**/test/snapshots' >> .gitignore
git commit -m "Move Paparazzi snapshots to Screenshotbot"
The .gitignore entry stops anyone from accidentally committing new snapshots by running the old record task. Older commits still reference the old images in LFS, so keep them around until you no longer need to build those commits.
Developers can still check their changes before pushing. After a one-time ./gradlew :installScreenshotbot to set up an API key, record a baseline and then verify your changes against it:
./gradlew :app:recordPaparazziDebugScreenshotbot
# make your changes
./gradlew :app:verifyPaparazziDebugScreenshotbot
See local runs for how this works.
Git LFS is a fine way to start with Paparazzi. But golden images behave differently from source code: they change in bulk, they can't be merged, and they need a visual review. Once you have more than a few hundred of them, it's worth moving them out of Git and into a tool built for them.
Screenshotbot works with Paparazzi, Roborazzi, Compose Preview Screenshot Tests and the other popular Android screenshot testing library, as well as iOS and web. Try it for free, or get in touch if you'd like help migrating a large Paparazzi suite.
Did you enjoy this post? Share the knowledge!