From b8ba8469f8fe9b95a45b802ad738ac23af562656 Mon Sep 17 00:00:00 2001 From: Aliaksandr Babrykovich Date: Thu, 30 Apr 2026 22:46:52 +0200 Subject: [PATCH 1/4] fix: wait for sandbox ready element instead of fixed sleep in harness beforeAll --- apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx index b39c19d..dbf08d7 100644 --- a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx +++ b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx @@ -15,6 +15,7 @@ import App from '../App' const isIOS = Platform.OS === 'ios' const TEST_TIMEOUT = 60_000 const RENDER_TIMEOUT = 10_000 +const SANDBOX_READY_TIMEOUT = 90_000 async function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)) @@ -85,8 +86,10 @@ describe('Substitution OFF', () => { beforeAll(async () => { blockCleanup = true await render(, {timeout: RENDER_TIMEOUT}) - await sleep(4000) - }, 30_000) + await screen.findByTestId('sandbox-seg-file-access', { + timeout: SANDBOX_READY_TIMEOUT, + }) + }, SANDBOX_READY_TIMEOUT + RENDER_TIMEOUT) if (isIOS) { it( From 45da5f1d806920424df90ea167d467b61d11a1d0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Babrykovich Date: Thu, 30 Apr 2026 23:58:54 +0200 Subject: [PATCH 2/4] fix: signal sandbox readiness via postMessage and wait for host-side marker in harness --- apps/fs-experiment/App.tsx | 11 ++++++++++- .../__tests__/sandbox-isolation.harness.tsx | 2 +- apps/fs-experiment/sandbox.js | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/fs-experiment/App.tsx b/apps/fs-experiment/App.tsx index 50c6ae2..9a416da 100644 --- a/apps/fs-experiment/App.tsx +++ b/apps/fs-experiment/App.tsx @@ -40,6 +40,7 @@ const SANDBOXED_SUBSTITUTIONS: Record = { function App(): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark' const [useSubstitution, setUseSubstitution] = useState(false) + const [sandboxReady, setSandboxReady] = useState(false) const theme = { bg: isDarkMode ? '#000' : '#fff', @@ -54,6 +55,7 @@ function App(): React.JSX.Element { return ( + {sandboxReady && } console.log('Host received from sandbox:', msg)} + onMessage={msg => { + if (msg.cmd === 'ready') setSandboxReady(true) + console.log('Host received from sandbox:', msg) + }} onError={err => console.log('Host received error from sandbox:', err) } @@ -137,6 +142,10 @@ const styles = StyleSheet.create({ root: { flex: 1, }, + hidden: { + width: 0, + height: 0, + }, section: { borderBottomWidth: StyleSheet.hairlineWidth, }, diff --git a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx index dbf08d7..fd54f5c 100644 --- a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx +++ b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx @@ -86,7 +86,7 @@ describe('Substitution OFF', () => { beforeAll(async () => { blockCleanup = true await render(, {timeout: RENDER_TIMEOUT}) - await screen.findByTestId('sandbox-seg-file-access', { + await screen.findByTestId('sandbox-ready', { timeout: SANDBOX_READY_TIMEOUT, }) }, SANDBOX_READY_TIMEOUT + RENDER_TIMEOUT) diff --git a/apps/fs-experiment/sandbox.js b/apps/fs-experiment/sandbox.js index e0bc3a3..b116f39 100644 --- a/apps/fs-experiment/sandbox.js +++ b/apps/fs-experiment/sandbox.js @@ -58,6 +58,9 @@ function installTestCommandHandler() { function SandboxApp(props) { useEffect(() => { installTestCommandHandler() + if (typeof globalThis.postMessage === 'function') { + globalThis.postMessage({cmd: 'ready'}) + } }, []) return From 99c2dadba7ac82e20f98bb3c8f2521e768665acc Mon Sep 17 00:00:00 2001 From: Aliaksandr Babrykovich Date: Fri, 1 May 2026 07:01:49 +0200 Subject: [PATCH 3/4] fix: use testID on SandboxReactNativeView directly instead of extra hidden view --- apps/fs-experiment/App.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/fs-experiment/App.tsx b/apps/fs-experiment/App.tsx index 9a416da..ec548f7 100644 --- a/apps/fs-experiment/App.tsx +++ b/apps/fs-experiment/App.tsx @@ -55,7 +55,6 @@ function App(): React.JSX.Element { return ( - {sandboxReady && } Date: Fri, 1 May 2026 08:37:27 +0200 Subject: [PATCH 4/4] fix: correct onMessage payload extraction and increase bridgeTimeout for CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SandboxReactNativeView wrapper already unwraps e.nativeEvent.data before calling onMessage, so msg IS the data directly (not msg.data). iOS sends an object, Android sends a JSON string — handle both with typeof check. - Switch beforeAll to waitFor/queryByTestId to avoid findByTestId 3-arg API - Use a sibling hidden View for sandbox-ready marker instead of passing testID directly to SandboxReactNativeView: changing testID on the native view while the sandbox is running causes the iOS sandbox to restart, leading to a native crash during test execution. - Reset sandboxReady to false on substitution toggle so the sandbox-ready marker disappears and reappears once the new sandbox is ready; replace the fixed 3s sleep in setSubstitution with a proper waitFor. - Increase bridgeTimeout from default 60s to 120s so iOS CI simulator has enough time to install and boot the app after a long Xcode build. Co-Authored-By: Claude Sonnet 4.6 --- apps/fs-experiment/App.tsx | 21 +++++++++++++---- .../__tests__/sandbox-isolation.harness.tsx | 23 +++++++++++++++---- apps/fs-experiment/rn-harness.config.mjs | 1 + 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/apps/fs-experiment/App.tsx b/apps/fs-experiment/App.tsx index ec548f7..2c972b7 100644 --- a/apps/fs-experiment/App.tsx +++ b/apps/fs-experiment/App.tsx @@ -99,7 +99,10 @@ function App(): React.JSX.Element { useSubstitution ? 'substitution-on' : 'substitution-off' } style={styles.switchRow} - onPress={() => setUseSubstitution(v => !v)}> + onPress={() => { + setSandboxReady(false) + setUseSubstitution(v => !v) + }}> Module substitution{' '} @@ -108,14 +111,19 @@ function App(): React.JSX.Element { { + setSandboxReady(false) + setUseSubstitution(v) + }} trackColor={{false: theme.border, true: theme.green}} /> + {sandboxReady && ( + + )} { - if (msg.cmd === 'ready') setSandboxReady(true) + const payload = typeof msg === 'string' ? JSON.parse(msg) : msg + if (payload?.cmd === 'ready') setSandboxReady(true) console.log('Host received from sandbox:', msg) }} onError={err => @@ -142,6 +151,10 @@ const styles = StyleSheet.create({ root: { flex: 1, }, + readyMarker: { + width: 0, + height: 0, + }, section: { borderBottomWidth: StyleSheet.hairlineWidth, }, diff --git a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx index fd54f5c..d2b3cc9 100644 --- a/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx +++ b/apps/fs-experiment/__tests__/sandbox-isolation.harness.tsx @@ -8,6 +8,7 @@ import { expect, it, render, + waitFor, } from 'react-native-harness' import App from '../App' @@ -27,7 +28,16 @@ async function setSubstitution(enabled: boolean) { await userEvent.press(await screen.findByTestId('substitution-switch')) await screen.findByAccessibilityLabel(wantLabel) - await sleep(3000) + // Toggling substitution restarts the sandbox — wait for it to be ready again + // instead of a fixed sleep + await waitFor( + () => { + if (!screen.queryByTestId('sandbox-ready')) { + throw new Error('Sandbox not ready after substitution change') + } + }, + {timeout: SANDBOX_READY_TIMEOUT} + ) } async function writeAndRead( @@ -86,9 +96,14 @@ describe('Substitution OFF', () => { beforeAll(async () => { blockCleanup = true await render(, {timeout: RENDER_TIMEOUT}) - await screen.findByTestId('sandbox-ready', { - timeout: SANDBOX_READY_TIMEOUT, - }) + await waitFor( + () => { + if (!screen.queryByTestId('sandbox-ready')) { + throw new Error('Sandbox not ready') + } + }, + {timeout: SANDBOX_READY_TIMEOUT} + ) }, SANDBOX_READY_TIMEOUT + RENDER_TIMEOUT) if (isIOS) { diff --git a/apps/fs-experiment/rn-harness.config.mjs b/apps/fs-experiment/rn-harness.config.mjs index 240080c..5aab348 100644 --- a/apps/fs-experiment/rn-harness.config.mjs +++ b/apps/fs-experiment/rn-harness.config.mjs @@ -33,6 +33,7 @@ const config = { forwardClientLogs: true, resetEnvironmentBetweenTestFiles: true, disableViewFlattening: true, + bridgeTimeout: 120_000, } export default config