From 237efc87ee9b61d569d237b125e51a71c2009b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sat, 27 Jun 2026 14:03:44 +0200 Subject: [PATCH 1/6] perf: reuse Apple runner cache across version bumps --- .../ios/__tests__/runner-xctestrun.test.ts | 28 +++++++++++++++++++ src/platforms/ios/runner-xctestrun.ts | 4 +-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/platforms/ios/__tests__/runner-xctestrun.test.ts b/src/platforms/ios/__tests__/runner-xctestrun.test.ts index c9e78a6ee..b0c8394f8 100644 --- a/src/platforms/ios/__tests__/runner-xctestrun.test.ts +++ b/src/platforms/ios/__tests__/runner-xctestrun.test.ts @@ -15,6 +15,7 @@ import { markRunnerXctestrunArtifactBadForRun, prepareXctestrunWithEnv, resolveExpectedRunnerCacheMetadata, + resolveRunnerDerivedPath, resolveXcodebuildSimulatorDeviceSetPath, scoreXctestrunCandidate, } from '../runner-xctestrun.ts'; @@ -283,6 +284,33 @@ test('setup metadata script matches expected iOS simulator cache metadata', asyn }); }, 15_000); +test('runner cache key ignores package version but honors toolchain and SDK changes', () => { + const metadata = resolveExpectedRunnerCacheMetadata(iosSimulator); + const basePath = resolveRunnerDerivedPath(iosSimulator, metadata); + + assert.equal( + resolveRunnerDerivedPath(iosSimulator, { + ...metadata, + packageVersion: `${metadata.packageVersion}-next`, + }), + basePath, + ); + assert.notEqual( + resolveRunnerDerivedPath(iosSimulator, { + ...metadata, + xcodeBuildVersion: `${metadata.xcodeBuildVersion}-other`, + }), + basePath, + ); + assert.notEqual( + resolveRunnerDerivedPath(iosSimulator, { + ...metadata, + sdkBuildVersion: `${metadata.sdkBuildVersion}-other`, + }), + basePath, + ); +}); + function writeExecutable(filePath: string, contents: string): void { fs.writeFileSync(filePath, `${contents}\n`, { mode: 0o755 }); } diff --git a/src/platforms/ios/runner-xctestrun.ts b/src/platforms/ios/runner-xctestrun.ts index e23c8fe60..4c965df67 100644 --- a/src/platforms/ios/runner-xctestrun.ts +++ b/src/platforms/ios/runner-xctestrun.ts @@ -893,8 +893,8 @@ function evaluateRunnerCacheMetadata( function comparableRunnerCacheMetadata( metadata: RunnerXctestrunCacheMetadata, -): RunnerXctestrunCacheMetadata { - const { artifacts: _artifacts, ...comparable } = metadata; +): Omit { + const { artifacts: _artifacts, packageVersion: _packageVersion, ...comparable } = metadata; return comparable; } From ff9fdfc8a603311f54928d03a9f566d025f1c591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sat, 27 Jun 2026 14:31:33 +0200 Subject: [PATCH 2/6] perf: remove unused Apple runner symbols --- .../RunnerTests+FlatSnapshotFiltering.swift | 4 ---- .../AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift | 1 - 2 files changed, 5 deletions(-) diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift index 011c768b6..61c55cb5d 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift @@ -7,10 +7,6 @@ struct FlatSnapshotFilterNode { let valueText: String? let visible: Bool - var hasContent: Bool { - return !label.isEmpty || !identifier.isEmpty || valueText != nil - } - func matchesScope(_ scope: String) -> Bool { let haystack = [label, identifier, valueText ?? ""].joined(separator: "\n") return haystack.localizedCaseInsensitiveContains(scope) diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift index b06f5bda6..69d979af8 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift @@ -12,7 +12,6 @@ extension RunnerTests { enum TextEntryTiming { static let focusTimeout: TimeInterval = 0.4 - static let repairReadinessTimeout: TimeInterval = 1.0 static let readinessTimeout: TimeInterval = 2.0 static let hardwareKeyboardFallbackTimeout: TimeInterval = 0.35 static let pollInterval: TimeInterval = 0.02 From 4d24f3f3fc3b74b7cafd4bc6e147817a4a2754a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sat, 27 Jun 2026 14:53:14 +0200 Subject: [PATCH 3/6] perf: keep Swift runner unit tests out of runtime builds --- .../RunnerTests+AXSnapshotFallback.swift | 2 + .../RunnerTests+CommandExecution.swift | 2 + .../RunnerTests+CommandJournal.swift | 2 + .../RunnerTests+FlatSnapshotFiltering.swift | 2 + .../RunnerTests+Interaction.swift | 2 + .../RunnerTests+ScrollGesture.swift | 2 + .../RunnerTests+SequenceExecution.swift | 2 + .../RunnerTests+Snapshot.swift | 2 + .../RunnerTests+SnapshotCapturePlan.swift | 2 + package.json | 2 +- scripts/build-xcuitest-apple.sh | 7 ++- scripts/write-xcuitest-cache-metadata.mjs | 9 +++- .../ios/__tests__/runner-client.test.ts | 46 ++++++++++++++++--- src/platforms/ios/runner-xctestrun.ts | 15 +++++- 14 files changed, 85 insertions(+), 12 deletions(-) diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift index bf2c354de..cf7828dd8 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift @@ -208,6 +208,7 @@ extension RunnerTests { } } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS // MARK: - In-bundle unit tests extension RunnerTests { @@ -325,3 +326,4 @@ extension RunnerTests { XCTAssertFalse(labels.contains("Admin settings")) } } +#endif diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift index 620dca958..075969207 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift @@ -130,6 +130,7 @@ extension RunnerTests { return Response(ok: true, data: data) } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS func testGestureResponseIncludesSynthesizedTapFallbackDiagnostics() { let response = gestureResponse( message: "tapped", @@ -180,6 +181,7 @@ extension RunnerTests { ) XCTAssertNil(xctestRecordedFailureResponse(command: tapCommand, response: runnerFatalResponse)) } +#endif func execute(command: Command) throws -> Response { if command.command == .status { diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift index 2c28dd10e..1132e0254 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift @@ -153,6 +153,7 @@ final class RunnerCommandJournal { } } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS extension RunnerTests { func testUptimeBypassesCommandJournal() throws { let command = runnerJournalCommand("uptime", id: "uptime-probe") @@ -439,3 +440,4 @@ extension RunnerTests { return try JSONDecoder().decode(Response.self, from: Data(responseJson.utf8)) } } +#endif diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift index 61c55cb5d..9ce3b549c 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift @@ -64,6 +64,7 @@ extension RunnerTests { return type } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS func testFlatSnapshotFilterDecisionMatrixCoversOptions() { let visibleContent = FlatSnapshotFilterNode( isRoot: false, @@ -164,4 +165,5 @@ extension RunnerTests { "private AX marks scroll containers as interactive candidates" ) } +#endif } diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift index c44f41ed3..38460b54b 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift @@ -1265,6 +1265,7 @@ extension RunnerTests { return element.exists ? element : nil } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS // Identity in portrait/unknown, 90° per landscape, 180° upside-down. func testNativeSynthesizedPointRotatesByInterfaceOrientation() { let portrait = CGRect(x: 0, y: 0, width: 834, height: 1210) @@ -1333,4 +1334,5 @@ extension RunnerTests { XCTAssertEqual(events.count, 1) XCTAssertEqual(events.first?.vertical, -200) } +#endif } diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift index 085a859d8..8b5ac4df3 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift @@ -62,6 +62,7 @@ func runnerScrollGesturePlan( } } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS extension RunnerTests { // Cross-language parity vectors mirroring src/core/__tests__/scroll-gesture.test.ts. Keep these // in sync with the vitest vectors so the two buildScrollGesturePlan implementations cannot drift. @@ -216,3 +217,4 @@ extension RunnerTests { ) } } +#endif diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift index 9796e2e87..12427228b 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift @@ -273,6 +273,7 @@ extension RunnerTests { } } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS // MARK: - In-bundle unit tests (device-free) extension RunnerTests { @@ -448,3 +449,4 @@ extension RunnerTests { return try! JSONDecoder().decode(Command.self, from: data) } } +#endif diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift index 13e305ea9..45be79886 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift @@ -426,6 +426,7 @@ extension RunnerTests { ) } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS func testSnapshotAccessibilityUnavailableMarksSparseSnapshotRunnerFatal() { currentApp = app currentBundleId = "com.example.app" @@ -468,6 +469,7 @@ extension RunnerTests { XCTAssertTrue(failure.message.contains("\(Self.rawSnapshotMaxNodes) nodes")) XCTAssertEqual(failure.hint, Self.rawSnapshotTooLargeHint) } +#endif private func interactiveRootNode(rect: CGRect) -> SnapshotNode { SnapshotNode( diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift index 8d9f4f0b7..3cb85eeae 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift @@ -304,6 +304,7 @@ extension RunnerTests { } } +#if AGENT_DEVICE_RUNNER_UNIT_TESTS // MARK: - In-bundle unit tests extension RunnerTests { @@ -423,3 +424,4 @@ extension RunnerTests { XCTAssertEqual(payload.snapshotQuality?.reasonCode, "ax-rejected") } } +#endif diff --git a/package.json b/package.json index 32b2b22d8..c83767fb1 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "package:android-multitouch-helper": "sh ./scripts/package-android-multitouch-helper.sh $(node -p \"require('./package.json').version\") .tmp/android-multitouch-helper", "package:android-multitouch-helper:npm": "rm -rf android-multitouch-helper/dist && sh ./scripts/package-android-multitouch-helper.sh $(node -p \"require('./package.json').version\") android-multitouch-helper/dist", "build:macos-helper": "swift build -c release --package-path macos-helper", - "build:all": "pnpm build:node && pnpm build:xcuitest", + "build:all": "pnpm build:node && AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1 pnpm build:xcuitest", "ad": "node bin/agent-device.mjs", "size": "node scripts/size-report.mjs", "size:markdown": "node scripts/size-report.mjs --json .tmp/size-report.json --markdown .tmp/size-report.md", diff --git a/scripts/build-xcuitest-apple.sh b/scripts/build-xcuitest-apple.sh index c0f2f3233..8c367309f 100644 --- a/scripts/build-xcuitest-apple.sh +++ b/scripts/build-xcuitest-apple.sh @@ -93,6 +93,11 @@ if is_truthy "${AGENT_DEVICE_IOS_CLEAN_DERIVED:-}"; then rm -rf "$CLEAN_PATH" fi +SWIFT_FLAGS='$(inherited) -disable-sandbox' +if is_truthy "${AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS:-}"; then + SWIFT_FLAGS="$SWIFT_FLAGS -D AGENT_DEVICE_RUNNER_UNIT_TESTS" +fi + xcodebuild build-for-testing \ -project "$PROJECT_PATH" \ -scheme "$SCHEME" \ @@ -108,7 +113,7 @@ xcodebuild build-for-testing \ -IDEPackageSupportDisableManifestSandbox=1 \ -IDEPackageSupportDisablePluginExecutionSandbox=1 \ ENABLE_USER_SCRIPT_SANDBOXING=NO \ - OTHER_SWIFT_FLAGS='$(inherited) -disable-sandbox' \ + OTHER_SWIFT_FLAGS="$SWIFT_FLAGS" \ $SIGNING_BUILD_SETTINGS node --experimental-strip-types scripts/patch-xcuitest-runner-icon.ts "$DERIVED_PATH" diff --git a/scripts/write-xcuitest-cache-metadata.mjs b/scripts/write-xcuitest-cache-metadata.mjs index 13b1eb329..4c1d0d089 100644 --- a/scripts/write-xcuitest-cache-metadata.mjs +++ b/scripts/write-xcuitest-cache-metadata.mjs @@ -19,6 +19,10 @@ const metadataPath = path.join(derivedPath, '.agent-device-runner-cache.json'); const DEFAULT_IOS_RUNNER_APP_BUNDLE_ID = 'com.callstack.agentdevice.runner'; +function isTruthy(value) { + return ['1', 'true', 'TRUE', 'yes', 'YES', 'on', 'ON'].includes(String(value ?? '')); +} + function readPackageVersion() { try { const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')); @@ -196,11 +200,14 @@ function resolveSigningBuildSettings() { } function resolveSandboxBuildArgs() { + const swiftFlags = isTruthy(process.env.AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS) + ? '$(inherited) -disable-sandbox -D AGENT_DEVICE_RUNNER_UNIT_TESTS' + : '$(inherited) -disable-sandbox'; return [ '-IDEPackageSupportDisableManifestSandbox=1', '-IDEPackageSupportDisablePluginExecutionSandbox=1', 'ENABLE_USER_SCRIPT_SANDBOXING=NO', - 'OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox', + `OTHER_SWIFT_FLAGS=${swiftFlags}`, ]; } diff --git a/src/platforms/ios/__tests__/runner-client.test.ts b/src/platforms/ios/__tests__/runner-client.test.ts index 9fc2f0c07..b1cb4a96d 100644 --- a/src/platforms/ios/__tests__/runner-client.test.ts +++ b/src/platforms/ios/__tests__/runner-client.test.ts @@ -507,6 +507,25 @@ test('resolveRunnerSandboxBuildArgs disables nested Xcode and Swift sandboxing', ]); }); +test('resolveRunnerSandboxBuildArgs includes Swift runner unit tests only when requested', () => { + const previous = process.env.AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS; + try { + process.env.AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS = '1'; + assert.deepEqual(resolveRunnerSandboxBuildArgs(), [ + '-IDEPackageSupportDisableManifestSandbox=1', + '-IDEPackageSupportDisablePluginExecutionSandbox=1', + 'ENABLE_USER_SCRIPT_SANDBOXING=NO', + 'OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox -D AGENT_DEVICE_RUNNER_UNIT_TESTS', + ]); + } finally { + if (previous === undefined) { + delete process.env.AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS; + } else { + process.env.AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS = previous; + } + } +}); + test('resolveRunnerBundleBuildSettings returns default bundle identifiers', () => { assert.deepEqual(resolveRunnerBundleBuildSettings({}), [ 'AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID=com.callstack.agentdevice.runner', @@ -886,15 +905,19 @@ test('resolveRunnerDerivedPath keys default cache by runner metadata', () => { target: 'desktop', buildDestinationFamily: 'macos', }); - const staleVersionPath = resolveRunnerDerivedPath(iosSimulator, { + const unitTestPath = resolveRunnerDerivedPath(iosSimulator, { ...metadata, - packageVersion: '0.0.0-stale', + runnerSandboxBuildArgs: metadata.runnerSandboxBuildArgs.map((arg) => + arg.startsWith('OTHER_SWIFT_FLAGS=') + ? 'OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox -D AGENT_DEVICE_RUNNER_UNIT_TESTS' + : arg, + ), }); assert.match(iosPath, /\/ios-runner\/derived\/ios-simulator\/cache-[a-f0-9]{16}$/); assert.match(tvPath, /\/ios-runner\/derived\/tvos-simulator\/cache-[a-f0-9]{16}$/); assert.match(macPath, /\/ios-runner\/derived\/macos\/cache-[a-f0-9]{16}$/); - assert.notEqual(iosPath, staleVersionPath); + assert.notEqual(iosPath, unitTestPath); }); test('resolveRunnerDerivedPath reuses cache path for identical runner source fingerprints', async () => { @@ -1006,7 +1029,11 @@ test('ensureXctestrun rebuilds foreign artifacts when metadata does not match', }); const metadataPath = resolveRunnerCacheMetadataPath(derivedPath); const staleMetadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8')); - staleMetadata.packageVersion = '0.0.0-stale'; + staleMetadata.runnerSandboxBuildArgs = staleMetadata.runnerSandboxBuildArgs.map((arg: string) => + arg.startsWith('OTHER_SWIFT_FLAGS=') + ? 'OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox -D AGENT_DEVICE_RUNNER_UNIT_TESTS' + : arg, + ); fs.writeFileSync(metadataPath, JSON.stringify(staleMetadata, null, 2)); withRunnerDerivedPathEnv(derivedPath); @@ -1192,13 +1219,18 @@ test('ensureXctestrun falls back to scan when cache manifest is stale', async () assert.deepEqual(mockRepairMacOsRunnerProductsIfNeeded.mock.calls[0]?.[1], [newerProductPath]); }); -test('ensureXctestrun rebuilds cached runner when metadata package version mismatches', async () => { +test('ensureXctestrun rebuilds cached runner when Swift build flags mismatch', async () => { const projectRoot = repoRoot; const { derivedPath, existingXctestrunPath } = await makeCachedRunnerXctestrun(); const metadataPath = resolveRunnerCacheMetadataPath(derivedPath); + const expectedMetadata = resolveExpectedRunnerCacheMetadata(macOsDevice, repoRoot); const staleMetadata = { - ...resolveExpectedRunnerCacheMetadata(macOsDevice, repoRoot), - packageVersion: '0.0.0-stale', + ...expectedMetadata, + runnerSandboxBuildArgs: expectedMetadata.runnerSandboxBuildArgs.map((arg) => + arg.startsWith('OTHER_SWIFT_FLAGS=') + ? 'OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox -D AGENT_DEVICE_RUNNER_UNIT_TESTS' + : arg, + ), }; fs.writeFileSync(metadataPath, JSON.stringify(staleMetadata, null, 2)); diff --git a/src/platforms/ios/runner-xctestrun.ts b/src/platforms/ios/runner-xctestrun.ts index 4c965df67..94b350880 100644 --- a/src/platforms/ios/runner-xctestrun.ts +++ b/src/platforms/ios/runner-xctestrun.ts @@ -56,8 +56,10 @@ const RUNNER_SANDBOX_BUILD_ARGS = [ '-IDEPackageSupportDisableManifestSandbox=1', '-IDEPackageSupportDisablePluginExecutionSandbox=1', 'ENABLE_USER_SCRIPT_SANDBOXING=NO', - 'OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox', ] as const; +const RUNNER_RUNTIME_SWIFT_FLAGS = '$(inherited) -disable-sandbox'; +const RUNNER_UNIT_TEST_SWIFT_FLAGS = + '$(inherited) -disable-sandbox -D AGENT_DEVICE_RUNNER_UNIT_TESTS'; const runnerXctestrunBuildLocks = new Map>(); const badRunnerArtifactsForRun = new Set(); @@ -1503,7 +1505,16 @@ export function resolveRunnerPerformanceBuildSettings(): string[] { } export function resolveRunnerSandboxBuildArgs(): string[] { - return [...RUNNER_SANDBOX_BUILD_ARGS]; + return [ + ...RUNNER_SANDBOX_BUILD_ARGS, + `OTHER_SWIFT_FLAGS=${resolveRunnerSwiftFlags(process.env)}`, + ]; +} + +function resolveRunnerSwiftFlags(env: NodeJS.ProcessEnv): string { + return isEnvTruthy(env.AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS) + ? RUNNER_UNIT_TEST_SWIFT_FLAGS + : RUNNER_RUNTIME_SWIFT_FLAGS; } function shouldCleanDerived(): boolean { From fd64667cb63181e2750d1d4df256f9ca790dce3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sat, 27 Jun 2026 15:18:28 +0200 Subject: [PATCH 4/6] perf: skip Apple runner asset catalog in runtime builds --- .github/workflows/ci.yml | 19 +++++++++ .../project.pbxproj | 6 --- .../AccentColor.colorset/Contents.json | 11 ------ .../AppIcon.appiconset/Contents.json | 36 ------------------ .../AppIcon.appiconset/logo.jpg | Bin 25995 -> 0 bytes .../Assets.xcassets/Contents.json | 6 --- .../Logo.imageset/Contents.json | 21 ---------- .../Assets.xcassets/Logo.imageset/logo.jpg | Bin 5477 -> 0 bytes .../PoweredBy.imageset/Contents.json | 21 ---------- .../PoweredBy.imageset/powered-by.png | Bin 6006 -> 0 bytes package.json | 2 +- 11 files changed, 20 insertions(+), 102 deletions(-) delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/logo.jpg delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Contents.json delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Logo.imageset/Contents.json delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/Logo.imageset/logo.jpg delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/Contents.json delete mode 100644 ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/powered-by.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a63daf5f..3ae17fd70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,25 @@ jobs: exit 1 fi + swift-runner-unit-compile: + name: Swift Runner Unit Compile + runs-on: macos-26 + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup toolchain + uses: ./.github/actions/setup-node-pnpm + + - name: Compile Swift runner unit-test surface + uses: ./.github/actions/setup-apple-replay + with: + derived-path: ${{ github.workspace }}/.tmp/swift-runner-unit-derived + cache-key-prefix: swift-runner-unit + build-command: AGENT_DEVICE_XCUITEST_INCLUDE_UNIT_TESTS=1 pnpm build:xcuitest:macos + xcuitest-platform: macos + no-test-di-seams: name: No test-only DI seams runs-on: ubuntu-latest diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj b/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj index 5baf6047f..1ba70d1a1 100644 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +++ b/ios-runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj @@ -206,7 +206,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID = com.callstack.agentdevice.runner; AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID = "$(AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID).uitests"; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -273,7 +272,6 @@ ALWAYS_SEARCH_USER_PATHS = NO; AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID = com.callstack.agentdevice.runner; AGENT_DEVICE_IOS_RUNNER_TEST_BUNDLE_ID = "$(AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID).uitests"; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -330,8 +328,6 @@ 20EA2EE82F2CFC7C001CF0EF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 2S799L9W4M; @@ -366,8 +362,6 @@ 20EA2EE92F2CFC7C001CF0EF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 2S799L9W4M; diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AccentColor.colorset/Contents.json b/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb8789700..000000000 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index c027ad009..000000000 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "images" : [ - { - "filename" : "logo.jpg", - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/logo.jpg b/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/AppIcon.appiconset/logo.jpg deleted file mode 100644 index 1c2317edc9f4d978b45dab35f5b00b5396f76713..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25995 zcmb5Vc|26n`v81rj2eTCy->!M!Ptpp8H4QET9C45YoSPG8)F+=q=^)vyolOnyCOQNJ!x}@RI(ifRe+mL(g5eSWU{T;@27w5GsPtwcj?so-PJan! z;sG^8GG`)~N090N!XdahgoF~Y7zm=#*#}ixitL-9&w4q;D&?-zuDn{U<9->)&S-WNAt1hiqd#af}Kh`S}f!hLS+ANF+oN zgN&H8QFNhV{v(&a5SagkfC2<>DM>_So-wi)^Hk*2> z=p2*rBXgn^GIev3Xe_NX91)5YCNh~9L&cJeq2^5RPk;4KM~FlfLo|SmO2%Noql6Iz zp<|Ls$!7-20?t2STG_CxaOwSeyk1^9rcdQ86$!)LlN1JG2t`q1A-GX29$v=<;Qp!y zf-`27=9z;>h%PA%Rgy#^VTch*^bSY@1g90m6iWg-1S$fGLeOwf4vzV+760RhMg+kF z7!0NjCka@x&XcqzM&thrJK)tCvc_i7%wsXYxLlqUccXf7 z^Z_vD8ewQjtw<;jczr3Oa2>pk-U$vTmgZuUF_ysAgm8?QMEXlml+^yp5NV7oljC-U z=X47r`Qp09Hv8g!r$qh?Hrggc&^lboQ7bDUjQF`+~!6hSk;H{8w zJY;pol%v)7#tF@+$+amsgs$0xM%TUC&iu+#v%8}B(3TsHZyZ2pWM2$`RjG>62~SF|`AUp}qCk~pY5ik}+P6UPsf)P(d zKd&LKZ7YQBJZGO31tzPstzHyXu{k%+m*R)Zl|tqUld~xqo~FX41ZOM+2Ft#gDV8WN}-f}ds-CX3QV z2ouf*WRaPA%I@6=g|xd`&YZn>z4z~bQ&-5L)}Q=2_l}K>3k!q><{y(oGc`!lbTi}p zcvtSfD3$|Z5fXDjm?+X79rxeB6V=BAOIqeE+mS}XtwsGU-p)S^ z?-LIVm~k&|ZL9U>Az@gENgGRHESwJs9Rn&^4h(J)te{jf!8yqE5i${QC}5nXbfQ#z z4iV;>ccGh_RVJ!<7>rQ%e-V9FR)C##c{R-=Of4E5NYGmS6B5`Z@z%<@BXH+(5r2Z) z#-=^?*V5+PGC|+?Nng`SV+WlP-o>rJ6SjJY3!4v@u3hSA)_Ay>;Ks(UCa?ph&Qo-* zrWr8mVTo)gEUW`$>vb*r+^ar*ktf-HtP0`^NYV+r60^FYMGt7Xb#)I zfBySwm@gFS^?K|-`uNL59*I7k31LYfj(`aRbN~8>$2da0u_cXLxB!yCV-1WIn8e)@ zMmoCV@N|_t?IWxGm{XheR7%=?dShkt@{aljU4O7NmnAK>mmM=JEmkQXCp552<8))# z2IjXZShaH`<-}wnQD6@$y5D!`?&BOuj=G!^qkE9X%$s`v7Phpx{pJasyGg*E!$0nZ=Zl^f zuJ3-ZVIcgTf6lQ1838C_si$CwKRRHDoiJj$f3_960Y#V>{*i^ zWh!wTow{Z6$l!!7=Zux91mnSl;OB}t3JjVm!y9Q|&ZHE)R2!|lFy_3v9a8qL6La8N z%4)4jMcrJ1m)e}?g^$7`dEcky0Xyq|*@+)tI`X_N`03z4S)m*vv-A3!mV0v{*nVJa z3@clL%CoQ9<)PH{=MQJ^SWmwU-hkry%dwuZj-LT@T{3?jOB7Ai^=arZBNx zz3uaH%I|pBDfJCl{P<$J;LDbt!ZrWKJGEA8ad^Sl)dbcA{pl0t<%9-fE*rmq;IObS zlZmZtT1`KFcKA?8JQ9ctY0M0Kgs60}(vpbcqMW~R^RYH%8r}3j0W3_1Cs*12JM

*$pEQO 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 diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/Contents.json b/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/Contents.json deleted file mode 100644 index 35b43e987..000000000 --- a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "powered-by.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/powered-by.png b/ios-runner/AgentDeviceRunner/AgentDeviceRunner/Assets.xcassets/PoweredBy.imageset/powered-by.png deleted file mode 100644 index 5377d689d084bbc6c97d40f4b0a9965dc57b92ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6006 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 Date: Sun, 28 Jun 2026 08:29:37 +0200 Subject: [PATCH 5/6] perf: use concrete simulator for xcuitest script builds --- scripts/build-xcuitest-apple.sh | 38 +++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/scripts/build-xcuitest-apple.sh b/scripts/build-xcuitest-apple.sh index 8c367309f..0da8613a3 100644 --- a/scripts/build-xcuitest-apple.sh +++ b/scripts/build-xcuitest-apple.sh @@ -25,13 +25,13 @@ is_truthy() { resolve_default_destination() { case "$PLATFORM" in ios) - printf '%s\n' 'generic/platform=iOS Simulator' + resolve_simulator_destination 'iOS' 'iPhone' || printf '%s\n' 'generic/platform=iOS Simulator' ;; macos) printf 'platform=macOS,arch=%s\n' "$(uname -m)" ;; tvos) - printf '%s\n' 'generic/platform=tvOS Simulator' + resolve_simulator_destination 'tvOS' 'Apple TV' || printf '%s\n' 'generic/platform=tvOS Simulator' ;; *) echo "Unsupported AGENT_DEVICE_XCUITEST_PLATFORM: $PLATFORM" >&2 @@ -40,6 +40,40 @@ resolve_default_destination() { esac } +resolve_simulator_destination() { + command -v node >/dev/null 2>&1 || return 1 + node -e ' +const { execFileSync } = require("node:child_process"); +const platformName = process.argv[1]; +const deviceNamePattern = new RegExp(process.argv[2]); +const platformNameLower = platformName.toLowerCase(); +try { + const output = execFileSync("xcrun", ["simctl", "list", "devices", "available", "-j"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + timeout: 3000, + }); + const parsed = JSON.parse(output); + const devices = Object.entries(parsed.devices ?? {}) + .filter(([runtime]) => runtime.toLowerCase().includes(platformNameLower)) + .flatMap(([, runtimeDevices]) => Array.isArray(runtimeDevices) ? runtimeDevices : []) + .filter( + (device) => + device && + device.isAvailable !== false && + typeof device.udid === "string" && + typeof device.name === "string" && + deviceNamePattern.test(device.name), + ); + const selected = devices.find((device) => device.state === "Booted") ?? devices[0]; + if (!selected) process.exit(1); + console.log(`platform=${platformName} Simulator,id=${selected.udid}`); +} catch { + process.exit(1); +} +' "$1" "$2" +} + resolve_default_derived_path() { case "$PLATFORM" in ios) From c4abfa4228287281ee5e9aefe2d0d1aef4d633fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Sun, 28 Jun 2026 21:08:36 +0200 Subject: [PATCH 6/6] perf: show cold Apple runner startup progress --- src/__tests__/cli-network.test.ts | 24 +++++++++ src/__tests__/daemon-client-progress.test.ts | 46 ++++++++++++++++ src/cli.ts | 4 +- src/contracts.ts | 2 +- src/daemon-client-progress.ts | 20 +++++-- src/daemon/request-progress-protocol.ts | 2 +- src/daemon/request-progress.ts | 11 +++- .../ios/__tests__/runner-client.test.ts | 41 ++++++++++++++ .../ios/__tests__/runner-session.test.ts | 54 +++++++++++++++++++ src/platforms/ios/runner-session.ts | 8 +++ src/platforms/ios/runner-xctestrun.ts | 6 +++ 11 files changed, 209 insertions(+), 9 deletions(-) diff --git a/src/__tests__/cli-network.test.ts b/src/__tests__/cli-network.test.ts index bdbef7d7f..76b66f654 100644 --- a/src/__tests__/cli-network.test.ts +++ b/src/__tests__/cli-network.test.ts @@ -109,6 +109,30 @@ test('network dump prints parsed entries and metadata', async () => { assert.match(result.stderr, /best-effort parser/); }); +test('non-json commands opt into generic progress streaming', async () => { + const result = await runCliCapture(['snapshot'], async () => ({ + ok: true, + data: { nodes: [], truncated: false }, + })); + + assert.equal(result.code, null); + assert.equal(result.calls.length, 1); + assert.equal(result.calls[0]?.command, 'snapshot'); + assert.equal(result.calls[0]?.meta?.requestProgress, 'command'); +}); + +test('json commands do not opt into progress streaming', async () => { + const result = await runCliCapture(['snapshot', '--json'], async () => ({ + ok: true, + data: { nodes: [], truncated: false }, + })); + + assert.equal(result.code, null); + assert.equal(result.calls.length, 1); + assert.equal(result.calls[0]?.command, 'snapshot'); + assert.equal(result.calls[0]?.meta?.requestProgress, undefined); +}); + test('test command prints suite summary and exits non-zero on failures', async () => { const result = await runCliCapture(['test', './suite'], async () => makeReplaySuiteResponse()); diff --git a/src/__tests__/daemon-client-progress.test.ts b/src/__tests__/daemon-client-progress.test.ts index 0a552409d..bc5254570 100644 --- a/src/__tests__/daemon-client-progress.test.ts +++ b/src/__tests__/daemon-client-progress.test.ts @@ -129,6 +129,52 @@ test('readDaemonSocketProgressResponse parses split progress lines before respon } }); +test('readDaemonSocketProgressResponse renders generic command progress', async () => { + const socket = createMockSocket(); + const req: DaemonRequest = { + session: 'default', + command: 'snapshot', + positionals: [], + flags: {}, + token: 'secret', + meta: { requestId: 'req-command-progress', requestProgress: 'command' }, + }; + let stderr = ''; + const originalStderrWrite = process.stderr.write.bind(process.stderr); + + try { + (process.stderr as any).write = ((chunk: unknown) => { + stderr += String(chunk); + return true; + }) as typeof process.stderr.write; + + const responsePromise = readSocketProgressResponse(socket, req); + socket.emit( + 'data', + `${JSON.stringify({ + type: 'progress', + event: { + type: 'command', + status: 'progress', + message: 'Building Apple runner...', + }, + })}\n`, + ); + socket.emit( + 'data', + `${JSON.stringify({ + type: 'response', + response: { ok: true, data: { via: 'command-progress' } }, + })}\n`, + ); + + assert.deepEqual(await responsePromise, { ok: true, data: { via: 'command-progress' } }); + assert.equal(stderr, 'Building Apple runner...\n'); + } finally { + process.stderr.write = originalStderrWrite; + } +}); + test('readDaemonSocketProgressResponse rewrites live progress and clears it for final result', async () => { const socket = createMockSocket(); const req: DaemonRequest = { diff --git a/src/cli.ts b/src/cli.ts index 89505716a..45454f9ea 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -558,13 +558,13 @@ function createCliDaemonTransport(options: { transport: AgentDeviceDaemonTransport; }): AgentDeviceDaemonTransport { const { command, flags, transport } = options; - if (command !== 'test' || flags.json) return transport; + if (flags.json) return transport; return async (req) => await transport({ ...req, meta: { ...req.meta, - requestProgress: 'replay-test', + requestProgress: command === 'test' ? 'replay-test' : 'command', }, }); } diff --git a/src/contracts.ts b/src/contracts.ts index 1b5d808e2..d55702b1c 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -80,7 +80,7 @@ export type DaemonRequestMeta = { materializationId?: string; lockPolicy?: DaemonLockPolicy; lockPlatform?: PlatformSelector; - requestProgress?: 'replay-test'; + requestProgress?: 'replay-test' | 'command'; }; export type DaemonRequest = { diff --git a/src/daemon-client-progress.ts b/src/daemon-client-progress.ts index 26a647027..11481730a 100644 --- a/src/daemon-client-progress.ts +++ b/src/daemon-client-progress.ts @@ -6,7 +6,7 @@ import type { RequestProgressEvent } from './daemon/request-progress.ts'; import { consumeTextLines } from './utils/line-stream.ts'; import { createReplayTestProgressRenderer, - type ReplayTestProgressRenderer, + type ReplayTestProgressRender, } from './cli-test-progress.ts'; import { isDaemonProgressEnvelope, @@ -14,17 +14,29 @@ import { shouldStreamRequestProgress, } from './daemon/request-progress-protocol.ts'; -function createRequestProgressRenderer(req: DaemonRequest): ReplayTestProgressRenderer { - return createReplayTestProgressRenderer({ +type RequestProgressRenderer = { + render(event: RequestProgressEvent): ReplayTestProgressRender | undefined; +}; + +function createRequestProgressRenderer(req: DaemonRequest): RequestProgressRenderer { + const replayProgressRenderer = createReplayTestProgressRenderer({ verbose: Boolean(req.flags?.verbose || req.meta?.debug), liveProgress: shouldRenderLiveProgress(), columns: process.stderr.columns, }); + return { + render(event) { + if (event.type === 'command') { + return { text: event.message, newline: true }; + } + return replayProgressRenderer.render(event); + }, + }; } function writeRequestProgressEvent( event: RequestProgressEvent, - renderer: ReplayTestProgressRenderer, + renderer: RequestProgressRenderer, ): void { const output = renderer.render(event); if (!output) return; diff --git a/src/daemon/request-progress-protocol.ts b/src/daemon/request-progress-protocol.ts index cf580da78..5af4998cd 100644 --- a/src/daemon/request-progress-protocol.ts +++ b/src/daemon/request-progress-protocol.ts @@ -12,7 +12,7 @@ export type DaemonResponseEnvelope = { }; export function shouldStreamRequestProgress(req: Pick): boolean { - return req.meta?.requestProgress === 'replay-test'; + return req.meta?.requestProgress === 'replay-test' || req.meta?.requestProgress === 'command'; } export function isDaemonProgressEnvelope(value: unknown): value is DaemonProgressEnvelope { diff --git a/src/daemon/request-progress.ts b/src/daemon/request-progress.ts index 90c950eec..88aed6d81 100644 --- a/src/daemon/request-progress.ts +++ b/src/daemon/request-progress.ts @@ -32,7 +32,16 @@ export type ReplayTestProgressEvent = { deviceId?: string; }; -export type RequestProgressEvent = ReplayTestSuiteProgressEvent | ReplayTestProgressEvent; +export type CommandProgressEvent = { + type: 'command'; + status: 'progress'; + message: string; +}; + +export type RequestProgressEvent = + | ReplayTestSuiteProgressEvent + | ReplayTestProgressEvent + | CommandProgressEvent; export type RequestProgressSink = (event: RequestProgressEvent) => void; export type ReplayTestActionProgressContext = Omit< ReplayTestProgressEvent, diff --git a/src/platforms/ios/__tests__/runner-client.test.ts b/src/platforms/ios/__tests__/runner-client.test.ts index b1cb4a96d..998d4780f 100644 --- a/src/platforms/ios/__tests__/runner-client.test.ts +++ b/src/platforms/ios/__tests__/runner-client.test.ts @@ -30,6 +30,10 @@ vi.mock('../runner-macos-products.ts', async () => { }); import type { DeviceInfo } from '../../../utils/device.ts'; +import { + type RequestProgressEvent, + withRequestProgressSink, +} from '../../../daemon/request-progress.ts'; import { flushDiagnosticsToSessionFile, withDiagnosticsScope } from '../../../utils/diagnostics.ts'; import { AppError } from '../../../utils/errors.ts'; import { isReadOnlyRunnerCommand } from '../runner-command-traits.ts'; @@ -1290,6 +1294,43 @@ test('ensureXctestrunArtifact passes sandbox-disabling settings to xcodebuild', assert.equal(args.includes('OTHER_SWIFT_FLAGS=$(inherited) -disable-sandbox'), true); }); +test('ensureXctestrunArtifact emits build progress on cache miss', async () => { + const projectRoot = repoRoot; + const tmpDir = await makeProjectTmpDir(); + const derivedPath = path.join(tmpDir, 'custom-derived'); + const rebuiltXctestrunPath = path.join(derivedPath, 'Build', 'Products', 'rebuilt.xctestrun'); + const events: RequestProgressEvent[] = []; + + withRunnerDerivedPathEnv(derivedPath); + + mockRunCmdStreaming.mockImplementationOnce(async () => { + await fs.promises.mkdir(path.join(derivedPath, 'Build', 'Products', 'Runner.app'), { + recursive: true, + }); + writeXctestrunFixture(rebuiltXctestrunPath, { + projectRoot, + productRelativePaths: ['Runner.app'], + }); + }); + + const result = await withRequestProgressSink( + (event) => events.push(event), + async () => + await ensureXctestrunArtifact(iosSimulator, { + forceRunnerXctestrunRebuild: true, + }), + ); + + assert.equal(result.xctestrunPath, rebuiltXctestrunPath); + assert.deepEqual(events, [ + { + type: 'command', + status: 'progress', + message: 'Building Apple runner...', + }, + ]); +}); + test('ensureXctestrunArtifact stress-recovers after a bad restored artifact', async () => { const projectRoot = repoRoot; const tmpDir = await makeProjectTmpDir(); diff --git a/src/platforms/ios/__tests__/runner-session.test.ts b/src/platforms/ios/__tests__/runner-session.test.ts index 14e6ff3eb..091fc8d29 100644 --- a/src/platforms/ios/__tests__/runner-session.test.ts +++ b/src/platforms/ios/__tests__/runner-session.test.ts @@ -5,6 +5,10 @@ import os from 'node:os'; import path from 'node:path'; import { beforeEach, test, vi } from 'vitest'; import { IOS_DEVICE, IOS_SIMULATOR } from '../../../__tests__/test-utils/index.ts'; +import { + type RequestProgressEvent, + withRequestProgressSink, +} from '../../../daemon/request-progress.ts'; import { AppError } from '../../../utils/errors.ts'; import { flushDiagnosticsToSessionFile, withDiagnosticsScope } from '../../../utils/diagnostics.ts'; import type { RunnerSession } from '../runner-session-types.ts'; @@ -611,6 +615,56 @@ test('runner session starts xcodebuild through provider seams and reuses an aliv }); }); +test('runner session emits XCTest startup progress only after a runner rebuild', async () => { + const rebuiltDevice = { ...IOS_SIMULATOR, id: 'runner-session-rebuilt-progress-sim' }; + const rebuiltEvents: RequestProgressEvent[] = []; + + await withRequestProgressSink( + (event) => rebuiltEvents.push(event), + async () => { + await ensureRunnerSession(rebuiltDevice, {}); + }, + ); + + assert.deepEqual(rebuiltEvents, [ + { + type: 'command', + status: 'progress', + message: 'Starting XCTest runner...', + }, + ]); + + await abortAllIosRunnerSessions(); + vi.clearAllMocks(); + mockEnsureXctestrunArtifact.mockResolvedValue({ + xctestrunPath: '/tmp/cached-runner.xctestrun', + derived: '/tmp/derived', + cache: 'hit', + artifact: 'valid', + buildMs: 0, + xctestrunPathSource: 'manifest', + }); + mockGetFreePort.mockResolvedValue(8123); + mockPrepareXctestrunWithEnv.mockResolvedValue({ + xctestrunPath: '/tmp/session-runner.xctestrun', + jsonPath: '/tmp/session-runner.json', + }); + mockAcquireXcodebuildSimulatorSetRedirect.mockResolvedValue({ release: mockRedirectRelease }); + mockRunCmdBackground.mockReturnValue(makeBackgroundRunner(4242)); + mockWaitForRunner.mockResolvedValue(runnerResponse({ uptimeMs: 1 })); + + const cachedDevice = { ...IOS_SIMULATOR, id: 'runner-session-cached-progress-sim' }; + const cachedEvents: RequestProgressEvent[] = []; + await withRequestProgressSink( + (event) => cachedEvents.push(event), + async () => { + await ensureRunnerSession(cachedDevice, {}); + }, + ); + + assert.deepEqual(cachedEvents, []); +}); + test('runner session startup diagnostics include logical lease context', async () => { const device = { ...IOS_SIMULATOR, id: 'runner-session-lease-context-sim' }; diff --git a/src/platforms/ios/runner-session.ts b/src/platforms/ios/runner-session.ts index 516af68d4..ecf54ad72 100644 --- a/src/platforms/ios/runner-session.ts +++ b/src/platforms/ios/runner-session.ts @@ -5,6 +5,7 @@ import { Deadline } from '../../utils/retry.ts'; import type { DeviceInfo } from '../../utils/device.ts'; import type { RunnerLogicalLeaseContext } from '../../core/runner-lease-context.ts'; import type { AppleRunnerLifecycleOptions } from './runner-provider.ts'; +import { emitRequestProgress } from '../../daemon/request-progress.ts'; import { emitDiagnostic, withDiagnosticTimer } from '../../utils/diagnostics.ts'; import { buildSimctlArgsForDevice } from './simctl.ts'; import { runAppleToolCommand, runXcrun } from './tool-provider.ts'; @@ -192,6 +193,13 @@ async function startRunnerSessionWithLease( resolveRunnerDestination(device), ]; try { + if (xctestrunArtifact.buildMs > 0) { + emitRequestProgress({ + type: 'command', + status: 'progress', + message: 'Starting XCTest runner...', + }); + } ({ child, wait: testPromise } = await measureRunnerStartupStep( startupTimings, 'launch_xcodebuild', diff --git a/src/platforms/ios/runner-xctestrun.ts b/src/platforms/ios/runner-xctestrun.ts index 94b350880..9562737c1 100644 --- a/src/platforms/ios/runner-xctestrun.ts +++ b/src/platforms/ios/runner-xctestrun.ts @@ -11,6 +11,7 @@ import { isEnvTruthy } from '../../utils/retry.ts'; import type { DeviceInfo } from '../../utils/device.ts'; import type { DefinedEnvMap as EnvMap } from '../../utils/env-map.ts'; import { withKeyedLock } from '../../utils/keyed-lock.ts'; +import { emitRequestProgress } from '../../daemon/request-progress.ts'; import { emitDiagnostic } from '../../utils/diagnostics.ts'; import { findProjectRoot, readVersion } from '../../utils/version.ts'; import { resolveRunnerBuildFailureHint } from './runner-contract.ts'; @@ -631,6 +632,11 @@ async function buildXctestrunArtifact(params: { } const buildStartedAt = Date.now(); + emitRequestProgress({ + type: 'command', + status: 'progress', + message: 'Building Apple runner...', + }); await buildRunnerXctestrun(device, projectPath, derived, options); const buildMs = Math.max(0, Date.now() - buildStartedAt);