Set Up Snapshots

Set up snapshots for Android apps with the Sentry Android Gradle Plugin.

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.8.1 or higher is applied and configured.

Then enable snapshots in your build.gradle:

Copied
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:

VersionGradlecompileSdkJDKcompose-bom
2.0.0-alpha059.x+3621+> 2025.05.00
1.3.58.x or 9.x≤ 3517+≤ 2025.04.00

Then apply the plugin:

Copied
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:

Copied
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:

Copied
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:

Copied
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 snapshots upload:

Copied
sentry-cli snapshots upload ./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:

Copied
./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.

Copied
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
    }
  }
}
OptionDefaultDescription
themePaparazzi default (android:Theme.Material.NoActionBar.Fullscreen)Android theme resource used when rendering previews. Set this if your previews rely on a specific theme.
includePrivatePreviewstrueWhether @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:

Copied
sentry {
  snapshots {
    enabled = true
    previews {
      theme = "android:Theme.Translucent.NoTitleBar"
    }
  }
}

To ignore small pixel changes across all snapshots, set diffThreshold in the plugin configuration:

Copied
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:

Copied
dependencies {
  testImplementation("io.sentry:sentry-snapshots-runtime:6.18.0")
}

Then annotate any @Preview composable:

Copied
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.

When you use the Compose previews path, the plugin renders one image per @Preview and writes a JSON metadata file next to it. The annotations on the composable control everything about the snapshot: how it's rendered, how it's named and grouped in Sentry, and how it's compared against its baseline.

The table below lists all supported attributes across Google's @Preview annotation and Sentry's @SentrySnapshot annotation. The Metadata column shows how each attribute is recorded in the snapshot's JSON metadata. Tags are only written when the attribute differs from its default, and tag values are always serialized as strings, even for numeric and boolean attributes.

AttributeAnnotationDescriptionMetadata
name@PreviewDistinguishes multiple previews of the same composable — several @Preview annotations or a multipreview annotation like @PreviewLightDark. Appended to the display_name.preview_name tag
group@PreviewGroups related snapshots so they're reviewed as a set in Sentry. Also becomes part of the display_name.group field
device@PreviewDevice spec (for example id:pixel_7) that sets the screen size, density, and shape used for rendering.device tag
widthDp@PreviewFixed render width in dp. Overrides the width of the device.width_dp tag
heightDp@PreviewFixed render height in dp. Overrides the height of the device.height_dp tag
locale@PreviewLocale used for rendering. Defaults to en.locale tag
fontScale@PreviewFont scale multiplier applied when rendering.font_scale tag
apiLevel@PreviewAndroid API level to render against.api_level tag
uiMode@PreviewOnly the night mode bit is honored: UI_MODE_NIGHT_YES renders the preview in dark mode.ui_mode tag (dark or light)
showSystemUi@PreviewRenders the preview at full device size.show_system_ui tag
showBackground@PreviewDraws a background behind the preview — white unless backgroundColor is set.show_background tag
backgroundColor@PreviewARGB color of the background when showBackground is true.
wallpaper@PreviewIgnored. Dynamic color wallpapers aren't applied when rendering.
diffThreshold@SentrySnapshotMinimum share of changed pixels (0.0 to 1.0) required to report the snapshot as changed. Overrides the global threshold — see Set Diff Thresholds Per Preview.diff_threshold field

For example, this preview:

Copied
@Preview(name = "Dark", group = "Checkout")
@Composable
fun BillingPagePreview() {
  BillingPage()
}

produces the following metadata:

Copied
{
  "display_name": "BillingPagePreview.Dark_Checkout",
  "group": "Checkout",
  "tags": {
    "preview_name": "Dark"
  },
  "context": {
    "image_file_name": "com.example.billing.BillingScreenKt.BillingPagePreview.Dark_Checkout",
    "class_name": "com.example.billing.BillingScreenKt",
    "method_name": "BillingPagePreview"
  }
}
Was this helpful?
Help improve this content
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").