Ios ui testing
Skill almasumdev/awesome-ios-agent-skills/.github/skills/testing_and_automation/ios-ui-testing
Expert guidance on XCUITest — accessibility identifiers, robust element queries, screenshot flows, test plans, and CI stability. Use when writing end-to-end UI tests.From its SKILL.md
npx -y skills add almasumdev/awesome-ios-agent-skills --skill ios-ui-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.4 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
iOS UI Testing with XCUITest
Instructions
UI tests are expensive and flaky by default. Invest in stable selectors, deterministic launch state, and screenshot artifacts so failures are diagnosable.
1. Accessibility Identifiers Over Labels
Never query by localized label. Assign stable identifiers:
Button("Sign in") { signIn() }
.accessibilityIdentifier("signInButton")
TextField("Email", text: $email)
.accessibilityIdentifier("emailField")
Query them:
let app = XCUIApplication()
app.textFields["emailField"].typeText("[email protected]")
app.buttons["signInButton"].tap()
2. Launch Arguments & Environment
Drive app state from the test rather than tapping through it:
func test_empty_inbox() {
let app = XCUIApplication()
app.launchArguments = ["-UITests", "-InboxState", "empty"]
app.launchEnvironment = ["API_BASE_URL": "http://localhost:8080"]
app.launch()
XCTAssertTrue(app.staticTexts["inbox.empty.title"].waitForExistence(timeout: 2))
}
Inside the app:
#if DEBUG
if CommandLine.arguments.contains("-UITests") {
Bootstrap.useInMemoryStore()
if let idx = CommandLine.arguments.firstIndex(of: "-InboxState"),
CommandLine.arguments.indices.contains(idx + 1) {
InboxSeed.apply(CommandLine.arguments[idx + 1])
}
}
#endif
3. Waiting
Never Thread.sleep. Use waitForExistence(timeout:) or XCTWaiter:
let row = app.cells["article.row.123"]
XCTAssertTrue(row.waitForExistence(timeout: 5))
row.tap()
For more complex waits:
let predicate = NSPredicate(format: "isHittable == true")
let exp = expectation(for: predicate, evaluatedWith: button)
wait(for: [exp], timeout: 5)
4. Page Objects
Encapsulate queries in page objects so tests read like user flows:
struct LoginScreen {
let app: XCUIApplication
var email: XCUIElement { app.textFields["emailField"] }
var password: XCUIElement { app.secureTextFields["passwordField"] }
var signIn: XCUIElement { app.buttons["signInButton"] }
func signIn(as email: String, password pw: String) {
self.email.tap(); self.email.typeText(email)
self.password.tap(); self.password.typeText(pw)
self.signIn.tap()
}
}
5. Screenshots and Attachments
Capture a screenshot on failure:
override func tearDown() {
if let failureCount = testRun?.failureCount, failureCount > 0 {
let shot = XCUIScreen.main.screenshot()
let attach = XCTAttachment(screenshot: shot)
attach.name = name
attach.lifetime = .keepAlways
add(attach)
}
super.tearDown()
}
For release artifacts (App Store screenshots) use fastlane snapshot or a custom routine that walks every target device + locale.
6. Test Plans
Use Xcode Test Plans (.xctestplan) to:
- Run subsets (smoke vs full) from CI.
- Set default launch arguments.
- Repeat flaky tests
-retry-tests-on-failure. - Randomize order to catch hidden coupling.
7. Hermetic Tests
- Stub the network with a local mock server or
URLProtocolinjection gated by a launch argument. - Reset
UserDefaultsand the Keychain for the app's suite at test start. - Seed SwiftData/Core Data with an in-memory store during UI tests.
if ProcessInfo.processInfo.arguments.contains("-UITests") {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
}
8. Flakiness Patrol
Common causes and fixes:
| Symptom | Fix |
|---|---|
| Element not found intermittently | Query by identifier, use waitForExistence |
| Scroll-to-cell misses | app.cells.element(matching: ...).firstMatch + scroll |
| Animations interfere | UIView.setAnimationsEnabled(false) in test hook |
| Keyboard covers the field | Use hardware keyboard flag in simulator settings |
| Timeouts on CI only | Launch simulator cold, pre-boot in CI step |
9. Running on CI
- Use
xcodebuild test-without-buildingfor speed. - Enable Parallel Distributed Testing only when tests are hermetic.
- Collect
.xcresultbundles and upload as artifacts; screenshots travel inside them.
xcodebuild -workspace MyApp.xcworkspace \
-scheme MyAppUITests \
-testPlan Smoke \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-resultBundlePath build/UITests.xcresult \
test-without-building
10. Accessibility-Driven Testing
UI tests double as accessibility audits. If you can't query an element, neither can VoiceOver. Fix identifiers and traits — see the ios-accessibility skill.
Checklist
- All interactive elements have stable
accessibilityIdentifiers. - Tests launch with flags to drive initial state; no tap-through seed data.
- No
Thread.sleep; waits usewaitForExistenceor predicates. - Pages are modeled as page objects.
- Failure screenshots and
.xcresultbundles upload from CI. - Flaky tests are retried deterministically via a test plan, not hidden.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.