Set Up Snapshots
Set up snapshots for Android apps with the Sentry Android Gradle Plugin.
This feature is available only if you're in the Early Adopter program. Features available to Early Adopters are still in-progress and may have bugs. We recognize the irony.
Set up Snapshots for your Android app with the Sentry Android Gradle Plugin. Generate snapshots locally or in your own CI using your preferred snapshot library, then upload the generated images to Sentry for image diffing, visual review, and GitHub status checks.
Ensure the Sentry Android Gradle Plugin version 6.6.0 or higher is applied and configured.
Then enable snapshots in your build.gradle:
sentry {
snapshots {
enabled = true
}
}
On Android you can either generate images from compose previews (recommended) or from an existing snapshot tool like Roborazzi or Paparazzi.
These methods are not mutually exclusive — you can generate and upload snapshots from your previews and any existing snapshot tests.
Paparazzi and ComposePreviewScanner automatically turns every @Preview composable in your project into a snapshot image, so you don't have to maintain explicit snapshot tests.
First, choose a Paparazzi version compatible with your project:
| Version | Gradle | compileSdk | JDK | compose-bom |
|---|---|---|---|---|
2.0.0-alpha04 | 9.1+ | 36 | 21+ | > 2025.05.00 |
1.3.5 | 8.x or 9.x | ≤ 35 | 17+ | ≤ 2025.04.00 |
Paparazzi 2.0.0-alpha04 has known compatibility issues with newer Gradle versions:
Gradle 9.2+: Test tasks fail because Paparazzi tries to configure HTML reports in a way Gradle no longer allows. Add this workaround to your module's
build.gradle.kts(cashapp/paparazzi#2111):Copiedtasks.withType<Test>().configureEach { reports.html.required = false }AGP 9.x: Paparazzi doesn't support the new Android DSL yet. Add this to your
gradle.properties:Copiedandroid.newDsl=false
Then apply the plugin:
plugins {
// existing plugins
id("app.cash.paparazzi") version "<paparazzi_version>"
}
Continue to Step 3.
Use this path if you already generate snapshots with another tool. The configuration depends on which tool you use.
If you already have Paparazzi configured, set generateTests = false inside the previews block so Sentry uses your existing tests instead of auto-generating preview-based ones:
sentry {
snapshots {
enabled = true
previews {
generateTests = false
}
}
}
Continue to Step 3.
If you already have Roborazzi configured, wire the Sentry upload task to the output of your Roborazzi record task:
afterEvaluate {
tasks.named<SentryUploadSnapshotsTask>("sentryUploadSnapshotsDebug") {
dependsOn(tasks.named("recordRoborazziDebug"))
snapshotsPath.set(
project.extensions.getByType<RoborazziExtension>().outputDir
)
}
}
Continue to Step 3.
The same pattern works with any snapshot tool. Set snapshotsPath to the directory your tool writes images to, and add a dependsOn for the task that generates them:
afterEvaluate {
tasks.named<SentryUploadSnapshotsTask>("sentryUploadSnapshotsDebug") {
dependsOn(tasks.named("yourSnapshotTask"))
snapshotsPath.set(layout.projectDirectory.dir("path/to/snapshots"))
}
}
Alternatively, you can skip the Gradle plugin entirely and upload with sentry-cli build snapshots:
sentry-cli build snapshots ./build/paparazzi/snapshots \
--app-id android-app
Use this path if your build pipeline doesn't use Gradle, or if you want to run the upload outside of a Gradle task. See Uploading Snapshots for the expected directory layout.
Verify your setup by running:
./gradlew sentryUploadSnapshotsDebug
Once the local upload succeeds, wire the same command into your CI. See Integrating Into CI for an example GitHub Actions workflow.
The options below apply when using the Compose Previews path with generateTests = true (the default).
The previews block inside snapshots exposes options that shape the generated Paparazzi tests.
sentry {
snapshots {
enabled = true
previews {
theme = "@style/Theme.MyApp" // optional
includePrivatePreviews = false // optional, defaults to true
packageTrees = listOf("com.example") // optional, defaults to Android namespace
}
}
}
| Option | Default | Description |
|---|---|---|
theme | Paparazzi default (android:Theme.Material.NoActionBar.Fullscreen) | Android theme resource used when rendering previews. Set this if your previews rely on a specific theme. |
includePrivatePreviews | true | Whether @Preview composables declared private are snapshotted. Set to false to exclude in-progress or internal-only previews. |
packageTrees | [] (falls back to Android namespace) | Package prefixes to scan for @Preview composables. When empty, the plugin uses the Android namespace from the build variant. |
If you don't set a theme, Paparazzi uses its own default (android:Theme.Material.NoActionBar.Fullscreen). If the background of your previews isn't important — for example, when you just want to compare component geometry — you can use a translucent platform theme such as android:Theme.Translucent.NoTitleBar:
sentry {
snapshots {
enabled = true
previews {
theme = "android:Theme.Translucent.NoTitleBar"
}
}
}
If theme references a style the renderer can't resolve (typo, wrong prefix, or a theme not on the classpath), neither Sentry nor Paparazzi raises an error or warning. Snapshots still generate, but with incorrect visuals. Double-check the theme string if rendered output looks unexpected.
To ignore small pixel changes across all snapshots, set diffThreshold in the plugin configuration:
sentry {
snapshots {
enabled = true
diffThreshold = 0.01 // ignore changes of 1% or less
}
}
The diffThreshold is a value between 0.0 and 1.0. Sentry reports a snapshot as changed only when the share of changed pixels exceeds this value. The default is 0.0 (report any difference).
To override the global threshold for specific previews, annotate them with @SentrySnapshot and set a diff threshold. First, add the runtime dependency:
dependencies {
testImplementation("io.sentry:sentry-snapshots-runtime:6.7.0")
}
Then annotate any @Preview composable:
import io.sentry.snapshots.runtime.SentrySnapshot
@SentrySnapshot(diffThreshold = 0.01f) // ignore changes of 1% or less
@Preview
@Composable
fun BillingPagePreview() {
BillingPage()
}
Per-preview values take precedence over the global diffThreshold. See Diff Thresholds for other ways to configure thresholds.
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").