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>
333 lines
11 KiB
Swift
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")
|
|
}
|
|
}
|