diff --git a/SportsTimeUITests/Tests/HomeTests.swift b/SportsTimeUITests/Tests/HomeTests.swift index fee4131..c04b592 100644 --- a/SportsTimeUITests/Tests/HomeTests.swift +++ b/SportsTimeUITests/Tests/HomeTests.swift @@ -2,8 +2,8 @@ // HomeTests.swift // SportsTimeUITests // -// Tests for the Home tab: hero card, start planning, and toolbar button. -// QA Sheet: F-012, F-013, F-020 +// Tests for the Home tab: hero card, start planning, toolbar button, and recent trips. +// QA Sheet: F-012, F-013, F-014, F-017, F-019, F-020 // import XCTest @@ -79,6 +79,48 @@ final class HomeTests: BaseUITestCase { captureScreenshot(named: "F014-FeaturedTrips") } + /// F-017: Recent trips section shows saved trips with "See All" link on Home tab. + @MainActor + func testF017_RecentTripsSectionWithSavedTrips() { + // Plan a trip and select the first option + let (_, detail) = TestFlows.planAndSelectFirstTrip(app: app) + + // Save the trip + detail.assertSaveState(isSaved: false) + detail.tapFavorite() + detail.assertSaveState(isSaved: true) + + captureScreenshot(named: "F017-TripSaved") + + // Navigate back: Detail → Options → Wizard → Cancel (dismiss sheet) + app.navigationBars.buttons.firstMatch.tap() + app.navigationBars.buttons.firstMatch + .waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + TripWizardScreen(app: app).tapCancel() + + // We're back on Home tab. Wait for it to load. + let home = HomeScreen(app: app) + home.waitForLoad() + + // Scroll to find the recent trips section + let recentTrips = home.recentTripsSection + var scrollAttempts = 0 + while !recentTrips.exists && scrollAttempts < 15 { + app.swipeUp(velocity: .slow) + scrollAttempts += 1 + } + + XCTAssertTrue(recentTrips.exists, + "Recent trips section should appear after saving a trip") + + // Verify "See All" link is visible + let seeAllText = app.staticTexts["See All"] + XCTAssertTrue(seeAllText.exists, + "'See All' link should be visible in recent trips section") + + captureScreenshot(named: "F017-RecentTripsSection") + } + /// F-019: Planning tips section is visible at bottom of home tab. @MainActor func testF019_PlanningTipsSectionVisible() { diff --git a/SportsTimeUITests/Tests/TabNavigationTests.swift b/SportsTimeUITests/Tests/TabNavigationTests.swift index daaf327..a2158e8 100644 --- a/SportsTimeUITests/Tests/TabNavigationTests.swift +++ b/SportsTimeUITests/Tests/TabNavigationTests.swift @@ -44,6 +44,39 @@ final class TabNavigationTests: BaseUITestCase { captureScreenshot(named: "F008-TabNavigation-ReturnHome") } + /// F-010: Tapping the active tab scrolls the view back to the top. + @MainActor + func testF010_TapActiveTabScrollsToTop() { + let home = HomeScreen(app: app) + home.waitForLoad() + + // Verify "Adventure Awaits" hero card is hittable at the top + XCTAssertTrue(home.adventureAwaitsText.isHittable, + "Adventure Awaits should be hittable initially") + + // Scroll down until the hero card is off-screen + for _ in 0..<8 { + app.swipeUp(velocity: .slow) + } + + // Verify the hero card is no longer hittable (scrolled off-screen) + XCTAssertFalse(home.adventureAwaitsText.isHittable, + "Adventure Awaits should not be hittable after scrolling down") + + captureScreenshot(named: "F010-ScrolledDown") + + // Tap the Home tab (already the active tab) to trigger scroll-to-top + home.homeTab.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + + // Hero card should scroll back into view and become hittable + home.adventureAwaitsText.waitUntilHittable( + timeout: BaseUITestCase.defaultTimeout, + "Adventure Awaits should be hittable after tapping active tab (scroll to top)" + ) + + captureScreenshot(named: "F010-ScrolledToTop") + } + /// F-009: Tab state preserved — Schedule filters survive tab switch. @MainActor func testF009_TabStatePreservedOnSwitch() { diff --git a/docs/SportsTime_QA_Test_Plan.xlsx b/docs/SportsTime_QA_Test_Plan.xlsx index ecf0219..7d1f5d7 100644 Binary files a/docs/SportsTime_QA_Test_Plan.xlsx and b/docs/SportsTime_QA_Test_Plan.xlsx differ diff --git a/uiTestPrompt.md b/uiTestPrompt.md index d658254..eec811f 100644 --- a/uiTestPrompt.md +++ b/uiTestPrompt.md @@ -1,49 +1,105 @@ -# UI Test Prompt Template +# SportsTime UI Test Prompt (Paste Into Claude) -Use this prompt when asking an agent to add or modify SportsTime UI tests. +Use this when you want Claude to pick and implement the 3 easiest UI tests from QA IDs/names you provide. + +Replace the `INPUT` section, then paste the whole prompt. --- -You are updating SportsTime UI tests. +You are an iOS UI test engineer working in the SportsTime repo. -## Goal +## Mission -- [Describe the behavior to test or fix.] +From the provided QA cases (IDs/names from `docs/SportsTime_QA_Test_Plan.xlsx`), pick the **3 easiest, lowest-flake cases** and implement solid UI tests for them using the **existing SportsTime UI test architecture**. -## Scope +## Non-Negotiable Rules -- 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. +1. Do not reinvent the test framework. +2. Reuse existing architecture: + - `SportsTimeUITests/Framework/BaseUITestCase.swift` + - `SportsTimeUITests/Framework/Screens.swift` + - existing `TestFlows` helpers + - existing suites under `SportsTimeUITests/Tests/` +3. Prefer updating existing suites over creating new ones. +4. Use stable accessibility IDs already used in the app/tests (`home.*`, `wizard.*`, `tripOptions.*`, `tripDetail.*`, `schedule.*`, `settings.*`, `progress.*`, etc.). +5. Prefer robust waits (`waitForExistenceOrFail`, `waitUntilHittable`) and page-object methods. +6. Avoid `sleep` unless there is no reliable alternative; if used, justify it. +7. Keep tests deterministic with current local test data. +8. Follow naming conventions (`testF###_...`, `testP###_...`, etc.). +9. Do not change product code unless absolutely required for testability (and explain why). +10. Change only what is needed for these 3 tests. -## Required Changes +## Required Reading Before Coding -- [List test suites to modify.] -- [List new test names.] -- [List selectors/page-object methods to add if needed.] +1. `AGENTS.md` +2. `XCUITest-Authoring.md` +3. Existing tests in `SportsTimeUITests/Tests/` for patterns and style -## Constraints +## Selection Strategy (Pick Exactly 3) -- 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.). +Choose the 3 easiest cases using this priority order: -## Validation +1. Already mostly covered by existing screen objects/flows. +2. Requires minimal or no new selectors. +3. No unstable backend/timing dependency. +4. No complex multi-screen setup unless reusable via existing `TestFlows`. +5. Highest confidence of passing consistently on current simulator baseline. -Run these before finishing: +If a candidate looks flaky/high-risk, skip it and explain why. -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` +## Implementation Workflow -## Output Format +1. Evaluate all candidate QA cases. +2. Output a short ranked list with reason for each. +3. Confirm the chosen 3. +4. Implement tests: + - Put tests in existing or appropriate suite(s) in `SportsTimeUITests/Tests/`. + - Add page-object helpers in `Screens.swift` only when reusable. + - Keep assertions behavior-focused and explicit. + - Capture screenshots at key checkpoints for longer flows. +5. Run only the 3 selected tests first. +6. Fix failures. +7. Re-run each selected test at least 2 times to catch flake. +8. Stop only when all 3 are green and stable, or clearly blocked. -- 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. +## Validation Commands + +Use this destination: + +`-destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2'` + +Run each selected test explicitly: + +`xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -parallel-testing-enabled NO -only-testing:SportsTimeUITests//test_ ` + +If needed, run selected suite only: + +`xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -parallel-testing-enabled NO -only-testing:SportsTimeUITests/ ` + +Do not run the full suite unless I ask. + +## Output Contract + +Return: + +1. Chosen 3 QA cases and why they were selected. +2. Files changed and what changed in each. +3. Exact commands run. +4. Test results for each selected test (including repeat runs). +5. Any residual risk or blocker. +6. Optional next 2-3 QA cases to implement next (ranked by ease/confidence). + +## INPUT (Replace This Block) + +Candidate QA cases from `docs/SportsTime_QA_Test_Plan.xlsx`: + +- [F-___] +- [F-___] +- [F-___] +- [F-___] +- [F-___] + +Extra constraints: + +- [Example: Do not edit `SportsTimeUITests/Tests/TripSavingTests.swift`] +- [Example: Only use existing `TestFlows`]