-
-
Notifications
You must be signed in to change notification settings - Fork 468
Expand file tree
/
Copy pathBaseUiTest.kt
More file actions
182 lines (166 loc) · 6.17 KB
/
BaseUiTest.kt
File metadata and controls
182 lines (166 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package io.sentry.uitest.android
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.IdlingPolicies
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
import java.io.FileInputStream
import java.util.concurrent.TimeUnit
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
abstract class BaseUiTest {
/** Runner of the test. */
protected lateinit var runner: AndroidJUnitRunner
/** Application context for the current test. */
protected lateinit var context: Context
/** Mock dsn used to send envelopes to our mock [relay] server. */
protected lateinit var mockDsn: String
// The mockDsn cannot be changed. If a custom dsn needs to be used, it can be set in the options
// as usual
private set
/**
* Idling resource that will be checked by the relay server (if [initSentry] param
* relayWaitForRequests is true). This should be increased to match any envelope that will be sent
* during the test, so that they can later be checked.
*/
protected val relayIdlingResource = CountingIdlingResource("relay-requests")
/** Mock relay server that receives all envelopes sent during the test. */
protected val relay = MockRelay(false, relayIdlingResource)
private fun runCommand(cmd: String) {
val automation = InstrumentationRegistry.getInstrumentation().uiAutomation
val pfd = automation.executeShellCommand(cmd)
try {
FileInputStream(pfd.fileDescriptor).readBytes()
} catch (e: Throwable) {
// ignored
}
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
IdlingPolicies.setIdlingResourceTimeout(10, TimeUnit.SECONDS)
context = ApplicationProvider.getApplicationContext()
context.cacheDir.deleteRecursively()
relay.start()
mockDsn = relay.createMockDsn()
disableDontKeepActivities()
disableSystemAnimations()
}
@AfterTest
fun baseFinish() {
IdlingRegistry.getInstance().unregister(relayIdlingResource)
relay.shutdown()
Sentry.close()
}
/**
* Initializes the Sentry sdk through [SentryAndroid.init] with a default dsn used to catch
* envelopes with [relay]. [relayWaitForRequests] sets whether [relay] should wait for all the
* envelopes sent when doing assertions. If true, [relayIdlingResource] should be increased to
* match any envelope that will be sent during the test. Sentry options can be adjusted as usual
* through [optionsConfiguration].
*/
protected fun initSentry(
relayWaitForRequests: Boolean = false,
context: Context = this.context,
optionsConfiguration: ((options: SentryAndroidOptions) -> Unit)? = null,
) {
relay.waitForRequests = relayWaitForRequests
if (relayWaitForRequests) {
IdlingRegistry.getInstance().register(relayIdlingResource)
}
initForTest(context) {
it.dsn = mockDsn
it.isDebug = true
// We don't use test orchestrator, due to problems with Saucelabs.
// So the app data is not deleted between tests. Thus, We don't know when sessions will
// actually be sent.
// To avoid any interference between tests we can just disable them by default.
it.isEnableAutoSessionTracking = false
optionsConfiguration?.invoke(it)
}
}
}
/** Waits until the Sentry SDK is idle. */
fun waitUntilIdle() {
// We rely on Espresso's idling resources.
Espresso.onIdle()
}
fun classExists(className: String): Boolean {
try {
Class.forName(className)
return true
} catch (exception: ClassNotFoundException) {
// no-op
}
return false
}
fun initForTest(
context: Context,
optionsConfiguration: OptionsConfiguration<SentryAndroidOptions>,
) {
SentryAndroid.init(context) {
applyTestOptions(it)
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 ***"
}