fix(e2e): dismiss Android ANR dialog blocking Expo E2E on CI#358
Conversation
The E2E Android (expo) job failed because a system-level "System UI isn't responding" dialog blocked Maestro from seeing the home screen. The one-shot dismiss handler never fired for two reasons: 1. The YAML used a curly apostrophe (U+2019) but Android renders a straight apostrophe (U+0027), so the visibility check was skipped. 2. The dialog often appears mid-wait, after the single pre-check runs. Replace the one-shot handler with a repeat-while loop that taps Wait whenever the ANR dialog is visible, and re-add adb settings to suppress ANR prompts on the CI emulator. Co-authored-by: Perry <plrthink@gmail.com>
0045db1 to
6bde190
Compare
| adb shell settings put secure show_anr_messages 0 | ||
| adb shell settings put global anr_dialogs_disabled true | ||
| adb shell settings put global anr_show_background false |
There was a problem hiding this comment.
🚩 ADB ANR suppression settings may be no-ops on API 29
The three adb shell settings put commands (show_anr_messages, anr_dialogs_disabled, anr_show_background) at .github/workflows/e2e.yml:210-212 are not standard AOSP Settings keys documented for API 29. settings put won't fail even if the keys are unrecognized — it silently writes to the database — but the system may not read them. The anr_dialogs_disabled value is set to true (string) rather than 1 (integer), which could be an issue if the system uses Settings.Global.getInt(). This is mitigated by the Maestro-level fallback: the repeat loop in _setup.yaml:78-89 still actively dismisses any ANR dialogs that appear. So these commands are best-effort hardening, not the sole defense.
Was this helpful? React with 👍 or 👎 to provide feedback.
…ss, CI updates (#356) * harden: managed executor, resource cleanup, Zip Slip guard, TS types, CI updates - Replace raw Thread usage with ExecutorService in Android module - Close ZipFile/stream handles with try-with-resources on all paths - Extract Zip Slip validation into ZipSecurity utility and add JUnit tests - Emit progress events on the main thread - Fix TypeScript declarations (optional params, import placement) - Add iOS nullability annotations - Align Android minSdkVersion fallback with docs (23) - Update CI workflows to modern action versions and Node 20 * ci: add iOS build validation workflow for playground-rn * fix: close assetsInputStream in unzipAssets finally block * ci: restore expo e2e checks and add build checks for rn/expo on ios/android - Re-add Expo matrix entries to e2e.yml for iOS and Android - Rename e2e jobs to E2E iOS/Android (rn|expo) to match branch protection - Add Android build workflow for RN and Expo - Update iOS build workflow to build both RN and Expo - Disambiguate artifact names by app in matrix jobs * ci: bump Swift version and relax concurrency for Expo iOS builds Expo Modules Core uses @MainActor/Sendable which require Swift 5.5+ and fail under Xcode 16 strict concurrency when compiled as Swift 5.0. Apply SWIFT_VERSION=5.5 and SWIFT_STRICT_CONCURRENCY=minimal to the iOS build commands so both RN and Expo simulator builds pass. * ci: use Swift 6.0 for iOS builds to satisfy expo-modules-core SWIFT_VERSION=5.5 was rejected/ignored by Xcode 16, leaving ExpoModulesCore in Swift 5 mode where @mainactor is unknown. Use 6.0 (matching the podspec) along with SWIFT_STRICT_CONCURRENCY=minimal to keep RN builds relaxed. * ci: use Swift 5 language mode for iOS builds Swift 5 mode on the Xcode 16 / Swift 6 compiler supports @mainactor but treats strict concurrency diagnostics as warnings, avoiding the hard errors seen in react-native-screens and expo-modules-core under Swift 6 complete concurrency checking. * ci: run iOS jobs on macos-14 with Xcode 15.4 macos-latest uses Xcode 16.4 / Swift 6, which enforces strict concurrency as errors for Expo's dependencies. macos-14 defaults to Xcode 15.4 with Swift 5.10, avoiding those hard errors while keeping @mainactor support via SWIFT_VERSION=5.5. * ci: revert macos-14 and update react-native-screens to 4.25.2 - Keep iOS jobs on macos-latest (Xcode 16 / Swift 6) - Update react-native-screens 4.25.1 -> 4.25.2 in playground-expo * ci: restore Swift 6.0 + minimal concurrency with updated screens With react-native-screens bumped to 4.25.2, retry Swift 6.0 language mode and SWIFT_STRICT_CONCURRENCY=minimal to see if the RNScreens strict-concurrency errors are resolved. * ci: move iOS jobs back to macos-14 (Xcode 15.4) react-native-screens 4.25.2 still fails under Swift 6 on macos-latest. Use macos-14 with Xcode 15.4 / Swift 5.10 as the pragmatic workaround until upstream dependencies are Swift-6 clean. * ci: per-target Swift versions for Expo iOS Xcode 16 build React Native 0.83.9 requires Xcode >= 16.1, so we must stay on macos-latest. Use a Podfile post-install hook to pin: - ExpoModulesCore -> Swift 6.0 (needs @mainactor support) - RNScreens -> Swift 5.0 (still has Swift 6 strict-concurrency issues) Remove the global SWIFT_VERSION/SWIFT_STRICT_CONCURRENCY xcodebuild overrides so the per-target settings take effect. * ci: restore macos-latest for iOS jobs The per-target Swift version fix belongs on Xcode 16 / macos-latest, not macos-14. Restore the runner. * chore: force Swift 6.0 + minimal concurrency for Expo iOS * chore: patch generated xcconfigs for Swift 6.0 + minimal concurrency * chore: keep RNScreens on Swift 5.10 to avoid Swift 6 concurrency errors * chore: use Swift 5.10 across Expo iOS workspace * chore: Swift 6.0 targeted for Expo pods, Swift 5.0 for RNScreens * chore: enable Swift 6.2 isolated-conformance experimental features for Expo pods * ci: use macos-26 runner for Expo iOS builds * chore: enable InferIsolatedConformances for Expo iOS pods * chore: set Swift 6 strict concurrency to minimal for Expo pods * chore: enable main-actor-by-default for Expo iOS workspace * fix(ios): revert main-actor-by-default, use InferIsolatedConformances; pin RN iOS to macos-15 * fix(ios): patch expo-router isolated conformances for Swift 6.2 * fix(ios): patch expo-linking Sendable singleton for Swift 6.2 * fix(ios): patch ExpoHeadModule global mutable state for Swift 6.2 * fix(ios): patch expo-router main-actor isolation for Swift 6.2 * fix(ios): use actual newlines in @mainactor pod patches * fix(ios): patch expo-font global mutable state for Swift 6.2 * fix(ios): use glob to locate expo-font source for patching * fix(ios): patch expo-file-system isolated conformance for Swift 6.2 * fix(ios): isolate EXSessionHandlerProtocol conformance to main actor * fix(ios): patch expo-file-system FilePickingHandler and FileSystemLegacyModule for Swift 6.2 * fix(expo-ios): patch expo-file-system for Swift 6.2 main-actor/Sendable issues * fix(expo-ios): patch updates-interface, dom-webview, and dom-webview JS->MainActor bridge * fix(expo-ios): compile Expo SDK pods in Swift 5 to avoid Swift 6.2 strict-concurrency errors * test(maestro): dismiss Android system UI ANR dialog during setup * ci(e2e): increase Android emulator RAM and disable system ANR dialogs * ci(e2e): keep 4GB emulator RAM and add Maestro fallback for system UI ANR dialog * fix(e2e): poll for Android system-UI ANR dialog on Expo builds The GitHub Actions Android emulator (API 29, software GPU) sometimes shows a system-level "System UI isn't responding" dialog shortly after launch. The Expo dev client is heavy enough to trigger it; the RN Android E2E does not. The dialog is unrelated to react-native-zip-archive logic. A one-shot Maestro conditional can miss the dialog because it appears after the check. Replace it with an Android-only polling loop that taps "Wait" while the playground screen is not yet visible. Also give the emulator 4 cores and a 1024 MB heap to reduce UI strain. * fix(e2e): dismiss Android ANR dialog during home-screen wait The E2E Android (expo) job failed because a system-level "System UI isn't responding" dialog blocked Maestro from seeing the home screen. The one-shot dismiss handler never fired for two reasons: 1. The YAML used a curly apostrophe (U+2019) but Android renders a straight apostrophe (U+0027), so the visibility check was skipped. 2. The dialog often appears mid-wait, after the single pre-check runs. Replace the one-shot handler with a repeat-while loop that taps Wait whenever the ANR dialog is visible, and re-add adb settings to suppress ANR prompts on the CI emulator. Co-authored-by: Perry <plrthink@gmail.com> * fix(e2e): use documented hide_error_dialogs setting on API 29 Replace non-standard adb settings keys (show_anr_messages, anr_dialogs_disabled, anr_show_background) that may be no-ops on the API 29 emulator with Settings.Global.HIDE_ERROR_DIALOGS, the AOSP key used by CTS ANR tests to suppress system error dialogs. Addresses Devin review on PR #358. Co-authored-by: Perry <plrthink@gmail.com> * feat(progress): byte-weighted unzip progress on Android and iOS Align progress semantics across platforms: unzip/unzipWithPassword now report cumulative uncompressed bytes per entry, zip/zipWithPassword report per-file counts. - Android: weight unzip progress by FileHeader uncompressed sizes via a shared totalUncompressedSize helper; add missing terminal 100% event to unzipWithPassword - iOS: extract via SSZipArchive progressHandler variant, accumulating zipInfo.uncompressed_size against payloadSizeForArchiveAtPath with a per-entry fallback; restore per-entry filePath updates - iOS: pass progress handler in zipFiles/zipFilesWithPassword so they emit intermediate progress instead of only 0% and 100% - Drop unused SSZipArchiveDelegate conformance and dead delegate method - Document progress semantics in README * fix(playground-rn): pin native deps to match playground-expo playground-rn has no lockfile and used floating ranges, so CI resolved react-native-screens 4.26.x (released 2026-07-16) whose codegen spec fails RN codegen validation (setToolbarMenuElementOptions). Pin react-native to exact 0.83.9 and screens/gesture-handler/ safe-area-context to the same tilde ranges as playground-expo. * chore(release): bump to 9.0.0 and document breaking changes - MIGRATION.md: add v8.x to v9.0 guide covering progress semantics, iOS filePath format, serialized operations, and Zip Slip rejection - CHANGELOG.md: 9.0.0 unreleased entry for the hardening PR * fix(android): reject non-string zip file entries instead of crashing readableArrayToStringList called Dynamic.asString() on non-string elements, which throws UnexpectedNativeTypeException outside any try/catch and crashes the app. Validate element types up front and reject the promise with a clear message; add JUnit regression tests. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com>
Problem
PR #356's CI has been failing intermittently on E2E Android (expo). The latest run (760f77a) shows all build jobs and most E2E jobs passing; only
E2E Android (expo)fails.Maestro debug artifacts from that run show the Expo app did load — "RNZipArchive Playground" is visible behind a system-level "System UI isn't responding" ANR dialog. The assertion fails because the dialog blocks element visibility for the full 180s timeout.
Root cause
Two bugs in the ANR workaround added in 760f77a:
_setup.yamlused a curly apostrophe ('U+2019) in"System UI isn't responding", but Android renders a straight apostrophe ('U+0027). Maestro's one-shotwhen: visiblecheck was skipped every time.extendedWaitUntil. The ANR dialog typically appears during the 3-minute wait (after app launch under emulator memory pressure), so a single pre-check cannot catch it.The Expo playground is heavier than the RN playground (Expo Router, dev-client shell), making it more likely to trigger system UI ANR on the API 29 / software-GPU GitHub Actions emulator — which is why
E2E Android (rn)passes whileE2E Android (expo)fails.Fix
repeat/whileloop that taps Wait whenever the dialog is visible while waiting for the home screen (up to 90 iterations).adb shell settingscommands to suppress ANR dialogs on the CI emulator (removed in 760f77a when the Maestro fallback was added).Historical context (PR #356)
Earlier failures on this PR were different issues, now resolved:
Build jobs and iOS E2E are green on the latest commit; this fix targets the remaining Android Expo E2E failure.
Verification
U+0027in updated YAML).repeat/whilepattern matches official docs.