- Screens.swift: selectPlanningMode scrolls UP (modes are at top of wizard) - TripWizardFlowTests: F040 selects sport before dates to avoid async availability check disabling MLB for December off-season - SportsTimeUITests: add --ui-testing/--disable-animations/--reset-state launch args to accessibility, demo, and manual flow tests - Demo test: remove -DemoMode flag that caused double-toggle (demo auto-selects + manual taps toggled sports/regions off) - Manual test: replace blind swipes with scrollIntoView + wait-for-enabled for Plan button; use waitForExistence for trip card selection 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.shortTimeout).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.shortTimeout).tap()
|
|
}
|
|
|
|
let afterForward = wizard.monthLabel.label
|
|
|
|
// Go back 1 month
|
|
wizard.previousMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
|
|
wizard.previousMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).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")
|
|
}
|
|
}
|