The Regression Your Test Suite Cannot See
Three Tools, Two Rendering Engines
| Tool | Rendering engine | What you write | Record / verify |
|---|---|---|---|
| Compose Preview Screenshot Testing (Google) | Layoutlib | A @Preview marked @PreviewTest | Update and validate Gradle tasks, HTML report |
| Paparazzi (Cash App) | Layoutlib | A JUnit test with a Paparazzi rule | recordPaparazziDebug, verifyPaparazziDebug |
| Roborazzi | Robolectric Native Graphics | A Robolectric test that can click, scroll and navigate first | recordRoborazziDebug, verifyRoborazziDebug |
Google’s Tool Just Moved Into the Android Gradle Plugin
@Preview functions is forty screenshot tests waiting to be switched on. @Preview parameters such as uiMode and fontScale, and multi-previews, multiply them across themes and text sizes for free.
It is also a moving target, and it has just moved. The page now opens with a deprecation notice: "Starting with Android Gradle Plugin (AGP) 9.5.0-alpha03 and Compose Preview Screenshot Testing 0.0.1-alpha16, we recommend configuring screenshot tests using AGP test suites. The standalone plugin method described on this page is deprecated." It still carries the experimental label too: "Compose Preview Screenshot Testing is still in development. Its features and APIs are subject to change substantially during the alpha phase."
In the test-suites setup, you enable two flags in gradle.properties, declare a suite under testOptions, and get suite-named tasks: update{SuiteName}{Target}{Variant}TestSuite writes the references and test{SuiteName}{Target}{Variant}TestSuite compares against them. Failures produce an HTML report under build/reports/tests/{taskName}/. If you are still on the standalone plugin, its floor is AGP 9.0, Kotlin 2.2.10, JDK 17 and plugin 0.0.1-alpha16, and its tasks are updateDebugScreenshotTest and validateDebugScreenshotTest.
The honest recommendation for a production team: adopt it for a design-system module where previews already exist, pin the engine version, and budget for configuration churn. An alpha that has already changed its setup model once will change it again.# gradle.properties
android.experimental.enableScreenshotTest=true
android.experimental.testSuiteSupport=true
// build.gradle.kts (module)
android {
testOptions {
screenshotTests.create("screenshotTest") {
engineVersion = "0.0.1-alpha16"
targetVariants.add("debug")
// plus the suite's dependencies block from the setup guide
}
}
}
// src/screenshotTest/kotlin/.../ButtonScreenshots.kt
@PreviewTest
@Preview(name = "light", showBackground = true)
@Preview(name = "dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Preview(name = "large-text", fontScale = 1.5f, showBackground = true)
@Composable
fun PrimaryButtonPreview() {
AppTheme { PrimaryButton(text = "Continue", onClick = {}) }
}The Threshold Decides Whether the Suite Is Worth Running
imageDifferenceThreshold in 0.0.1-alpha06, and from alpha10 the update task "will only update images that have differences greater than a specified threshold", so re-recording no longer churns every golden by a sub-pixel.
The better fix is to make a tiny threshold safe by removing the sources of noise, and Google names the main one: "To use a pixel-perfect screenshot comparator, you must make sure that your tests take screenshots in the same conditions. To do so, you can use your Continuous Integration (CI) system or employ a cloud service." In practice:
- Record where you verify. Goldens recorded on a MacBook and verified on a Linux runner differ in font rasterisation. Generate references in CI, or in the same container CI uses.
- Pin the renderer. Paparazzi's changelog shows why: its 2.0.0 alphas moved LayoutLib versions and, from alpha04, require Java 21. A renderer upgrade is a golden-regeneration event; do it in its own pull request.
- Feed fixed data. No clocks, no network images, no random avatars. A preview that renders "3 minutes ago" is a flaky test with extra steps.
- Store goldens outside normal Git history. Paparazzi's README: "It is recommended you use Git LFS to store your snapshots." A few hundred PNGs re-recorded weekly will bloat a repository fast.Wiring It Into CI So Diffs Get Read
# .github/workflows/screenshots.yml
name: screenshots
on: pull_request
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
lfs: true # goldens live in Git LFS
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Verify screenshots
# test{SuiteName}{Target}{Variant}TestSuite
run: ./gradlew testScreenshotTestDefaultDebugTestSuite
- name: Upload diff report
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshot-report
path: '**/build/reports/tests/**'What to Screenshot, and What to Leave to Assertions
| Screenshot it | Assert it instead |
|---|---|
| Design-system components across light, dark and large font scale | Business rules and state transitions |
| Empty, loading and error states of key screens | Navigation and back-stack behaviour |
| Right-to-left and long-string locales | That a button is enabled or a field is valid |
| Adaptive layouts at compact, medium and expanded widths | Data mapping and formatting logic |
Key Takeaways
- 1Compose UI tests assert semantics; a collapsed margin, a broken dark theme or a truncated label passes them. Screenshot tests are the layer that sees pixels.
- 2Host-side tools render with Layoutlib (Compose Preview Screenshot Testing, Paparazzi) or Robolectric Native Graphics (Roborazzi); pick RNG when the state you need requires interaction.
- 3Google now recommends configuring Compose Preview Screenshot Testing through AGP test suites (AGP 9.5.0-alpha03, engine 0.0.1-alpha16); the standalone plugin is deprecated and the tool is still experimental.
- 4Thresholds cut both ways: too loose misses real regressions. Record and verify in the same CI environment so a tight threshold stays stable.
- 5Keep goldens in Git LFS, pin the renderer version, and feed previews fixed data.
- 6Upload the HTML diff report on failure and keep golden updates in the author’s pull request, where reviewers see the image next to the code.
Frequently Asked
Do Compose screenshot tests need an emulator?
No. Compose Preview Screenshot Testing and Paparazzi render on the JVM with Layoutlib, and Roborazzi renders with Robolectric Native Graphics. All three run as host-side tests.
Should I use the standalone Compose Preview Screenshot Testing plugin?
Google marks it deprecated from AGP 9.5.0-alpha03 and engine 0.0.1-alpha16 and recommends AGP test suites instead. Both paths are still experimental, so pin versions.
Why do my screenshot tests fail on CI but pass locally?
Rendering differs between machines, most often in fonts. Record and verify references in the same CI environment, pin the renderer version, and remove clocks and network images from previews.
Ready to architect your next Android app?
ANDROID-ARCHITECT generates production-ready Kotlin code, architecture blueprints, and CI/CD configurations from plain-language descriptions. Start building for free.