295 lines
12 KiB
Swift
295 lines
12 KiB
Swift
//
|
|
// 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: - 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()
|
|
}
|
|
}
|
|
}
|