// // SportsTimeUITests.swift // SportsTimeUITests // // Created by Trey Tartt on 1/6/26. // import XCTest final class SportsTimeUITests: XCTestCase { override func setUpWithError() throws { // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false } override func tearDownWithError() throws { // Put teardown code here. } // MARK: - Accessibility Smoke Tests /// Verifies primary entry flow remains usable at a large accessibility text size. @MainActor func testAccessibilitySmoke_LargeDynamicTypeEntryFlow() throws { let app = XCUIApplication() app.launchArguments = [ "--ui-testing", "--disable-animations", "--reset-state", "-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXXXL" ] app.launch() let startPlanningButton = app.buttons["home.startPlanningButton"] XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 20), "Start Planning should exist at large Dynamic Type") // At XXXL the button may be pushed below the fold — scroll into view startPlanningButton.scrollIntoView(in: app.scrollViews.firstMatch) XCTAssertTrue(startPlanningButton.isHittable, "Start Planning should remain hittable at large Dynamic Type") startPlanningButton.tap() let dateRangeMode = app.buttons["wizard.planningMode.dateRange"] XCTAssertTrue(dateRangeMode.waitForExistence(timeout: 10), "Planning mode options should load") dateRangeMode.scrollIntoView(in: app.scrollViews.firstMatch) XCTAssertTrue(dateRangeMode.isHittable, "Planning mode option should remain hittable at large Dynamic Type") } // MARK: - Demo Flow Test (Continuous Scroll Mode) /// Complete trip planning demo with continuous smooth scrolling. /// /// In demo mode, the app auto-selects each step as it appears on screen: /// - Planning Mode: "By Dates" /// - Dates: June 11-16, 2026 /// - Sport: MLB /// - Region: Central US /// - Sort: Most Games /// - Trip: 4th option /// - Action: Auto-favorite /// /// The test just needs to: /// 1. Launch with -DemoMode argument /// 2. Tap "Start Planning" /// 3. Continuously scroll - items auto-select as they appear /// 4. Wait for transitions to complete @MainActor func testTripPlanningDemoFlow() throws { let app = XCUIApplication() app.launchArguments = [ "--ui-testing", "--disable-animations", "--reset-state" ] app.launch() // MARK: Step 1 - Tap "Start Planning" let startPlanningButton = app.buttons["home.startPlanningButton"] XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 10), "Start Planning button should exist") startPlanningButton.tap() // MARK: Step 2 - Fill wizard steps // Note: -DemoMode removed because demo auto-selections conflict with manual // taps (toggling sports/regions off). This test verifies the full flow manually. let dateRangeMode = app.buttons["wizard.planningMode.dateRange"] XCTAssertTrue(dateRangeMode.waitForExistence(timeout: 10), "Date Range mode should exist") dateRangeMode.tap() // Navigate to June 2026 let nextMonthButton = app.buttons["wizard.dates.nextMonth"] nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) let monthLabel = app.staticTexts["wizard.dates.monthLabel"] var attempts = 0 while !monthLabel.label.contains("June 2026") && attempts < 12 { nextMonthButton.tap() Thread.sleep(forTimeInterval: 0.3) attempts += 1 } // Select June 11-16 let june11 = app.buttons["wizard.dates.day.2026-06-11"] june11.scrollIntoView(in: app.scrollViews.firstMatch) june11.tap() Thread.sleep(forTimeInterval: 0.5) let june16 = app.buttons["wizard.dates.day.2026-06-16"] june16.scrollIntoView(in: app.scrollViews.firstMatch) june16.tap() Thread.sleep(forTimeInterval: 0.5) // Select MLB let mlbButton = app.buttons["wizard.sports.mlb"] mlbButton.scrollIntoView(in: app.scrollViews.firstMatch) mlbButton.tap() // Select Central region let centralRegion = app.buttons["wizard.regions.central"] centralRegion.scrollIntoView(in: app.scrollViews.firstMatch) centralRegion.tap() // MARK: Step 3 - Plan trip let planTripButton = app.buttons["wizard.planTripButton"] planTripButton.scrollIntoView(in: app.scrollViews.firstMatch) let enabledPred = NSPredicate(format: "isEnabled == true") let enabledExp = XCTNSPredicateExpectation(predicate: enabledPred, object: planTripButton) let waitResult = XCTWaiter.wait(for: [enabledExp], timeout: 10) XCTAssertEqual(waitResult, .completed, "Plan My Trip should become enabled") planTripButton.tap() // MARK: Step 4 - Wait for planning results let sortDropdown = app.buttons["tripOptions.sortDropdown"] XCTAssertTrue(sortDropdown.waitForExistence(timeout: 30), "Sort dropdown should exist") sortDropdown.tap() Thread.sleep(forTimeInterval: 0.5) let mostGamesOption = app.buttons["tripOptions.sortOption.mostgames"] if mostGamesOption.waitForExistence(timeout: 3) { mostGamesOption.tap() } else { app.buttons["Most Games"].tap() } Thread.sleep(forTimeInterval: 1) // MARK: Step 5 - Select a trip and navigate to detail let anyTrip = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'tripOptions.trip.'")).firstMatch XCTAssertTrue(anyTrip.waitForExistence(timeout: 10), "At least one trip option should exist") anyTrip.tap() Thread.sleep(forTimeInterval: 2) // MARK: Step 6 - Scroll through itinerary for _ in 1...5 { slowSwipeUp(app: app) Thread.sleep(forTimeInterval: 1.5) } // MARK: Step 7 - Favorite the trip let favoriteButton = app.buttons["tripDetail.favoriteButton"] favoriteButton.scrollIntoView(in: app.scrollViews.firstMatch, direction: .up) XCTAssertTrue(favoriteButton.exists, "Favorite button should exist") favoriteButton.tap() sleep(2) } // MARK: - Manual Demo Flow Test (Original) /// Original manual test flow for comparison or when demo mode is not desired @MainActor func testTripPlanningManualFlow() throws { let app = XCUIApplication() app.launchArguments = [ "--ui-testing", "--disable-animations", "--reset-state" ] app.launch() // MARK: Step 1 - Tap "Start Planning" let startPlanningButton = app.buttons["home.startPlanningButton"] XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 10), "Start Planning button should exist") startPlanningButton.tap() // MARK: Step 2 - Choose "By Dates" mode let dateRangeMode = app.buttons["wizard.planningMode.dateRange"] XCTAssertTrue(dateRangeMode.waitForExistence(timeout: 5), "Date Range mode should exist") dateRangeMode.tap() // MARK: Step 3 - Select June 11-16, 2026 let nextMonthButton = app.buttons["wizard.dates.nextMonth"] nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) let monthLabel = app.staticTexts["wizard.dates.monthLabel"] var attempts = 0 while !monthLabel.label.contains("June 2026") && attempts < 12 { nextMonthButton.tap() Thread.sleep(forTimeInterval: 0.3) attempts += 1 } let june11 = app.buttons["wizard.dates.day.2026-06-11"] june11.scrollIntoView(in: app.scrollViews.firstMatch) june11.tap() Thread.sleep(forTimeInterval: 0.5) let june16 = app.buttons["wizard.dates.day.2026-06-16"] june16.scrollIntoView(in: app.scrollViews.firstMatch) june16.tap() Thread.sleep(forTimeInterval: 0.5) // MARK: Step 4 - Pick MLB let mlbButton = app.buttons["wizard.sports.mlb"] mlbButton.scrollIntoView(in: app.scrollViews.firstMatch) mlbButton.tap() // MARK: Step 5 - Select Central US region let centralRegion = app.buttons["wizard.regions.central"] centralRegion.scrollIntoView(in: app.scrollViews.firstMatch) centralRegion.tap() // MARK: Step 6 - Scroll to Plan button and wait for it to be enabled // Scrolling reveals RoutePreference and RepeatCities steps whose .onAppear // auto-set flags required for canPlanTrip to return true. let planTripButton = app.buttons["wizard.planTripButton"] planTripButton.scrollIntoView(in: app.scrollViews.firstMatch) let enabledPred = NSPredicate(format: "isEnabled == true") let enabledExp = XCTNSPredicateExpectation(predicate: enabledPred, object: planTripButton) let waitResult = XCTWaiter.wait(for: [enabledExp], timeout: 10) XCTAssertEqual(waitResult, .completed, "Plan My Trip should become enabled") planTripButton.tap() // MARK: Step 7 - Wait for planning results let sortDropdown = app.buttons["tripOptions.sortDropdown"] XCTAssertTrue(sortDropdown.waitForExistence(timeout: 30), "Sort dropdown should exist") sortDropdown.tap() Thread.sleep(forTimeInterval: 0.5) let mostGamesOption = app.buttons["tripOptions.sortOption.mostgames"] if mostGamesOption.waitForExistence(timeout: 3) { mostGamesOption.tap() } else { app.buttons["Most Games"].tap() } Thread.sleep(forTimeInterval: 1) // MARK: Step 8 - Select a trip option let anyTrip = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'tripOptions.trip.'")).firstMatch XCTAssertTrue(anyTrip.waitForExistence(timeout: 10), "At least one trip option should exist") anyTrip.tap() Thread.sleep(forTimeInterval: 2) // MARK: Step 9 - Scroll through itinerary for _ in 1...5 { slowSwipeUp(app: app) Thread.sleep(forTimeInterval: 1.5) } // MARK: Step 10 - Favorite the trip let favoriteButton = app.buttons["tripDetail.favoriteButton"] favoriteButton.scrollIntoView(in: app.scrollViews.firstMatch, direction: .up) XCTAssertTrue(favoriteButton.exists, "Favorite button should exist") favoriteButton.tap() sleep(2) } // MARK: - Helper Methods /// Performs a slow swipe up gesture for smooth scrolling private func slowSwipeUp(app: XCUIApplication) { let start = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.7)) let end = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.3)) start.press(forDuration: 0.1, thenDragTo: end, withVelocity: .slow, thenHoldForDuration: 0.1) } /// Performs a slow swipe down gesture for smooth scrolling private func slowSwipeDown(app: XCUIApplication) { let start = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.3)) let end = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.7)) start.press(forDuration: 0.1, thenDragTo: end, withVelocity: .slow, thenHoldForDuration: 0.1) } // MARK: - Basic Tests @MainActor func testExample() throws { let app = XCUIApplication() app.launch() } @MainActor func testLaunchPerformance() throws { measure(metrics: [XCTApplicationLaunchMetric()]) { XCUIApplication().launch() } } }