SKILL.md
SKILL: XCUIAutomation — Critical iOS Workflow Testing
Summary
This skill is an expert in XCUIAutomation (XCUITest) for iOS applications, with a strict focus on validating critical user workflows.
It prioritizes:
- Deterministic UI automation
- Accessibility-driven querying
- End-to-end workflow validation
- Maintainable, low-flakiness tests
It explicitly rejects snapshot, screenshot, or visual-diff testing.
Scope & Non-Goals
In Scope
- XCUIAutomation / XCUITest
- XCUIApplication lifecycle control
- XCUIElementQuery querying strategies
- XCUIElement interaction & assertions
- Testing real user flows (login, onboarding, CRUD, navigation)
Out of Scope
- Screenshot testing
- Snapshot testing
- Pixel comparison
- Visual diffing
- Internal UIKit / SwiftUI state inspection
All validation must be done via observable UI behavior.
Core Philosophy
- Test workflows, not views
- Assert behavior, not appearance
- Query by accessibility, never layout
- Fail fast, fail clearly
- Flakiness is a design bug
Mental Model
XCUIAutomation tests the app out-of-process, through the accessibility layer.
You are not:
- Calling UIKit
- Touching SwiftUI
- Inspecting internal state
You are:
- Acting as a real user
- Discovering elements via accessibility
- Interacting via taps, typing, gestures
- Verifying externally observable results
Core Types & Responsibilities
XCUIApplication
Represents the application under test.
Responsibilities:
- Launching and terminating the app
- Acting as the root element query
- Managing launch arguments and environment
Example:
let app = XCUIApplication()
app.launch()
Best Practices:
- Launch once per test unless isolation requires otherwise
- Use launch arguments to control app state
- Never assume initial UI state without asserting it
XCUIElementQuery
Used to locate UI elements.
Principles:
- Prefer accessibility identifiers
- Avoid positional queries
- Use predicates for dynamic content
- Always narrow queries intentionally
Good:
app.buttons["loginButton"]
Acceptable:
app.cells.containing(.staticText, identifier: "Task").firstMatch
Avoid:
app.otherElements.element(boundBy: 3)
XCUIElement
Represents a concrete UI element.
Primary Capabilities:
- Interaction (tap, typeText, swipe)
- State validation (exists, isHittable, value)
- Synchronization (waitForExistence)
Golden Rule: Never interact with an element before asserting it exists.
Example:
let submit = app.buttons["Submit"]
XCTAssertTrue(submit.waitForExistence(timeout: 5))
submit.tap()
Test Structure Pattern
All tests should follow this structure:
- Arrange – launch app and assert starting state
- Act – perform user interactions
- Assert – verify resulting UI state
- Isolate – leave no shared state behind
Example:
func testUserCanLogIn() {
let app = XCUIApplication()
app.launch()
let username = app.textFields["username"]
XCTAssertTrue(username.waitForExistence(timeout: 5))
username.tap()
username.typeText("testuser")
let password = app.secureTextFields["password"]
password.tap()
password.typeText("password")
app.buttons["login"].tap()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
}
Assertions: What Is Valid
Valid Assertions:
- Element existence
- Element hittability
- Navigation completion
- Text values
- Enabled / disabled state
- Alerts appearing
- Screen transitions
Invalid Assertions:
- Screenshot comparisons
- Pixel alignment
- Font size
- Color checks
- Layout spacing
If a user cannot perceive it functionally, it should not be asserted.
Synchronization Rules
Required:
- waitForExistence(timeout:) for async UI
- Assertions before interactions
Forbidden:
- sleep()
- Arbitrary delays
- Timing-based assumptions
Accessibility Requirements
This skill assumes:
- Every critical interactive element has a stable accessibility identifier
- Identifiers are semantic, not visual
Bad: button1
Good: addTaskButton
If an element is hard to test, fix accessibility, not the test.
Navigation & Gestures
Supported interactions:
- tap()
- typeText(\_:)
- swipeUp() / swipeDown()
- press(forDuration:)
Avoid gesture-heavy flows unless they are core to the app experience.
Handling Flakiness
Flakiness is addressed by:
- Strong queries
- Explicit waits
- Clear state assertions
- Test isolation
Never fix flakiness by:
- Increasing timeouts blindly
- Adding sleeps
- Retrying without understanding failure
Performance Tests
Performance tests:
- Should not use @MainActor
- Must avoid artificial serialization
- Measure real user-visible behavior only
Example:
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
Actor & Concurrency Rules
- UI tests do not require @MainActor
- XCUIAutomation is already serialized and synchronized
- Use @MainActor only in unit tests touching app code directly
Example Workflows to Automate
- App launch & first render
- Authentication (login / logout)
- Onboarding completion
- Creating, editing, deleting data
- Tab & navigation flows
- Error handling & validation states
Each workflow must:
- Start from a known state
- End with a clear assertion
- Be readable without comments
Final Rule
If a test does not prove that a user can complete a critical task, it does not belong in the UI test suite.