From f53bca3b0a2e65736e97043003e273bfa12717d1 Mon Sep 17 00:00:00 2001 From: Marcin Kilarski Date: Wed, 15 Jul 2026 09:39:52 +0200 Subject: [PATCH 1/6] fix(e2e): defer detox synchronization during iOS cold start --- .../e2e/appleAppDetoxUtils.cjs | 52 +++++++++++-------- .../e2e/appleAppExpoBrownfield.e2e.js | 2 +- .../e2e/detoxUtils.cjs | 16 ++++-- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs index 013f29b2..e849736c 100644 --- a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs @@ -217,31 +217,39 @@ 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. - } + 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[0], 0); - return; - } catch { - // Expo 55 does not expose tab IDs; fall back to the visible tab label. - } + 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 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[1], 0); + return; + } catch { + // Some Expo builds render the screen title before the tab labels settle. + } - await waitForEmbeddedExpoMatcher(EXPO_WELCOME_TITLE, 0); + 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/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; From 39018af0ebaeb6c1b3516b740a9ff3ab8603c209 Mon Sep 17 00:00:00 2001 From: Marcin Kilarski Date: Wed, 15 Jul 2026 10:31:29 +0200 Subject: [PATCH 2/6] test(e2e): handle detox sync state inside expo ready method --- apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs index e849736c..bf5fc519 100644 --- a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs @@ -216,6 +216,8 @@ async function waitForAppleAppReadyVanilla() { } async function waitForAppleAppReadyExpo() { + await device.disableSynchronization(); + try { try { await waitForAnyVisible( From 65c0753807da1b08c2eb8c73a409875182983566 Mon Sep 17 00:00:00 2001 From: Marcin Kilarski Date: Wed, 15 Jul 2026 11:33:51 +0200 Subject: [PATCH 3/6] test(e2e): handle detox sync state inside vanilla ready method --- .../e2e/appleAppBrownfield.e2e.js | 2 +- .../e2e/appleAppDetoxUtils.cjs | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) 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 bf5fc519..363c9112 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 { @@ -238,7 +240,7 @@ async function waitForAppleAppReadyExpo() { await waitForEmbeddedExpoMatcher(EXPO_HOME_TAB_MATCHERS[0], 0); return; } catch { - // Expo 55 does not expose tab IDs; fall back to the visible tab label. + // Some Expo builds render the screen title before the tab labels settle. } try { From e399634a48ea88056b5451c3409def291a32ac9b Mon Sep 17 00:00:00 2001 From: Marcin Kilarski Date: Wed, 15 Jul 2026 12:17:20 +0200 Subject: [PATCH 4/6] test(e2e): fix vanilla toast timeout --- apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs | 2 +- apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs | 2 +- apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs index 363c9112..c6a3938c 100644 --- a/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs @@ -240,7 +240,7 @@ async function waitForAppleAppReadyExpo() { await waitForEmbeddedExpoMatcher(EXPO_HOME_TAB_MATCHERS[0], 0); return; } catch { - // Some Expo builds render the screen title before the tab labels settle. + // Expo 55 does not expose tab IDs; fall back to the visible tab label. } try { diff --git a/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs b/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs index efc79b16..a0001399 100644 --- a/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs +++ b/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs @@ -15,7 +15,7 @@ const DETOX_TIMING = { TOAST_VISIBILITY_TIMEOUT_MS: 15_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 39d16eb0..8675f71e 100644 --- a/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs @@ -88,7 +88,7 @@ async function waitForNativeOverlayVisible( ); } } - await detoxExpect(target()).toBeVisible(); + await waitFor(target()).toBeVisible().withTimeout(10000); } finally { await device.enableSynchronization(); } From bdc18891bcc743101216f0c5300228044332d75b Mon Sep 17 00:00:00 2001 From: Marcin Kilarski Date: Wed, 15 Jul 2026 13:05:51 +0200 Subject: [PATCH 5/6] test(e2e): increase vanilla toast timeout --- apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs | 2 +- apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs b/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs index a0001399..c140b41f 100644 --- a/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs +++ b/apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs @@ -12,7 +12,7 @@ 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: 720_000, diff --git a/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs b/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs index 8675f71e..39d16eb0 100644 --- a/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs +++ b/apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs @@ -88,7 +88,7 @@ async function waitForNativeOverlayVisible( ); } } - await waitFor(target()).toBeVisible().withTimeout(10000); + await detoxExpect(target()).toBeVisible(); } finally { await device.enableSynchronization(); } From 1bf9dafdd23b0095939cf2bb6f69777d97b863b1 Mon Sep 17 00:00:00 2001 From: Marcin Kilarski Date: Thu, 16 Jul 2026 08:03:08 +0200 Subject: [PATCH 6/6] fix(rnapp): keep send-message button tappable --- apps/RNApp/src/HomeScreen.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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,