feat: add XCUITest suite with 10 critical flow tests and QA test plan

Add comprehensive UI test infrastructure with Page Object pattern,
accessibility identifiers, UI test mode (--ui-testing, --reset-state,
--disable-animations), and 10 passing tests covering app launch, tab
navigation, trip wizard, trip saving, settings, schedule, and
accessibility at XXXL Dynamic Type. Also adds a 229-case QA test plan
Excel workbook for manual QA handoff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-16 16:23:59 -06:00
parent 787a0f795e
commit d53f222489
16 changed files with 1528 additions and 25 deletions

View File

@@ -0,0 +1,139 @@
//
// BaseUITestCase.swift
// SportsTimeUITests
//
// Base class for all UI tests. Provides launch configuration,
// screenshot-on-failure, and centralized wait helpers.
//
import XCTest
// MARK: - Base Test Case
class BaseUITestCase: XCTestCase {
/// The application under test. Configured in setUp.
var app: XCUIApplication!
/// Standard timeout for element existence checks.
static let defaultTimeout: TimeInterval = 15
/// Short timeout for elements expected to appear quickly.
static let shortTimeout: TimeInterval = 5
/// Extended timeout for bootstrap / planning engine operations.
static let longTimeout: TimeInterval = 30
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = [
"--ui-testing",
"--disable-animations",
"--reset-state"
]
app.launch()
}
override func tearDownWithError() throws {
// Capture a screenshot on test failure for post-mortem debugging.
if let failure = testRun, failure.failureCount > 0 {
let screenshot = XCTAttachment(screenshot: app.screenshot())
screenshot.name = "Failure-\(name)"
screenshot.lifetime = .keepAlways
add(screenshot)
}
app = nil
}
// MARK: - Screenshot Helpers
/// Captures a named screenshot attached to the test report.
func captureScreenshot(named name: String) {
let screenshot = XCTAttachment(screenshot: app.screenshot())
screenshot.name = name
screenshot.lifetime = .keepAlways
add(screenshot)
}
}
// MARK: - Wait Helpers
extension XCUIElement {
/// Waits until the element exists, failing with a descriptive message.
@discardableResult
func waitForExistenceOrFail(
timeout: TimeInterval = BaseUITestCase.defaultTimeout,
_ message: String? = nil,
file: StaticString = #filePath,
line: UInt = #line
) -> XCUIElement {
let msg = message ?? "Expected \(self) to exist within \(timeout)s"
XCTAssertTrue(waitForExistence(timeout: timeout), msg, file: file, line: line)
return self
}
/// Waits until the element exists AND is hittable.
@discardableResult
func waitUntilHittable(
timeout: TimeInterval = BaseUITestCase.defaultTimeout,
_ message: String? = nil,
file: StaticString = #filePath,
line: UInt = #line
) -> XCUIElement {
waitForExistenceOrFail(timeout: timeout, message, file: file, line: line)
let predicate = NSPredicate(format: "isHittable == true")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
let msg = message ?? "Expected \(self) to be hittable within \(timeout)s"
XCTAssertEqual(result, .completed, msg, file: file, line: line)
return self
}
/// Waits until the element no longer exists.
func waitForNonExistence(
timeout: TimeInterval = BaseUITestCase.defaultTimeout,
_ message: String? = nil,
file: StaticString = #filePath,
line: UInt = #line
) {
let predicate = NSPredicate(format: "exists == false")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
let msg = message ?? "Expected \(self) to disappear within \(timeout)s"
XCTAssertEqual(result, .completed, msg, file: file, line: line)
}
/// Scrolls a scroll view until this element is hittable, or times out.
@discardableResult
func scrollIntoView(
in scrollView: XCUIElement,
direction: ScrollDirection = .down,
maxScrolls: Int = 10,
file: StaticString = #filePath,
line: UInt = #line
) -> XCUIElement {
var scrollsRemaining = maxScrolls
while !exists || !isHittable {
guard scrollsRemaining > 0 else {
XCTFail("Could not scroll \(self) into view after \(maxScrolls) scrolls",
file: file, line: line)
return self
}
switch direction {
case .down:
scrollView.swipeUp(velocity: .slow)
case .up:
scrollView.swipeDown(velocity: .slow)
}
scrollsRemaining -= 1
}
return self
}
}
enum ScrollDirection {
case up, down
}

