diff --git a/apps/RNApp/src/HomeScreen.tsx b/apps/RNApp/src/HomeScreen.tsx index be2c1374..60b8d764 100644 --- a/apps/RNApp/src/HomeScreen.tsx +++ b/apps/RNApp/src/HomeScreen.tsx @@ -285,8 +285,8 @@ const styles = StyleSheet.create({ textAlign: 'center', }, messageSection: { - flex: 1, - minHeight: 0, + flexGrow: 1, + flexBasis: 'auto', width: '100%', marginTop: 12, }, @@ -303,7 +303,6 @@ const styles = StyleSheet.create({ }, messageList: { flex: 1, - minHeight: 0, }, messageListContent: { paddingBottom: 8, diff --git a/apps/brownfield-example-shared-tests/e2e/appleAppBrownfield.e2e.js b/apps/brownfield-example-shared-tests/e2e/appleAppBrownfield.e2e.js index f1f8a7a9..3874c4ae 100644 --- a/apps/brownfield-example-shared-tests/e2e/appleAppBrownfield.e2e.js +++ b/apps/brownfield-example-shared-tests/e2e/appleAppBrownfield.e2e.js @@ -15,7 +15,7 @@ const { describe('Brownfield (AppleApp — Vanilla)', () => { beforeEach(async () => { - await launchBrownfieldAppForDetox({ newInstance: true }); + await launchBrownfieldAppForDetox({ newInstance: true, enableSync: false }); await waitForAppleAppReadyVanilla(); }); diff --git a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs index 013f29b2..c6a3938c 100644 --- a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs @@ -191,23 +191,25 @@ async function waitForEmbeddedExpoMatcher(matcher, index = 0) { async function waitForAppleAppReadyVanilla() { const rnHomeMatcher = by.id(ids.rnAppHome); - try { - await scrollToEmbeddedRnVanilla(); - } catch { - // Continue polling visibility. - } - - try { - await waitForVisible(rnHomeMatcher, DETOX_TIMING.VISIBILITY_TIMEOUT_MS); - return; - } catch { - // AppleApp is a SwiftUI host, not an RCTAppDelegate-based shell, so Detox - // cannot safely call reloadReactNative() here. Fall back to scroll-based - // recovery when the embedded surface is mounted off-screen. - } await device.disableSynchronization(); + try { + try { + await scrollToEmbeddedRnVanilla(); + } catch { + // Continue polling visibility. + } + + try { + await waitForVisible(rnHomeMatcher, DETOX_TIMING.VISIBILITY_TIMEOUT_MS); + return; + } catch { + // AppleApp is a SwiftUI host, not an RCTAppDelegate-based shell, so Detox + // cannot safely call reloadReactNative() here. Fall back to scroll-based + // recovery when the embedded surface is mounted off-screen. + } + await scrollToEmbeddedRnVanilla(); await waitFor(element(rnHomeMatcher)).toBeVisible().withTimeout(20_000); } finally { @@ -216,32 +218,42 @@ async function waitForAppleAppReadyVanilla() { } async function waitForAppleAppReadyExpo() { - try { - await waitForAnyVisible( - [EXPO_HOME_TAB_MATCHERS[0], EXPO_HOME_TAB_MATCHERS[1], EXPO_WELCOME_TITLE], - 10_000 - ); - return; - } catch { - // Continue with scroll-based recovery when the embedded surface starts - // outside the initial viewport. - } + await device.disableSynchronization(); try { - await waitForEmbeddedExpoMatcher(EXPO_HOME_TAB_MATCHERS[0], 0); - return; - } catch { - // Expo 55 does not expose tab IDs; fall back to the visible tab label. - } + try { + await waitForAnyVisible( + [ + EXPO_HOME_TAB_MATCHERS[0], + EXPO_HOME_TAB_MATCHERS[1], + EXPO_WELCOME_TITLE, + ], + 60_000 + ); + return; + } catch { + // Continue with scroll-based recovery when the embedded surface starts + // outside the initial viewport. + } - try { - await waitForEmbeddedExpoMatcher(EXPO_HOME_TAB_MATCHERS[1], 0); - return; - } catch { - // Some Expo builds render the screen title before the tab labels settle. - } + try { + await waitForEmbeddedExpoMatcher(EXPO_HOME_TAB_MATCHERS[0], 0); + return; + } catch { + // Expo 55 does not expose tab IDs; fall back to the visible tab label. + } - await waitForEmbeddedExpoMatcher(EXPO_WELCOME_TITLE, 0); + try { + await waitForEmbeddedExpoMatcher(EXPO_HOME_TAB_MATCHERS[1], 0); + return; + } catch { + // Some Expo builds render the screen title before the tab labels settle. + } + + await waitForEmbeddedExpoMatcher(EXPO_WELCOME_TITLE, 0); + } finally { + await device.enableSynchronization(); + } } async function openHomeTabExpo() { diff --git a/apps/brownfield-example-shared-tests/e2e/appleAppExpoBrownfield.e2e.js b/apps/brownfield-example-shared-tests/e2e/appleAppExpoBrownfield.e2e.js index 61a2fb7c..f93e2251 100644 --- a/apps/brownfield-example-shared-tests/e2e/appleAppExpoBrownfield.e2e.js +++ b/apps/brownfield-example-shared-tests/e2e/appleAppExpoBrownfield.e2e.js @@ -16,7 +16,7 @@ const { describe('Brownfield (AppleApp — Expo)', () => { beforeEach(async () => { - await launchBrownfieldAppForDetox({ newInstance: true }); + await launchBrownfieldAppForDetox({ newInstance: true, enableSync: false }); await waitForAppleAppReadyExpo(); }); diff --git a/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs b/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs index efc79b16..c140b41f 100644 --- a/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs +++ b/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs @@ -12,10 +12,10 @@ const DETOX_TIMING = { POST_MESSAGE_BUBBLE_TIMEOUT_MS: 15_000, /** Timeout for the visibility of toasts. */ - TOAST_VISIBILITY_TIMEOUT_MS: 15_000, + TOAST_VISIBILITY_TIMEOUT_MS: 60_000, /** Timeout for the entire E2E test suite (detox + Jest + Maestro). */ - TEST_TIMEOUT_MS: 300_000, + TEST_TIMEOUT_MS: 720_000, }; module.exports = { DETOX_TIMING }; diff --git a/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs b/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs index 4f163f59..39d16eb0 100644 --- a/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs @@ -41,7 +41,10 @@ async function configureDetoxForBrownfieldIos() { * Sync is disabled only via launchArgs — disableSynchronization() before launchApp() * fails because Detox is not connected to the app yet. */ -async function launchBrownfieldAppForDetox({ newInstance = true } = {}) { +async function launchBrownfieldAppForDetox({ + newInstance = true, + enableSync = true, +} = {}) { await device.launchApp({ newInstance, launchArgs: { @@ -50,7 +53,10 @@ async function launchBrownfieldAppForDetox({ newInstance = true } = {}) { }, }); await configureDetoxForBrownfieldIos(); - await device.enableSynchronization(); + + if (enableSync) { + await device.enableSynchronization(); + } } async function waitForVisible(matcher, timeoutMs = 20000, index = 0) { @@ -63,7 +69,11 @@ async function waitForVisible(matcher, timeoutMs = 20000, index = 0) { * Poll native-only / short-lived UI (toasts, popups, pushed native screens) with sync * temporarily off. RN Debug can keep sync busy while a native overlay is already visible. */ -async function waitForNativeOverlayVisible(matcher, timeoutMs = 20000, index = 0) { +async function waitForNativeOverlayVisible( + matcher, + timeoutMs = 20000, + index = 0 +) { await device.disableSynchronization(); try { const deadline = Date.now() + timeoutMs;