Skip to content

feat(snapshot): Add io.sentry.android.snapshot Gradle plugin#1112

Merged
runningcode merged 6 commits intomainfrom
no/snapshot-plugin
Mar 19, 2026
Merged

feat(snapshot): Add io.sentry.android.snapshot Gradle plugin#1112
runningcode merged 6 commits intomainfrom
no/snapshot-plugin

Conversation

@runningcode
Copy link
Contributor

@runningcode runningcode commented Mar 18, 2026

Summary

  • Adds a new io.sentry.android.snapshot Gradle plugin that generates Paparazzi screenshot tests for all Compose @Preview composables
  • Registers a generateSnapshotTests task that produces a parameterized JUnit 4 test using ComposablePreviewScanner for preview discovery
  • Configurable via sentrySnapshot extension (packageTrees, includePrivatePreviews)

Usage

plugins {
    id("com.android.application")
    id("app.cash.paparazzi")
    id("io.sentry.android.snapshot")
}

sentrySnapshot {
    // optional — defaults to android.namespace
    packageTrees.set(listOf("com.example.app"))
    includePrivatePreviews.set(true)
}

#skip-changelog

🤖 Generated with Claude Code

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


This PR will not appear in the changelog.


🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against a2c0d87

import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property

abstract class SentrySnapshotExtension(objects: ObjectFactory) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now im keeping this independent of the rest of the extensions so that this plugin is independent but we will eventually merge this with the rest of the sentry configuration.

@runningcode runningcode marked this pull request as ready for review March 18, 2026 09:26
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Generated code passes invalid parameter to HtmlReportWriter
    • Removed the invalid maxPercentDifference parameter from HtmlReportWriter constructor call, which only accepts runName, rootDirectory, and snapshotRootDirectory.
  • ✅ Fixed: Regex recompiled for every configured task
    • Extracted the regex pattern to a class member variable to compile it once and reuse across all task configurations.

Create PR

Or push these changes by commenting:

@cursor push 2cf75cdb03
Preview (2cf75cdb03)
diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt
--- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt
+++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt
@@ -209,7 +209,7 @@
 private class PreviewHtmlReportWriter(
     maxPercentDifference: Double,
 ) : SnapshotHandler {
-    private val delegate = HtmlReportWriter(maxPercentDifference = maxPercentDifference)
+    private val delegate = HtmlReportWriter()
 
     override fun newFrameHandler(
         snapshot: Snapshot,

diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/SentrySnapshotPlugin.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/SentrySnapshotPlugin.kt
--- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/SentrySnapshotPlugin.kt
+++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/SentrySnapshotPlugin.kt
@@ -6,6 +6,8 @@
 
 class SentrySnapshotPlugin : Plugin<Project> {
 
+  private val unitTestKotlinRegex = Regex("(compile|ksp).*UnitTestKotlin")
+
   override fun apply(project: Project) {
     val extension =
       project.extensions.create(
@@ -29,7 +31,7 @@
       android.sourceSets.getByName("test").kotlin.srcDir(generateTask.flatMap { it.outputDir })
 
       project.tasks.configureEach { task ->
-        if (task.name.matches(Regex("(compile|ksp).*UnitTestKotlin"))) {
+        if (task.name.matches(unitTestKotlinRegex)) {
           task.dependsOn(generateTask)
         }
       }

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

android.sourceSets.getByName("test").kotlin.srcDir(generateTask.flatMap { it.outputDir })

project.tasks.configureEach { task ->
if (task.name.matches(Regex("(compile|ksp).*UnitTestKotlin"))) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex recompiled for every configured task

Low Severity

A new Regex object is compiled inside the configureEach lambda, meaning a fresh regex is compiled for every task that gets realized in the project. Extracting this to a val outside the lambda avoids redundant regex compilations across potentially hundreds of tasks.

Fix in Cursor Fix in Web

runningcode and others added 4 commits March 19, 2026 10:54
Add a new Gradle plugin that generates Paparazzi screenshot tests for
all Compose @Preview composables. When applied to an app module
alongside Paparazzi, it registers a `generateSnapshotTests` task that
produces a parameterized JUnit 4 test class using ComposablePreviewScanner
to discover previews at test runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace PreviewSnapshotVerifier and PreviewHtmlReportWriter with a
single TestNameOverrideHandler that wraps any SnapshotHandler delegate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
android.namespace is always non-null, so packageTrees can never be
empty after the fallback. Remove the dead scanAllPackages branch and
fail the build if Paparazzi is not applied instead of just warning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@runningcode runningcode merged commit be57112 into main Mar 19, 2026
20 of 21 checks passed
@runningcode runningcode deleted the no/snapshot-plugin branch March 19, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants