Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/pre-merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # pin@v4
Expand All @@ -46,3 +46,7 @@ jobs:

- name: Build the Debug and Release variants
run: ./gradlew assemble

- name: Record and upload snapshots
if: env.SENTRY_AUTH_TOKEN != ''
run: ./gradlew :examples:android-instrumentation-sample:sentryUploadSnapshotsStagingDebug
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Snapshot upload runs redundantly on all three OS matrices

Medium Severity

The new "Record and upload snapshots" step runs on all three matrix OSes (ubuntu-latest, macos-latest, windows-latest) since it only guards on SENTRY_AUTH_TOKEN. Paparazzi snapshot rendering can produce platform-dependent output (e.g., font rendering differences), so uploading from all three OSes can result in non-deterministic snapshots overwriting each other in Sentry. This likely needs a runner.os condition (similar to the existing preMerge step) to restrict it to a single OS.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c0d0a21. Configure here.

Comment on lines +49 to +52
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The use of .first() to find the Paparazzi snapshots directory is unsafe and will throw an exception if the directory is not found in the test task's outputs.
Severity: MEDIUM

Suggested Fix

Replace the unsafe .first() call with .firstOrNull(). This allows for checking if the directory exists and throwing a more informative GradleException if it is not found, preventing unexpected build failures.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: .github/workflows/pre-merge.yaml#L49-L52

Potential issue: The code uses `.first { file -> file.name == "snapshots" }` to find a
snapshots directory within a test task's output files. This will throw a
`NoSuchElementException` if no file with that name is found, causing the build to fail.
Standard test tasks do not typically produce a "snapshots" directory in their output,
and while the Paparazzi library might configure this, it's an undocumented behavior.
This makes the code fragile and reliant on a specific, unconfirmed output structure of
an alpha version of a library.

4 changes: 4 additions & 0 deletions examples/android-instrumentation-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {
alias(libs.plugins.androidApplication) version BuildPluginsVersion.AGP
alias(libs.plugins.kotlinAndroid) version BuildPluginsVersion.KOTLIN
alias(libs.plugins.ksp)
alias(libs.plugins.paparazzi)
id("io.sentry.android.gradle")
id("io.sentry.kotlin.compiler.gradle")
}
Expand Down Expand Up @@ -86,6 +87,7 @@ dependencies {
implementation(libs.sample.androidx.composeFoundation)
implementation(libs.sample.androidx.composeFoundationLayout)
implementation(libs.sample.androidx.composeNavigation)
implementation(libs.sample.androidx.composeUiToolingPreview)

implementation(libs.sample.coroutines.core)
implementation(libs.sample.coroutines.android)
Expand Down Expand Up @@ -113,4 +115,6 @@ sentry {
telemetryDsn.set(CI.SENTRY_SDKS_DSN)

tracingInstrumentation { forceInstrumentDependencies.set(true) }

snapshots { enabled.set(true) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
Expand Down Expand Up @@ -62,6 +63,12 @@ class ComposeActivity : ComponentActivity() {
}
}

@Preview
@Composable
fun HomeTextPreview() {
Comment on lines +66 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Calling rememberNavController() inside @Preview composables will crash Paparazzi snapshot tests because a ViewModelStoreOwner is not available in the test environment.
Severity: HIGH

Suggested Fix

To prevent the crash, do not call rememberNavController() directly within @Preview functions. Instead, create and pass a mock NavController to the composable under test. For example: HomeText(navController = NavController(LocalContext.current), ...). Alternatively, you can wrap the preview composable with a CompositionLocalProvider to supply a fake ViewModelStoreOwner for the test environment.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location:
examples/android-instrumentation-sample/src/main/java/io/sentry/samples/instrumentation/ui/ComposeActivity.kt#L66-L68

Potential issue: The `@Preview` composables `HomeTextPreview` and `DetailsTextPreview`
directly invoke `rememberNavController()`. This function internally depends on
`LocalViewModelStoreOwner.current` to get a `ViewModelStoreOwner`. The Paparazzi
snapshot testing environment, which runs on a JVM, does not provide this context by
default. As a result, attempting to generate snapshots for these previews will cause a
runtime crash, likely an `IllegalStateException`. This crash will cause the
`recordPaparazziStagingDebug` Gradle task to fail, which in turn will block the CI
workflow step `sentryUploadSnapshotsStagingDebug` from completing.

Did we get this right? 👍 / 👎 to inform future reviews.

HomeText(rememberNavController(), RoundedCornerShape(50))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Preview composables may crash in Paparazzi rendering context

Medium Severity

HomeTextPreview and DetailsTextPreview call rememberNavController(), which internally uses checkNotNull(LocalViewModelStoreOwner.current). Paparazzi's rendering context may not provide LocalViewModelStoreOwner, causing an IllegalStateException at snapshot recording time. This is a known class of issues with navigation components in Paparazzi (cashapp/paparazzi#635). Simpler preview-safe composables that don't require NavController would avoid this risk.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 951e483. Configure here.


@Composable
fun HomeText(navController: NavController, pillShape: RoundedCornerShape) {
BasicText(
Expand All @@ -74,6 +81,12 @@ fun HomeText(navController: NavController, pillShape: RoundedCornerShape) {
)
}

@Preview
@Composable
fun DetailsTextPreview() {
DetailsText(rememberNavController(), RoundedCornerShape(50))
}

@Composable
fun DetailsText(navController: NavController, pillShape: RoundedCornerShape) {
BasicText(
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ buildConfig = { id = "com.github.gmazzo.buildconfig", version = "4.2.0" }
springBoot = { id = "org.springframework.boot", version = "3.4.1" }
springDependencyManagement = { id = "io.spring.dependency-management", version = "1.1.7" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version = "2.1.0" }
paparazzi = { id = "app.cash.paparazzi", version = "2.0.0-alpha04" }

[libraries]
junit = { group = "junit", name = "junit", version = "4.13.2" }
Expand Down Expand Up @@ -70,6 +71,7 @@ sample-androidx-composeNavigation = "androidx.navigation:navigation-compose:2.5.
sample-androidx-composeActivity = "androidx.activity:activity-compose:1.4.0"
sample-androidx-composeFoundation = "androidx.compose.foundation:foundation:1.5.4"
sample-androidx-composeFoundationLayout = "androidx.compose.foundation:foundation-layout:1.5.4"
sample-androidx-composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview:1.5.4"

sample-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "sampleCoroutines" }
sample-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "sampleCoroutines" }
Expand Down
Loading