SKIE doesn't expose Kotlin default parameters to Swift, so isCacheValid calls need explicit ttlMs argument. Renamed struct-based screen objects to avoid ambiguity with class-based PageObjects (LoginScreenObject, RegisterScreenObject, MainTabScreenObject). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Casera iOS UI Testing Architecture
Directory Structure
CaseraUITests/
├── PageObjects/ # Screen abstractions (Page Object pattern)
│ ├── BaseScreen.swift # Common wait/assert utilities
│ ├── LoginScreen.swift # Login screen elements and actions
│ ├── RegisterScreen.swift # Registration screen
│ └── MainTabScreen.swift # Main tab navigation + settings + logout
├── TestConfiguration/ # Launch config, environment setup
│ └── TestLaunchConfig.swift
├── Fixtures/ # Test data builders
│ └── TestFixtures.swift
├── CriticalPath/ # Must-pass tests for CI gating
│ ├── SmokeTests.swift # Fast smoke suite (<2 min)
│ ├── AuthCriticalPathTests.swift # Auth flow validation
│ └── NavigationCriticalPathTests.swift # Tab + navigation validation
├── UITestHelpers.swift # Shared login/logout/navigation helpers
├── AccessibilityIdentifiers.swift # UI element IDs (synced with app-side copy)
└── README.md # This file
Test Suites
| Suite | Purpose | CI Gate | Target Time |
|---|---|---|---|
| SmokeTests | App launches, basic auth, tab existence | Every PR | <2 min |
| AuthCriticalPathTests | Login, logout, registration entry, forgot password | Every PR | <3 min |
| NavigationCriticalPathTests | Tab navigation, settings, add buttons | Every PR | <3 min |
Patterns
Page Object Pattern
Every screen has a corresponding PageObject in PageObjects/. Use these instead of raw XCUIElement queries in tests. Page objects encapsulate element lookups and common actions, making tests more readable and easier to maintain when the UI changes.
Wait Helpers
NEVER use sleep() or Thread.sleep(). Use waitForElement(), waitForElementToDisappear(), waitForHittable(), or waitForCondition() from BaseScreen. These are condition-based waits that return as soon as the condition is met, making tests both faster and more reliable.
Test Data
Use TestFixtures builders for consistent, unique test data. Random numbers and UUIDs ensure test isolation so tests can run in any order without interfering with each other.
Launch Configuration
Use TestLaunchConfig.launchApp() for standard launches. Use launchAuthenticated() to skip login when the app supports test authentication bypass. The standard configuration disables animations and forces English locale.
Accessibility Identifiers
All interactive elements must have identifiers defined in AccessibilityIdentifiers.swift. Use .accessibilityIdentifier() in SwiftUI views. Page objects reference these identifiers for element lookup. The test-side copy must stay in sync with the app-side copy at iosApp/Helpers/AccessibilityIdentifiers.swift.
CI Configuration
Critical Path (every PR)
xcodebuild test -project iosApp.xcodeproj -scheme iosApp \
-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:CaseraUITests/SmokeTests \
-only-testing:CaseraUITests/AuthCriticalPathTests \
-only-testing:CaseraUITests/NavigationCriticalPathTests
Full Regression (nightly)
xcodebuild test -project iosApp.xcodeproj -scheme iosApp \
-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 17' \
-only-testing:CaseraUITests
Flake Reduction
- Target: <2% flake rate on critical-path suite
- All waits use condition-based predicates (zero fixed sleeps)
- Test data uses unique identifiers to prevent cross-test interference
- UI animations disabled via launch arguments
- Element lookups use accessibility identifiers exclusively
Adding New Tests
- If the screen does not have a page object yet, create one in
PageObjects/that extendsBaseScreen. - Define accessibility identifiers in
AccessibilityIdentifiers.swiftfor any new UI elements. - Sync the app-side copy of
AccessibilityIdentifiers.swiftwith matching identifiers. - Add test data builders to
TestFixtures.swiftif needed. - Write the test in
CriticalPath/for must-pass CI tests. - Verify zero
sleep()calls before merging.