Use when writing, running, or debugging XCUITests. Covers element queries, waiting strategies, accessibility identifiers, test plans, and CI/CD test execution patterns.
View on GitHubSelect agents to install to:
npx add-skill https://github.com/CharlesWiltgen/Axiom/blob/main/.claude-plugin/plugins/axiom/skills/axiom-xctest-automation/SKILL.md -a claude-code --skill axiom-xctest-automationInstallation paths:
.claude/skills/axiom-xctest-automation/# XCUITest Automation Patterns
Comprehensive guide to writing reliable, maintainable UI tests with XCUITest.
## Core Principle
**Reliable UI tests require three things**:
1. Stable element identification (accessibilityIdentifier)
2. Condition-based waiting (never hardcoded sleep)
3. Clean test isolation (no shared state)
## Element Identification
### The Accessibility Identifier Pattern
**ALWAYS use accessibilityIdentifier for test-critical elements.**
```swift
// SwiftUI
Button("Login") { ... }
.accessibilityIdentifier("loginButton")
TextField("Email", text: $email)
.accessibilityIdentifier("emailTextField")
// UIKit
loginButton.accessibilityIdentifier = "loginButton"
emailTextField.accessibilityIdentifier = "emailTextField"
```
### Query Selection Guidelines
From WWDC 2025-344 "Recording UI Automation":
1. **Localized strings change** → Use accessibilityIdentifier instead
2. **Deeply nested views** → Use shortest possible query
3. **Dynamic content** → Use generic query or identifier
```swift
// BAD - Fragile queries
app.buttons["Login"] // Breaks with localization
app.tables.cells.element(boundBy: 0).buttons.firstMatch // Too specific
// GOOD - Stable queries
app.buttons["loginButton"] // Uses identifier
app.tables.cells.containing(.staticText, identifier: "itemTitle").firstMatch
```
## Waiting Strategies
### Never Use sleep()
```swift
// BAD - Hardcoded wait
sleep(5)
XCTAssertTrue(app.buttons["submit"].exists)
// GOOD - Condition-based wait
let submitButton = app.buttons["submit"]
XCTAssertTrue(submitButton.waitForExistence(timeout: 5))
```
### Wait Patterns
```swift
// Wait for element to appear
func waitForElement(_ element: XCUIElement, timeout: TimeInterval = 10) -> Bool {
element.waitForExistence(timeout: timeout)
}
// Wait for element to disappear
func waitForElementToDisappear(_ element: XCUIElement, timeout: TimeInterval = 10) -> Bool {
let predicate = NSPredicate(format: "exists == false")
let expectation = XC