XCTest and XCUITest execution workflows and flaky test detection patterns
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Comprehensive guide to XCTest and XCUITest execution, analysis, and flaky test detection
This skill provides patterns and workflows for executing iOS tests using XCTest and XCUITest frameworks. It covers test execution strategies, result analysis, flaky test detection, and troubleshooting common test failures.
Use this skill when:
| Task | Operation | Key Parameters |
|---|---|---|
| Run all tests | test | scheme, destination |
| Run specific test | test | scheme, only_testing |
| Skip tests | test | scheme, skip_testing |
| Use test plan | test | scheme, test_plan |
| Parallel execution | test | destination (multiple) |
Use ios-testing-patterns when:
Related Skills:
XCTest (Unit/Integration Tests):
XCUITest (UI Tests):
Sequential Execution:
Parallel Execution:
Test Sharding:
Basic Unit Test Execution:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15,OS=18.0"
}
Note: The destination parameter now supports auto-resolution! You can omit the OS version (e.g., "platform=iOS Simulator,name=iPhone 15") and the tool will automatically select the latest available OS version for that device.
Run Specific Test Class:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15,OS=18.0",
"options": {
"only_testing": ["MyAppTests/NetworkTests"]
}
}
Run Specific Test Method:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15,OS=18.0",
"options": {
"only_testing": ["MyAppTests/NetworkTests/testAPIRequest"]
}
}
Basic UI Test Execution:
{
"operation": "test",
"scheme": "MyAppUITests",
"destination": "platform=iOS Simulator,name=iPhone 15"
}
UI Tests with Specific Device:
{
"operation": "test",
"scheme": "MyAppUITests",
"destination": "platform=iOS Simulator,name=iPad Pro (12.9-inch)"
}
UI Tests with Test Plan:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"test_plan": "SmokeTests"
}
}
Multiple Destinations:
Run tests on multiple simulators simultaneously:
{
"operation": "test",
"scheme": "MyApp",
"destination": [
"platform=iOS Simulator,name=iPhone 15",
"platform=iOS Simulator,name=iPhone SE (3rd generation)"
],
"options": {
"parallel": true
}
}
Parallel Test Benefits:
Parallel Test Considerations:
Skip Specific Tests:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"skip_testing": [
"MyAppUITests/SlowTests",
"MyAppTests/NetworkTests/testLargeDownload"
]
}
}
Run Only Fast Tests:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"only_testing": ["MyAppTests/UnitTests"],
"skip_testing": ["MyAppTests/UnitTests/testSlowOperation"]
}
}
Success Response:
{
"success": true,
"tests_run": 45,
"tests_passed": 45,
"tests_failed": 0,
"execution_time": "12.4s",
"scheme": "MyApp"
}
Failure Response:
{
"success": false,
"tests_run": 45,
"tests_passed": 43,
"tests_failed": 2,
"failures": [
{
"test": "MyAppTests.LoginTests.testInvalidPassword",
"message": "XCTAssertEqual failed: (\"error\") is not equal to (\"invalid\")",
"file": "LoginTests.swift",
"line": 42
},
{
"test": "MyAppUITests.CheckoutTests.testPaymentFlow",
"message": "Failed to find button \"Confirm\"",
"file": "CheckoutTests.swift",
"line": 78
}
],
"execution_time": "18.7s"
}
Analyzing Test Results:
success statustests_failed countfailures array for detailsexecution_time for performance issuesWhat are Flaky Tests?
Tests that intermittently pass or fail without code changes:
Detection Strategy 1: Multiple Runs
Run tests multiple times to detect flakiness:
# Run tests 5 times and compare results
for i in {1..5}; do
execute_xcode_command(test, scheme, destination)
# Record results
done
Detection Strategy 2: Pattern Analysis
Look for common flaky test patterns:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"only_testing": ["MyAppTests/SuspectedFlakyTest"]
}
}
Flaky Test Indicators:
Flaky Test Workflow:
1. Identify suspect test (intermittent failures)
2. Run test 10+ times in isolation
3. Analyze failure patterns
4. Check for:
- Hard-coded waits (sleep/wait)
- Expectation timeouts too short
- Shared mutable state
- Network dependencies
- Animation timing assumptions
5. Fix root cause
6. Verify with repeated runs
Clean State Before Tests:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"clean_before_build": true
}
}
Reset Simulator State:
# Use simulator-workflows skill
execute_simulator_command(operation: "erase", udid: <device-udid>)
Test Isolation Best Practices:
setUp() and tearDown() methodsTypical Test Scheme Setup:
Multiple Test Plans:
UnitTests.xctestplan → Fast unit tests only
UITests.xctestplan → UI automation tests
SmokeTests.xctestplan → Critical path tests
RegressionTests.xctestplan → Full test suite
Pattern 1: Test Fixtures
class TestData {
static let validUser = User(name: "Test", email: "test@example.com")
static let invalidUser = User(name: "", email: "invalid")
}
Pattern 2: Factory Methods
extension User {
static func makeTestUser(name: String = "Test") -> User {
return User(name: name, email: "\(name)@test.com")
}
}
Pattern 3: Test Database
class TestDatabase {
func setupTestData() {
// Create known test data
}
func teardownTestData() {
// Clean up after tests
}
}
UI Test Screenshot Pattern:
override func setUp() {
continueAfterFailure = false
}
override func tearDown() {
if let testRun = testRun, testRun.failureCount > 0 {
let screenshot = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.lifetime = .keepAlways
add(attachment)
}
}
Execute Tests with Screenshots:
{
"operation": "test",
"scheme": "MyAppUITests",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"result_bundle_path": "./TestResults.xcresult"
}
}
XCTest Performance Measurement:
func testPerformance() {
measure {
// Code to measure
performExpensiveOperation()
}
}
Performance Test Execution:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"only_testing": ["MyAppTests/PerformanceTests"]
}
}
Causes:
Solutions:
Causes:
Solutions:
Example Fix:
// Bad: Immediate query may fail
app.buttons["Submit"].tap()
// Good: Wait for element
let submitButton = app.buttons["Submit"]
XCTAssert(submitButton.waitForExistence(timeout: 5))
submitButton.tap()
Causes:
Solutions:
Example Fix:
// Bad: May timeout
wait(for: [expectation], timeout: 1.0)
// Good: Reasonable timeout
wait(for: [expectation], timeout: 10.0)
Causes:
Solutions:
UI Test Timeouts:
{
"operation": "test",
"scheme": "MyAppUITests",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"test_timeout": 600
}
}
Common Timeout Scenarios:
App Launch Timeout: App takes too long to launch
Element Query Timeout: UI element not found
Network Timeout: API requests fail
Animation Timeout: Waiting for animation
Symptom: Tests fail due to simulator state
Common Issues:
Solutions:
# Erase simulator before tests
execute_simulator_command({
"operation": "erase",
"udid": "<simulator-udid>"
})
# Then run tests
execute_xcode_command({
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15"
})
Symptom: Tests pass individually but fail in suite
Causes:
Solutions:
override func setUp() {
super.setUp()
// Reset all shared state
AppState.shared.reset()
clearDatabase()
}
// Bad: Shared state
class APIManager {
static let shared = APIManager()
}
// Good: Dependency injection
class APIManager {
// Inject per test
}
{
"operation": "test",
"scheme": "MyApp",
"options": {
"test_order": "random"
}
}
Always isolate tests:
tearDown()Example:
class UserTests: XCTestCase {
var sut: UserManager!
override func setUp() {
super.setUp()
sut = UserManager()
}
override func tearDown() {
sut = nil
super.tearDown()
}
func testCreateUser() {
// Test uses fresh sut instance
}
}
Optimize test speed:
UI Test Speed:
// Disable animations for faster tests
app.launchArguments += ["DISABLE_ANIMATIONS"]
Use descriptive test names:
// Bad
func test1() { }
// Good
func testLoginWithValidCredentialsSucceeds() { }
func testLoginWithInvalidPasswordShowsError() { }
func testCheckoutWithEmptyCartDisplaysWarning() { }
Structure tests clearly:
func testAddItemToCart() {
// Arrange
let cart = ShoppingCart()
let item = Product(name: "Test", price: 10)
// Act
cart.add(item)
// Assert
XCTAssertEqual(cart.items.count, 1)
XCTAssertEqual(cart.total, 10)
}
Use consistent test data:
struct TestFixtures {
static let validEmail = "test@example.com"
static let invalidEmail = "invalid"
static let testUser = User(name: "Test", email: validEmail)
}
CI Test Workflow:
1. Clean build environment
2. Build app for testing
3. Boot fresh simulator
4. Run test suite
5. Collect code coverage
6. Parse test results
7. Archive test artifacts
8. Report results
CI Test Command:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"clean_before_build": true,
"code_coverage": true,
"result_bundle_path": "./TestResults.xcresult"
}
}
Maintain proper test distribution:
/\
/UI\ ← Few, slow, broad
/----\
/Integ\ ← Some, medium, focused
/------\
/ Unit \ ← Many, fast, isolated
/----------\
Recommended ratio:
Enable coverage collection:
{
"operation": "test",
"scheme": "MyApp",
"destination": "platform=iOS Simulator,name=iPhone 15",
"options": {
"code_coverage": true
}
}
Coverage goals:
Track test metrics:
When to investigate:
xc://operations/test: Complete test operations referencexc://reference/xctest: XCTest framework documentationxc://reference/xcuitest: XCUITest framework documentationxc://patterns/flaky-tests: Flaky test detection patternsTip: Isolate tests, mock dependencies, and run tests frequently during development.