Files
Sportstime/SportsTimeUITests/SportsTimeUITests.swift
Trey t d63d311cab feat: add WCAG AA accessibility app-wide, fix CloudKit container config, remove debug logs
- Add VoiceOver labels, hints, and element grouping across all 60+ views
- Add Reduce Motion support (Theme.Animation.prefersReducedMotion) to all animations
- Replace fixed font sizes with semantic Dynamic Type styles
- Hide decorative elements from VoiceOver with .accessibilityHidden(true)
- Add .minimumHitTarget() modifier ensuring 44pt touch targets
- Add AccessibilityAnnouncer utility for VoiceOver announcements
- Improve color contrast values in Theme.swift for WCAG AA compliance
- Extract CloudKitContainerConfig for explicit container identity
- Remove PostHog debug console log from AnalyticsManager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 09:27:23 -06:00

298 lines
10 KiB
Swift

//
// 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 = [
"-UIPreferredContentSizeCategoryName",
"UICTContentSizeCategoryAccessibilityXXXL"
]
app.launch()
let startPlanningButton = app.buttons["home.startPlanningButton"]
XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 20), "Start Planning should exist at large Dynamic Type")
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")
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 = ["-DemoMode"]
app.launch()
// Wait for app to fully load
sleep(2)
// MARK: Step 1 - Tap "Start Planning"
let startPlanningButton = app.buttons["home.startPlanningButton"]
XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 10), "Start Planning button should exist")
startPlanningButton.tap()
// Wait for demo mode to auto-select planning mode
sleep(2)
// MARK: Step 2 - Continuous scroll through wizard
// Demo mode auto-selects: Date Range June 11-16 MLB Central
// Each step auto-selects 0.5s after appearing, so we scroll slowly
for _ in 1...8 {
slowSwipeUp(app: app)
sleep(2) // Give time for auto-selections
}
// MARK: Step 3 - Click "Plan My Trip"
let planTripButton = app.buttons["wizard.planTripButton"]
XCTAssertTrue(planTripButton.waitForExistence(timeout: 5), "Plan My Trip button should exist")
planTripButton.tap()
// Wait for planning to complete
sleep(6)
// MARK: Step 4 - Demo mode auto-selects "Most Games" and navigates to 4th trip
// Wait for TripOptionsView to load and auto-selections to complete
let sortDropdown = app.buttons["tripOptions.sortDropdown"]
XCTAssertTrue(sortDropdown.waitForExistence(timeout: 15), "Sort dropdown should exist")
// Wait for demo mode to auto-select sort and navigate to trip detail
sleep(3)
// MARK: Step 5 - Scroll through trip detail (demo mode auto-favorites)
// Wait for TripDetailView to appear
let favoriteButton = app.buttons["tripDetail.favoriteButton"]
if favoriteButton.waitForExistence(timeout: 10) {
// Demo mode will auto-favorite, but we scroll to show the itinerary
for _ in 1...6 {
slowSwipeUp(app: app)
sleep(2)
}
// Scroll back up to show the favorited state
for _ in 1...4 {
slowSwipeDown(app: app)
sleep(1)
}
}
// Wait to display final state
sleep(3)
// Test complete - demo flow finished with trip favorited
}
// 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.launch()
// Wait for app to fully load
sleep(2)
// MARK: Step 1 - Tap "Start Planning"
let startPlanningButton = app.buttons["home.startPlanningButton"]
XCTAssertTrue(startPlanningButton.waitForExistence(timeout: 10), "Start Planning button should exist")
startPlanningButton.tap()
sleep(1)
// 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()
sleep(1)
// Scroll down to see dates step
app.swipeUp()
sleep(1)
// MARK: Step 3 - Select June 11-16, 2026
// Navigate to June 2026
let nextMonthButton = app.buttons["wizard.dates.nextMonth"]
XCTAssertTrue(nextMonthButton.waitForExistence(timeout: 5), "Next month button should exist")
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
let june11 = app.buttons["wizard.dates.day.2026-06-11"]
XCTAssertTrue(june11.waitForExistence(timeout: 5), "June 11 should exist")
june11.tap()
Thread.sleep(forTimeInterval: 0.5)
// Select June 16
let june16 = app.buttons["wizard.dates.day.2026-06-16"]
XCTAssertTrue(june16.waitForExistence(timeout: 5), "June 16 should exist")
june16.tap()
sleep(1)
// Scroll down to see sports step
app.swipeUp(velocity: .slow)
sleep(1)
// MARK: Step 4 - Pick MLB
let mlbButton = app.buttons["wizard.sports.mlb"]
XCTAssertTrue(mlbButton.waitForExistence(timeout: 5), "MLB button should exist")
mlbButton.tap()
sleep(1)
// Scroll down to see regions step
app.swipeUp(velocity: .slow)
sleep(1)
// MARK: Step 5 - Select Central US region
let centralRegion = app.buttons["wizard.regions.central"]
XCTAssertTrue(centralRegion.waitForExistence(timeout: 5), "Central region should exist")
centralRegion.tap()
sleep(1)
// Scroll down to see remaining steps
app.swipeUp(velocity: .slow)
sleep(1)
// Keep scrolling for defaults
app.swipeUp(velocity: .slow)
sleep(1)
// MARK: Step 8 - Click "Plan My Trip"
let planTripButton = app.buttons["wizard.planTripButton"]
XCTAssertTrue(planTripButton.waitForExistence(timeout: 5), "Plan My Trip button should exist")
planTripButton.tap()
// Wait for planning to complete
sleep(5)
// MARK: Step 9 - Select "Most Games" from dropdown
let sortDropdown = app.buttons["tripOptions.sortDropdown"]
XCTAssertTrue(sortDropdown.waitForExistence(timeout: 15), "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 10 - Scroll and select 4th trip
for _ in 1...3 {
slowSwipeUp(app: app)
sleep(1)
}
let fourthTrip = app.buttons["tripOptions.trip.3"]
if fourthTrip.waitForExistence(timeout: 5) {
fourthTrip.tap()
} else {
slowSwipeUp(app: app)
sleep(1)
if fourthTrip.waitForExistence(timeout: 3) {
fourthTrip.tap()
} else {
let anyTrip = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'tripOptions.trip.'")).firstMatch
XCTAssertTrue(anyTrip.waitForExistence(timeout: 5), "At least one trip option should exist")
anyTrip.tap()
}
}
sleep(2)
// MARK: Step 12 - Scroll through itinerary
for _ in 1...5 {
slowSwipeUp(app: app)
Thread.sleep(forTimeInterval: 1.5)
}
// MARK: Step 13 - Favorite the trip
for _ in 1...5 {
slowSwipeDown(app: app)
Thread.sleep(forTimeInterval: 0.5)
}
let favoriteButton = app.buttons["tripDetail.favoriteButton"]
XCTAssertTrue(favoriteButton.waitForExistence(timeout: 5), "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()
}
}
}