Skip to content

Commit 1b8668a

Browse files
ci(android): Run UI tests incl. replay on API 30 in AGP matrix, drop Sauce Labs UI job (#6492)
* ci(android): Run UI tests incl. replay on API 35 in AGP matrix, drop Sauce Labs UI job Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io> * ci(android): Pull replay snapshots even when the AGP matrix UI tests fail android-emulator-runner runs each script line in its own shell and stops at the first failing line, so a failing connectedReleaseAndroidTest skipped the snapshot pull. Run the tests and the pull from one script that exits with the tests' status. #skip-changelog * ci(android): Drop --daemon from the AGP matrix UI test run Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io> #skip-changelog * ci(android): Run the AGP matrix UI tests on API 30 google_apis API 30 google_apis captures replay frames with PixelCopy (verified in #6495) and avoids the Android 15 ART method-tracing crash that the profiling tests hit on API 35. Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io> * test(profiling): Run checkTimedOutProfile on emulators with a shorter timeout The emulator skip was added in #3074 because the timed-out profile's trace file was sometimes missing on emulators. Since #3478 timed-out profiles are discarded and the test asserts no profile is sent, so it no longer reads a trace file. Drop the skip, and add a test-only hook to shorten the 30s profiling timeout so the test waits 5s instead of 35s. Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io> * test(uitest): Remove checkTimedOutProfile The test only checks that a timed-out transaction profile is discarded, which AndroidProfilerTest and AndroidTransactionProfilerTest already cover. It was skipped on emulators, so nothing in CI ran it after the Sauce Labs job was removed. Also reverts the test-only profiling timeout hook. Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
1 parent 0135df8 commit 1b8668a

11 files changed

Lines changed: 56 additions & 199 deletions

File tree

‎.github/workflows/agp-matrix.yml‎

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
env:
2525
VERSION_AGP: ${{ matrix.agp }}
2626
APPLY_SENTRY_INTEGRATIONS: ${{ matrix.integrations }}
27+
# Upload replay snapshots from one cell only. Forks don't have access to the upload secret.
28+
UPLOAD_REPLAY_SNAPSHOTS: ${{ matrix.agp == '9.4.0' && matrix.integrations && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
2729

2830
steps:
2931
- name: Checkout Repo
@@ -51,20 +53,21 @@ jobs:
5153
path: |
5254
~/.android/avd/*
5355
~/.android/adb*
54-
key: avd-api-30-x86_64-aosp_atd
56+
key: avd-api-30-x86_64-google_apis
5557

5658
- name: Create AVD and generate snapshot for caching
5759
if: steps.avd-cache.outputs.cache-hit != 'true'
5860
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # pin@v2
5961
with:
6062
api-level: 30
61-
target: aosp_atd
62-
channel: canary # Necessary for ATDs
63+
# Not an ATD image: PixelCopy returns no data on ATDs, which the replay tests need
64+
target: google_apis
65+
channel: canary
6366
arch: x86_64
6467
force-avd-creation: false
6568
disable-animations: true
6669
disable-spellchecker: true
67-
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
70+
emulator-options: -memory 4096 -no-window -gpu auto -noaudio -no-boot-anim -camera-back none
6871
disk-size: 4096M
6972
script: echo "Generated AVD snapshot for caching."
7073

@@ -77,15 +80,28 @@ jobs:
7780
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # pin@v2
7881
with:
7982
api-level: 30
80-
target: aosp_atd
81-
channel: canary # Necessary for ATDs
83+
# Not an ATD image: PixelCopy returns no data on ATDs, which the replay tests need
84+
target: google_apis
85+
channel: canary
8286
arch: x86_64
8387
force-avd-creation: false
8488
disable-animations: true
8589
disable-spellchecker: true
86-
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
90+
emulator-options: -memory 4096 -no-snapshot-save -no-window -gpu auto -noaudio -no-boot-anim -camera-back none
8791
disk-size: 4096M
88-
script: ./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest -Denvironment=github --daemon
92+
script: ./scripts/test-ui-android.sh replay-snapshots
93+
94+
- name: Install Sentry CLI
95+
if: ${{ !cancelled() && env.UPLOAD_REPLAY_SNAPSHOTS == 'true' }}
96+
uses: getsentry/action-setup-cli@70d7e587b84c2e78cf4d37cd33d7b74fb3729c1b # v1
97+
98+
- name: Upload Replay Snapshots to Sentry
99+
if: ${{ !cancelled() && env.UPLOAD_REPLAY_SNAPSHOTS == 'true' && hashFiles('replay-snapshots/*.png') != '' }}
100+
run: sentry-cli snapshots upload ./replay-snapshots --app-id sentry-android-replay
101+
env:
102+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
103+
SENTRY_ORG: sentry-sdks
104+
SENTRY_PROJECT: sentry-android
89105

90106
- name: Upload test results
91107
if: always()
@@ -96,6 +112,7 @@ jobs:
96112
**/build/reports/*
97113
**/build/outputs/*/connected/*
98114
**/build/outputs/mapping/release/*
115+
replay-snapshots/
99116
100117
- name: Test Report
101118
uses: phoenix-actions/test-reporting@7317eea6e13c47348dd0bb318669485157c518d6 # pin@v16

‎.github/workflows/integration-tests-ui.yml‎

Lines changed: 0 additions & 93 deletions
This file was deleted.

‎.sauce/sentry-uitest-android-ui.yml‎

Lines changed: 0 additions & 36 deletions
This file was deleted.

‎scripts/test-ui-android.sh‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/sh
2+
# Runs the sentry-uitest-android instrumentation tests on the connected device, then pulls the
3+
# replay frame snapshots that ReplaySnapshotTest writes, also when the tests fail.
4+
# android-emulator-runner runs each `script:` line in its own shell, so this stays one script to
5+
# keep the tests' exit code. Keep the device directory in sync with ReplaySnapshotTest.
6+
set -eu
7+
8+
out_dir=$1
9+
device_dir=/sdcard/Download/sentry_replay_snapshots
10+
11+
test_status=0
12+
./gradlew sentry-android-integration-tests:sentry-uitest-android:connectedReleaseAndroidTest || test_status=$?
13+
14+
mkdir -p "$out_dir"
15+
if adb shell test -d "$device_dir"; then
16+
adb pull "$device_dir/." "$out_dir"
17+
else
18+
printf 'No replay snapshots on the device, skipping\n'
19+
fi
20+
21+
exit "$test_status"

‎sentry-android-integration-tests/sentry-uitest-android/README.md‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@ By default the envelopes sent to relay are caught by a mock server which allows
77

88
Simply run `./gradlew connectedCheck` to run all ui tests of all modules (requires a connected device, either physical or an emulator).
99

10-
# SauceLabs
11-
To run on saucelabs execute following commands (need also `SAUCE_USERNAME` and `SAUCE_ACCESS_KEY` environment variables):
12-
For End 2 End:
13-
```
14-
./gradlew :sentry-android-integration-tests:sentry-uitest-android:assembleRelease :sentry-android-integration-tests:sentry-uitest-android:assembleAndroidTest
15-
saucectl run -c .sauce/sentry-uitest-android-end2end.yml
16-
```
10+
# CI
11+
CI runs these tests on a GitHub Actions emulator in the AGP matrix workflow (`.github/workflows/agp-matrix.yml`).
12+
The emulator must not be an ATD image: PixelCopy returns no data there, and the replay tests need it.
1713

1814
# Troubleshooting
1915

‎sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ android {
3030
// This doesn't work on some devices with Android 11+. Clearing package data resets permissions.
3131
// Check the readme for more info.
3232
testInstrumentationRunnerArguments["clearPackageData"] = "true"
33-
buildConfigField("String", "ENVIRONMENT", "\"${System.getProperty("environment", "")}\"")
3433
}
3534

3635
testOptions { execution = "ANDROIDX_TEST_ORCHESTRATOR" }
@@ -40,7 +39,6 @@ android {
4039
// Note that the viewBinding.enabled property is now deprecated.
4140
viewBinding = true
4241
compose = true
43-
buildConfig = true
4442
}
4543

4644
composeOptions { kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() }

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ fun waitUntilIdle() {
125125
Espresso.onIdle()
126126
}
127127

128+
/** Whether sentry-android-replay is on the classpath (APPLY_SENTRY_INTEGRATIONS=true). */
129+
fun isReplayAvailable(): Boolean = classExists("io.sentry.android.replay.ReplayIntegration")
130+
128131
fun classExists(className: String): Boolean {
129132
try {
130133
Class.forName(className)

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

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ package io.sentry.uitest.android
33
import androidx.lifecycle.Lifecycle
44
import androidx.test.core.app.launchActivity
55
import androidx.test.espresso.Espresso
6-
import androidx.test.espresso.IdlingPolicies
76
import androidx.test.espresso.IdlingRegistry
87
import androidx.test.espresso.action.ViewActions
98
import androidx.test.espresso.matcher.ViewMatchers
109
import androidx.test.ext.junit.runners.AndroidJUnit4
11-
import io.sentry.NoOpLogger
1210
import io.sentry.ProfilingTraceData
1311
import io.sentry.Sentry
1412
import io.sentry.SentryEvent
1513
import io.sentry.android.core.AndroidLogger
16-
import io.sentry.android.core.BuildInfoProvider
1714
import io.sentry.android.core.SentryAndroidOptions
1815
import io.sentry.assertEnvelopeProfile
1916
import io.sentry.assertEnvelopeTransaction
@@ -25,7 +22,6 @@ import kotlin.test.Test
2522
import kotlin.test.assertEquals
2623
import kotlin.test.assertNotNull
2724
import kotlin.test.assertTrue
28-
import org.junit.Assume
2925
import org.junit.Assume.assumeNotNull
3026
import org.junit.runner.RunWith
3127

@@ -196,44 +192,6 @@ class EnvelopeTests : BaseUiTest() {
196192
}
197193
}
198194

199-
@Test
200-
fun checkTimedOutProfile() {
201-
Assume.assumeFalse(
202-
"Sometimes traceFile does not exist for profiles on emulators and it fails." +
203-
" Although, Android Runtime is the one that manages the file, so something" +
204-
" must be wrong with Debug.startMethodTracing",
205-
BuildInfoProvider(NoOpLogger.getInstance()).isEmulator ?: true,
206-
)
207-
// We increase the IdlingResources timeout to exceed the profiling timeout
208-
IdlingPolicies.setIdlingResourceTimeout(1, TimeUnit.MINUTES)
209-
initSentry(true) { options: SentryAndroidOptions ->
210-
options.tracesSampleRate = 1.0
211-
options.profilesSampleRate = 1.0
212-
}
213-
relayIdlingResource.increment()
214-
val transaction = Sentry.startTransaction("timedOutProfile", "testTimeout")
215-
Thread {
216-
Thread.sleep(35_000)
217-
transaction.finish()
218-
}
219-
.start()
220-
// We call transaction.finish() after 35 seconds, and check profile times out after 30 seconds
221-
222-
relay.assert {
223-
findEnvelope {
224-
assertEnvelopeTransaction(it.items.toList(), AndroidLogger()).transaction ==
225-
"timedOutProfile"
226-
}
227-
.assert {
228-
val transactionItem: SentryTransaction = it.assertTransaction()
229-
// Profile should not be present, as it timed out and is discarded
230-
it.assertNoOtherItems()
231-
assertEquals("timedOutProfile", transactionItem.transaction)
232-
}
233-
assertNoOtherEnvelopes()
234-
}
235-
}
236-
237195
@Test
238196
fun sendProfiledTransaction() {
239197
// This is a dogfooding test

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ import shark.ReferencePattern
1818
class ReplayTest : BaseUiTest() {
1919
@Before
2020
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))
21+
assumeThat(isReplayAvailable(), `is`(true))
2422
// crash on swallowed Compose masking errors (e.g. broken obfuscated internals) so regressions
2523
// fail this on-device test instead of silently under-masking (see SentryReplayDebug)
2624
System.setProperty("io.sentry.replay.compose.fail-fast", "true")

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,7 @@ class UserFeedbackUiTest : BaseUiTest() {
464464

465465
@Test
466466
fun userFeedbackSendEnvelope() {
467-
// GH actions emulator don't allow capturing screenshots properly
468-
val enableReplay = BuildConfig.ENVIRONMENT != "github"
467+
val enableReplay = isReplayAvailable()
469468
initSentry(true) {
470469
// When sending the feedback we want to wait for relay to receive it.
471470
// We can't simply increment the idling resource,

0 commit comments

Comments
 (0)