// // ProgressTests.swift // SportsTimeUITests // // Tests for the Progress tab (Pro-gated). // QA Sheet: F-066, F-097, F-099, F-100, F-101, F-106, F-110 // import XCTest final class ProgressTests: BaseUITestCase { /// F-066: Progress tab loads for Pro user — stadium quest and navigation visible. @MainActor func testF066_ProgressTabLoads() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() // Stadium Quest label should be visible (proves data loaded) XCTAssertTrue( progress.stadiumQuestLabel.waitForExistence( timeout: BaseUITestCase.longTimeout), "Stadium Quest label should appear on Progress tab" ) captureScreenshot(named: "F066-ProgressTab-Loaded") } /// F-097: League/sport selector toggles between sports. @MainActor func testF097_LeagueSportSelector() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() // Sport buttons inherit the parent's identifier (progress.sportSelector), // so find them by their accessibility label which includes the sport name. let mlbButton = app.buttons.matching(NSPredicate( format: "label BEGINSWITH 'MLB'" )).firstMatch XCTAssertTrue(mlbButton.waitForExistence(timeout: BaseUITestCase.longTimeout), "MLB sport button should exist in sport selector") mlbButton.tap() captureScreenshot(named: "F097-SportSelector-MLB") // Switch to NBA let nbaButton = app.buttons.matching(NSPredicate( format: "label BEGINSWITH 'NBA'" )).firstMatch if nbaButton.waitForExistence(timeout: BaseUITestCase.shortTimeout) { nbaButton.tap() captureScreenshot(named: "F097-SportSelector-NBA") } } /// F-101: Add visit — Save button disabled until stadium is selected. @MainActor func testF101_AddVisitRequiredFields() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() progress.tapAddManualVisit() let visitSheet = StadiumVisitSheetScreen(app: app) visitSheet.waitForLoad() // Save button should exist but be disabled (no stadium selected) XCTAssertTrue( visitSheet.saveButton.waitForExistence(timeout: BaseUITestCase.shortTimeout), "Save button should exist" ) XCTAssertFalse( visitSheet.saveButton.isEnabled, "Save button should be disabled without a stadium selected" ) captureScreenshot(named: "F101-SaveDisabledNoStadium") } /// F-100: Add visit — full manual entry flow (select stadium, save). @MainActor func testF100_AddVisitManualEntry() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() progress.tapAddManualVisit() let visitSheet = StadiumVisitSheetScreen(app: app) visitSheet.waitForLoad() // Pick a stadium visitSheet.pickFirstStadium() // Save button should now be enabled XCTAssertTrue( visitSheet.saveButton.waitForExistence(timeout: BaseUITestCase.shortTimeout), "Save button should exist after picking a stadium" ) XCTAssertTrue( visitSheet.saveButton.isEnabled, "Save button should be enabled after selecting a stadium" ) // Save the visit visitSheet.tapSave() // Sheet should dismiss — nav bar disappears visitSheet.navigationBar.waitForNonExistence( timeout: BaseUITestCase.defaultTimeout, "Visit sheet should dismiss after save" ) captureScreenshot(named: "F100-ManualEntryComplete") } /// F-106: View Games History — add a visit, then navigate to history view. @MainActor func testF106_ViewGamesHistory() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() // Add a visit first (prerequisite: Recent Visits section only shows with visits) progress.tapAddManualVisit() let visitSheet = StadiumVisitSheetScreen(app: app) visitSheet.waitForLoad() visitSheet.pickFirstStadium() visitSheet.tapSave() visitSheet.navigationBar.waitForNonExistence(timeout: BaseUITestCase.defaultTimeout) // Wait for data reload — Recent Visits section should appear in hierarchy XCTAssertTrue( progress.recentVisitsTitle.waitForExistence(timeout: BaseUITestCase.longTimeout), "Recent Visits section should appear after adding a visit" ) // Scroll to Recent Visits (same pattern as F-110 which uses app.swipeUp) var scrollAttempts = 0 while !progress.recentVisitsTitle.isHittable && scrollAttempts < 20 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } // Find and tap "See All" — try button, link, then generic element let seeAllPredicate = NSPredicate(format: "label CONTAINS[c] 'See All'") let seeAllButton = app.buttons.matching(seeAllPredicate).firstMatch let seeAllGeneric = app.descendants(matching: .any).matching(seeAllPredicate).firstMatch if seeAllButton.exists && seeAllButton.isHittable { seeAllButton.tap() } else if seeAllGeneric.exists { // One more scroll to ensure it's on screen if !seeAllGeneric.isHittable { app.swipeUp(velocity: .slow) } seeAllGeneric.tap() } else { XCTFail("Could not find 'See All' link in Recent Visits section") } // Verify Games History view loads let gamesHistory = GamesHistoryScreen(app: app) gamesHistory.waitForLoad() captureScreenshot(named: "F106-GamesHistory") } /// F-099: Progress percentage updates after adding a stadium visit. @MainActor func testF099_ProgressPercentageUpdates() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() // Check initial progress — look for the accessibility label with "percent complete" let progressCircle = app.descendants(matching: .any).matching(NSPredicate( format: "label CONTAINS 'stadiums visited'" )).firstMatch let initialLabel = progressCircle.exists ? progressCircle.label : "" // Add a stadium visit progress.tapAddManualVisit() let visitSheet = StadiumVisitSheetScreen(app: app) visitSheet.waitForLoad() visitSheet.pickFirstStadium() visitSheet.tapSave() visitSheet.navigationBar.waitForNonExistence(timeout: BaseUITestCase.defaultTimeout) // Progress should have updated — verify the progress circle label changed let updatedCircle = app.descendants(matching: .any).matching(NSPredicate( format: "label CONTAINS 'stadiums visited'" )).firstMatch waitUntil(timeout: BaseUITestCase.longTimeout, "Progress label should update after adding a visit") { guard updatedCircle.exists else { return false } return initialLabel.isEmpty || updatedCircle.label != initialLabel } captureScreenshot(named: "F099-ProgressPercentageUpdated") } /// F-110: Achievements gallery is visible with badge grid. @MainActor func testF110_AchievementsGalleryVisible() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.progressTab) let progress = ProgressScreen(app: app) progress.waitForLoad() // Achievements section is below the fold — swipe up to find it let achievementsTitle = progress.achievementsTitle var scrollAttempts = 0 while !achievementsTitle.exists && scrollAttempts < 15 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(achievementsTitle.exists, "Achievements section should be visible") captureScreenshot(named: "F110-AchievementsGallery") } }