Skip to content

ci: run E2E suites in demo apps (Android)#381

Open
alpharius-ck wants to merge 37 commits into
mainfrom
e2e-for-android
Open

ci: run E2E suites in demo apps (Android)#381
alpharius-ck wants to merge 37 commits into
mainfrom
e2e-for-android

Conversation

@alpharius-ck

@alpharius-ck alpharius-ck commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR adds and stabilizes Android Detox E2E for the brownfield example apps (AndroidApp + embedded RN hosts), with a focus on Expo 55 reliability in CI.

CI pipeline

  • Vanilla — unchanged: single job builds the AAR, assembles Detox APKs, and runs E2E (120 min timeout).
  • Expo 54 — road test only (packaging + consumer build, no E2E).
  • Expo 55 — split into two jobs to avoid job timeouts:
  • android-androidapp-expo55-build — package AAR, Detox build, upload APK artifact (120 min).
  • android-androidapp-expo55-e2e — download prebuilt APKs, boot emulator, run Detox tests (90 min).
  • androidapp-road-test action — new e2e-phase input: none | full | build | test.

E2E test fixes (Expo 55)

Expo 55 tests were flaky/failing on CI (Pixel 6, headless emulator) because:

  • Espresso/Detox sync stalled on native greeting visibility while UIAutomator already saw the UI.
  • Fixed adb swipe coordinates assumed ~2280px height; CI uses ~2400px (Pixel 6).
  • ensureAndroidAppWindowFocus() in beforeEach reset scroll position between tests.
  • RN testIDs and tab labels are unreliable in UIAutomator; Expo home copy has nbsp/uppercase quirks.

Approach: UIAutomator-first assertions via adb, resolution-aware scrolling, Detox swipes as primary with adb fallbacks, and tab navigation via UIAutomator content-desc (Home, postMessage API, Send message to Native).

Emulator / runner hardening

  • Removed aggressive second free-disk-space step that broke QEMU libs.
  • Install emulator runtime libs after Gradle builds (libasound2t64, etc.).
  • cores: 1, ram-size: 3072M, no custom disk-size on AVD.
  • Detox uses android.attached on CI (no second emulator boot).
  • launchTimeout: 300000, Expo Jest testTimeout: 600_000.

Tests covered (Expo 55 Android)

  • Native greeting shell + embedded Expo home (GET STARTED / welcome copy).
  • RN postMessage tab → tap “Send message to Native” → bubble shows Hello from Expo!.

@artus9033 artus9033 mentioned this pull request Jun 23, 2026
3 tasks
@alpharius-ck alpharius-ck marked this pull request as ready for review June 30, 2026 07:18
Comment thread .github/workflows/ci.yml
Comment on lines 150 to -149
- version: '54'
- version: '55'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Q: Does it mean we only test for Expo 54 now?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No — Expo 54 is still covered; Expo 55 was moved to dedicated jobs, not dropped.
Expo 55 was removed from the shared matrix because a single job couldn’t reliably finish build + E2E within the timeout. iOS still uses one matrix for both versions (run-e2e: 'false' for 54, 'true' for 55) — Android mirrors that intent, with a split layout for 55.

Comment on lines +75 to +86
} catch (error: AssertionError) {
if (
error.message?.contains(
"Pausing an activity that is not the current activity"
) == true
) {
Log.w(
"ReactNativeFragment",
"Ignoring stale onHostPause for ${activity?.javaClass?.simpleName}",
error
)
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we know why or what causes this? Is it only for Detox or also happen in real usage?

@alpharius-ck alpharius-ck Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

What causes it: ReactFragment.onPause() forwards to the RN host (onHostPause). RN asserts that the pausing activity is the current RN host activity. The assertion text is:

"Pausing an activity that is not the current activity"

When it shows up in this project:

Brownfield embedding — MainActivity hosts RN via ReactNativeFragment inside Compose, not as the sole RN activity.
Tab / fragment lifecycle — switching embedded RN tabs pauses one fragment while RN may still consider another context current.
Detox / instrumentation — focus churn, ActivityTestRule, and the DetoxE2E window-focus workarounds in MainActivity can deliver stale onPause after RN has already moved on.

ReactNativeFragment.kt
Lines 72-89
    override fun onPause() {
        try {
            super.onPause()
        } catch (error: AssertionError) {
            if (
                error.message?.contains(
                    "Pausing an activity that is not the current activity"
                ) == true
            ) {
                Log.w(
                    "ReactNativeFragment",
                    "Ignoring stale onHostPause for ${activity?.javaClass?.simpleName}",
                    error
                )
                return
            }
            throw error
        }
    }

Detox-only vs production: We’ve seen it most reliably under Detox (CI + emulator). The same class of race is possible in production brownfield apps with fragment-based RN and fast navigation, but we haven’t had production reports yet. The handler is narrow: only this specific assertion is swallowed; other errors are rethrown.

Comment on lines +15 to +18
class BrownfieldApplication : Application(), ReactApplication {
@Suppress("DEPRECATION")
override val reactNativeHost: ReactNativeHost
get() = ReactNativeHostManager.reactNativeHost

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe you need to declare reactNativeHost only because of Expo54 as it contains the deprecated API usage.

If yes, then we can hold this PR until we remove Expo54, which will be done soon after #360

The reason I am suggesting to hold this PR is because we are shipping deprecated stuff as part of Expo config plugin for apps above SDK 54 and they do not require it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So the reactNativeHost override in BrownfieldApplication is effectively required for Expo 54 Detox; for Expo 55 and vanilla, reactHost is sufficient and matches RN’s direction (same as Detox #4895 around New Architecture).

Config plugin vs test app:

The deprecated pre55 template (ReactNativeHostWrapper, etc.) is only generated for Expo < 55 — not for SDK 55+ consumers.
BrownfieldApplication lives in the example AndroidApp, not in the published library API.
On holding until #360: Reasonable if the goal is zero deprecated surface before merge. Counter-arguments for merging now:

Android E2E targets Vanilla + Expo 55; Expo 54 is road-test only (no E2E).
We could drop the reactNativeHost override from shared BrownfieldApplication and move it to an expo54 flavor-specific source set once #360 lands, leaving reactHost only for 55+.
Proposed path: Merge E2E for 55/vanilla now, or gate on #360 — your call. If we proceed, a follow-up to flavor-split BrownfieldApplication (expo54 vs 55+) would address the deprecated API concern without blocking E2E.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the response, appreciate it. I would prefer to hold this PR until end of this week, in the meantime, we will get SDK 56 support merged and SDK 54 apps removal. Then we can rebase this PR and get rid of deprecated API stuff and get it merged.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sounds good to me thanks!

…roid/example/BrownfieldApplication.kt

Co-authored-by: Hur Ali <hurali97@gmail.com>
@alpharius-ck alpharius-ck requested a review from hurali97 June 30, 2026 14:04
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.

3 participants