Skip to content

Commit 703d523

Browse files
authored
tests now search the envelope among all the ones sent to the mock relay server and make assertion on them, instead of relying on the order the envelopes were created (#2422)
* ui tests now search the envelope among all the ones sent to the mock relay server and make assertion on them, instead of relying on the order the envelopes were created * added androidx test orchestrator to saucelabs configuration with "clearPackageData" option enabled * updated androidx orchestrator dependency
1 parent b3a8fb3 commit 703d523

File tree

9 files changed

+122
-42
lines changed

9 files changed

+122
-42
lines changed

.sauce/sentry-uitest-android-benchmark-lite.yml

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ espresso:
1919
suites:
2020

2121
- name: "Android 11 (api 30)"
22+
testOptions:
23+
clearPackageData: true
24+
useTestOrchestrator: true
2225
devices:
2326
- id: Google_Pixel_3a_real # Google Pixel 3a - api 30 (11)
2427

.sauce/sentry-uitest-android-benchmark.yml

+9
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,26 @@ suites:
2020

2121
# Devices are chosen so that there is a high-end and a low-end device for each api level
2222
- name: "Android 12 (api 31)"
23+
testOptions:
24+
clearPackageData: true
25+
useTestOrchestrator: true
2326
devices:
2427
- id: Google_Pixel_6_Pro_real_us # Google Pixel 6 Pro - api 31 (12) - high end
2528
- id: Google_Pixel_6a_real_us # Google Pixel 6a - api 31 (12) - low end
2629

2730
- name: "Android 11 (api 30)"
31+
testOptions:
32+
clearPackageData: true
33+
useTestOrchestrator: true
2834
devices:
2935
- id: OnePlus_9_Pro_real_us # OnePlus 9 Pro - api 30 (11) - high end
3036
- id: Google_Pixel_4_real_us # Google Pixel 4 - api 30 (11) - mid end
3137
- id: Google_Pixel_3a_real # Google Pixel 3a - api 30 (11) - low end
3238

3339
- name: "Android 10 (api 29)"
40+
testOptions:
41+
clearPackageData: true
42+
useTestOrchestrator: true
3443
devices:
3544
- id: Google_Pixel_4_XL_real_us1 # Google Pixel 4 XL - api 29 (10)
3645
- id: Nokia_7_1_real_us # Nokia 7.1 - api 29 (10)

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

+12
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,30 @@ espresso:
1919
suites:
2020

2121
- name: "Android 13 Ui test (api 33)"
22+
testOptions:
23+
clearPackageData: true
24+
useTestOrchestrator: true
2225
devices:
2326
- id: Google_Pixel_5_13_real_us # Google Pixel 5 - api 33 (13)
2427

2528
- name: "Android 12 Ui test (api 31)"
29+
testOptions:
30+
clearPackageData: true
31+
useTestOrchestrator: true
2632
devices:
2733
- id: Samsung_Galaxy_S22_Ultra_5G_real_us # Samsung Galaxy S22 Ultra 5G - api 31 (12)
2834

2935
- name: "Android 11 Ui test (api 30)"
36+
testOptions:
37+
clearPackageData: true
38+
useTestOrchestrator: true
3039
devices:
3140
- id: OnePlus_9_Pro_real_us # OnePlus 9 Pro - api 30 (11)
3241

3342
- name: "Android 10 Ui test (api 29)"
43+
testOptions:
44+
clearPackageData: true
45+
useTestOrchestrator: true
3446
devices:
3547
- id: OnePlus_7T_real_us # OnePlus 7T - api 29 (10)
3648

buildSrc/src/main/java/Config.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ object Config {
167167
val androidxTestRules = "androidx.test:rules:$androidxTestVersion"
168168
val espressoCore = "androidx.test.espresso:espresso-core:$espressoVersion"
169169
val espressoIdlingResource = "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
170-
val androidxTestOrchestrator = "androidx.test:orchestrator:1.4.1"
170+
val androidxTestOrchestrator = "androidx.test:orchestrator:1.4.2"
171171
val androidxJunit = "androidx.test.ext:junit:1.1.3"
172172
val androidxCoreKtx = "androidx.core:core-ktx:1.7.0"
173173
val robolectric = "org.robolectric:robolectric:4.7.3"

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ android {
2626
// https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner#enable-gradle
2727
// This doesn't work on some devices with Android 11+. Clearing package data resets permissions.
2828
// Check the readme for more info.
29-
// Test orchestrator was removed due to issues with SauceLabs
30-
// testInstrumentationRunnerArguments["clearPackageData"] = "true"
29+
testInstrumentationRunnerArguments["clearPackageData"] = "true"
30+
}
31+
32+
testOptions {
33+
execution = "ANDROIDX_TEST_ORCHESTRATOR"
3134
}
3235

3336
buildFeatures {

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ android {
2525
// https://developer.android.com/training/testing/instrumented-tests/androidx-test-libraries/runner#enable-gradle
2626
// This doesn't work on some devices with Android 11+. Clearing package data resets permissions.
2727
// Check the readme for more info.
28-
// Test orchestrator was removed due to issues with SauceLabs
29-
// testInstrumentationRunnerArguments["clearPackageData"] = "true"
28+
testInstrumentationRunnerArguments["clearPackageData"] = "true"
29+
}
30+
31+
testOptions {
32+
execution = "ANDROIDX_TEST_ORCHESTRATOR"
3033
}
3134

3235
buildFeatures {

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

+40-20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import io.sentry.ProfilingTraceData
1212
import io.sentry.Sentry
1313
import io.sentry.SentryEvent
1414
import io.sentry.android.core.SentryAndroidOptions
15+
import io.sentry.assertEnvelopeItem
1516
import io.sentry.profilemeasurements.ProfileMeasurement
1617
import io.sentry.protocol.SentryTransaction
1718
import org.junit.runner.RunWith
@@ -34,7 +35,7 @@ class EnvelopeTests : BaseUiTest() {
3435
Sentry.captureMessage("Message captured during test")
3536

3637
relay.assert {
37-
assertEnvelope {
38+
assertFirstEnvelope {
3839
val event: SentryEvent = it.assertItem()
3940
it.assertNoOtherItems()
4041
assertTrue(event.message?.formatted == "Message captured during test")
@@ -53,7 +54,7 @@ class EnvelopeTests : BaseUiTest() {
5354

5455
relayIdlingResource.increment()
5556
IdlingRegistry.getInstance().register(ProfilingSampleActivity.scrollingIdlingResource)
56-
val transaction = Sentry.startTransaction("e2etests", "test1")
57+
val transaction = Sentry.startTransaction("profiledTransaction", "test1")
5758
val sampleScenario = launchActivity<ProfilingSampleActivity>()
5859
swipeList(1)
5960
sampleScenario.moveToState(Lifecycle.State.DESTROYED)
@@ -63,21 +64,29 @@ class EnvelopeTests : BaseUiTest() {
6364

6465
transaction.finish()
6566
relay.assert {
66-
assertEnvelope {
67+
findEnvelope {
68+
assertEnvelopeItem<SentryTransaction>(it.items.toList()).transaction == "ProfilingSampleActivity"
69+
}.assert {
6770
val transactionItem: SentryTransaction = it.assertItem()
6871
it.assertNoOtherItems()
6972
assertEquals("ProfilingSampleActivity", transactionItem.transaction)
7073
}
71-
assertEnvelope {
74+
75+
findEnvelope {
76+
assertEnvelopeItem<SentryTransaction>(it.items.toList()).transaction == "profiledTransaction"
77+
}.assert {
7278
val transactionItem: SentryTransaction = it.assertItem()
7379
it.assertNoOtherItems()
74-
assertEquals("e2etests", transactionItem.transaction)
80+
assertEquals("profiledTransaction", transactionItem.transaction)
7581
}
76-
assertEnvelope {
82+
83+
findEnvelope {
84+
assertEnvelopeItem<ProfilingTraceData>(it.items.toList()).transactionName == "profiledTransaction"
85+
}.assert {
7786
val profilingTraceData: ProfilingTraceData = it.assertItem()
7887
it.assertNoOtherItems()
7988
assertEquals(profilingTraceData.transactionId, transaction.eventId.toString())
80-
assertEquals("e2etests", profilingTraceData.transactionName)
89+
assertEquals("profiledTransaction", profilingTraceData.transactionName)
8190
assertTrue(profilingTraceData.environment.isNotEmpty())
8291
assertTrue(profilingTraceData.cpuArchitecture.isNotEmpty())
8392
assertTrue(profilingTraceData.transactions.isNotEmpty())
@@ -127,24 +136,31 @@ class EnvelopeTests : BaseUiTest() {
127136
transaction3.finish()
128137

129138
relay.assert {
130-
assertEnvelope {
139+
findEnvelope {
140+
assertEnvelopeItem<SentryTransaction>(it.items.toList()).transaction == "e2etests"
141+
}.assert {
131142
val transactionItem: SentryTransaction = it.assertItem()
132143
it.assertNoOtherItems()
133144
assertEquals(transaction.eventId.toString(), transactionItem.eventId.toString())
134145
}
135-
assertEnvelope {
146+
findEnvelope {
147+
assertEnvelopeItem<SentryTransaction>(it.items.toList()).transaction == "e2etests1"
148+
}.assert {
136149
val transactionItem: SentryTransaction = it.assertItem()
137150
it.assertNoOtherItems()
138151
assertEquals(transaction2.eventId.toString(), transactionItem.eventId.toString())
139152
}
140-
// The profile is sent only in the last transaction envelope
141-
assertEnvelope {
153+
findEnvelope {
154+
assertEnvelopeItem<SentryTransaction>(it.items.toList()).transaction == "e2etests2"
155+
}.assert {
142156
val transactionItem: SentryTransaction = it.assertItem()
143157
it.assertNoOtherItems()
144158
assertEquals(transaction3.eventId.toString(), transactionItem.eventId.toString())
145159
}
146-
// The profile is sent only in the last transaction envelope
147-
assertEnvelope {
160+
// The profile is sent in its own envelope
161+
findEnvelope {
162+
assertEnvelopeItem<ProfilingTraceData>(it.items.toList()).transactionName == "e2etests2"
163+
}.assert {
148164
val profilingTraceData: ProfilingTraceData = it.assertItem()
149165
it.assertNoOtherItems()
150166
assertEquals("e2etests2", profilingTraceData.transactionName)
@@ -191,7 +207,7 @@ class EnvelopeTests : BaseUiTest() {
191207
relayIdlingResource.increment()
192208
relayIdlingResource.increment()
193209
val profilesDirPath = Sentry.getCurrentHub().options.profilingTracesDirPath
194-
val transaction = Sentry.startTransaction("e2etests", "test empty")
210+
val transaction = Sentry.startTransaction("emptyProfileTransaction", "test empty")
195211

196212
var finished = false
197213
Thread {
@@ -209,13 +225,15 @@ class EnvelopeTests : BaseUiTest() {
209225
}
210226

211227
relay.assert {
212-
assertEnvelope {
228+
findEnvelope {
229+
assertEnvelopeItem<SentryTransaction>(it.items.toList()).transaction == "emptyProfileTransaction"
230+
}.assert {
213231
val transactionItem: SentryTransaction = it.assertItem()
214232
it.assertNoOtherItems()
215-
assertEquals("e2etests", transactionItem.transaction)
233+
assertEquals("emptyProfileTransaction", transactionItem.transaction)
216234
}
217235
// The profile failed to be sent. Trying to read the envelope from the data transmitted throws an exception
218-
assertFailsWith<IllegalArgumentException> { assertEnvelope {} }
236+
assertFailsWith<IllegalArgumentException> { assertFirstEnvelope {} }
219237
assertNoOtherEnvelopes()
220238
assertNoOtherRequests()
221239
}
@@ -230,14 +248,16 @@ class EnvelopeTests : BaseUiTest() {
230248
options.profilesSampleRate = 1.0
231249
}
232250
relayIdlingResource.increment()
233-
Sentry.startTransaction("e2etests", "testTimeout")
251+
Sentry.startTransaction("timedOutProfile", "testTimeout")
234252
// We don't call transaction.finish() and let the timeout do its job
235253

236254
relay.assert {
237-
assertEnvelope {
255+
findEnvelope {
256+
assertEnvelopeItem<ProfilingTraceData>(it.items.toList()).transactionName == "timedOutProfile"
257+
}.assert {
238258
val profilingTraceData: ProfilingTraceData = it.assertItem()
239259
it.assertNoOtherItems()
240-
assertEquals("e2etests", profilingTraceData.transactionName)
260+
assertEquals("timedOutProfile", profilingTraceData.transactionName)
241261
assertEquals(ProfilingTraceData.TRUNCATION_REASON_TIMEOUT, profilingTraceData.truncationReason)
242262
}
243263
assertNoOtherEnvelopes()

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

+43-16
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package io.sentry.uitest.android.mockservers
22

33
import io.sentry.EnvelopeReader
44
import io.sentry.Sentry
5+
import io.sentry.SentryEnvelope
56
import okhttp3.mockwebserver.MockResponse
67
import okhttp3.mockwebserver.RecordedRequest
8+
import java.io.IOException
79
import java.util.zip.GZIPInputStream
810

911
/** Class used to assert requests sent to [MockRelay]. */
@@ -13,19 +15,17 @@ class RelayAsserter(
1315
) {
1416

1517
/**
16-
* Asserts an envelope request exists and allows to make other assertions on it and its response.
17-
* The asserted envelope request is then removed from internal list of unasserted envelope.
18+
* Asserts an envelope request exists and allows to make other assertions on the first one and on its response.
19+
* The asserted envelope request is then removed from internal list of unasserted envelopes.
1820
*/
19-
fun assertRawEnvelope(assertion: ((request: RecordedRequest, response: MockResponse) -> Unit)? = null) {
21+
fun assertFirstRawEnvelope(assertion: ((relayResponse: RelayResponse) -> Unit)? = null) {
2022
val relayResponse = unassertedEnvelopes.removeFirstOrNull()
2123
?: throw AssertionError("No envelope request found")
22-
assertion?.let {
23-
it(relayResponse.request, relayResponse.response)
24-
}
24+
assertion?.let { it(relayResponse) }
2525
}
2626

2727
/**
28-
* Asserts a request exists and makes other assertions on it and its response.
28+
* Asserts a request exists and makes other assertions on the first one and on its response.
2929
* The asserted request is then removed from internal list of unasserted requests.
3030
*/
3131
fun assertRawRequest(assertion: ((request: RecordedRequest, response: MockResponse) -> Unit)? = null) {
@@ -37,19 +37,27 @@ class RelayAsserter(
3737
}
3838

3939
/**
40-
* Asserts a request exists, parses it as an envelope and makes other assertions through a [EnvelopeAsserter].
40+
* Parses the first request as an envelope and makes other assertions through a [EnvelopeAsserter].
4141
* The asserted envelope is then removed from internal list of unasserted envelopes.
4242
*/
43-
fun assertEnvelope(assertion: (asserter: EnvelopeAsserter) -> Unit) {
44-
assertRawEnvelope { request, response ->
45-
// Parse the request to rebuild the original envelope. If it fails we throw an assertion error.
46-
val envelope = EnvelopeReader(Sentry.getCurrentHub().options.serializer)
47-
.read(GZIPInputStream(request.body.inputStream()))
48-
?: throw AssertionError("Was unable to parse the request as an envelope: $request")
49-
assertion(EnvelopeAsserter(envelope, response))
43+
fun assertFirstEnvelope(assertion: (asserter: EnvelopeAsserter) -> Unit) {
44+
assertFirstRawEnvelope { relayResponse ->
45+
relayResponse.assert(assertion)
5046
}
5147
}
5248

49+
/**
50+
* Returns the first request that can be parsed as an envelope and that satisfies [filter].
51+
* Throws an [AssertionError] if the envelope was not found.
52+
*/
53+
fun findEnvelope(
54+
filter: (envelope: SentryEnvelope) -> Boolean = { true }
55+
): RelayResponse {
56+
val relayResponseIndex = unassertedEnvelopes.indexOfFirst { it.envelope?.let(filter) ?: false }
57+
if (relayResponseIndex == -1) throw AssertionError("No envelope request found with specified filter")
58+
return unassertedEnvelopes.removeAt(relayResponseIndex)
59+
}
60+
5361
/** Asserts no other envelopes were sent. */
5462
fun assertNoOtherEnvelopes() {
5563
if (unassertedEnvelopes.isNotEmpty()) {
@@ -71,5 +79,24 @@ class RelayAsserter(
7179
assertNoOtherRawRequests()
7280
}
7381

74-
data class RelayResponse(val request: RecordedRequest, val response: MockResponse)
82+
data class RelayResponse(val request: RecordedRequest, val response: MockResponse) {
83+
84+
/** Request parsed as envelope. */
85+
val envelope: SentryEnvelope? by lazy {
86+
try {
87+
EnvelopeReader(Sentry.getCurrentHub().options.serializer)
88+
.read(GZIPInputStream(request.body.inputStream()))
89+
} catch (e: IOException) {
90+
null
91+
}
92+
}
93+
94+
/** Run [assertion] on this request parsed as an envelope. */
95+
fun assert(assertion: (asserter: EnvelopeAsserter) -> Unit) {
96+
// Parse the request to rebuild the original envelope. If it fails we throw an assertion error.
97+
envelope?.let {
98+
assertion(EnvelopeAsserter(it, response))
99+
} ?: throw AssertionError("Was unable to parse the request as an envelope: $request")
100+
}
101+
}
75102
}

sentry-test-support/src/main/kotlin/io/sentry/Assertions.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ fun checkTransaction(predicate: (SentryTransaction) -> Unit): SentryEnvelope {
3232
/**
3333
* Asserts an envelope item of [T] exists in [items] and returns the first one. Otherwise it throws an [AssertionError].
3434
*/
35-
inline fun <reified T> assertEnvelopeItem(items: List<SentryEnvelopeItem>, predicate: (index: Int, item: T) -> Unit): T {
35+
inline fun <reified T> assertEnvelopeItem(
36+
items: List<SentryEnvelopeItem>,
37+
predicate: (index: Int, item: T) -> Unit = { _, _ -> }
38+
): T {
3639
val item = items.mapIndexedNotNull { index, it ->
3740
val deserialized = JsonSerializer(SentryOptions()).deserialize(it.data.inputStream().reader(), T::class.java)
3841
deserialized?.let { Pair(index, it) }

0 commit comments

Comments
 (0)