Skip to content

Commit bf98137

Browse files
fix(e2e): harden Android E2E against launcher ANR and slow cold start (#84)
* fix(e2e): harden Android E2E against launcher ANR and slow cold start * ci(e2e): fix Android E2E retry — the emulator-runner executes the script line-by-line, so the multi-line run_e2e() function broke; collapse the retry to a single line
1 parent 4500e94 commit bf98137

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

.github/workflows/react-native-sdk-e2e.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,9 @@ jobs:
121121
adb shell settings put global window_animation_scale 0
122122
adb shell settings put global transition_animation_scale 0
123123
adb shell settings put global animator_duration_scale 0
124+
adb shell settings put global hide_error_dialogs 1
124125
sleep 5
125-
./gradlew :app:connectedDebugAndroidTest --no-daemon -Pandroid.testInstrumentationRunnerArguments.LOGIN_EMAIL="$LOGIN_EMAIL" -Pandroid.testInstrumentationRunnerArguments.LOGIN_PASSWORD="$LOGIN_PASSWORD" -Pandroid.testInstrumentationRunnerArguments.LOGIN_WRONG_PASSWORD="$LOGIN_WRONG_PASSWORD" -Pandroid.testInstrumentationRunnerArguments.TENANT_NAME_1="$TENANT_NAME_1" -Pandroid.testInstrumentationRunnerArguments.TENANT_NAME_2="$TENANT_NAME_2" -Pandroid.testInstrumentationRunnerArguments.GOOGLE_EMAIL="$GOOGLE_EMAIL" -Pandroid.testInstrumentationRunnerArguments.GOOGLE_PASSWORD="$GOOGLE_PASSWORD"
126+
for attempt in 1 2 3; do echo "Android E2E attempt $attempt"; if ./gradlew :app:connectedDebugAndroidTest --no-daemon -Pandroid.testInstrumentationRunnerArguments.LOGIN_EMAIL="$LOGIN_EMAIL" -Pandroid.testInstrumentationRunnerArguments.LOGIN_PASSWORD="$LOGIN_PASSWORD" -Pandroid.testInstrumentationRunnerArguments.LOGIN_WRONG_PASSWORD="$LOGIN_WRONG_PASSWORD" -Pandroid.testInstrumentationRunnerArguments.TENANT_NAME_1="$TENANT_NAME_1" -Pandroid.testInstrumentationRunnerArguments.TENANT_NAME_2="$TENANT_NAME_2" -Pandroid.testInstrumentationRunnerArguments.GOOGLE_EMAIL="$GOOGLE_EMAIL" -Pandroid.testInstrumentationRunnerArguments.GOOGLE_PASSWORD="$GOOGLE_PASSWORD"; then exit 0; fi; echo "Android E2E attempt $attempt failed (likely emulator/launcher flake); retrying"; adb shell am force-stop com.frontegg.demo || true; adb shell settings put global hide_error_dialogs 1 || true; sleep 5; done; exit 1
126127
127128
- name: Collect JUnit reports
128129
if: always()

example/android/app/src/androidTest/java/com/frontegg/demo/e2e/utils/UiTestInstrumentation.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,24 @@ class UiTestInstrumentation(
3434
activityName: String = "com.frontegg.demo.MainActivity",
3535
applicationPackage: String = "com.frontegg.demo",
3636
) {
37+
// CI flake guard: stop the system from popping "… isn't responding" dialogs over the app
38+
// when the emulator is slow under load (the launcher ANRs and hides the app under test).
39+
runCatching { uiDevice.executeShellCommand("settings put global hide_error_dialogs 1") }
40+
3741
val intent = Intent(Intent.ACTION_MAIN).apply {
3842
setClassName(targetContext, activityName)
3943
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
4044
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
4145
}
42-
targetContext.startActivity(intent)
43-
uiDevice.wait(Until.hasObject(By.pkg(applicationPackage).depth(0)), 5_000)
46+
// A slow cold start can leave the launcher (not the app) foregrounded on the first try,
47+
// so retry the launch until the app package is actually on top.
48+
repeat(3) {
49+
targetContext.startActivity(intent)
50+
dismissBlockingSystemDialogs()
51+
if (uiDevice.wait(Until.hasObject(By.pkg(applicationPackage).depth(0)), 15_000)) {
52+
return
53+
}
54+
}
4455
}
4556

4657
fun terminateApp(applicationPackage: String = "com.frontegg.demo") {
@@ -55,11 +66,25 @@ class UiTestInstrumentation(
5566
val deadline = SystemClock.uptimeMillis() + timeout
5667
while (SystemClock.uptimeMillis() < deadline) {
5768
uiDevice.findObject(selector)?.let { return it }
69+
dismissBlockingSystemDialogs()
5870
delay(250)
5971
}
6072
return null
6173
}
6274

75+
/**
76+
* CI flake guard: under load the emulator's launcher (or the app) can ANR, and the
77+
* system shows an "… isn't responding" dialog on top of the app, hiding the views
78+
* under test. Tap "Wait" (android:id/aerr_wait) to dismiss it and let the process
79+
* recover, so a transient ANR doesn't fail the whole suite.
80+
*/
81+
private fun dismissBlockingSystemDialogs() {
82+
uiDevice.findObject(By.res("android:id/aerr_wait"))?.let {
83+
it.click()
84+
delay(500)
85+
}
86+
}
87+
6388
fun requireView(selector: BySelector, timeout: Long = defaultTimeoutMs): UiObject2 =
6489
waitForView(selector, timeout)
6590
?: error("Timed out waiting for $selector after ${timeout}ms. ${dumpVisible()}")

0 commit comments

Comments
 (0)