View File

@@ -0,0 +1,410 @@
//
// Screens.swift
// SportsTimeUITests
//
// Page Object / Screen Object layer.
// Each struct wraps an XCUIApplication and exposes user-intent methods.
// Tests read like: homeScreen.tapStartPlanning()
//
import XCTest
// MARK: - Home Screen
struct HomeScreen {
let app: XCUIApplication
// MARK: Elements
var startPlanningButton: XCUIElement {
app.buttons["home.startPlanningButton"]
}
var homeTab: XCUIElement {
app.tabBars.buttons["Home"]
}
var scheduleTab: XCUIElement {
app.tabBars.buttons["Schedule"]
}
var myTripsTab: XCUIElement {
app.tabBars.buttons["My Trips"]
}
var progressTab: XCUIElement {
app.tabBars.buttons["Progress"]
}
var settingsTab: XCUIElement {
app.tabBars.buttons["Settings"]
}
var adventureAwaitsText: XCUIElement {
app.staticTexts["Adventure Awaits"]
}
var createTripToolbarButton: XCUIElement {
app.buttons["Create new trip"]
}
// MARK: Actions
/// Waits for the home screen to fully load after bootstrap.
@discardableResult
func waitForLoad() -> HomeScreen {
startPlanningButton.waitForExistenceOrFail(
timeout: BaseUITestCase.longTimeout,
"Home screen should load after bootstrap"
)
return self
}
/// Taps "Start Planning" to open the Trip Wizard sheet.
func tapStartPlanning() {
startPlanningButton.waitUntilHittable().tap()
}
/// Switches to a tab by tapping its tab bar button.
func switchToTab(_ tab: XCUIElement) {
tab.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
}
// MARK: Assertions
/// Asserts the tab bar is visible with all 5 tabs.
func assertTabBarVisible() {
XCTAssertTrue(homeTab.exists, "Home tab should exist")
XCTAssertTrue(scheduleTab.exists, "Schedule tab should exist")
XCTAssertTrue(myTripsTab.exists, "My Trips tab should exist")
XCTAssertTrue(progressTab.exists, "Progress tab should exist")
XCTAssertTrue(settingsTab.exists, "Settings tab should exist")
}
}
// MARK: - Trip Wizard Screen
struct TripWizardScreen {
let app: XCUIApplication
// MARK: Elements
var cancelButton: XCUIElement {
app.buttons["Cancel"]
}
var planTripButton: XCUIElement {
app.buttons["wizard.planTripButton"]
}
var navigationTitle: XCUIElement {
app.navigationBars["Plan a Trip"]
}
// Planning modes
func planningModeButton(_ mode: String) -> XCUIElement {
app.buttons["wizard.planningMode.\(mode)"]
}
// Sports
func sportButton(_ sport: String) -> XCUIElement {
app.buttons["wizard.sports.\(sport)"]
}
// Regions
func regionButton(_ region: String) -> XCUIElement {
app.buttons["wizard.regions.\(region)"]
}
// Date picker
var nextMonthButton: XCUIElement {
app.buttons["wizard.dates.nextMonth"]
}
var previousMonthButton: XCUIElement {
app.buttons["wizard.dates.previousMonth"]
}
var monthLabel: XCUIElement {
app.staticTexts["wizard.dates.monthLabel"]
}
func dayButton(_ dateString: String) -> XCUIElement {
app.buttons["wizard.dates.day.\(dateString)"]
}
// MARK: Actions
/// Waits for the wizard sheet to appear.
@discardableResult
func waitForLoad() -> TripWizardScreen {
navigationTitle.waitForExistenceOrFail(
timeout: BaseUITestCase.defaultTimeout,
"Trip Wizard should appear"
)
return self
}
/// Selects the "By Dates" planning mode and waits for steps to expand.
func selectDateRangeMode() {
let btn = planningModeButton("dateRange")
btn.scrollIntoView(in: app.scrollViews.firstMatch)
btn.tap()
}
/// Navigates the calendar to a target month/year and selects start/end dates.
func selectDateRange(
targetMonth: String,
targetYear: String,
startDay: String,
endDay: String
) {
// Navigate forward to the target month
let target = "\(targetMonth) \(targetYear)"
var attempts = 0
// First ensure the month label is visible
monthLabel.scrollIntoView(in: app.scrollViews.firstMatch)
while !monthLabel.label.contains(target) && attempts < 18 {
nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
attempts += 1
}
XCTAssertTrue(monthLabel.label.contains(target),
"Should navigate to \(target)")
// Select start date scroll calendar grid into view first
let startBtn = dayButton(startDay)
startBtn.scrollIntoView(in: app.scrollViews.firstMatch)
startBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
// Select end date
let endBtn = dayButton(endDay)
endBtn.scrollIntoView(in: app.scrollViews.firstMatch)
endBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
}
/// Selects a sport (e.g., "mlb").
func selectSport(_ sport: String) {
let btn = sportButton(sport)
btn.scrollIntoView(in: app.scrollViews.firstMatch)
btn.tap()
}
/// Selects a region (e.g., "central").
func selectRegion(_ region: String) {
let btn = regionButton(region)
btn.scrollIntoView(in: app.scrollViews.firstMatch)
btn.tap()
}
/// Scrolls to and taps "Plan My Trip".
func tapPlanTrip() {
let btn = planTripButton
btn.scrollIntoView(in: app.scrollViews.firstMatch)
btn.waitUntilHittable().tap()
}
/// Dismisses the wizard via the Cancel button.
func tapCancel() {
cancelButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
}
}
// MARK: - Trip Options Screen
struct TripOptionsScreen {
let app: XCUIApplication
// MARK: Elements
var sortDropdown: XCUIElement {
app.buttons["tripOptions.sortDropdown"]
}
func tripCard(_ index: Int) -> XCUIElement {
app.buttons["tripOptions.trip.\(index)"]
}
func sortOption(_ name: String) -> XCUIElement {
app.buttons["tripOptions.sortOption.\(name)"]
}
// MARK: Actions
/// Waits for planning results to load.
@discardableResult
func waitForLoad() -> TripOptionsScreen {
sortDropdown.waitForExistenceOrFail(
timeout: BaseUITestCase.longTimeout,
"Trip Options should appear after planning completes"
)
return self
}
/// Selects a trip option card by index.
func selectTrip(at index: Int) {
let card = tripCard(index)
card.scrollIntoView(in: app.scrollViews.firstMatch)
card.tap()
}
/// Opens the sort dropdown and selects an option.
func sort(by option: String) {
sortDropdown.waitUntilHittable().tap()
let optionBtn = sortOption(option)
optionBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
}
// MARK: Assertions
/// Asserts at least one trip option is visible.
func assertHasResults() {
XCTAssertTrue(tripCard(0).waitForExistence(timeout: BaseUITestCase.shortTimeout),
"At least one trip option should exist")
}
}
// MARK: - Trip Detail Screen
struct TripDetailScreen {
let app: XCUIApplication
// MARK: Elements
var favoriteButton: XCUIElement {
app.buttons["tripDetail.favoriteButton"]
}
var itineraryTitle: XCUIElement {
app.staticTexts["Itinerary"]
}
// MARK: Actions
/// Waits for the trip detail view to load.
@discardableResult
func waitForLoad() -> TripDetailScreen {
favoriteButton.waitForExistenceOrFail(
timeout: BaseUITestCase.defaultTimeout,
"Trip Detail should load with favorite button"
)
return self
}
/// Taps the favorite/save button.
func tapFavorite() {
favoriteButton.waitUntilHittable().tap()
}
// MARK: Assertions
/// Asserts the itinerary section is visible.
func assertItineraryVisible() {
let title = itineraryTitle
title.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertTrue(title.exists, "Itinerary section should be visible")
}
/// Asserts the favorite button label matches the expected state.
func assertSaveState(isSaved: Bool) {
let expected = isSaved ? "Remove from favorites" : "Save to favorites"
XCTAssertEqual(favoriteButton.label, expected,
"Favorite button label should reflect saved state")
}
}
// MARK: - My Trips Screen
struct MyTripsScreen {
let app: XCUIApplication
// MARK: Elements
var emptyState: XCUIElement {
// VStack with accessibilityIdentifier can map to different element types on iOS 26;
// use descendants(matching: .any) for a type-agnostic match.
app.descendants(matching: .any)["myTrips.emptyState"]
}
var savedTripsTitle: XCUIElement {
app.staticTexts["Saved Trips"]
}
// MARK: Assertions
func assertEmpty() {
XCTAssertTrue(
emptyState.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Empty state should be visible when no trips saved"
)
}
func assertHasTrips() {
XCTAssertFalse(emptyState.exists, "Empty state should not show when trips exist")
}
}
// MARK: - Schedule Screen
struct ScheduleScreen {
let app: XCUIApplication
// MARK: Elements
var searchField: XCUIElement {
app.searchFields.firstMatch
}
var filterButton: XCUIElement {
// The filter menu button uses an accessibilityLabel
app.buttons.matching(NSPredicate(
format: "label CONTAINS 'Filter options'"
)).firstMatch
}
// MARK: Assertions
func assertLoaded() {
// Schedule tab should show the filter button with "Filter options" label
XCTAssertTrue(filterButton.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Schedule filter button should appear")
}
}
// MARK: - Settings Screen
struct SettingsScreen {
let app: XCUIApplication
// MARK: Elements
var versionLabel: XCUIElement {
app.staticTexts["settings.versionLabel"]
}
var subscriptionSection: XCUIElement {
app.staticTexts["Subscription"]
}
var privacySection: XCUIElement {
app.staticTexts["Privacy"]
}
var aboutSection: XCUIElement {
app.staticTexts["About"]
}
// MARK: Assertions
func assertLoaded() {
XCTAssertTrue(subscriptionSection.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Settings should show Subscription section")
}
func assertVersionDisplayed() {
// SwiftUI List renders as UICollectionView on iOS 26, not UITableView
versionLabel.scrollIntoView(in: app.collectionViews.firstMatch, direction: .down)
XCTAssertTrue(versionLabel.exists, "App version should be displayed")
XCTAssertFalse(versionLabel.label.isEmpty, "Version label should not be empty")
}
}

View File

@@ -0,0 +1,46 @@
//
// AccessibilityTests.swift
// SportsTimeUITests
//
// Smoke test for Dynamic Type accessibility at XXXL text size.
//
import XCTest
final class AccessibilityTests: BaseUITestCase {
/// Verifies the entry flow is usable at AX XXL text size.
@MainActor
func testLargeDynamicTypeEntryFlow() {
// Re-launch with large Dynamic Type
app.terminate()
app.launchArguments = [
"--ui-testing",
"--disable-animations",
"--reset-state",
"-UIPreferredContentSizeCategoryName",
"UICTContentSizeCategoryAccessibilityXXXL"
]
app.launch()
let home = HomeScreen(app: app)
home.waitForLoad()
// At XXXL text the button may be pushed below the fold; scroll into view
home.startPlanningButton.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertTrue(home.startPlanningButton.isHittable,
"Start Planning should remain hittable at large Dynamic Type")
// Open wizard and verify planning mode options are reachable
home.tapStartPlanning()
let wizard = TripWizardScreen(app: app)
wizard.waitForLoad()
let dateRangeMode = wizard.planningModeButton("dateRange")
dateRangeMode.scrollIntoView(in: app.scrollViews.firstMatch)
XCTAssertTrue(dateRangeMode.isHittable,
"Planning mode should be hittable at large Dynamic Type")
captureScreenshot(named: "Accessibility-LargeType")
}
}

View File

@@ -0,0 +1,38 @@
//
// AppLaunchTests.swift
// SportsTimeUITests
//
// Verifies app boot, bootstrap, and initial screen rendering.
//
import XCTest
final class AppLaunchTests: BaseUITestCase {
/// Verifies the app boots, shows the home screen, and all 5 tabs are present.
@MainActor
func testAppLaunchShowsHomeWithAllTabs() {
let home = HomeScreen(app: app)
home.waitForLoad()
// Assert: Hero card text visible
XCTAssertTrue(home.adventureAwaitsText.exists,
"Hero card should display 'Adventure Awaits'")
// Assert: All tabs present
home.assertTabBarVisible()
captureScreenshot(named: "HomeScreen-Launch")
}
/// Verifies the bootstrap loading indicator disappears and content renders.
@MainActor
func testBootstrapCompletesWithContent() {
let home = HomeScreen(app: app)
home.waitForLoad()
// Assert: Start Planning button is interactable (proves bootstrap loaded data)
XCTAssertTrue(home.startPlanningButton.isHittable,
"Start Planning should be hittable after bootstrap")
}
}

View File

@@ -0,0 +1,24 @@
//
// ScheduleTests.swift
// SportsTimeUITests
//
// Verifies the Schedule tab loads and displays content.
//
import XCTest
final class ScheduleTests: BaseUITestCase {
/// Verifies the schedule tab loads and shows content.
@MainActor
func testScheduleTabLoads() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.scheduleTab)
let schedule = ScheduleScreen(app: app)
schedule.assertLoaded()
captureScreenshot(named: "Schedule-Loaded")
}
}

