// // HomeTests.swift // SportsTimeUITests // // Tests for the Home tab: hero card, start planning, toolbar button, recent trips, polls. // QA Sheet: F-012, F-013, F-014, F-015, F-017, F-018, F-019, F-020, F-081, F-082, F-083, F-084 // import XCTest final class HomeTests: BaseUITestCase { /// F-012: Hero card displays — "Adventure Awaits" text visible, Start Planning tappable. @MainActor func testF012_HeroCardDisplaysCorrectly() { let home = HomeScreen(app: app) home.waitForLoad() XCTAssertTrue(home.adventureAwaitsText.exists, "Hero card should display 'Adventure Awaits'") XCTAssertTrue(home.startPlanningButton.isHittable, "Start Planning button should be tappable") captureScreenshot(named: "F012-HeroCard") } /// F-013: Start Planning opens wizard sheet with "Plan a Trip" title. @MainActor func testF013_StartPlanningOpensWizard() { let home = HomeScreen(app: app) home.waitForLoad() home.tapStartPlanning() let wizard = TripWizardScreen(app: app) wizard.waitForLoad() XCTAssertTrue(wizard.navigationTitle.exists, "Wizard should show 'Plan a Trip' title") captureScreenshot(named: "F013-WizardOpened") } /// F-020: Create trip toolbar button (+) opens the same wizard sheet. @MainActor func testF020_CreateTripToolbarButtonOpensWizard() { let home = HomeScreen(app: app) home.waitForLoad() home.createTripToolbarButton.waitUntilHittable( timeout: BaseUITestCase.shortTimeout ).tap() let wizard = TripWizardScreen(app: app) wizard.waitForLoad() XCTAssertTrue(wizard.navigationTitle.exists, "Toolbar '+' button should open trip wizard") captureScreenshot(named: "F020-ToolbarCreateTrip") } /// F-015: Featured trips refresh button reloads suggestions. @MainActor func testF015_FeaturedTripsRefreshButton() { let home = HomeScreen(app: app) home.waitForLoad() // Wait for featured trips section to load let section = home.featuredTripsSection if !section.waitForExistence(timeout: BaseUITestCase.longTimeout) { section.scrollIntoView(in: app.scrollViews.firstMatch, maxScrolls: 15) } guard section.exists else { XCTFail("Featured trips section not found — cannot test refresh") return } // Find the refresh button by accessibility label let refreshButton = app.buttons["Refresh trips"] refreshButton.scrollIntoView(in: app.scrollViews.firstMatch) XCTAssertTrue(refreshButton.exists, "Refresh trips button should exist in featured section") // Tap refresh and verify no crash refreshButton.tap() // Featured section should still exist after refresh waitUntil(timeout: BaseUITestCase.longTimeout, "Featured trips section should remain after refresh") { section.exists && refreshButton.exists } captureScreenshot(named: "F015-FeaturedTripsRefresh") } /// F-014: Featured trips carousel loads and is visible. @MainActor func testF014_FeaturedTripsCarouselLoads() { let home = HomeScreen(app: app) home.waitForLoad() // Featured trips load asynchronously — wait for them to appear // Then scroll to make them visible. Use the app itself as scroll target // since NavigationStack + ScrollView can nest scroll containers. let section = home.featuredTripsSection if !section.waitForExistence(timeout: BaseUITestCase.longTimeout) { // Try scrolling to find it section.scrollIntoView(in: app.scrollViews.firstMatch, maxScrolls: 15) } XCTAssertTrue(section.exists, "Featured trips section should be visible") captureScreenshot(named: "F014-FeaturedTrips") } /// F-017: Recent trips section shows saved trips with "See All" link on Home tab. @MainActor func testF017_RecentTripsSectionWithSavedTrips() { // Plan a trip and select the first option let (_, detail) = TestFlows.planAndSelectFirstTrip(app: app) // Save the trip detail.assertSaveState(isSaved: false) detail.tapFavorite() detail.assertSaveState(isSaved: true) captureScreenshot(named: "F017-TripSaved") // Navigate back: Detail → Options → Wizard → Cancel (dismiss sheet) app.navigationBars.buttons.firstMatch.tap() app.navigationBars.buttons.firstMatch .waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() TripWizardScreen(app: app).tapCancel() // We're back on Home tab. Wait for it to load. let home = HomeScreen(app: app) home.waitForLoad() // Scroll to find the recent trips section let recentTrips = home.recentTripsSection var scrollAttempts = 0 while !recentTrips.exists && scrollAttempts < 15 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(recentTrips.exists, "Recent trips section should appear after saving a trip") // Verify "See All" link is visible let seeAllText = app.staticTexts["See All"] XCTAssertTrue(seeAllText.exists, "'See All' link should be visible in recent trips section") captureScreenshot(named: "F017-RecentTripsSection") } /// F-018: Fresh launch with no saved trips — Recent Trips section should not appear. @MainActor func testF018_RecentTripsSectionHiddenNoSavedTrips() { let home = HomeScreen(app: app) home.waitForLoad() // Scroll down to check the full home tab var scrollAttempts = 0 while scrollAttempts < 5 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } // Recent trips section should NOT exist on a fresh launch XCTAssertFalse( home.recentTripsSection.exists, "Recent trips section should not appear when no trips are saved" ) captureScreenshot(named: "F018-NoRecentTrips") } /// F-019: Planning tips section is visible at bottom of home tab. @MainActor func testF019_PlanningTipsSectionVisible() { let home = HomeScreen(app: app) home.waitForLoad() // Tips section is at the very bottom — need to scroll far down. // The home screen has nested scroll views; swipe on the main view. let section = home.tipsSection var scrollAttempts = 0 while !section.exists && scrollAttempts < 15 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(section.exists, "Planning tips section should be visible") captureScreenshot(named: "F019-PlanningTips") } // MARK: - Polls Section (F-081, F-083) /// F-081: Group Polls section is visible on My Trips tab. @MainActor func testF081_PollsSectionVisible() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.myTripsTab) // Scroll to find "Group Polls" text let groupPollsText = app.staticTexts["Group Polls"] var scrollAttempts = 0 while !groupPollsText.exists && scrollAttempts < 10 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(groupPollsText.exists, "Group Polls section should be visible on My Trips tab") captureScreenshot(named: "F081-PollsSectionVisible") } /// F-082: Create poll button appears when 2+ trips are saved. @MainActor func testF082_CreatePollButtonWith2PlusTrips() { // Save first trip using the standard flow let (wizard1, detail1) = TestFlows.planAndSelectFirstTrip(app: app) detail1.assertSaveState(isSaved: false) detail1.tapFavorite() detail1.assertSaveState(isSaved: true) // Navigate back: Detail → Options → Wizard → Cancel app.navigationBars.buttons.firstMatch.tap() app.navigationBars.buttons.firstMatch .waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() wizard1.tapCancel() // Save second trip using a different date range let home = HomeScreen(app: app) home.waitForLoad() let (wizard2, options2) = TestFlows.planDateRangeTrip( app: app, month: "July", year: "2026", startDay: "2026-07-01", endDay: "2026-07-07" ) options2.selectTrip(at: 0) let detail2 = TripDetailScreen(app: app) detail2.waitForLoad() detail2.assertSaveState(isSaved: false) detail2.tapFavorite() detail2.assertSaveState(isSaved: true) // Navigate back: Detail → Options → Wizard → Cancel app.navigationBars.buttons.firstMatch.tap() app.navigationBars.buttons.firstMatch .waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() wizard2.tapCancel() // Navigate to My Trips tab home.waitForLoad() home.switchToTab(home.myTripsTab) // Scroll to find the Create poll button let createPollButton = app.buttons["Create poll"] var scrollAttempts = 0 while !createPollButton.exists && scrollAttempts < 10 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(createPollButton.exists, "Create poll button should appear with 2+ saved trips") captureScreenshot(named: "F082-CreatePollWith2Trips") } /// F-083: Create poll button hidden when fewer than 2 trips saved. @MainActor func testF083_CreatePollButtonHiddenLessThan2Trips() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.myTripsTab) // With 0 saved trips, create poll button should not exist let createPollButton = app.buttons["Create poll"] var scrollAttempts = 0 while scrollAttempts < 5 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertFalse(createPollButton.exists, "Create poll button should not appear with fewer than 2 saved trips") captureScreenshot(named: "F083-CreatePollHidden") } // MARK: - Pull to Refresh (F-084) /// F-084: Pull to refresh on My Trips tab doesn't crash. @MainActor func testF084_PullToRefresh() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.myTripsTab) let myTrips = MyTripsScreen(app: app) waitUntil(timeout: BaseUITestCase.longTimeout, "My Trips should load before refreshing") { myTrips.emptyState.exists || myTrips.tripCard(0).exists || self.app.staticTexts["Group Polls"].exists } // Pull down to refresh app.swipeDown(velocity: .slow) waitUntil(timeout: BaseUITestCase.longTimeout, "My Trips should still be loaded after pull to refresh") { myTrips.emptyState.exists || myTrips.tripCard(0).exists || self.app.staticTexts["Group Polls"].exists } // Verify the tab is still functional (no crash) let groupPolls = app.staticTexts["Group Polls"] var scrollAttempts = 0 while !groupPolls.exists && scrollAttempts < 5 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(groupPolls.exists, "My Trips tab should remain functional after pull to refresh") captureScreenshot(named: "F084-PullToRefresh") } }