Skip to content

Commit 5c1d06d

Browse files
runningcodeclaude
andauthored
feat(snapshots): Add global diffThreshold setting (#1186)
* feat(snapshots): Add global diffThreshold setting The @SentrySnapshot annotation already supports per-snapshot diffThreshold overrides, but there was no way to set a default threshold for all snapshots globally. This adds a diffThreshold property to SnapshotsExtension so users can configure it in the DSL without annotating every composable. The per-snapshot @SentrySnapshot annotation still takes precedence when present. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(snapshots): Pass diffThreshold as CLI arg, not sidecar metadata Move the global diffThreshold from sidecar metadata to a --diff-threshold CLI argument on the upload task. Per-snapshot @SentrySnapshot annotation thresholds in sidecar metadata remain unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: Add diffThreshold to changelog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(snapshots): Remove redundant assertion in diffThreshold test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 360b199 commit 5c1d06d

8 files changed

Lines changed: 104 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Add global `diffThreshold` option for snapshots ([#1186](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1186))
8+
59
### Fixes
610

711
- Change auto-install trigger for log4j2 from -api to -core dependency ([#1155](https://github.com/getsentry/sentry-android-gradle-plugin/pull/1155))

plugin-build/src/main/kotlin/io/sentry/android/gradle/AndroidComponentsConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ private fun ApplicationVariant.configureSnapshotsTasks(
509509
val generateTask =
510510
GenerateSnapshotTestsTask.register(
511511
project,
512-
extension.snapshots.previews,
512+
extension.snapshots,
513513
android,
514514
this@configureSnapshotsTasks,
515515
paparazziMajorVersion,

plugin-build/src/main/kotlin/io/sentry/android/gradle/extensions/SnapshotsExtension.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ open class SnapshotsExtension @Inject constructor(objects: ObjectFactory) {
1111

1212
val enabled: Property<Boolean> = objects.property(Boolean::class.java).convention(false)
1313

14+
val diffThreshold: Property<Double> = objects.property(Double::class.java).convention(0.0)
15+
1416
val previews: SnapshotPreviewsExtension =
1517
objects.newInstance(SnapshotPreviewsExtension::class.java)
1618

plugin-build/src/main/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTask.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.sentry.android.gradle.snapshot
33
import com.android.build.api.variant.ApplicationVariant
44
import com.android.build.gradle.BaseExtension
55
import io.sentry.android.gradle.SentryTasksProvider.capitalized
6-
import io.sentry.android.gradle.extensions.SnapshotPreviewsExtension
6+
import io.sentry.android.gradle.extensions.SnapshotsExtension
77
import java.io.File
88
import org.gradle.api.DefaultTask
99
import org.gradle.api.Project
@@ -32,6 +32,8 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() {
3232

3333
@get:Input @get:Optional abstract val theme: Property<String>
3434

35+
@get:Input abstract val diffThreshold: Property<Double>
36+
3537
@get:Input abstract val paparazziMajorVersion: Property<Int>
3638

3739
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@@ -51,6 +53,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() {
5153
includePrivatePreviews = includePrivatePreviews.get(),
5254
packageTrees = packageTrees.get(),
5355
theme = theme.orNull,
56+
diffThreshold = diffThreshold.get(),
5457
paparazziMajorVersion = paparazziMajorVersion.get(),
5558
)
5659
File(packageDir, "$CLASS_NAME.kt").writeText(content)
@@ -63,7 +66,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() {
6366

6467
fun register(
6568
project: Project,
66-
extension: SnapshotPreviewsExtension,
69+
extension: SnapshotsExtension,
6770
android: BaseExtension,
6871
variant: ApplicationVariant,
6972
paparazziMajorVersion: Provider<Int>,
@@ -72,12 +75,13 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() {
7275
"sentryGenerateSnapshotsTests${variant.name.capitalized}",
7376
GenerateSnapshotTestsTask::class.java,
7477
) { task ->
75-
task.includePrivatePreviews.set(extension.includePrivatePreviews)
76-
task.theme.set(extension.theme)
78+
task.includePrivatePreviews.set(extension.previews.includePrivatePreviews)
79+
task.theme.set(extension.previews.theme)
80+
task.diffThreshold.set(extension.diffThreshold)
7781
task.paparazziMajorVersion.value(paparazziMajorVersion)
7882
// Fall back to the Android namespace when the user doesn't configure packageTrees
7983
task.packageTrees.set(
80-
extension.packageTrees.map { packages ->
84+
extension.previews.packageTrees.map { packages ->
8185
packages.ifEmpty { listOf(android.namespace!!) }
8286
}
8387
)
@@ -92,6 +96,7 @@ abstract class GenerateSnapshotTestsTask : DefaultTask() {
9296
includePrivatePreviews: Boolean,
9397
packageTrees: List<String>,
9498
theme: String? = null,
99+
diffThreshold: Double = 0.0,
95100
paparazziMajorVersion: Int = 2,
96101
): String {
97102
val includePrivateExpr =
@@ -232,7 +237,7 @@ private object PaparazziPreviewRule {
232237
true -> env
233238
false -> env.copy(compileSdkVersion = previewInfo.apiLevel)
234239
}
235-
val tolerance = 0.0
240+
val tolerance = $diffThreshold
236241
return Paparazzi(
237242
environment = environment,
238243
deviceConfig = DeviceConfigBuilder.build(preview.previewInfo),

plugin-build/src/main/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTask.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ abstract class SentryUploadSnapshotsTask : SentryCliExecTask() {
3737
snapshotsPath.set(project.file(path))
3838
}
3939

40+
@get:Input @get:Optional abstract val diffThreshold: Property<Double>
41+
4042
@get:Input @get:Optional abstract val vcsHeadSha: Property<String>
4143
@get:Input @get:Optional abstract val vcsBaseSha: Property<String>
4244
@get:Input @get:Optional abstract val vcsProvider: Property<String>
@@ -52,6 +54,10 @@ abstract class SentryUploadSnapshotsTask : SentryCliExecTask() {
5254
args.add("--app-id")
5355
args.add(appId.get())
5456

57+
diffThreshold.orNull?.let {
58+
if (it != 0.0) args.addAll(listOf("--diff-threshold", it.toString()))
59+
}
60+
5561
vcsHeadSha.orNull?.let { args.addAll(listOf("--head-sha", it)) }
5662
vcsBaseSha.orNull?.let { args.addAll(listOf("--base-sha", it)) }
5763
vcsProvider.orNull?.let { args.addAll(listOf("--vcs-provider", it)) }
@@ -93,6 +99,7 @@ abstract class SentryUploadSnapshotsTask : SentryCliExecTask() {
9399
task.sentryAuthToken.set(extension.authToken)
94100
task.sentryUrl.set(extension.url)
95101
task.appId.set(applicationId)
102+
task.diffThreshold.set(extension.snapshots.diffThreshold)
96103
task.vcsHeadSha.set(extension.vcsInfo.headSha)
97104
task.vcsBaseSha.set(extension.vcsInfo.baseSha)
98105
task.vcsProvider.set(extension.vcsInfo.vcsProvider)

plugin-build/src/test/kotlin/io/sentry/android/gradle/extensions/SnapshotsExtensionTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.android.gradle.extensions
22

3+
import kotlin.test.assertEquals
34
import kotlin.test.assertFalse
45
import kotlin.test.assertNull
56
import kotlin.test.assertTrue
@@ -16,6 +17,24 @@ class SnapshotsExtensionTest {
1617
assertFalse(extension.enabled.get())
1718
}
1819

20+
@Test
21+
fun `diffThreshold is zero by default`() {
22+
val project = ProjectBuilder.builder().build()
23+
val extension = project.objects.newInstance(SnapshotsExtension::class.java)
24+
25+
assertEquals(0.0, extension.diffThreshold.get())
26+
}
27+
28+
@Test
29+
fun `diffThreshold can be configured`() {
30+
val project = ProjectBuilder.builder().build()
31+
val extension = project.objects.newInstance(SnapshotsExtension::class.java)
32+
33+
extension.diffThreshold.set(0.05)
34+
35+
assertEquals(0.05, extension.diffThreshold.get())
36+
}
37+
1938
@Test
2039
fun `previews generateTests is true by default`() {
2140
val project = ProjectBuilder.builder().build()

plugin-build/src/test/kotlin/io/sentry/android/gradle/snapshot/GenerateSnapshotTestsTaskTest.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,28 @@ class GenerateSnapshotTestsTaskTest {
225225
assertFalse(content.contains("HtmlReportWriter()"))
226226
}
227227

228+
@Test
229+
fun `generated file uses default tolerance of zero`() {
230+
val content = generateAndRead(packageTrees = listOf("com.example"))
231+
232+
assertTrue(content.contains("val tolerance = 0.0"))
233+
}
234+
235+
@Test
236+
fun `generated file uses configured diffThreshold as tolerance`() {
237+
val content = generateAndRead(packageTrees = listOf("com.example"), diffThreshold = 0.05)
238+
239+
assertTrue(content.contains("val tolerance = 0.05"))
240+
}
241+
228242
private fun generateAndRead(
229243
packageTrees: List<String>,
230244
includePrivatePreviews: Boolean = false,
245+
diffThreshold: Double = 0.0,
231246
paparazziMajorVersion: Int = 2,
232247
): String {
233-
val task = createTask(packageTrees, includePrivatePreviews, paparazziMajorVersion)
248+
val task =
249+
createTask(packageTrees, includePrivatePreviews, diffThreshold, paparazziMajorVersion)
234250
task.generate()
235251
val file =
236252
File(task.outputDir.get().asFile, "io/sentry/snapshot/ComposablePreviewSnapshotTest.kt")
@@ -240,13 +256,15 @@ class GenerateSnapshotTestsTaskTest {
240256
private fun createTask(
241257
packageTrees: List<String>,
242258
includePrivatePreviews: Boolean = false,
259+
diffThreshold: Double = 0.0,
243260
paparazziMajorVersion: Int = 2,
244261
): GenerateSnapshotTestsTask {
245262
val project = ProjectBuilder.builder().build()
246263
return project.tasks
247264
.register("testGenerateSnapshotTests", GenerateSnapshotTestsTask::class.java) { task ->
248265
task.includePrivatePreviews.set(includePrivatePreviews)
249266
task.packageTrees.set(packageTrees)
267+
task.diffThreshold.set(diffThreshold)
250268
task.paparazziMajorVersion.set(paparazziMajorVersion)
251269
task.outputDir.set(tmpDir.newFolder("output"))
252270
}

plugin-build/src/test/kotlin/io/sentry/android/gradle/tasks/SentryUploadSnapshotsTaskTest.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,47 @@ class SentryUploadSnapshotsTaskTest {
156156
assertThat(args).containsAtLeast("--pr-number", "123").inOrder()
157157
}
158158

159+
@Test
160+
fun `diffThreshold is passed to CLI when non-zero`() {
161+
val task = createTestTask {
162+
it.cliExecutable.set("sentry-cli")
163+
it.appId.set("com.example")
164+
it.snapshotsPath.set(File("/path/to/snapshots"))
165+
it.diffThreshold.set(0.05)
166+
}
167+
168+
val args = task.computeCommandLineArgs()
169+
170+
assertThat(args).containsAtLeast("--diff-threshold", "0.05").inOrder()
171+
}
172+
173+
@Test
174+
fun `diffThreshold is omitted when zero`() {
175+
val task = createTestTask {
176+
it.cliExecutable.set("sentry-cli")
177+
it.appId.set("com.example")
178+
it.snapshotsPath.set(File("/path/to/snapshots"))
179+
it.diffThreshold.set(0.0)
180+
}
181+
182+
val args = task.computeCommandLineArgs()
183+
184+
assertThat(args).doesNotContain("--diff-threshold")
185+
}
186+
187+
@Test
188+
fun `diffThreshold is omitted when not set`() {
189+
val task = createTestTask {
190+
it.cliExecutable.set("sentry-cli")
191+
it.appId.set("com.example")
192+
it.snapshotsPath.set(File("/path/to/snapshots"))
193+
}
194+
195+
val args = task.computeCommandLineArgs()
196+
197+
assertThat(args).doesNotContain("--diff-threshold")
198+
}
199+
159200
@Test
160201
fun `vcs parameters are omitted when not set`() {
161202
val task = createTestTask {

0 commit comments

Comments
 (0)