From 09a4e08b95725eef617eee28c8983182099558ec Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 14 Aug 2025 11:42:46 +0200 Subject: [PATCH 1/8] Update Android benchmark configurations to include newer API levels and devices increased number of benchmark runs to increase accuracy increased passing threshold from 5% to 5.5% --- .../sentry-uitest-android-benchmark-lite.yml | 5 ++- .sauce/sentry-uitest-android-benchmark.yml | 41 ++++++------------- .sauce/sentry-uitest-android-ui.yml | 12 +++--- .../android/benchmark/SentryBenchmarkTest.kt | 5 ++- 4 files changed, 25 insertions(+), 38 deletions(-) diff --git a/.sauce/sentry-uitest-android-benchmark-lite.yml b/.sauce/sentry-uitest-android-benchmark-lite.yml index b9408053ccb..fec4a141def 100644 --- a/.sauce/sentry-uitest-android-benchmark-lite.yml +++ b/.sauce/sentry-uitest-android-benchmark-lite.yml @@ -18,12 +18,13 @@ espresso: suites: - - name: "Android 11 (api 30)" + - name: "Android 15 Benchmark lite (api 35)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - - id: Google_Pixel_3a_real # Google Pixel 3a - api 30 (11) + - name: ".*" + platformVersion: "15" artifacts: download: diff --git a/.sauce/sentry-uitest-android-benchmark.yml b/.sauce/sentry-uitest-android-benchmark.yml index 48737b5fa50..12995ea5e07 100644 --- a/.sauce/sentry-uitest-android-benchmark.yml +++ b/.sauce/sentry-uitest-android-benchmark.yml @@ -19,49 +19,34 @@ espresso: suites: # Devices are chosen so that there is a high-end and a low-end device for each api level - - name: "Android 12 (api 31)" + - name: "Android 15 (api 35)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - - id: Google_Pixel_6_Pro_real_us # Google Pixel 6 Pro - api 31 (12) - high end - - id: Google_Pixel_5_12_real_us # Google Pixel 5 - api 31 (12) - low end + - id: Google_Pixel_9_Pro_XL_15_real_sjc1 # Google Pixel 9 Pro XL - api 35 (15) - high end + - id: Samsung_Galaxy_S23_15_real_sjc1 # Samsung Galaxy S23 - api 35 (15) - mid end + - id: Google_Pixel_6a_15_real_sjc1 # Google Pixel 6a - api 35 (15) - low end - - name: "Android 11 (api 30)" + - name: "Android 14 (api 34)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - - id: Samsung_Galaxy_S10_Plus_11_real_us # Samsung Galaxy S10+ - api 30 (11) - high end - - id: Google_Pixel_4a_real_us # Google Pixel 4a - api 30 (11) - mid end - - id: Google_Pixel_3a_real # Google Pixel 3a - api 30 (11) - low end + - id: Google_Pixel_9_Pro_XL_real_sjc1 # Google Pixel 9 Pro XL - api 34 (14) - high end + - id: Samsung_Galaxy_A54_real_sjc1 # Samsung Galaxy A54 - api 34 (14) - low end - - name: "Android 10 (api 29)" + - name: "Android 13 (api 33)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - - id: Google_Pixel_3a_XL_real # Google Pixel 3a XL - api 29 (10) - - id: OnePlus_6T_real # OnePlus 6T - api 29 (10) + - id: Google_Pixel_7_Pro_real_us # Google Pixel 7 Pro - api 33 (13) - high end + - id: Samsung_Galaxy_A32_5G_real_sjc1 # Samsung Galaxy A32 5G - api 33 (13) - low end -# At the time of writing (July, 4, 2022), the market share per android version is: -# 12.0 = 17.54%, 11.0 = 31.65%, 10.0 = 21.92% -# Using these 3 versions we cover 71,11% of all devices out there. Currently, this is enough for benchmarking scope -# Leaving these devices here in case we change mind on them -# devices: -# - id: Samsung_Galaxy_S8_plus_real_us # Samsung Galaxy S8+ - api 28 (9) -# - id: LG_G8_ThinQ_real_us # LG G8 ThinQ - api 28 (9) -# - id: OnePlus_5_real_us # OnePlus 5 - api 27 (8.1.0) -# - id: LG_K30_real_us1 # LG K30 - api 27 (8.1.0) -# - id: HTC_10_real_us # HTC 10 - api 26 (8.0.0) -# - id: Samsung_A3_real # Samsung Galaxy A3 2017 - api 26 (8.0.0) -# - id: ZTE_Axon_7_real2_us # ZTE Axon 7 - api 25 (7.1.1) -# - id: Motorola_Moto_X_Play_real # Motorola Moto X Play - api 25 (7.1.1) -# - id: Samsung_note_5_real_us # Samsung Galaxy Note 5 - api 24 (7.0) -# - id: LG_K10_real # LG K10 - api 24 (7.0) -# - id: Samsung_Galaxy_S6_Edge_Plus_real # Samsung Galaxy S6 Edge+ - api 23 (6.0.1) -# - id: Samsung_Tab_E_real_us # Samsung Tab E - api 23 (6.0.1) -# - id: Amazon_Kindle_Fire_HD_8_real_us # Amazon Kindle Fire HD 8 - api 22 (5.1.1) +# At the time of writing (August, 13, 2025), the market share per android version is: +# 15.0 = 26.75%, 14.0 = 19.5%, 13 = 15.95% +# Using these 3 versions we cover 62.2% of all devices out there. Currently, this is enough for benchmarking scope artifacts: download: diff --git a/.sauce/sentry-uitest-android-ui.yml b/.sauce/sentry-uitest-android-ui.yml index 5cec16cf7c5..be371d0c314 100644 --- a/.sauce/sentry-uitest-android-ui.yml +++ b/.sauce/sentry-uitest-android-ui.yml @@ -18,29 +18,29 @@ espresso: testApp: ./sentry-android-integration-tests/sentry-uitest-android/build/outputs/apk/androidTest/release/sentry-uitest-android-release-androidTest.apk suites: - - name: "Android 14 Ui test (api 34)" + - name: "Android 15 Ui test (api 35)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - name: ".*" - platformVersion: "14" + platformVersion: "15" - - name: "Android 13 Ui test (api 33)" + - name: "Android 14 Ui test (api 34)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - name: ".*" - platformVersion: "13" + platformVersion: "14" - - name: "Android 11 Ui test (api 31)" + - name: "Android 13 Ui test (api 33)" testOptions: clearPackageData: true useTestOrchestrator: true devices: - name: ".*" - platformVersion: "11" + platformVersion: "13" # Controls what artifacts to fetch when the suite on Sauce Cloud has finished. artifacts: diff --git a/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt b/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt index dbed89029a3..f2066577c6e 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android-benchmark/src/androidTest/java/io/sentry/uitest/android/benchmark/SentryBenchmarkTest.kt @@ -101,6 +101,7 @@ class SentryBenchmarkTest : BaseBenchmarkTest() { benchmarkOperationProfiled, "ProfiledTransaction", refreshRate, + measuredIterations = 40, ) comparisonResults.printAllRuns("Profiling Benchmark") val comparisonResult = comparisonResults.getSummaryResult() @@ -108,8 +109,8 @@ class SentryBenchmarkTest : BaseBenchmarkTest() { // Currently we just want to assert the cpu overhead assertTrue( - comparisonResult.cpuTimeIncreasePercentage in 0F..5F, - "Expected ${comparisonResult.cpuTimeIncreasePercentage} to be in range 0 < x < 5", + comparisonResult.cpuTimeIncreasePercentage in 0F..5.5F, + "Expected ${comparisonResult.cpuTimeIncreasePercentage} to be in range 0 < x < 5.5", ) } From e1a93be5457c02213fcb2a7116b6e9e9a4f319b3 Mon Sep 17 00:00:00 2001 From: Stefano Date: Tue, 26 Aug 2025 09:59:03 +0200 Subject: [PATCH 2/8] Added SentryEnvelope.describeForTest() to BaseUiTest Added sent envelopes to assert exception description --- .../io/sentry/uitest/android/BaseUiTest.kt | 44 +++++++++++++++++++ .../io/sentry/uitest/android/SdkInitTests.kt | 10 +++++ .../uitest/android/mockservers/MockRelay.kt | 12 +++++ .../android/mockservers/RelayAsserter.kt | 42 ++---------------- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt index 2f9c7a8bf14..f76ec11db6c 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt @@ -8,10 +8,17 @@ import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.idling.CountingIdlingResource import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnitRunner +import io.sentry.JsonSerializer +import io.sentry.ProfilingTraceData import io.sentry.Sentry import io.sentry.Sentry.OptionsConfiguration +import io.sentry.SentryEnvelope +import io.sentry.SentryEvent +import io.sentry.SentryItemType +import io.sentry.SentryOptions import io.sentry.android.core.SentryAndroid import io.sentry.android.core.SentryAndroidOptions +import io.sentry.protocol.SentryTransaction import io.sentry.test.applyTestOptions import io.sentry.test.initForTest import io.sentry.uitest.android.mockservers.MockRelay @@ -126,3 +133,40 @@ fun initForTest( optionsConfiguration.configure(it) } } + +/** + * Function used to describe the content of the envelope to print in the logs. For debugging + * purposes only. + */ +internal fun SentryEnvelope.describeForTest(): String { + var descr = "" + items.forEach { item -> + when (item.header.type) { + SentryItemType.Event -> { + val deserialized = + JsonSerializer(SentryOptions()) + .deserialize(item.data.inputStream().reader(), SentryEvent::class.java)!! + descr += + "Event (${deserialized.eventId}) - message: ${deserialized.message!!.formatted} -- " + } + SentryItemType.Transaction -> { + val deserialized = + JsonSerializer(SentryOptions()) + .deserialize(item.data.inputStream().reader(), SentryTransaction::class.java)!! + descr += + "Transaction (${deserialized.eventId}) - transaction: ${deserialized.transaction} - spans: ${deserialized.spans.joinToString { "${it.op} ${it.description}" }} -- " + } + SentryItemType.Profile -> { + val deserialized = + JsonSerializer(SentryOptions()) + .deserialize(item.data.inputStream().reader(), ProfilingTraceData::class.java)!! + descr += + "Profile (${deserialized.profileId}) - transactionName: ${deserialized.transactionName} -- " + } + else -> { + descr += "${item.header.type} -- " + } + } + } + return "*** Envelope: $descr ***" +} diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt index 38a3d6a0d7b..f7d5b375381 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt @@ -97,6 +97,14 @@ class SdkInitTests : BaseUiTest() { } } + @Test + fun doubleInitDoesNotWait2() { + repeat(150) { + println("Run number $it") + doubleInitDoesNotWait() + } + } + @Test fun doubleInitDoesNotWait() { relayIdlingResource.increment() @@ -145,6 +153,8 @@ class SdkInitTests : BaseUiTest() { } assertNoOtherEnvelopes() } + context.cacheDir.deleteRecursively() + context.cacheDir.mkdirs() } @Test diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt index 519b5c973b1..14c486c55eb 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt @@ -1,6 +1,7 @@ package io.sentry.uitest.android.mockservers import androidx.test.espresso.idling.CountingIdlingResource +import io.sentry.uitest.android.describeForTest import io.sentry.uitest.android.waitUntilIdle import kotlin.test.assertNotNull import okhttp3.mockwebserver.Dispatcher @@ -119,6 +120,17 @@ class MockRelay( fun assert(assertion: RelayAsserter.() -> Unit) { if (waitForRequests) { waitUntilIdle() + try { + waitUntilIdle() + } catch (e: Exception) { + if (unassertedEnvelopes.isNotEmpty()) { + throw AssertionError( + "There was a total of ${unassertedEnvelopes.size} envelopes: " + + unassertedEnvelopes.joinToString { it.envelope!!.describeForTest() }, + e + ) + } + } } assertion(RelayAsserter(unassertedEnvelopes)) } diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt index 6a7db0d8d2f..ed6826df2a8 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt @@ -9,6 +9,7 @@ import io.sentry.SentryEvent import io.sentry.SentryItemType import io.sentry.SentryOptions import io.sentry.protocol.SentryTransaction +import io.sentry.uitest.android.describeForTest import java.io.IOException import java.util.zip.GZIPInputStream import okhttp3.mockwebserver.MockResponse @@ -42,7 +43,7 @@ class RelayAsserter(private val unassertedEnvelopes: MutableList) throw AssertionError( "No envelope request found with specified filter.\n" + "There was a total of ${originalUnassertedEnvelopes.size} envelopes: " + - originalUnassertedEnvelopes.joinToString { describeEnvelope(it.envelope!!) } + originalUnassertedEnvelopes.joinToString { it.envelope!!.describeForTest() } ) } return unassertedEnvelopes.removeAt(relayResponseIndex) @@ -53,48 +54,11 @@ class RelayAsserter(private val unassertedEnvelopes: MutableList) if (unassertedEnvelopes.isNotEmpty()) { throw AssertionError( "There was a total of ${originalUnassertedEnvelopes.size} envelopes: " + - originalUnassertedEnvelopes.joinToString { describeEnvelope(it.envelope!!) } + originalUnassertedEnvelopes.joinToString { it.envelope!!.describeForTest() } ) } } - /** - * Function used to describe the content of the envelope to print in the logs. For debugging - * purposes only. - */ - private fun describeEnvelope(envelope: SentryEnvelope): String { - var descr = "" - envelope.items.forEach { item -> - when (item.header.type) { - SentryItemType.Event -> { - val deserialized = - JsonSerializer(SentryOptions()) - .deserialize(item.data.inputStream().reader(), SentryEvent::class.java)!! - descr += - "Event (${deserialized.eventId}) - message: ${deserialized.message!!.formatted} -- " - } - SentryItemType.Transaction -> { - val deserialized = - JsonSerializer(SentryOptions()) - .deserialize(item.data.inputStream().reader(), SentryTransaction::class.java)!! - descr += - "Transaction (${deserialized.eventId}) - transaction: ${deserialized.transaction} - spans: ${deserialized.spans.joinToString { "${it.op} ${it.description}" }} -- " - } - SentryItemType.Profile -> { - val deserialized = - JsonSerializer(SentryOptions()) - .deserialize(item.data.inputStream().reader(), ProfilingTraceData::class.java)!! - descr += - "Profile (${deserialized.profileId}) - transactionName: ${deserialized.transactionName} -- " - } - else -> { - descr += "${item.header.type} -- " - } - } - } - return "*** Envelope: $descr ***" - } - data class RelayResponse(val request: RecordedRequest, val response: MockResponse) { /** Request parsed as envelope. */ From 4dee4a223b6f7b975c3feedb23654b2d639f51a9 Mon Sep 17 00:00:00 2001 From: Stefano Date: Tue, 2 Sep 2025 18:05:31 +0200 Subject: [PATCH 3/8] run command to disable system animations removed frame timestamp check, as useless in that case upgraded androidx test dependency fixed doubleInitWithSameOption UI test by waiting for the last envelope moved lazy field load after old executor service shutdown removed saucelabs test devices (now run UI tests on 1 device only) wrapped SystemEventsBreadcrumbsIntegration.unregisterReceiver in try catch fixed UserFeedbackUiTest.userFeedbackSendEnvelope UI test by checking the replay was sent, too --- .sauce/sentry-uitest-android-ui.yml | 16 -------- gradle/libs.versions.toml | 2 +- .../SystemEventsBreadcrumbsIntegration.java | 39 +++++++++++-------- .../io/sentry/uitest/android/BaseUiTest.kt | 15 ++++++- .../io/sentry/uitest/android/EnvelopeTests.kt | 19 ++++----- .../io/sentry/uitest/android/SdkInitTests.kt | 25 +++++++----- .../uitest/android/UserFeedbackUiTest.kt | 4 ++ .../android/mockservers/RelayAsserter.kt | 6 --- sentry/src/main/java/io/sentry/Sentry.java | 27 +++++++------ 9 files changed, 78 insertions(+), 75 deletions(-) diff --git a/.sauce/sentry-uitest-android-ui.yml b/.sauce/sentry-uitest-android-ui.yml index be371d0c314..8d84f865c95 100644 --- a/.sauce/sentry-uitest-android-ui.yml +++ b/.sauce/sentry-uitest-android-ui.yml @@ -26,22 +26,6 @@ suites: - name: ".*" platformVersion: "15" - - name: "Android 14 Ui test (api 34)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - name: ".*" - platformVersion: "14" - - - name: "Android 13 Ui test (api 33)" - testOptions: - clearPackageData: true - useTestOrchestrator: true - devices: - - name: ".*" - platformVersion: "13" - # Controls what artifacts to fetch when the suite on Sauce Cloud has finished. artifacts: download: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06fa80f9436..4ab66aeaebd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ apollo = "2.5.9" androidxLifecycle = "2.2.0" androidxNavigation = "2.4.2" -androidxTestCore = "1.6.1" +androidxTestCore = "1.7.0" androidxCompose = "1.6.3" composeCompiler = "1.5.14" coroutines = "1.6.1" diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java index 561f507c5a1..85d4803bf7b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SystemEventsBreadcrumbsIntegration.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -181,26 +182,30 @@ private void registerReceiver( } } - private void unregisterReceiver() { + @SuppressWarnings("Convert2MethodRef") // older AGP versions do not support method references + private void scheduleUnregisterReceiver() { if (options == null) { return; } - options - .getExecutorService() - .submit( - () -> { - final @Nullable SystemEventsBroadcastReceiver receiverRef; - try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) { - isStopped = true; - receiverRef = receiver; - receiver = null; - } + try { + options.getExecutorService().submit(() -> unregisterReceiver()); + } catch (RejectedExecutionException e) { + unregisterReceiver(); + } + } - if (receiverRef != null) { - context.unregisterReceiver(receiverRef); - } - }); + private void unregisterReceiver() { + final @Nullable SystemEventsBroadcastReceiver receiverRef; + try (final @NotNull ISentryLifecycleToken ignored = receiverLock.acquire()) { + isStopped = true; + receiverRef = receiver; + receiver = null; + } + + if (receiverRef != null) { + context.unregisterReceiver(receiverRef); + } } @Override @@ -215,7 +220,7 @@ public void close() throws IOException { } AppState.getInstance().removeAppStateListener(this); - unregisterReceiver(); + scheduleUnregisterReceiver(); if (options != null) { options.getLogger().log(SentryLevel.DEBUG, "SystemEventsBreadcrumbsIntegration removed."); @@ -264,7 +269,7 @@ public void onForeground() { @Override public void onBackground() { - unregisterReceiver(); + scheduleUnregisterReceiver(); } final class SystemEventsBroadcastReceiver extends BroadcastReceiver { diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt index f76ec11db6c..03667b537c1 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/BaseUiTest.kt @@ -50,9 +50,9 @@ abstract class BaseUiTest { /** Mock relay server that receives all envelopes sent during the test. */ protected val relay = MockRelay(false, relayIdlingResource) - private fun disableDontKeepActivities() { + private fun runCommand(cmd: String) { val automation = InstrumentationRegistry.getInstrumentation().uiAutomation - val pfd = automation.executeShellCommand("settings put global always_finish_activities 0") + val pfd = automation.executeShellCommand(cmd) try { FileInputStream(pfd.fileDescriptor).readBytes() } catch (e: Throwable) { @@ -61,6 +61,16 @@ abstract class BaseUiTest { pfd.close() } + private fun disableDontKeepActivities() { + runCommand("settings put global always_finish_activities 0") + } + + fun disableSystemAnimations() { + runCommand("settings put global window_animation_scale 0") + runCommand("settings put global transition_animation_scale 0") + runCommand("settings put global animator_duration_scale 0") + } + @BeforeTest fun baseSetUp() { runner = InstrumentationRegistry.getInstrumentation() as AndroidJUnitRunner @@ -70,6 +80,7 @@ abstract class BaseUiTest { relay.start() mockDsn = relay.createMockDsn() disableDontKeepActivities() + disableSystemAnimations() } @AfterTest diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt index 87f2fa39bb5..30cdfefde25 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/EnvelopeTests.kt @@ -56,7 +56,6 @@ class EnvelopeTests : BaseUiTest() { relayIdlingResource.increment() IdlingRegistry.getInstance().register(ProfilingSampleActivity.scrollingIdlingResource) - Thread.sleep(1000) val transaction = Sentry.startTransaction("profiledTransaction", "test1") val sampleScenario = launchActivity() swipeList(1) @@ -171,15 +170,17 @@ class EnvelopeTests : BaseUiTest() { values.last().relativeStartNs.toLong() <= maxTimestampAllowed, "Last measurement value for '$name' is outside bounds (was: ${values.last().relativeStartNs.toLong()}ns, max: ${maxTimestampAllowed}ns", ) - } - // Timestamps of measurements should differ at least 10 milliseconds from each other - (1 until values.size).forEach { i -> - assertTrue( - values[i].relativeStartNs.toLong() >= - values[i - 1].relativeStartNs.toLong() + TimeUnit.MILLISECONDS.toNanos(10), - "Measurement value timestamp for '$name' does not differ at least 10ms", - ) + // Timestamps of measurements should differ at least 10 milliseconds from each other + (1 until values.size).forEach { i -> + val measurementTimestampDiff = + values[i].relativeStartNs.toLong() - values[i - 1].relativeStartNs.toLong() + + assertTrue( + measurementTimestampDiff >= TimeUnit.MILLISECONDS.toNanos(10), + "Measurement value timestamp for '$name' should differ at least 10ms, but was $measurementTimestampDiff", + ) + } } } diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt index f7d5b375381..35fa5b2bd80 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt @@ -56,6 +56,7 @@ class SdkInitTests : BaseUiTest() { } val transaction = Sentry.startTransaction("e2etests", "testInit") val sampleScenario = launchActivity() + initSentry(true) { it.tracesSampleRate = 1.0 it.profilesSampleRate = 1.0 @@ -63,6 +64,8 @@ class SdkInitTests : BaseUiTest() { it.executorService = options.executorService it.isDebug = true } + + relayIdlingResource.increment() relayIdlingResource.increment() relayIdlingResource.increment() transaction.finish() @@ -79,9 +82,19 @@ class SdkInitTests : BaseUiTest() { it.assertNoOtherItems() assertEquals("e2etests", transactionItem.transaction) } - } - relay.assert { + findEnvelope { + assertEnvelopeTransaction(it.items.toList(), AndroidLogger()).transaction == + "EmptyActivity" + } + .assert { + val transactionItem: SentryTransaction = it.assertTransaction() + // Transaction-based Profiling is already in e2etests transaction, so it won't run again + // here + it.assertNoOtherItems() + assertEquals("EmptyActivity", transactionItem.transaction) + } + findEnvelope { assertEnvelopeTransaction(it.items.toList(), AndroidLogger()).transaction == "e2etests2" } @@ -97,14 +110,6 @@ class SdkInitTests : BaseUiTest() { } } - @Test - fun doubleInitDoesNotWait2() { - repeat(150) { - println("Run number $it") - doubleInitDoesNotWait() - } - } - @Test fun doubleInitDoesNotWait() { relayIdlingResource.increment() diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt index ac434535c13..8f574ffc92c 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Sentry import io.sentry.SentryEvent import io.sentry.SentryFeedbackOptions.SentryFeedbackCallback +import io.sentry.SentryItemType import io.sentry.SentryOptions import io.sentry.android.core.AndroidLogger import io.sentry.android.core.R @@ -471,6 +472,7 @@ class UserFeedbackUiTest : BaseUiTest() { // because it would block the espresso interactions (button click) it.feedbackOptions.onSubmitSuccess = SentryFeedbackCallback { relayIdlingResource.increment() + relayIdlingResource.increment() } // Let's capture a replay, so we can check the replayId in the feedback if (enableReplay) { @@ -511,6 +513,8 @@ class UserFeedbackUiTest : BaseUiTest() { ) } } + findEnvelope { it.items.first().header.type == SentryItemType.ReplayVideo } + assertNoOtherEnvelopes() } } diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt index ed6826df2a8..a449be66ae4 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/RelayAsserter.kt @@ -1,14 +1,8 @@ package io.sentry.uitest.android.mockservers import io.sentry.EnvelopeReader -import io.sentry.JsonSerializer -import io.sentry.ProfilingTraceData import io.sentry.Sentry import io.sentry.SentryEnvelope -import io.sentry.SentryEvent -import io.sentry.SentryItemType -import io.sentry.SentryOptions -import io.sentry.protocol.SentryTransaction import io.sentry.uitest.android.describeForTest import java.io.IOException import java.util.zip.GZIPInputStream diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index bf96c5f3232..d5a9d5efe6b 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -311,18 +311,6 @@ private static void init(final @NotNull SentryOptions options, final boolean glo "Sentry has been already initialized. Previous configuration will be overwritten."); } - // load lazy fields of the options in a separate thread - try { - options.getExecutorService().submit(() -> options.loadLazyFields()); - } catch (RejectedExecutionException e) { - options - .getLogger() - .log( - SentryLevel.DEBUG, - "Failed to call the executor. Lazy fields will not be loaded. Did you call Sentry.close()?", - e); - } - final IScopes scopes = getCurrentScopes(); scopes.close(true); @@ -340,13 +328,24 @@ private static void init(final @NotNull SentryOptions options, final boolean glo globalScope.bindClient(new SentryClient(options)); // If the executorService passed in the init is the same that was previously closed, we have - // to - // set a new one + // to set a new one if (options.getExecutorService().isClosed()) { options.setExecutorService(new SentryExecutorService(options)); options.getExecutorService().prewarm(); } + // load lazy fields of the options in a separate thread + try { + options.getExecutorService().submit(() -> options.loadLazyFields()); + } catch (RejectedExecutionException e) { + options + .getLogger() + .log( + SentryLevel.DEBUG, + "Failed to call the executor. Lazy fields will not be loaded. Did you call Sentry.close()?", + e); + } + movePreviousSession(options); // when integrations are registered on Scopes ctor and async integrations are fired, // it might and actually happened that integrations called captureSomething From 21f4db8ea5f9bad211d09b101c263aba73abcc11 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 2 Sep 2025 16:10:17 +0000 Subject: [PATCH 4/8] Format code --- .../java/io/sentry/uitest/android/mockservers/MockRelay.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt index 14c486c55eb..7bb45138602 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt @@ -127,7 +127,7 @@ class MockRelay( throw AssertionError( "There was a total of ${unassertedEnvelopes.size} envelopes: " + unassertedEnvelopes.joinToString { it.envelope!!.describeForTest() }, - e + e, ) } } From d05b092c595a48784c67674752e977b7f35c2a52 Mon Sep 17 00:00:00 2001 From: Stefano Date: Tue, 2 Sep 2025 18:12:52 +0200 Subject: [PATCH 5/8] last cleanup --- .../androidTest/java/io/sentry/uitest/android/SdkInitTests.kt | 2 -- .../java/io/sentry/uitest/android/mockservers/MockRelay.kt | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt index 35fa5b2bd80..07c9cd391a3 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/SdkInitTests.kt @@ -158,8 +158,6 @@ class SdkInitTests : BaseUiTest() { } assertNoOtherEnvelopes() } - context.cacheDir.deleteRecursively() - context.cacheDir.mkdirs() } @Test diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt index 14c486c55eb..da889625a18 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/mockservers/MockRelay.kt @@ -119,7 +119,6 @@ class MockRelay( /** Wait to receive all requests (if [waitForRequests] is true) and run the [assertion]. */ fun assert(assertion: RelayAsserter.() -> Unit) { if (waitForRequests) { - waitUntilIdle() try { waitUntilIdle() } catch (e: Exception) { @@ -129,6 +128,8 @@ class MockRelay( unassertedEnvelopes.joinToString { it.envelope!!.describeForTest() }, e ) + } else { + throw e } } } From d213e237e1cc6ad1375a8b685e66581308cfcdcd Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 3 Sep 2025 16:29:08 +0200 Subject: [PATCH 6/8] reverted androidxTestCore update due to failing CI in gh emulators --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2f81572a7d7..06b2a7b5d70 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ apollo = "2.5.9" androidxLifecycle = "2.2.0" androidxNavigation = "2.4.2" -androidxTestCore = "1.7.0" +androidxTestCore = "1.6.1" # 1.7.0 cause CI AGP workflow to fail with internal kotlin errors androidxCompose = "1.6.3" composeCompiler = "1.5.14" coroutines = "1.6.1" From 4724c059be5cdb0e7eb622f32cec2b63c17b1177 Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 3 Sep 2025 16:55:48 +0200 Subject: [PATCH 7/8] upgraded androidxTest and espresso versions --- gradle/libs.versions.toml | 4 ++-- .../java/io/sentry/uitest/android/UserFeedbackUiTest.kt | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06b2a7b5d70..ffbe1c3d735 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,11 +2,11 @@ apollo = "2.5.9" androidxLifecycle = "2.2.0" androidxNavigation = "2.4.2" -androidxTestCore = "1.6.1" # 1.7.0 cause CI AGP workflow to fail with internal kotlin errors +androidxTestCore = "1.7.0" androidxCompose = "1.6.3" composeCompiler = "1.5.14" coroutines = "1.6.1" -espresso = "3.5.0" +espresso = "3.7.0" feign = "11.6" jacoco = "0.8.7" jackson = "2.18.3" diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt index 8f574ffc92c..bae17b5760f 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt @@ -472,7 +472,9 @@ class UserFeedbackUiTest : BaseUiTest() { // because it would block the espresso interactions (button click) it.feedbackOptions.onSubmitSuccess = SentryFeedbackCallback { relayIdlingResource.increment() - relayIdlingResource.increment() + if (enableReplay) { + relayIdlingResource.increment() + } } // Let's capture a replay, so we can check the replayId in the feedback if (enableReplay) { From 4a9a750a8edfde584232a0b50028031a25980c77 Mon Sep 17 00:00:00 2001 From: Stefano Date: Wed, 3 Sep 2025 17:33:37 +0200 Subject: [PATCH 8/8] upgraded androidxTest and espresso versions --- .../java/io/sentry/uitest/android/UserFeedbackUiTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt index bae17b5760f..0f84101738b 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt +++ b/sentry-android-integration-tests/sentry-uitest-android/src/androidTest/java/io/sentry/uitest/android/UserFeedbackUiTest.kt @@ -515,7 +515,9 @@ class UserFeedbackUiTest : BaseUiTest() { ) } } - findEnvelope { it.items.first().header.type == SentryItemType.ReplayVideo } + if (enableReplay) { + findEnvelope { it.items.first().header.type == SentryItemType.ReplayVideo } + } assertNoOtherEnvelopes() } }