From 7e54ff2ef2e582b3920fa15f2d180d6392bef313 Mon Sep 17 00:00:00 2001 From: treyt Date: Wed, 18 Feb 2026 13:24:46 -0600 Subject: [PATCH 1/3] Add XCUITest authoring docs and templates --- AGENTS.md | 27 +++++++++ CLAUDE.md | 8 ++- README.md | 13 +++++ XCUITest-Authoring.md | 108 ++++++++++++++++++++++++++++++++++++ XCUITestSuiteTemplate.swift | 48 ++++++++++++++++ uiTestPrompt.md | 49 ++++++++++++++++ 6 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 100644 XCUITest-Authoring.md create mode 100644 XCUITestSuiteTemplate.swift create mode 100644 uiTestPrompt.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..05c8835 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# SportsTime Agent Notes + +This file is for AI/code agents working in this repo. + +## UI Test Docs + +- Start with `XCUITest-Authoring.md`. +- Use `XCUITestSuiteTemplate.swift` when creating new suites. +- Use `uiTestPrompt.md` when asking an agent to add/fix UI tests. + +## UI Test Ground Rules + +- Keep new UI tests in `SportsTimeUITests/Tests/`. +- Reuse page objects in `SportsTimeUITests/Framework/Screens.swift`. +- Reuse shared setup and helpers from `SportsTimeUITests/Framework/BaseUITestCase.swift`. +- Reuse high-level flows from `TestFlows` before adding duplicate test steps. +- Prefer robust waits (`waitForExistenceOrFail`, `waitUntilHittable`) over sleeps. +- Use existing accessibility identifiers (`wizard.*`, `tripOptions.*`, `tripDetail.*`, etc.). +- Capture screenshots for key checkpoints in longer end-to-end tests. + +## Before Merging UI Test Changes + +- Run the touched test class. +- Run full UI suite: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests` +- Run full scheme verification if behavior touched shared flows: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO` diff --git a/CLAUDE.md b/CLAUDE.md index 30ba2d5..028a5bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,9 @@ xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platfo # Run a single test xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TripPlanningEngineTests/planningMode_dateRange test +# Run UI tests only +xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests + # Data scraping (Python) cd Scripts && pip install -r requirements.txt python scrape_schedules.py --sport all --season 2026 @@ -298,7 +301,10 @@ View → modelContext.delete(model) → modelContext.save() → reload data → - **Test directory**: `SportsTimeTests/` — mirrors source structure - **File naming**: `{ClassName}Tests.swift` or `{Feature}Tests.swift` - **Helper files**: `SportsTimeTests/Helpers/MockServices.swift`, `SportsTimeTests/Helpers/TestFixtures.swift` -- **UI tests**: `SportsTimeUITests/` (template only, not actively used) +- **UI tests**: `SportsTimeUITests/` is active and uses XCTest + page-object patterns +- **UI authoring guide**: `XCUITest-Authoring.md` +- **UI suite template**: `XCUITestSuiteTemplate.swift` +- **UI request template**: `uiTestPrompt.md` ### Existing Test Suites diff --git a/README.md b/README.md index d16b9ba..041c325 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ SportsTime/ │ ├── Export/ # PDF generation │ └── Resources/ # Bundled JSON data ├── SportsTimeTests/ # Unit tests +├── SportsTimeUITests/ # UI test suites + screen objects ├── Scripts/ # Python data pipeline │ └── sportstime_parser/ # Schedule scraping & CloudKit upload ├── data/ # Local data files @@ -124,6 +125,14 @@ xcodebuild -project SportsTime.xcodeproj \ -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ -only-testing:SportsTimeTests/EdgeCaseTests \ test + +# UI test target only +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests ``` ### Data Pipeline @@ -151,7 +160,11 @@ python -m sportstime_parser upload --sport all ## Documentation - `CLAUDE.md` - Development guidelines and architecture details +- `AGENTS.md` - Agent-specific execution notes for this repo - `ARCHITECTURE.md` - Detailed system architecture +- `XCUITest-Authoring.md` - How existing UI tests are structured and how to add new ones +- `XCUITestSuiteTemplate.swift` - Starter suite template for new UI test files +- `uiTestPrompt.md` - Reusable prompt template for requesting UI test work - `docs/TEST_PLAN.md` - Test suite documentation - `docs/MARKET_RESEARCH.md` - Competitive analysis diff --git a/XCUITest-Authoring.md b/XCUITest-Authoring.md new file mode 100644 index 0000000..4dc0f7e --- /dev/null +++ b/XCUITest-Authoring.md @@ -0,0 +1,108 @@ +# XCUITest Authoring Guide + +This guide documents the current SportsTime UI test foundation and how to add new tests safely. + +## Current Foundation + +## Layout + +- Test target: `SportsTimeUITests` +- Base setup/helpers: `SportsTimeUITests/Framework/BaseUITestCase.swift` +- Page objects + shared flows: `SportsTimeUITests/Framework/Screens.swift` +- Suite files: `SportsTimeUITests/Tests/*.swift` +- Legacy mixed tests: `SportsTimeUITests/SportsTimeUITests.swift` and `SportsTimeUITests/SportsTimeUITestsLaunchTests.swift` + +## Existing Suite Coverage + +- `AppLaunchTests` +- `HomeTests` +- `TabNavigationTests` +- `ScheduleTests` +- `TripWizardFlowTests` +- `TripOptionsTests` +- `TripSavingTests` +- `ProgressTests` +- `SettingsTests` +- `AccessibilityTests` +- `StabilityTests` + +## Key Conventions + +- Inherit from `BaseUITestCase`. +- Use `@MainActor` test methods. +- Prefer page-object actions over direct element taps. +- Prefer existing high-level flows (`TestFlows.planDateRangeTrip`, `TestFlows.planAndSelectFirstTrip`) for shared setup. +- Use deterministic selectors with accessibility identifiers. +- Use `waitForExistenceOrFail` and `waitUntilHittable` instead of arbitrary sleeps. + +## Adding a New UI Test + +1. Pick the closest existing suite in `SportsTimeUITests/Tests/`. +2. If none fits, create a new suite using `XCUITestSuiteTemplate.swift`. +3. Add/extend page-object methods in `Screens.swift` before writing raw element code. +4. Reuse `TestFlows` when setup overlaps existing end-to-end flows. +5. Keep assertion messages explicit and behavior-focused. +6. Capture screenshot(s) for key milestone state in longer flows. +7. Run targeted tests, then full UI tests. + +## When to Edit `Screens.swift` + +Edit page objects when: + +- A new screen element needs a stable selector. +- The interaction is reused across multiple tests. +- The flow can be standardized (especially wizard planning flow). + +Do not add one-off test-only branching logic unless it removes a real flake. + +## Running Tests + +## Fast Loop (single test) + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests/TripWizardFlowTests/testF026_DateRangeSelection +``` + +## Per Class + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests/TripOptionsTests +``` + +## Full UI Suite + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO \ + -only-testing:SportsTimeUITests +``` + +## Full Scheme Validation (unit + UI) + +```bash +xcodebuild test-without-building \ + -project SportsTime.xcodeproj \ + -scheme SportsTime \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -parallel-testing-enabled NO +``` + +## Flake Prevention Notes + +- Keep simulator orientation consistent (portrait baseline from `BaseUITestCase`). +- For wizard date selection, navigate by month/year first and use day-cell fallback when specific IDs are unavailable. +- For cross-season planning tests, prefer deterministic fallback sports if selected sport has no viable schedule for current test data. +- Increase waits only where planning computation is the actual bottleneck. diff --git a/XCUITestSuiteTemplate.swift b/XCUITestSuiteTemplate.swift new file mode 100644 index 0000000..18ab132 --- /dev/null +++ b/XCUITestSuiteTemplate.swift @@ -0,0 +1,48 @@ +// +// __FeatureName__Tests.swift +// SportsTimeUITests +// +// Copy this file into SportsTimeUITests/Tests and rename placeholders. +// + +import XCTest + +final class __FeatureName__Tests: BaseUITestCase { + + // MARK: - Helpers + + @MainActor + private func openHome() -> HomeScreen { + let home = HomeScreen(app: app) + home.waitForLoad() + return home + } + + @MainActor + private func openWizard() -> TripWizardScreen { + let home = openHome() + home.tapStartPlanning() + + let wizard = TripWizardScreen(app: app) + wizard.waitForLoad() + return wizard + } + + // MARK: - Tests + + /// Replace with test case ID and behavior, e.g. F-200. + @MainActor + func testF___BehaviorName() { + let wizard = openWizard() + wizard.selectDateRangeMode() + + // Add scenario actions + // wizard.selectSport("mlb") + // wizard.selectRegion("central") + + // Add assertions + XCTAssertTrue(wizard.planTripButton.exists, "Plan button should be visible") + + captureScreenshot(named: "F___-BehaviorName") + } +} diff --git a/uiTestPrompt.md b/uiTestPrompt.md new file mode 100644 index 0000000..d658254 --- /dev/null +++ b/uiTestPrompt.md @@ -0,0 +1,49 @@ +# UI Test Prompt Template + +Use this prompt when asking an agent to add or modify SportsTime UI tests. + +--- + +You are updating SportsTime UI tests. + +## Goal + +- [Describe the behavior to test or fix.] + +## Scope + +- Update/add tests under `SportsTimeUITests/Tests/`. +- Reuse page objects in `SportsTimeUITests/Framework/Screens.swift`. +- Reuse shared setup in `SportsTimeUITests/Framework/BaseUITestCase.swift`. +- Reuse existing `TestFlows` where possible. + +## Required Changes + +- [List test suites to modify.] +- [List new test names.] +- [List selectors/page-object methods to add if needed.] + +## Constraints + +- Do not add raw sleeps unless strictly necessary. +- Prefer `waitForExistenceOrFail` and `waitUntilHittable`. +- Keep tests deterministic with current local test data. +- Keep existing naming style (`testF###_...`, `testP###_...`, etc.). + +## Validation + +Run these before finishing: + +1. Targeted class or test: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests/` +2. Full UI suite: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO -only-testing:SportsTimeUITests` +3. If requested, full scheme: + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -parallel-testing-enabled NO` + +## Output Format + +- Summarize files changed. +- Summarize root causes fixed. +- Include exact commands run and pass/fail outcomes. +- Call out any remaining flaky behavior or follow-up work. From 7eaa21abd43b5d90c0cef9e7ae137b0b8d56456d Mon Sep 17 00:00:00 2001 From: treyt Date: Wed, 18 Feb 2026 14:51:04 -0600 Subject: [PATCH 2/3] Stabilize flaky UI wizard and settings test flows --- .../Framework/BaseUITestCase.swift | 43 ++++++--- SportsTimeUITests/Framework/Screens.swift | 90 +++++++++++++++++-- 2 files changed, 114 insertions(+), 19 deletions(-) diff --git a/SportsTimeUITests/Framework/BaseUITestCase.swift b/SportsTimeUITests/Framework/BaseUITestCase.swift index c394ab4..689b00e 100644 --- a/SportsTimeUITests/Framework/BaseUITestCase.swift +++ b/SportsTimeUITests/Framework/BaseUITestCase.swift @@ -118,25 +118,40 @@ extension XCUIElement { 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 + if exists && isHittable { return self } + + func attemptScroll(_ scrollDirection: ScrollDirection, attempts: Int) -> Bool { + var remaining = attempts + while (!exists || !isHittable) && remaining > 0 { + switch scrollDirection { + case .down: + scrollView.swipeUp(velocity: .slow) + case .up: + scrollView.swipeDown(velocity: .slow) + } + remaining -= 1 } - switch direction { - case .down: - scrollView.swipeUp(velocity: .slow) - case .up: - scrollView.swipeDown(velocity: .slow) - } - scrollsRemaining -= 1 + return exists && isHittable } + + if attemptScroll(direction, attempts: maxScrolls) { + return self + } + + let reverseDirection: ScrollDirection = direction == .down ? .up : .down + if attemptScroll(reverseDirection, attempts: maxScrolls) { + return self + } + + XCTFail( + "Could not scroll \(self) into view after \(maxScrolls) scrolls in either direction", + file: file, + line: line + ) return self } } -enum ScrollDirection { +enum ScrollDirection: Equatable { case up, down } diff --git a/SportsTimeUITests/Framework/Screens.swift b/SportsTimeUITests/Framework/Screens.swift index 0a55409..3322e78 100644 --- a/SportsTimeUITests/Framework/Screens.swift +++ b/SportsTimeUITests/Framework/Screens.swift @@ -74,7 +74,35 @@ struct HomeScreen { /// Taps "Start Planning" to open the Trip Wizard sheet. func tapStartPlanning() { - startPlanningButton.waitUntilHittable().tap() + let navTitle = app.navigationBars["Plan a Trip"] + let dateRangeMode = app.buttons["wizard.planningMode.dateRange"] + + if navTitle.exists || dateRangeMode.exists { + return + } + + func tapIfVisible(_ element: XCUIElement, timeout: TimeInterval) -> Bool { + guard element.waitForExistence(timeout: timeout), element.isHittable else { return false } + element.tap() + return true + } + + _ = tapIfVisible(startPlanningButton, timeout: BaseUITestCase.defaultTimeout) || + tapIfVisible(createTripToolbarButton, timeout: BaseUITestCase.shortTimeout) + + if navTitle.waitForExistence(timeout: BaseUITestCase.shortTimeout) || + dateRangeMode.waitForExistence(timeout: BaseUITestCase.shortTimeout) { + return + } + + _ = tapIfVisible(createTripToolbarButton, timeout: BaseUITestCase.shortTimeout) || + tapIfVisible(startPlanningButton, timeout: BaseUITestCase.shortTimeout) + + XCTAssertTrue( + navTitle.waitForExistence(timeout: BaseUITestCase.defaultTimeout) || + dateRangeMode.waitForExistence(timeout: BaseUITestCase.defaultTimeout), + "Trip Wizard should appear after tapping start planning" + ) } /// Switches to a tab by tapping its tab bar button. @@ -150,10 +178,21 @@ struct TripWizardScreen { /// Waits for the wizard sheet to appear. @discardableResult func waitForLoad() -> TripWizardScreen { - if navigationTitle.waitForExistence(timeout: BaseUITestCase.defaultTimeout) || - planningModeButton("dateRange").waitForExistence(timeout: BaseUITestCase.defaultTimeout) { + if navigationTitle.waitForExistence(timeout: BaseUITestCase.longTimeout) || + planningModeButton("dateRange").waitForExistence(timeout: BaseUITestCase.longTimeout) { return self } + + // Fallback: if we're still on Home, trigger planning again. + let home = HomeScreen(app: app) + if home.startPlanningButton.exists || home.createTripToolbarButton.exists { + home.tapStartPlanning() + if navigationTitle.waitForExistence(timeout: BaseUITestCase.defaultTimeout) || + planningModeButton("dateRange").waitForExistence(timeout: BaseUITestCase.defaultTimeout) { + return self + } + } + XCTFail("Trip Wizard should appear") return self } @@ -169,6 +208,19 @@ struct TripWizardScreen { /// Selects the "By Dates" planning mode and waits for steps to expand. func selectDateRangeMode() { selectPlanningMode("dateRange") + + if monthLabel.waitForExistence(timeout: BaseUITestCase.shortTimeout) || + nextMonthButton.waitForExistence(timeout: BaseUITestCase.shortTimeout) { + return + } + + // Retry once for occasional dropped taps under simulator load. + selectPlanningMode("dateRange") + XCTAssertTrue( + monthLabel.waitForExistence(timeout: BaseUITestCase.defaultTimeout) || + nextMonthButton.waitForExistence(timeout: BaseUITestCase.defaultTimeout), + "Date range controls should appear after selecting planning mode" + ) } /// Navigates the calendar to a target month/year and selects start/end dates. @@ -178,6 +230,11 @@ struct TripWizardScreen { startDay: String, endDay: String ) { + // Ensure date controls are rendered before attempting calendar navigation. + if !monthLabel.waitForExistence(timeout: BaseUITestCase.shortTimeout) { + selectDateRangeMode() + } + // First, navigate by month label so tests that assert month visibility stay stable. monthLabel.scrollIntoView(in: app.scrollViews.firstMatch) let targetMonthYear = "\(targetMonth) \(targetYear)" @@ -560,8 +617,31 @@ struct SettingsScreen { // MARK: Assertions func assertLoaded() { - XCTAssertTrue(subscriptionSection.waitForExistence(timeout: BaseUITestCase.defaultTimeout), - "Settings should show Subscription section") + if subscriptionSection.waitForExistence(timeout: BaseUITestCase.defaultTimeout) { + return + } + + let proLabel = app.staticTexts["SportsTime Pro"] + let manageSubscriptionButton = app.buttons["Manage Subscription"] + if proLabel.exists || manageSubscriptionButton.exists || + upgradeProButton.exists || restorePurchasesButton.exists { + return + } + + // Retry tab switch once when the first tap doesn't switch tabs under load. + let settingsTab = app.tabBars.buttons["Settings"] + if settingsTab.waitForExistence(timeout: BaseUITestCase.shortTimeout), settingsTab.isHittable { + settingsTab.tap() + } + + XCTAssertTrue( + subscriptionSection.waitForExistence(timeout: BaseUITestCase.defaultTimeout) || + proLabel.waitForExistence(timeout: BaseUITestCase.shortTimeout) || + manageSubscriptionButton.waitForExistence(timeout: BaseUITestCase.shortTimeout) || + upgradeProButton.waitForExistence(timeout: BaseUITestCase.shortTimeout) || + restorePurchasesButton.waitForExistence(timeout: BaseUITestCase.shortTimeout), + "Settings should show Subscription section" + ) } func assertVersionDisplayed() { From ba41866602abfadf078dee60fcb44efe8a3523c0 Mon Sep 17 00:00:00 2001 From: treyt Date: Wed, 18 Feb 2026 21:44:08 -0600 Subject: [PATCH 3/3] Fix flaky UI tests: increase calendar wait timeouts and disable parallel UI testing Calendar navigation buttons used shortTimeout (5s) which was too tight under simulator load, causing cascading failures in wizard and trip saving tests. Bumped to defaultTimeout (15s) and disabled parallel execution for UI tests. Co-Authored-By: Claude Opus 4.6 --- .../xcshareddata/xcschemes/SportsTime.xcscheme | 2 +- SportsTimeUITests/Framework/Screens.swift | 18 +++++++++--------- .../Tests/TripWizardFlowTests.swift | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/SportsTime.xcodeproj/xcshareddata/xcschemes/SportsTime.xcscheme b/SportsTime.xcodeproj/xcshareddata/xcschemes/SportsTime.xcscheme index fe5e1eb..f670ef8 100644 --- a/SportsTime.xcodeproj/xcshareddata/xcschemes/SportsTime.xcscheme +++ b/SportsTime.xcodeproj/xcshareddata/xcschemes/SportsTime.xcscheme @@ -43,7 +43,7 @@ + parallelizable = "NO"> targetIdx { previousMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - previousMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + previousMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } else if currentIdx < targetIdx { nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } else { break } } else { nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } } else { nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } monthAttempts += 1 } @@ -280,27 +280,27 @@ struct TripWizardScreen { let startFallback = dayCells.element(boundBy: 0) let endFallback = dayCells.element(boundBy: min(4, max(1, dayCells.count - 1))) startFallback.scrollIntoView(in: app.scrollViews.firstMatch) - startFallback.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + startFallback.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() endFallback.scrollIntoView(in: app.scrollViews.firstMatch) - endFallback.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + endFallback.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() return } // Select start date — scroll calendar grid into view first startBtn.scrollIntoView(in: app.scrollViews.firstMatch) - startBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + startBtn.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() // Select end date let endBtn = dayButton(endDay) if endBtn.exists { endBtn.scrollIntoView(in: app.scrollViews.firstMatch) - endBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + endBtn.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } else { let dayCells = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'wizard.dates.day.'")) guard dayCells.count > 1 else { return } let fallback = dayCells.element(boundBy: min(4, max(1, dayCells.count - 1))) fallback.scrollIntoView(in: app.scrollViews.firstMatch) - fallback.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + fallback.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } } diff --git a/SportsTimeUITests/Tests/TripWizardFlowTests.swift b/SportsTimeUITests/Tests/TripWizardFlowTests.swift index c4132eb..9ff83b2 100644 --- a/SportsTimeUITests/Tests/TripWizardFlowTests.swift +++ b/SportsTimeUITests/Tests/TripWizardFlowTests.swift @@ -92,7 +92,7 @@ final class TripWizardFlowTests: BaseUITestCase { // Navigate forward 3 times for _ in 0..<3 { wizard.nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - wizard.nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + wizard.nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } // Month label should have changed @@ -113,14 +113,14 @@ final class TripWizardFlowTests: BaseUITestCase { // Go forward 3 months for _ in 0..<3 { wizard.nextMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - wizard.nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + wizard.nextMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() } let afterForward = wizard.monthLabel.label // Go back 1 month wizard.previousMonthButton.scrollIntoView(in: app.scrollViews.firstMatch) - wizard.previousMonthButton.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() + wizard.previousMonthButton.waitUntilHittable(timeout: BaseUITestCase.defaultTimeout).tap() XCTAssertNotEqual(wizard.monthLabel.label, afterForward, "Month should change after navigating backward")