Files
Reflect/Tests iOS/TEST_RULES.md
Trey T d97db4910e Rewrite all UI tests following fail-fast TEST_RULES patterns
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>
2026-03-24 17:00:30 -05:00

1.8 KiB

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 anywhereapp.coordinate(withNormalizedOffset:) is banned
  3. Use screen objects for all interactions — test bodies should read like user stories

Timeouts

  1. defaultTimeout = 2 seconds — if an element on the current screen isn't there in 2s, the app is broken
  2. navigationTimeout = 5 seconds — screen transitions, tab switches
  3. No retry loops in test helpers — tap once, check once, fail fast

Independence

  1. Every suite runs alone, in combination, or in parallel — no ordering dependencies
  2. Every test creates its own data via fixture seeding in setUp
  3. No shared mutable state — no static var, no class-level properties mutated across tests

Clarity

  1. One logical assertion per test — test name describes the exact behavior
  2. XCTFail with a message that tells you what went wrong without reading the code
  3. No guard ... else { return } that silently passes — if a precondition fails, XCTFail and stop

Speed

  1. No sleep(), usleep(), or Thread.sleep in tests — condition-based waits only
  2. Target: each individual test completes in under 15 seconds (excluding setUp/tearDown)
  3. No swipe loops — if content needs scrolling, use scrollIntoView() with a fail-fast bound

Parallel Safety

  1. Each test process gets a unique session IDUI_TEST_SESSION_ID isolates UserDefaults and SwiftData
  2. In-memory SwiftData containers — no shared on-disk state between parallel runners
  3. Session-scoped UserDefaults suitesuitest.<sessionID> prevents cross-test contamination