Rewrote 60+ test files to follow honeydue-style test guidelines:
- defaultTimeout=2s, navigationTimeout=5s — fail fast, no long waits
- No coordinate taps (except onboarding paged TabView swipes)
- No sleep(), no retry loops
- No guard...else { return } silent passes — XCTFail everywhere
- All elements by accessibility ID via UITestID constants
- Screen objects for all navigation/actions/assertions
- One logical assertion per test method
Added missing accessibility identifiers to app views:
- MonthView.swift: added AccessibilityID.MonthView.grid to ScrollView
- YearView.swift: added AccessibilityID.YearView.heatmap to ScrollView
Framework rewrites:
- BaseUITestCase: added session ID, localeArguments, extraLaunchArguments
- WaitHelpers: waitForExistenceOrFail, waitUntilHittableOrFail,
waitForNonExistence, scrollIntoView, forceTap
- All 7 screen objects rewritten with fail-fast semantics
- TEST_RULES.md added with non-negotiable rules
Known remaining issues:
- OnboardingTests: paged TabView swipes unreliable on iOS 26 simulator
- SettingsLegalLinksTests: EULA/Privacy buttons too deep in DEBUG scroll
- Customization horizontal picker scrolling needs further tuning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
34 lines
1.8 KiB
Markdown
34 lines
1.8 KiB
Markdown
# UI Test Rules
|
|
|
|
These rules are non-negotiable. Every test, every suite, every helper must follow them.
|
|
|
|
## Element Interaction
|
|
1. **All elements found by accessibility identifier** — never `label CONTAINS` for app elements
|
|
2. **No coordinate taps anywhere** — `app.coordinate(withNormalizedOffset:)` is banned
|
|
3. **Use screen objects for all interactions** — test bodies should read like user stories
|
|
|
|
## Timeouts
|
|
4. **`defaultTimeout` = 2 seconds** — if an element on the current screen isn't there in 2s, the app is broken
|
|
5. **`navigationTimeout` = 5 seconds** — screen transitions, tab switches
|
|
6. **No retry loops in test helpers** — tap once, check once, fail fast
|
|
|
|
## Independence
|
|
7. **Every suite runs alone, in combination, or in parallel** — no ordering dependencies
|
|
8. **Every test creates its own data via fixture seeding in setUp**
|
|
9. **No shared mutable state** — no `static var`, no class-level properties mutated across tests
|
|
|
|
## Clarity
|
|
10. **One logical assertion per test** — test name describes the exact behavior
|
|
11. **`XCTFail` with a message that tells you what went wrong** without reading the code
|
|
12. **No `guard ... else { return }` that silently passes** — if a precondition fails, `XCTFail` and stop
|
|
|
|
## Speed
|
|
13. **No `sleep()`, `usleep()`, or `Thread.sleep`** in tests — condition-based waits only
|
|
14. **Target: each individual test completes in under 15 seconds** (excluding setUp/tearDown)
|
|
15. **No swipe loops** — if content needs scrolling, use `scrollIntoView()` with a fail-fast bound
|
|
|
|
## Parallel Safety
|
|
16. **Each test process gets a unique session ID** — `UI_TEST_SESSION_ID` isolates UserDefaults and SwiftData
|
|
17. **In-memory SwiftData containers** — no shared on-disk state between parallel runners
|
|
18. **Session-scoped UserDefaults suites** — `uitest.<sessionID>` prevents cross-test contamination
|