From 7e54ff2ef2e582b3920fa15f2d180d6392bef313 Mon Sep 17 00:00:00 2001 From: treyt Date: Wed, 18 Feb 2026 13:24:46 -0600 Subject: [PATCH] Add XCUITest authoring docs and templates --- AGENTS.md | 27 +++++++++ CLAUDE.md | 8 ++- README.md | 13 +++++ XCUITest-Authoring.md | 108 ++++++++++++++++++++++++++++++++++++ XCUITestSuiteTemplate.swift | 48 ++++++++++++++++ uiTestPrompt.md | 49 ++++++++++++++++ 6 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 100644 XCUITest-Authoring.md create mode 100644 XCUITestSuiteTemplate.swift create mode 100644 uiTestPrompt.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..05c8835 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# SportsTime Agent Notes + +This file is for AI/code agents working in this repo. + +## UI Test Docs + +- Start with `XCUITest-Authoring.md`. +- Use `XCUITestSuiteTemplate.swift` when creating new suites. +- Use `uiTestPrompt.md` when asking an agent to add/fix UI tests. + +## UI Test Ground Rules + +- Keep new UI tests in `SportsTimeUITests/Tests/`. +- Reuse page objects in `SportsTimeUITests/Framework/Screens.swift`. +- Reuse shared setup and helpers from `SportsTimeUITests/Framework/BaseUITestCase.swift`. +- Reuse high-level flows from `TestFlows` before adding duplicate test steps. +- Prefer robust waits (`waitForExistenceOrFail`, `waitUntilHittable`) over sleeps. +- Use existing accessibility identifiers (`wizard.*`, `tripOptions.*`, `tripDetail.*`, etc.). +- Capture screenshots for key checkpoints in longer end-to-end tests. + +## Before Merging UI Test Changes + +- Run the touched test class. +- Run full UI suite: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests` +- Run full scheme verification if behavior touched shared flows: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO` diff --git a/CLAUDE.md b/CLAUDE.md index 30ba2d5..028a5bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,9 @@ xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platfo # Run a single test xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TripPlanningEngineTests/planningMode_dateRange test +# Run UI tests only +xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests + # Data scraping (Python) cd Scripts && pip install -r requirements.txt python scrape_schedules.py --sport all --season 2026 @@ -298,7 +301,10 @@ View → modelContext.delete(model) → modelContext.save() → reload data → - **Test directory**: `SportsTimeTests/` — mirrors source structure - **File naming**: `{ClassName}Tests.swift` or `{Feature}Tests.swift` - **Helper files**: `SportsTimeTests/Helpers/MockServices.swift`, `SportsTimeTests/Helpers/TestFixtures.swift` -- **UI tests**: `SportsTimeUITests/` (template only, not actively used) +- **UI tests**: `SportsTimeUITests/` is active and uses XCTest + page-object patterns +- **UI authoring guide**: `XCUITest-Authoring.md` +- **UI suite template**: `XCUITestSuiteTemplate.swift` +- **UI request template**: `uiTestPrompt.md` ### Existing Test Suites diff --git a/README.md b/README.md index d16b9ba..041c325 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ SportsTime/ │ ├── Export/ # PDF generation │ └── Resources/ # Bundled JSON data ├── SportsTimeTests/ # Unit tests +├── SportsTimeUITests/ # UI test suites + screen objects ├── Scripts/ # Python data pipeline │ └── sportstime_parser/ # Schedule scraping & CloudKit upload ├── data/ # Local data files @@ -124,6 +125,14 @@ xcodebuild -project SportsTime.xcodeproj \ -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ -only-testing:SportsTimeTests/EdgeCaseTests \ test + +# UI test target only +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests ``` ### Data Pipeline @@ -151,7 +160,11 @@ python -m sportstime_parser upload --sport all ## Documentation - `CLAUDE.md` - Development guidelines and architecture details +- `AGENTS.md` - Agent-specific execution notes for this repo - `ARCHITECTURE.md` - Detailed system architecture +- `XCUITest-Authoring.md` - How existing UI tests are structured and how to add new ones +- `XCUITestSuiteTemplate.swift` - Starter suite template for new UI test files +- `uiTestPrompt.md` - Reusable prompt template for requesting UI test work - `docs/TEST_PLAN.md` - Test suite documentation - `docs/MARKET_RESEARCH.md` - Competitive analysis diff --git a/XCUITest-Authoring.md b/XCUITest-Authoring.md new file mode 100644 index 0000000..4dc0f7e --- /dev/null +++ b/XCUITest-Authoring.md @@ -0,0 +1,108 @@ +# XCUITest Authoring Guide + +This guide documents the current SportsTime UI test foundation and how to add new tests safely. + +## Current Foundation + +## Layout + +- Test target: `SportsTimeUITests` +- Base setup/helpers: `SportsTimeUITests/Framework/BaseUITestCase.swift` +- Page objects + shared flows: `SportsTimeUITests/Framework/Screens.swift` +- Suite files: `SportsTimeUITests/Tests/*.swift` +- Legacy mixed tests: `SportsTimeUITests/SportsTimeUITests.swift` and `SportsTimeUITests/SportsTimeUITestsLaunchTests.swift` + +## Existing Suite Coverage + +- `AppLaunchTests` +- `HomeTests` +- `TabNavigationTests` +- `ScheduleTests` +- `TripWizardFlowTests` +- `TripOptionsTests` +- `TripSavingTests` +- `ProgressTests` +- `SettingsTests` +- `AccessibilityTests` +- `StabilityTests` + +## Key Conventions + +- Inherit from `BaseUITestCase`. +- Use `@MainActor` test methods. +- Prefer page-object actions over direct element taps. +- Prefer existing high-level flows (`TestFlows.planDateRangeTrip`, `TestFlows.planAndSelectFirstTrip`) for shared setup. +- Use deterministic selectors with accessibility identifiers. +- Use `waitForExistenceOrFail` and `waitUntilHittable` instead of arbitrary sleeps. + +## Adding a New UI Test + +1. Pick the closest existing suite in `SportsTimeUITests/Tests/`. +2. If none fits, create a new suite using `XCUITestSuiteTemplate.swift`. +3. Add/extend page-object methods in `Screens.swift` before writing raw element code. +4. Reuse `TestFlows` when setup overlaps existing end-to-end flows. +5. Keep assertion messages explicit and behavior-focused. +6. Capture screenshot(s) for key milestone state in longer flows. +7. Run targeted tests, then full UI tests. + +## When to Edit `Screens.swift` + +Edit page objects when: + +- A new screen element needs a stable selector. +- The interaction is reused across multiple tests. +- The flow can be standardized (especially wizard planning flow). + +Do not add one-off test-only branching logic unless it removes a real flake. + +## Running Tests + +## Fast Loop (single test) + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests/TripWizardFlowTests/testF026_DateRangeSelection +``` + +## Per Class + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests/TripOptionsTests +``` + +## Full UI Suite + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests +``` + +## Full Scheme Validation (unit + UI) + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO +``` + +## Flake Prevention Notes + +- Keep simulator orientation consistent (portrait baseline from `BaseUITestCase`). +- For wizard date selection, navigate by month/year first and use day-cell fallback when specific IDs are unavailable. +- For cross-season planning tests, prefer deterministic fallback sports if selected sport has no viable schedule for current test data. +- Increase waits only where planning computation is the actual bottleneck. diff --git a/XCUITestSuiteTemplate.swift b/XCUITestSuiteTemplate.swift new file mode 100644 index 0000000..18ab132 --- /dev/null +++ b/XCUITestSuiteTemplate.swift @@ -0,0 +1,48 @@ +// +// __FeatureName__Tests.swift +// SportsTimeUITests +// +// Copy this file into SportsTimeUITests/Tests and rename placeholders. +// + +import XCTest + +final class __FeatureName__Tests: BaseUITestCase { + + // MARK: - Helpers + + @MainActor + private func openHome() -> HomeScreen { + let home = HomeScreen(app: app) + home.waitForLoad() + return home + } + + @MainActor + private func openWizard() -> TripWizardScreen { + let home = openHome() + home.tapStartPlanning() + + let wizard = TripWizardScreen(app: app) + wizard.waitForLoad() + return wizard + } + + // MARK: - Tests + + /// Replace with test case ID and behavior, e.g. F-200. + @MainActor + func testF___BehaviorName() { + let wizard = openWizard() + wizard.selectDateRangeMode() + + // Add scenario actions + // wizard.selectSport("mlb") + // wizard.selectRegion("central") + + // Add assertions + XCTAssertTrue(wizard.planTripButton.exists, "Plan button should be visible") + + captureScreenshot(named: "F___-BehaviorName") + } +} diff --git a/uiTestPrompt.md b/uiTestPrompt.md new file mode 100644 index 0000000..d658254 --- /dev/null +++ b/uiTestPrompt.md @@ -0,0 +1,49 @@ +# UI Test Prompt Template + +Use this prompt when asking an agent to add or modify SportsTime UI tests. + +--- + +You are updating SportsTime UI tests. + +## Goal + +- [Describe the behavior to test or fix.] + +## Scope + +- Update/add tests under `SportsTimeUITests/Tests/`. +- Reuse page objects in `SportsTimeUITests/Framework/Screens.swift`. +- Reuse shared setup in `SportsTimeUITests/Framework/BaseUITestCase.swift`. +- Reuse existing `TestFlows` where possible. + +## Required Changes + +- [List test suites to modify.] +- [List new test names.] +- [List selectors/page-object methods to add if needed.] + +## Constraints + +- Do not add raw sleeps unless strictly necessary. +- Prefer `waitForExistenceOrFail` and `waitUntilHittable`. +- Keep tests deterministic with current local test data. +- Keep existing naming style (`testF###_...`, `testP###_...`, etc.). + +## Validation + +Run these before finishing: + +1. Targeted class or test: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests/` +2. Full UI suite: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests` +3. If requested, full scheme: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO` + +## Output Format + +- Summarize files changed. +- Summarize root causes fixed. +- Include exact commands run and pass/fail outcomes. +- Call out any remaining flaky behavior or follow-up work.