View File

@@ -0,0 +1,48 @@
//
// SettingsTests.swift
// SportsTimeUITests
//
// Verifies the Settings screen loads, displays version, and shows all sections.
//
import XCTest
final class SettingsTests: BaseUITestCase {
/// Verifies the Settings screen loads and displays the app version.
@MainActor
func testSettingsShowsVersion() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
settings.assertVersionDisplayed()
captureScreenshot(named: "Settings-Version")
}
/// Verifies key sections are present in Settings.
@MainActor
func testSettingsSectionsPresent() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
// Assert: Key sections exist
XCTAssertTrue(settings.subscriptionSection.waitForExistence(
timeout: BaseUITestCase.shortTimeout),
"Subscription section should exist")
// SwiftUI List renders as UICollectionView on iOS 26
settings.privacySection.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.privacySection.exists, "Privacy section should exist")
settings.aboutSection.scrollIntoView(in: app.collectionViews.firstMatch)
XCTAssertTrue(settings.aboutSection.exists, "About section should exist")
}
}

View File

@@ -0,0 +1,47 @@
//
// TabNavigationTests.swift
// SportsTimeUITests
//
// Verifies navigation through all 5 tabs.
//
import XCTest
final class TabNavigationTests: BaseUITestCase {
/// Navigates through all 5 tabs and asserts each one loads.
@MainActor
func testTabNavigationCycle() {
let home = HomeScreen(app: app)
home.waitForLoad()
// Schedule tab
home.switchToTab(home.scheduleTab)
let schedule = ScheduleScreen(app: app)
schedule.assertLoaded()
// My Trips tab
home.switchToTab(home.myTripsTab)
let myTrips = MyTripsScreen(app: app)
myTrips.assertEmpty()
// Progress tab (Pro-gated, but UI test mode forces Pro)
home.switchToTab(home.progressTab)
// Just verify the tab switched without crash
let progressNav = app.navigationBars.firstMatch
XCTAssertTrue(progressNav.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Progress tab should load")
// Settings tab
home.switchToTab(home.settingsTab)
let settings = SettingsScreen(app: app)
settings.assertLoaded()
// Return home
home.switchToTab(home.homeTab)
XCTAssertTrue(home.startPlanningButton.waitForExistence(timeout: BaseUITestCase.shortTimeout),
"Should return to Home tab")
captureScreenshot(named: "TabNavigation-ReturnHome")
}
}

View File

@@ -0,0 +1,66 @@
//
// TripSavingTests.swift
// SportsTimeUITests
//
// Tests the end-to-end trip saving flow: plan select save verify in My Trips.
//
import XCTest
final class TripSavingTests: BaseUITestCase {
/// Plans a trip, selects an option, saves it, and verifies it appears in My Trips.
@MainActor
func testSaveTripAppearsInMyTrips() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.tapStartPlanning()
// Plan a trip using date range mode
let wizard = TripWizardScreen(app: app)
wizard.waitForLoad()
wizard.selectDateRangeMode()
wizard.nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
wizard.selectDateRange(
targetMonth: "June",
targetYear: "2026",
startDay: "2026-06-11",
endDay: "2026-06-16"
)
wizard.selectSport("mlb")
wizard.selectRegion("central")
wizard.tapPlanTrip()
// Select first trip option
let options = TripOptionsScreen(app: app)
options.waitForLoad()
options.selectTrip(at: 0)
// Save the trip
let detail = TripDetailScreen(app: app)
detail.waitForLoad()
detail.assertSaveState(isSaved: false)
detail.tapFavorite()
// Allow save to persist
detail.assertSaveState(isSaved: true)
captureScreenshot(named: "TripSaving-Favorited")
// Navigate back to My Trips tab
// Dismiss the entire wizard sheet: Detail Options Wizard Cancel
app.navigationBars.buttons.firstMatch.tap() // Back from detail to options
// Back from options to wizard
let wizardBackBtn = app.navigationBars.buttons.firstMatch
wizardBackBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
// Cancel the wizard sheet
wizard.tapCancel()
// Now the tab bar is accessible
home.switchToTab(home.myTripsTab)
// Assert: Saved trip appears (empty state should NOT be visible)
let myTrips = MyTripsScreen(app: app)
myTrips.assertHasTrips()
}
}

