From 11e2cc021783b5f3ca010a00eab94db3c9404fb4 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 15 Jul 2026 08:40:01 +0200 Subject: [PATCH 1/2] perf: cut XCTest permission watchdog polling cost and make interval tunable --- .../cut-xctest-permission-watchdog-polling.md | 5 +++++ packages/platform-ios/src/xctest-agent.ts | 9 +++++++++ .../HarnessXCTestAgentUITests.swift | 18 ++++++++++++++++-- .../PermissionPromptWatchdog.swift | 8 ++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 .nx/version-plans/cut-xctest-permission-watchdog-polling.md diff --git a/.nx/version-plans/cut-xctest-permission-watchdog-polling.md b/.nx/version-plans/cut-xctest-permission-watchdog-polling.md new file mode 100644 index 00000000..8af00b5f --- /dev/null +++ b/.nx/version-plans/cut-xctest-permission-watchdog-polling.md @@ -0,0 +1,5 @@ +--- +__default__: patch +--- + +The iOS permission-dialog watchdog now uses far less CPU, and its polling interval is tunable via HARNESS_XCTEST_AGENT_TICK_INTERVAL_MS. diff --git a/packages/platform-ios/src/xctest-agent.ts b/packages/platform-ios/src/xctest-agent.ts index 00e3034e..fc0d657c 100644 --- a/packages/platform-ios/src/xctest-agent.ts +++ b/packages/platform-ios/src/xctest-agent.ts @@ -25,6 +25,8 @@ const xctestAgentLogger = logger.child('ios-xctest-agent'); const XCTEST_AGENT_PROJECT_NAME = 'HarnessXCTestAgent'; const XCTEST_AGENT_SCHEME_NAME = 'HarnessXCTestAgent'; const XCTEST_AGENT_PORT_ENV = 'HARNESS_XCTEST_AGENT_PORT'; +const XCTEST_AGENT_TICK_INTERVAL_MS_ENV = + 'HARNESS_XCTEST_AGENT_TICK_INTERVAL_MS'; const XCTEST_AGENT_TARGET_BUNDLE_ID_ENV = 'HARNESS_XCTEST_AGENT_TARGET_BUNDLE_ID'; const XCTEST_AGENT_XCTESTRUN_FILE_ENV = 'HARNESS_IOS_XCTESTRUN_FILE'; @@ -937,6 +939,8 @@ export const createXCTestAgentController = (options: { let processTask: Promise | null = null; const getLaunchEnvironment = (): Record => { + const tickIntervalMs = getEnvironmentPath(XCTEST_AGENT_TICK_INTERVAL_MS_ENV); + return Object.assign( {}, options.appBundleId @@ -944,6 +948,11 @@ export const createXCTestAgentController = (options: { [XCTEST_AGENT_TARGET_BUNDLE_ID_ENV]: options.appBundleId, } : {}, + tickIntervalMs + ? { + [XCTEST_AGENT_TICK_INTERVAL_MS_ENV]: tickIntervalMs, + } + : {}, ...capabilities.map( (capability) => capability.getLaunchEnvironment?.() ?? {} ) diff --git a/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/HarnessXCTestAgentUITests.swift b/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/HarnessXCTestAgentUITests.swift index 31abf4b7..736e52b8 100644 --- a/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/HarnessXCTestAgentUITests.swift +++ b/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/HarnessXCTestAgentUITests.swift @@ -180,11 +180,24 @@ private final class XCTestAgentHTTPServer { final class HarnessXCTestAgentUITests: XCTestCase { private enum Environment { static let targetBundleIdentifier = "HARNESS_XCTEST_AGENT_TARGET_BUNDLE_ID" + static let tickIntervalMs = "HARNESS_XCTEST_AGENT_TICK_INTERVAL_MS" } private enum Constants { static let defaultSessionDuration: TimeInterval = 60 * 60 - static let tickInterval: TimeInterval = 1 + static let defaultTickInterval: TimeInterval = 1 + } + + private static func resolveTickInterval() -> TimeInterval { + guard + let rawValue = ProcessInfo.processInfo.environment[Environment.tickIntervalMs], + let milliseconds = Int(rawValue), + milliseconds > 0 + else { + return Constants.defaultTickInterval + } + + return TimeInterval(milliseconds) / 1000 } private let state = HarnessXCTestAgentState( @@ -194,6 +207,7 @@ final class HarnessXCTestAgentUITests: XCTestCase { private let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") private var capabilities: [AgentCapability] = [] private var httpServer: XCTestAgentHTTPServer? + private let tickInterval = HarnessXCTestAgentUITests.resolveTickInterval() private func log(_ message: String) { NSLog("[HarnessXCTestAgent] %@", message) @@ -309,7 +323,7 @@ final class HarnessXCTestAgentUITests: XCTestCase { } RunLoop.current.run( - until: Date().addingTimeInterval(Constants.tickInterval) + until: Date().addingTimeInterval(tickInterval) ) } diff --git a/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/PermissionPromptWatchdog.swift b/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/PermissionPromptWatchdog.swift index 7b0631f8..8a732266 100644 --- a/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/PermissionPromptWatchdog.swift +++ b/packages/platform-ios/xctest-agent/HarnessXCTestAgentUITests/PermissionPromptWatchdog.swift @@ -54,6 +54,14 @@ final class PermissionPromptWatchdog: AgentCapability { return } + let guardQuery = springboard.buttons.matching( + NSPredicate(format: "label IN %@", Constants.knownPositiveButtonLabels) + ).firstMatch + + guard guardQuery.exists else { + return + } + for label in Constants.knownPositiveButtonLabels { let button = springboard.buttons[label].firstMatch From 4ff6f269395ca7b026da335f5e0cb05f49ba9e23 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Tue, 14 Jul 2026 22:03:36 +0200 Subject: [PATCH 2/2] test: raise playground testTimeout to 10s for slow CI UI interactions --- apps/playground/rn-harness.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/playground/rn-harness.config.mjs b/apps/playground/rn-harness.config.mjs index 1053d2f9..bb4badfe 100644 --- a/apps/playground/rn-harness.config.mjs +++ b/apps/playground/rn-harness.config.mjs @@ -116,6 +116,7 @@ export default { defaultRunner: 'android', platformReadyTimeout: 300000, bridgeTimeout: 120000, + testTimeout: 10000, permissions: true, detectNativeCrashes: true,