An XCUITest flake is a UI test failure that moves between pass and fail without a matching source change. The failure usually comes from an unstable selector, an element that appears later than the test expects, leftover app state, simulator state, a network dependency, or a CI environment difference.
Xcode test reports and .xcresult bundles give the failure message, screenshot, activity timeline, and run destination for each attempt. Running the same selected test repeatedly with xcodebuild makes the intermittent failure easier to catch while keeping the evidence for the exact test method.
A short investigation should happen before retry behavior becomes a permanent CI setting. Retry on failure can collect useful data while debugging, but a test that passes only after retries can still hide a product bug or a test that races the app.
Related: How to run XCUITest tests locally
Related: How to save XCUITest result bundles
Related: How to view device logs for XCUITest tests
Related: How to retry failed XCUITest tests
xcodebuild test identifiers use the form TestTarget/TestClass/TestMethod, such as MyAppUITests/LoginUITests/testValidLogin.
$ mkdir -p TestResults
$ xcodebuild test \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \ -only-testing:MyAppUITests/LoginUITests/testValidLogin \ -test-iterations 20 \ -run-tests-until-failure \ -test-repetition-relaunch-enabled YES \ -resultBundlePath TestResults/LoginFlake.xcresult Test Suite 'Selected tests' started. Test Case '-[MyAppUITests.LoginUITests testValidLogin]' passed (4.218 seconds). ##### snipped ##### Test Case '-[MyAppUITests.LoginUITests testValidLogin]' failed (5.704 seconds). ** TEST FAILED **
-run-tests-until-failure stops when the selected test fails or when -test-iterations reaches the maximum. -test-repetition-relaunch-enabled YES starts a new test process for each repetition so leftover in-process state is less likely to mask the failure.
$ open TestResults/LoginFlake.xcresult
Select the failed test run, then compare the failure message, screenshot, activity timeline, and run destination. Record whether the failure is a missing element, wrong element, timeout, app crash, network response, permission prompt, or simulator launch problem.
Related: How to save XCUITest result bundles
$ xcrun simctl spawn booted log stream \ --style compact \ --level info \ --timeout 10m \ --predicate 'subsystem == "com.example.MyApp"' \ > TestResults/LoginFlake-device.log
Start the log stream in a second terminal before repeating the selected test. Replace com.example.MyApp with the app's OSLog subsystem, or use --process MyApp when the app logs do not use a subsystem.
Related: How to view device logs for XCUITest tests
If the element exists in the screenshot but the test cannot find it, fix the accessibility identifier or query. If the element appears late, replace fixed sleeps with an explicit wait. If the first repetition passes and later repetitions fail, reset app or simulator state. If the failure appears only in CI, compare simulator model, OS, locale, permissions, network stubs, and launch arguments.
Do not mark the test fixed because -retry-tests-on-failure eventually passes. Use retry only as temporary evidence while the underlying selector, wait, state reset, network stub, permission path, or simulator setup is corrected.
$ xcodebuild test \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \ -only-testing:MyAppUITests/LoginUITests/testValidLogin \ -test-iterations 20 \ -test-repetition-relaunch-enabled YES \ -resultBundlePath TestResults/LoginFlakeFixed.xcresult Test Suite 'Selected tests' started. Test Case '-[MyAppUITests.LoginUITests testValidLogin]' passed (4.031 seconds). ##### snipped ##### Test Case '-[MyAppUITests.LoginUITests testValidLogin]' passed (3.912 seconds). ** TEST SUCCEEDED **
A clean repeated run does not prove every simulator, locale, or CI runner is covered, but it proves the original selected test no longer fails under the same local repetition path. Keep both result bundles when the cause needs review; the failed bundle shows the original symptom, and the fixed bundle shows the same filtered test passing after the change.