View File

@@ -0,0 +1,71 @@
//
// TripWizardFlowTests.swift
// SportsTimeUITests
//
// Tests the trip planning wizard: date range mode, calendar navigation,
// sport/region selection, and planning engine results.
//
import XCTest
final class TripWizardFlowTests: BaseUITestCase {
/// Full flow: Start Planning Date Range Select dates MLB Central Plan.
/// Asserts the planning engine returns results.
@MainActor
func testDateRangeTripPlanningFlow() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.tapStartPlanning()
let wizard = TripWizardScreen(app: app)
wizard.waitForLoad()
// Step 1: Select "By Dates" mode
wizard.selectDateRangeMode()
// Step 2: Navigate to June 2026 and select June 11-16
// Scroll to see dates step
wizard.nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch)
wizard.selectDateRange(
targetMonth: "June",
targetYear: "2026",
startDay: "2026-06-11",
endDay: "2026-06-16"
)
// Step 3: Select MLB
wizard.selectSport("mlb")
// Step 4: Select Central region
wizard.selectRegion("central")
// Step 5: Tap Plan My Trip
wizard.tapPlanTrip()
// Assert: Trip Options screen appears with results
let options = TripOptionsScreen(app: app)
options.waitForLoad()
options.assertHasResults()
captureScreenshot(named: "TripWizard-PlanningResults")
}
/// Verifies the wizard can be dismissed via Cancel.
@MainActor
func testWizardCanBeDismissed() {
let home = HomeScreen(app: app)
home.waitForLoad()
home.tapStartPlanning()
let wizard = TripWizardScreen(app: app)
wizard.waitForLoad()
wizard.tapCancel()
// Assert: Back on home screen
XCTAssertTrue(
home.startPlanningButton.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Should return to Home after cancelling wizard"
)
}
}