Skip to content

fix(e2e): dismiss Android ANR dialog blocking Expo E2E on CI#358

Merged
cursor[bot] merged 1 commit into
harden-native-modulesfrom
cursor/fix-e2e-android-anr-5cc7
Jun 30, 2026
Merged

fix(e2e): dismiss Android ANR dialog blocking Expo E2E on CI#358
cursor[bot] merged 1 commit into
harden-native-modulesfrom
cursor/fix-e2e-android-anr-5cc7

Conversation

@plrthink

Copy link
Copy Markdown
Collaborator

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:

  1. Apostrophe mismatch_setup.yaml used a curly apostrophe (' U+2019) in "System UI isn't responding", but Android renders a straight apostrophe (' U+0027). Maestro's one-shot when: visible check was skipped every time.
  2. Timing — The dismiss handler ran once before 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 while E2E Android (expo) fails.

Fix

  • Replace the one-shot ANR handler with a repeat/while loop that taps Wait whenever the dialog is visible while waiting for the home screen (up to 90 iterations).
  • Use the correct straight ASCII apostrophe in the visibility selector.
  • Re-add adb shell settings commands 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:

Phase Failing job Cause
Jun 23 iOS Build (expo) Swift 6.2 strict-concurrency errors in Expo SDK pods
Jun 23 E2E Android (both) Same ANR / emulator instability
Jun 30 E2E Android (expo) only ANR dialog + broken Maestro dismiss handler

Build jobs and iOS E2E are green on the latest commit; this fix targets the remaining Android Expo E2E failure.

Verification

  • Confirmed apostrophe encoding with Python (U+0027 in updated YAML).
  • Maestro repeat/while pattern matches official docs.
  • Full E2E validation requires CI run on this branch.
Open in Web Open in Cursor 

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>
@cursor
cursor Bot force-pushed the cursor/fix-e2e-android-anr-5cc7 branch from 0045db1 to 6bde190 Compare June 30, 2026 03:25
@cursor
cursor Bot marked this pull request as ready for review June 30, 2026 03:25
@cursor
cursor Bot merged commit b23895e into harden-native-modules Jun 30, 2026
@cursor
cursor Bot deleted the cursor/fix-e2e-android-anr-5cc7 branch June 30, 2026 03:26

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread .github/workflows/e2e.yml
Comment on lines +210 to +212
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 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.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

plrthink added a commit that referenced this pull request Jul 17, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants