// // SportsTimeUITests.swift // SportsTimeUITests // // Created by Trey Tartt on 1/6/26. // import XCTest @MainActor 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: - Shared Wizard Helpers /// Launches the app with standard UI testing arguments and taps Start Planning. private func launchAndStartPlanning() -> XCUIApplication { let app = XCUIApplication() app.launchArguments = [ "--ui-testing", "--disable-animations", "--reset-state" ] app.launch() let startPlanningButton = app.buttons["home.startPlanningButton"] XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 10), "Start Planning button should exist") startPlanningButton.tap() return app } /// Fills the wizard: selects By Dates mode, navigates to June 2026, /// picks June 11-16, MLB, and Central region. private func fillWizardSteps(app: XCUIApplication) { // Choose "By Dates" mode 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() // Wait for the month label to update after tap let updatedLabel = app.staticTexts["wizard.dates.monthLabel"] _ = updatedLabel.waitForExistence(timeout: 2) attempts += 1 } // Select June 11 let june11 = app.buttons["wizard.dates.day.2026-06-11"] june11.scrollIntoView(in: app.scrollViews.firstMatch) june11.tap() // Wait for June 16 to be available after selecting the start date let june16 = app.buttons["wizard.dates.day.2026-06-16"] XCTAssertTrue(june16.waitForExistence(timeout: 5), "June 16 button should exist after selecting start date") june16.scrollIntoView(in: app.scrollViews.firstMatch) june16.tap() // 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() } /// Taps "Plan My Trip" after waiting for it to become enabled. private func tapPlanTrip(app: XCUIApplication) { 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() } /// Selects "Most Games" sort option from the sort dropdown. private func selectMostGamesSort(app: XCUIApplication) { let sortDropdown = app.buttons["tripOptions.sortDropdown"] XCTAssertTrue(sortDropdown.waitForExistence(timeout: 30), "Sort dropdown should exist") sortDropdown.tap() // Wait for the sort option to appear let mostGamesOption = app.buttons["tripOptions.sortOption.mostgames"] do { let found = mostGamesOption.waitForExistence(timeout: 3) if found { mostGamesOption.tap() } else { XCTFail("Expected element with accessibility ID 'tripOptions.sortOption.mostgames' but it did not appear") } } // Wait for sort to be applied and trip list to update let anyTrip = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'tripOptions.trip.'")).firstMatch XCTAssertTrue(anyTrip.waitForExistence(timeout: 10), "Trip options should appear after sorting") } /// Selects the first available trip option and waits for the detail view to load. private func selectFirstTrip(app: XCUIApplication) { 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() // Wait for the trip detail view to load let favoriteButton = app.buttons["tripDetail.favoriteButton"] XCTAssertTrue(favoriteButton.waitForExistence(timeout: 10), "Trip detail view should load with favorite button") } // 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 = launchAndStartPlanning() fillWizardSteps(app: app) tapPlanTrip(app: app) selectMostGamesSort(app: app) selectFirstTrip(app: app) // Scroll through itinerary for _ in 1...5 { slowSwipeUp(app: app) // Wait for scroll content to settle let scrollView = app.scrollViews.firstMatch _ = scrollView.waitForExistence(timeout: 3) } // 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() // Wait for favorite state to update let favoritedPredicate = NSPredicate(format: "isSelected == true OR label CONTAINS 'Favorited' OR label CONTAINS 'Unfavorite'") let favoritedExpectation = XCTNSPredicateExpectation(predicate: favoritedPredicate, object: favoriteButton) let result = XCTWaiter.wait(for: [favoritedExpectation], timeout: 5) // If the button doesn't change state visually, at least verify it still exists if result != .completed { XCTAssertTrue(favoriteButton.exists, "Favorite button should still exist after tapping") } } // MARK: - Manual Demo Flow Test (Original) /// Original manual test flow for comparison or when demo mode is not desired. /// This test verifies the same wizard flow as the demo test, plus additional /// scrolling through the itinerary detail view. @MainActor func testTripPlanningManualFlow() throws { let app = launchAndStartPlanning() fillWizardSteps(app: app) tapPlanTrip(app: app) selectMostGamesSort(app: app) selectFirstTrip(app: app) // Scroll through itinerary for _ in 1...5 { slowSwipeUp(app: app) // Wait for scroll content to settle let scrollView = app.scrollViews.firstMatch _ = scrollView.waitForExistence(timeout: 3) } // 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() // Wait for favorite state to update let favoritedPredicate = NSPredicate(format: "isSelected == true OR label CONTAINS 'Favorited' OR label CONTAINS 'Unfavorite'") let favoritedExpectation = XCTNSPredicateExpectation(predicate: favoritedPredicate, object: favoriteButton) let result = XCTWaiter.wait(for: [favoritedExpectation], timeout: 5) // If the button doesn't change state visually, at least verify it still exists if result != .completed { XCTAssertTrue(favoriteButton.exists, "Favorite button should still exist after tapping") } } // 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() } } }