Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/RNApp/src/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ const styles = StyleSheet.create({
textAlign: 'center',
},
messageSection: {
flex: 1,
minHeight: 0,
flexGrow: 1,
flexBasis: 'auto',
width: '100%',
marginTop: 12,
},
Comment on lines 287 to 292
Expand All @@ -303,7 +303,6 @@ const styles = StyleSheet.create({
},
messageList: {
flex: 1,
minHeight: 0,
},
messageListContent: {
paddingBottom: 8,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const {

describe('Brownfield (AppleApp — Vanilla)', () => {
beforeEach(async () => {
await launchBrownfieldAppForDetox({ newInstance: true });
await launchBrownfieldAppForDetox({ newInstance: true, enableSync: false });
await waitForAppleAppReadyVanilla();
});

Expand Down
84 changes: 48 additions & 36 deletions apps/brownfield-example-shared-tests/e2e/appleAppDetoxUtils.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
);
Comment on lines +231 to +232
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const {

describe('Brownfield (AppleApp — Expo)', () => {
beforeEach(async () => {
await launchBrownfieldAppForDetox({ newInstance: true });
await launchBrownfieldAppForDetox({ newInstance: true, enableSync: false });
await waitForAppleAppReadyExpo();
});

Expand Down
4 changes: 2 additions & 2 deletions apps/brownfield-example-shared-tests/e2e/detoxTiming.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
16 changes: 13 additions & 3 deletions apps/brownfield-example-shared-tests/e2e/detoxUtils.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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) {
Expand All @@ -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;
Expand Down
Loading