Skip to content

Commit f48172d

Browse files
romtsnclaude
andcommitted
feat(replay): fail fast on swallowed Compose masking errors in CI
Add an internal SentryReplayDebug.failFast switch (gated on the io.sentry.replay.compose.fail-fast system property) that re-throws the exceptions ComposeViewHierarchyNode normally swallows in fromComposeNode and fromView. Enabled in the sentry-samples-android app so our release/obfuscated builds running on real devices in CI crash instead of silently degrading masking. Defaults off, so customers are unaffected. Also guard the on-device ReplayTest against GitHub-hosted emulators, which can't capture screenshots reliably. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b8d1c8d commit f48172d

4 files changed

Lines changed: 55 additions & 0 deletions

File tree

sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/ReplayTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,23 @@ import kotlin.test.Test
88
import leakcanary.LeakAssertions
99
import leakcanary.LeakCanary
1010
import org.awaitility.kotlin.await
11+
import org.hamcrest.CoreMatchers.`is`
12+
import org.junit.Assume.assumeThat
13+
import org.junit.Before
1114
import shark.AndroidReferenceMatchers
1215
import shark.IgnoredReferenceMatcher
1316
import shark.ReferencePattern
1417

1518
class ReplayTest : BaseUiTest() {
19+
@Before
20+
fun setup() {
21+
// we can't run on GH actions emulator, because they don't allow capturing screenshots properly
22+
@Suppress("KotlinConstantConditions")
23+
assumeThat(BuildConfig.ENVIRONMENT != "github", `is`(true))
24+
// crash on swallowed Compose masking errors (e.g. broken obfuscated internals) so regressions
25+
// fail this on-device test instead of silently under-masking (see SentryReplayDebug)
26+
System.setProperty("io.sentry.replay.compose.fail-fast", "true")
27+
}
1628

1729
@Test
1830
fun composeReplayDoesNotLeak() {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.sentry.android.replay.util
2+
3+
/**
4+
* Internal, undocumented escape hatch used to make Session Replay fail fast instead of silently
5+
* degrading masking when an exception is swallowed (e.g. unsupported/obfuscated Compose internals).
6+
*
7+
* It is intended to be enabled only in our own sample/UI-test apps that run on real devices in CI
8+
* (which are release/obfuscated builds, so [io.sentry.android.replay.BuildConfig.DEBUG] can't be
9+
* used), so that regressions surface as crashes rather than under-masked replays. Customers should
10+
* never set this.
11+
*
12+
* Enable via:
13+
* ```
14+
* System.setProperty("io.sentry.replay.compose.fail-fast", "true")
15+
* ```
16+
*/
17+
internal object SentryReplayDebug {
18+
private const val FAIL_FAST_PROPERTY = "io.sentry.replay.compose.fail-fast"
19+
20+
/**
21+
* Read live (not cached) so it's only evaluated on the error path and unit tests can toggle it
22+
* between cases.
23+
*/
24+
val failFast: Boolean
25+
get() = "true".equals(System.getProperty(FAIL_FAST_PROPERTY), ignoreCase = true)
26+
}

sentry-android-replay/src/main/java/io/sentry/android/replay/viewhierarchy/ComposeViewHierarchyNode.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.sentry.SentryLevel
2222
import io.sentry.SentryMaskingOptions
2323
import io.sentry.android.replay.SentryReplayModifiers
2424
import io.sentry.android.replay.util.ComposeTextLayout
25+
import io.sentry.android.replay.util.SentryReplayDebug
2526
import io.sentry.android.replay.util.boundsInWindow
2627
import io.sentry.android.replay.util.findPainter
2728
import io.sentry.android.replay.util.findTextColor
@@ -147,6 +148,12 @@ internal object ComposeViewHierarchyNode {
147148
)
148149
}
149150

151+
// fail fast in our own sample/UI-test apps (see SentryReplayDebug), so regressions surface
152+
// as crashes instead of silently degrading masking
153+
if (SentryReplayDebug.failFast) {
154+
throw t
155+
}
156+
150157
// If we're unable to retrieve the semantics configuration
151158
// we should play safe and mask the whole node.
152159
return GenericViewHierarchyNode(
@@ -291,6 +298,11 @@ internal object ComposeViewHierarchyNode {
291298
"""
292299
.trimIndent(),
293300
)
301+
// fail fast in our own sample/UI-test apps (see SentryReplayDebug), so regressions surface
302+
// as crashes instead of silently skipping the whole Compose subtree (i.e. not masking it)
303+
if (SentryReplayDebug.failFast) {
304+
throw e
305+
}
294306
return false
295307
}
296308

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/MyApplication.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ public class MyApplication extends Application {
99

1010
@Override
1111
public void onCreate() {
12+
// Make Session Replay fail fast instead of silently degrading masking when an exception is
13+
// swallowed (e.g. unsupported/obfuscated Compose internals). This way regressions surface as
14+
// crashes in our release/obfuscated builds that run on real devices in CI. Only meant for our
15+
// own sample/UI-test apps, customers should never set this.
16+
System.setProperty("io.sentry.replay.compose.fail-fast", "true");
1217
Sentry.startProfiler();
1318
strictMode();
1419
super.onCreate();

0 commit comments

Comments
 (0)