From e424f7b8d1f73e66cf36b2c87fed3e48b0b9a46a Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 22 Jul 2026 09:35:39 +0200 Subject: [PATCH 1/2] perf(platform-ios): overlap XCTest agent build with simulator boot The XCTest agent build (xcodebuild build-for-testing, destination generic/platform=iOS Simulator) does not need a booted device, only the later launch phase does. Kick off the build immediately after resolving the simulator udid so it overlaps with bootSimulator/waitForBoot instead of running strictly after it, cutting startup time on simulator runs with permissions enabled, especially on a cold build cache. prepare() is idempotent and ensureStarted() already calls it internally, so calling it early and awaiting it again before ensureStarted() is safe. The permissions-disabled path is unchanged (xctestAgent stays null). --- ...-xctest-agent-build-with-simulator-boot.md | 5 +++ .../__tests__/instance-xctest-agent.test.ts | 6 ++- packages/platform-ios/src/instance.ts | 40 +++++++++++++------ 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 .nx/version-plans/overlap-ios-xctest-agent-build-with-simulator-boot.md diff --git a/.nx/version-plans/overlap-ios-xctest-agent-build-with-simulator-boot.md b/.nx/version-plans/overlap-ios-xctest-agent-build-with-simulator-boot.md new file mode 100644 index 0000000..e4bc8f3 --- /dev/null +++ b/.nx/version-plans/overlap-ios-xctest-agent-build-with-simulator-boot.md @@ -0,0 +1,5 @@ +--- +__default__: patch +--- + +The iOS XCTest permission agent now starts building while the simulator boots instead of waiting for the boot to finish first, cutting startup time for simulator runs that need permission auto-acceptance—especially on a cold build cache. diff --git a/packages/platform-ios/src/__tests__/instance-xctest-agent.test.ts b/packages/platform-ios/src/__tests__/instance-xctest-agent.test.ts index 16ebd95..0fe7237 100644 --- a/packages/platform-ios/src/__tests__/instance-xctest-agent.test.ts +++ b/packages/platform-ios/src/__tests__/instance-xctest-agent.test.ts @@ -85,8 +85,11 @@ describe('iOS XCTest agent runner integration', () => { }, signal: initSignal, }); - expect(mocks.prepare).not.toHaveBeenCalled(); + expect(mocks.prepare).toHaveBeenCalledTimes(1); expect(mocks.ensureStarted).toHaveBeenCalledTimes(1); + expect( + mocks.prepare.mock.invocationCallOrder[0] + ).toBeLessThan(mocks.ensureStarted.mock.invocationCallOrder[0]); expect(mocks.dispose).toHaveBeenCalledTimes(1); }); @@ -168,6 +171,7 @@ describe('iOS XCTest agent runner integration', () => { ); expect(mocks.createXCTestAgentController).not.toHaveBeenCalled(); + expect(mocks.prepare).not.toHaveBeenCalled(); expect(mocks.ensureStarted).not.toHaveBeenCalled(); }); }); diff --git a/packages/platform-ios/src/instance.ts b/packages/platform-ios/src/instance.ts index 227ca48..e055ace 100644 --- a/packages/platform-ios/src/instance.ts +++ b/packages/platform-ios/src/instance.ts @@ -74,6 +74,33 @@ export const getAppleSimulatorPlatformInstance = async ( throw new DeviceNotFoundError(getDeviceName(config.device)); } + const xctestAgent = permissionsEnabled + ? createXCTestAgentController({ + appBundleId: config.bundleId, + target: { + kind: 'simulator', + id: udid, + }, + capabilities: [createPermissionPromptAutoAcceptCapability()], + signal: init.signal, + }) + : null; + + // The XCTest agent build (build-for-testing, destination + // "generic/platform=iOS Simulator") does not need a booted device, so + // kick it off now to overlap with the boot/waitForBoot sequence below. + // It is awaited (not raced) inside the try block further down so build + // errors are still surfaced; the .catch here only prevents an + // unhandled-rejection warning if boot fails first and this promise is + // left to settle unobserved in the meantime. + const preparePromise = xctestAgent?.prepare(); + preparePromise?.catch((error: unknown) => { + iosInstanceLogger.debug( + 'XCTest agent prepare failed while overlapping with simulator boot (surfaced later): %s', + error + ); + }); + const simulatorStatus = await simctl.getSimulatorStatus(udid); let startedByHarness = false; @@ -127,20 +154,9 @@ export const getAppleSimulatorPlatformInstance = async ( `localhost:${harnessConfig.metroPort}` ); - const xctestAgent = permissionsEnabled - ? createXCTestAgentController({ - appBundleId: config.bundleId, - target: { - kind: 'simulator', - id: udid, - }, - capabilities: [createPermissionPromptAutoAcceptCapability()], - signal: init.signal, - }) - : null; - let agentStarted = false; try { + await preparePromise; await xctestAgent?.ensureStarted(); agentStarted = true; } finally { From 30ab83ebc1f2c46a61f19b6c9fcd5bbf5a55ec73 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 22 Jul 2026 14:12:13 +0200 Subject: [PATCH 2/2] ci: bump platformReadyTimeout to 8 minutes for E2E --- apps/playground/rn-harness.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/playground/rn-harness.config.mjs b/apps/playground/rn-harness.config.mjs index bb4badf..006e4d2 100644 --- a/apps/playground/rn-harness.config.mjs +++ b/apps/playground/rn-harness.config.mjs @@ -114,7 +114,7 @@ export default { }), ], defaultRunner: 'android', - platformReadyTimeout: 300000, + platformReadyTimeout: 480000, bridgeTimeout: 120000, testTimeout: 10000,