Files
Sportstime/SportsTimeUITests/Tests/TripWizardFlowTests.swift
treyt ba41866602 Fix flaky UI tests: increase calendar wait timeouts and disable parallel UI testing
Calendar navigation buttons used shortTimeout (5s) which was too tight under
simulator load, causing cascading failures in wizard and trip saving tests.
Bumped to defaultTimeout (15s) and disabled parallel execution for UI tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:44:08 -06:00

333 lines
11 KiB
Swift

//
// TripWizardFlowTests.swift
// SportsTimeUITests
//
// Tests the trip planning wizard: planning modes, calendar navigation,
// sport/region selection, and planning engine results.
// QA Sheet: F-018 through F-042
//
import XCTest
final class TripWizardFlowTests: BaseUITestCase {
// MARK: - Helpers
/// Opens wizard and returns screen objects ready for interaction.
@MainActor
private func openWizard() -> (home: HomeScreen, wizard: TripWizardScreen) {
let home = HomeScreen(app: app)
home.waitForLoad()
home.tapStartPlanning()
let wizard = TripWizardScreen(app: app)
wizard.waitForLoad()
return (home, wizard)
}
// MARK: - Date Range Mode (F-018)
/// F-018: Full flow Start Planning Date Range Select dates MLB Central Plan.
@MainActor
func testF018_DateRangeTripPlanningFlow() {
let (_, options) = TestFlows.planDateRangeTrip(app: app)
options.assertHasResults()
captureScreenshot(named: "F018-PlanningResults")
}
// MARK: - Planning Mode Selection (F-019 through F-022)
/// F-019: "By Games" mode button is available and selectable.
@MainActor
func testF019_ByGamesModeSelectable() {
let (_, wizard) = openWizard()
wizard.selectPlanningMode("gameFirst")
wizard.assertPlanningModeAvailable("gameFirst")
captureScreenshot(named: "F019-ByGamesMode")
}
/// F-020: "By Route" mode button is available and selectable.
@MainActor
func testF020_ByRouteModeSelectable() {
let (_, wizard) = openWizard()
wizard.selectPlanningMode("locations")
wizard.assertPlanningModeAvailable("locations")
captureScreenshot(named: "F020-ByRouteMode")
}
/// F-021: "Follow Team" mode button is available and selectable.
@MainActor
func testF021_FollowTeamModeSelectable() {
let (_, wizard) = openWizard()
wizard.selectPlanningMode("followTeam")
wizard.assertPlanningModeAvailable("followTeam")
captureScreenshot(named: "F021-FollowTeamMode")
}
/// F-022: "By Teams" mode button is available and selectable.
@MainActor
func testF022_ByTeamsModeSelectable() {
let (_, wizard) = openWizard()
wizard.selectPlanningMode("teamFirst")
wizard.assertPlanningModeAvailable("teamFirst")
captureScreenshot(named: "F022-ByTeamsMode")
}
// MARK: - Calendar Navigation (F-024, F-025)
/// F-024: Calendar forward navigation month label updates correctly.
@MainActor
func testF024_CalendarNavigationForward() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
// Capture the initial month label
wizard.monthLabel.scrollIntoView(in: app.scrollViews.firstMatch)
let initialMonth = wizard.monthLabel.label
// Navigate forward 3 times
for _ in 0..<3 {
wizard.nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
wizard.nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap()
}
// Month label should have changed
XCTAssertNotEqual(wizard.monthLabel.label, initialMonth,
"Month label should update after navigating forward")
captureScreenshot(named: "F024-CalendarForward")
}
/// F-025: Calendar backward navigation can go back after going forward.
@MainActor
func testF025_CalendarNavigationBackward() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
wizard.monthLabel.scrollIntoView(in: app.scrollViews.firstMatch)
// Go forward 3 months
for _ in 0..<3 {
wizard.nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
wizard.nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap()
}
let afterForward = wizard.monthLabel.label
// Go back 1 month
wizard.previousMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
wizard.previousMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap()
XCTAssertNotEqual(wizard.monthLabel.label, afterForward,
"Month should change after navigating backward")
captureScreenshot(named: "F025-CalendarBackward")
}
// MARK: - Date Range Selection (F-026)
/// F-026: Select start and end dates both buttons respond to tap.
@MainActor
func testF026_DateRangeSelection() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
// Navigate to June 2026
wizard.selectDateRange(
targetMonth: "June",
targetYear: "2026",
startDay: "2026-06-11",
endDay: "2026-06-16"
)
// Verify month label shows June
XCTAssertTrue(wizard.monthLabel.label.contains("June"),
"Calendar should show June after navigation")
captureScreenshot(named: "F026-DateRangeSelected")
}
// MARK: - Sport Selection (F-030, F-031)
/// F-030: Single sport selection MLB highlights.
@MainActor
func testF030_SingleSportSelection() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
wizard.selectSport("mlb")
let mlbButton = wizard.sportButton("mlb")
XCTAssertTrue(mlbButton.exists, "MLB sport button should exist after selection")
captureScreenshot(named: "F030-SingleSport")
}
/// F-031: Multiple sport selection MLB + NBA both highlighted.
@MainActor
func testF031_MultipleSportSelection() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
wizard.selectSport("mlb")
wizard.selectSport("nba")
XCTAssertTrue(wizard.sportButton("mlb").exists,
"MLB should remain after selecting NBA")
XCTAssertTrue(wizard.sportButton("nba").exists,
"NBA sport button should exist")
captureScreenshot(named: "F031-MultipleSports")
}
// MARK: - Region Selection (F-033)
/// F-033: Region toggle west, central, east buttons respond to tap.
@MainActor
func testF033_RegionSelection() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
// Select each region to verify they're tappable
let regions = ["west", "central", "east"]
for region in regions {
let btn = wizard.regionButton(region)
btn.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertTrue(btn.isHittable,
"\(region) region button should be hittable")
}
// Tap west to toggle it
wizard.selectRegion("west")
captureScreenshot(named: "F033-RegionSelection")
}
// MARK: - Switching Modes Resets (F-023)
/// F-023: Switching planning modes resets fields Plan button becomes disabled.
@MainActor
func testF023_SwitchingModesResetsFields() {
let (_, wizard) = openWizard()
// Select date range mode and fill fields
wizard.selectDateRangeMode()
wizard.selectDateRange(
targetMonth: "June",
targetYear: "2026",
startDay: "2026-06-11",
endDay: "2026-06-16"
)
wizard.selectSport("mlb")
wizard.selectRegion("central")
// Switch to a different mode
wizard.selectPlanningMode("gameFirst")
// Switch back to dateRange
wizard.selectPlanningMode("dateRange")
// Plan button should be disabled (fields were reset)
let planBtn = wizard.planTripButton
planBtn.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertFalse(planBtn.isEnabled,
"Plan My Trip should be disabled after mode switch resets fields")
captureScreenshot(named: "F023-ModeSwitch-Reset")
}
// MARK: - Plan Button States (F-038)
/// F-038: Plan My Trip disabled when required fields are incomplete.
@MainActor
func testF038_PlanButtonDisabledState() {
let (_, wizard) = openWizard()
// Select mode but don't fill required fields
wizard.selectDateRangeMode()
let planBtn = wizard.planTripButton
planBtn.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertFalse(planBtn.isEnabled,
"Plan My Trip should be disabled without filling required fields")
// Missing fields warning should be visible
let warning = app.descendants(matching: .any)["wizard.missingFieldsWarning"]
warning.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertTrue(warning.exists,
"Missing fields warning should appear")
captureScreenshot(named: "F038-PlanButton-Disabled")
}
// MARK: - Planning Error (F-040)
/// F-040: Planning with no games in date range shows error alert.
@MainActor
func testF040_NoGamesFoundError() {
let (_, wizard) = openWizard()
wizard.selectDateRangeMode()
// Select sport BEFORE dates so the async sport-availability check
// (triggered by date selection) doesn't disable the button.
wizard.selectSport("mlb")
// Pick December 2026 MLB off-season, no games expected.
// Scroll up to the dates section first since sport step is below dates.
wizard.monthLabel.scrollIntoView(in: app.scrollViews.firstMatch, direction: .up)
wizard.selectDateRange(
targetMonth: "December",
targetYear: "2026",
startDay: "2026-12-01",
endDay: "2026-12-07"
)
wizard.selectRegion("central")
wizard.tapPlanTrip()
// Wait for the planning error alert
let alert = app.alerts["Planning Error"]
XCTAssertTrue(alert.waitForExistence(timeout: BaseUITestCase.longTimeout),
"Planning Error alert should appear for off-season dates")
// Dismiss the alert
alert.buttons["OK"].tap()
captureScreenshot(named: "F040-NoGamesFound")
}
// MARK: - Wizard Dismiss (F-042)
/// F-042: Cancel wizard returns to home screen.
@MainActor
func testF042_WizardCanBeDismissed() {
let (home, wizard) = openWizard()
wizard.tapCancel()
XCTAssertTrue(
home.startPlanningButton.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Should return to Home after cancelling wizard"
)
}
// MARK: - All 5 Modes Available (F-018 to F-022 combined)
/// Verifies all 5 planning mode buttons are present in the wizard.
@MainActor
func testF018_F022_AllPlanningModesAvailable() {
let (_, wizard) = openWizard()
let modes = ["dateRange", "gameFirst", "locations", "followTeam", "teamFirst"]
for mode in modes {
wizard.assertPlanningModeAvailable(mode)
}
captureScreenshot(named: "F018-F022-AllModes")
}
}