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>
1.8 KiB
1.8 KiB
UI Test Rules
These rules are non-negotiable. Every test, every suite, every helper must follow them.
Element Interaction
- All elements found by accessibility identifier — never
label CONTAINSfor app elements - No coordinate taps anywhere —
app.coordinate(withNormalizedOffset:)is banned - Use screen objects for all interactions — test bodies should read like user stories
Timeouts
defaultTimeout= 2 seconds — if an element on the current screen isn't there in 2s, the app is brokennavigationTimeout= 5 seconds — screen transitions, tab switches- No retry loops in test helpers — tap once, check once, fail fast
Independence
- Every suite runs alone, in combination, or in parallel — no ordering dependencies
- Every test creates its own data via fixture seeding in setUp
- No shared mutable state — no
static var, no class-level properties mutated across tests
Clarity
- One logical assertion per test — test name describes the exact behavior
XCTFailwith a message that tells you what went wrong without reading the code- No
guard ... else { return }that silently passes — if a precondition fails,XCTFailand stop
Speed
- No
sleep(),usleep(), orThread.sleepin tests — condition-based waits only - Target: each individual test completes in under 15 seconds (excluding setUp/tearDown)
- No swipe loops — if content needs scrolling, use
scrollIntoView()with a fail-fast bound
Parallel Safety
- Each test process gets a unique session ID —
UI_TEST_SESSION_IDisolates UserDefaults and SwiftData - In-memory SwiftData containers — no shared on-disk state between parallel runners
- Session-scoped UserDefaults suites —
uitest.<sessionID>prevents cross-test contamination