diff --git a/AGENTS.md b/AGENTS.md index 05c8835..c71affd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,6 @@ This file is for AI/code agents working in this repo. - 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` + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' -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` + - `xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' -parallel-testing-enabled NO` diff --git a/CLAUDE.md b/CLAUDE.md index 8f65c48..65ebb86 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,19 +6,19 @@ iOS app for planning multi-stop sports road trips. Offline-first architecture wi ```bash # Build the iOS app -xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' build # Run all tests -xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' test # Run specific test suite -xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TripPlanningEngineTests test +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' -only-testing:SportsTimeTests/TripPlanningEngineTests test # 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 +xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' -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 +xcodebuild test-without-building -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' -parallel-testing-enabled NO -only-testing:SportsTimeUITests # Data scraping (Python) cd Scripts && pip install -r requirements.txt diff --git a/README.md b/README.md index 041c325..f342afd 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ All schedule data flows through `AppDataProvider.shared` - never access CloudKit ```bash xcodebuild -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ build ``` @@ -116,13 +116,13 @@ xcodebuild -project SportsTime.xcodeproj \ # All tests xcodebuild -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ test # Specific test suite xcodebuild -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ -only-testing:SportsTimeTests/EdgeCaseTests \ test @@ -130,7 +130,7 @@ xcodebuild -project SportsTime.xcodeproj \ xcodebuild test-without-building \ -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ -parallel-testing-enabled NO \ -only-testing:SportsTimeUITests ``` diff --git a/SportsTimeTests/Planning/ScenarioEPlannerRealDataTest.swift b/SportsTimeTests/Planning/ScenarioEPlannerRealDataTest.swift new file mode 100644 index 0000000..7dedbda --- /dev/null +++ b/SportsTimeTests/Planning/ScenarioEPlannerRealDataTest.swift @@ -0,0 +1,241 @@ +// +// ScenarioEPlannerRealDataTest.swift +// SportsTimeTests +// +// Regression test: runs ScenarioEPlanner with real PHI/WSN/BAL data +// to verify the past-date filtering and full-season coverage fixes. +// + +import Foundation +import Testing +import CoreLocation +@testable import SportsTime + +@Suite("ScenarioE Real Data Regression") +struct ScenarioEPlannerRealDataTest { + + @Test("PHI + WSN + BAL: returns future regular season results, not spring training") + func phiWsnBal_returnsFutureRegularSeasonResults() throws { + let fixture = try FixtureLoader.loadCanonicalGames() + let rawGames = fixture.games + + // Filter to MLB only + let mlbRows = rawGames.filter { $0.sport?.lowercased() == "mlb" } + let games = mlbRows.compactMap(\.domainGame) + let skippedMLBRows = mlbRows.count - games.count + #expect(!games.isEmpty, "Expected MLB canonical fixture to contain valid games") + + // Team IDs + let teamIds: Set = ["team_mlb_phi", "team_mlb_wsn", "team_mlb_bal"] + + // Build stadium map with real coordinates + let stadiums: [String: Stadium] = [ + "stadium_mlb_citizens_bank_park": Stadium( + id: "stadium_mlb_citizens_bank_park", name: "Citizens Bank Park", + city: "Philadelphia", state: "PA", + latitude: 39.9061, longitude: -75.1665, capacity: 43035, sport: .mlb + ), + "stadium_mlb_nationals_park": Stadium( + id: "stadium_mlb_nationals_park", name: "Nationals Park", + city: "Washington", state: "DC", + latitude: 38.8730, longitude: -77.0074, capacity: 41339, sport: .mlb + ), + "stadium_mlb_oriole_park_at_camden_yards": Stadium( + id: "stadium_mlb_oriole_park_at_camden_yards", name: "Oriole Park at Camden Yards", + city: "Baltimore", state: "MD", + latitude: 39.2838, longitude: -76.6216, capacity: 45971, sport: .mlb + ), + // Spring training stadiums (should be excluded by date filter) + "stadium_mlb_spring_baycare_ballpark": Stadium( + id: "stadium_mlb_spring_baycare_ballpark", name: "BayCare Ballpark", + city: "Clearwater", state: "FL", + latitude: 27.9781, longitude: -82.7337, capacity: 8500, sport: .mlb + ), + "stadium_mlb_spring_cacti_park": Stadium( + id: "stadium_mlb_spring_cacti_park", name: "CACTI Park", + city: "West Palm Beach", state: "FL", + latitude: 26.7367, longitude: -80.1197, capacity: 6671, sport: .mlb + ), + "stadium_mlb_spring_ed_smith_stadium": Stadium( + id: "stadium_mlb_spring_ed_smith_stadium", name: "Ed Smith Stadium", + city: "Sarasota", state: "FL", + latitude: 27.3381, longitude: -82.5226, capacity: 8500, sport: .mlb + ), + ] + + // Build team map + let teams: [String: Team] = [ + "team_mlb_phi": Team(id: "team_mlb_phi", name: "Phillies", abbreviation: "PHI", + sport: .mlb, city: "Philadelphia", + stadiumId: "stadium_mlb_citizens_bank_park", + primaryColor: "#E81828", secondaryColor: "#002D72"), + "team_mlb_wsn": Team(id: "team_mlb_wsn", name: "Nationals", abbreviation: "WSN", + sport: .mlb, city: "Washington", + stadiumId: "stadium_mlb_nationals_park", + primaryColor: "#AB0003", secondaryColor: "#14225A"), + "team_mlb_bal": Team(id: "team_mlb_bal", name: "Orioles", abbreviation: "BAL", + sport: .mlb, city: "Baltimore", + stadiumId: "stadium_mlb_oriole_park_at_camden_yards", + primaryColor: "#DF4601", secondaryColor: "#000000"), + ] + + let prefs = TripPreferences( + planningMode: .teamFirst, + sports: [.mlb], + selectedTeamIds: teamIds + ) + + let request = PlanningRequest( + preferences: prefs, + availableGames: games, + teams: teams, + stadiums: stadiums + ) + + // Use April 2, 2026 as current date (the date the bug was reported) + let currentDate = TestFixtures.date(year: 2026, month: 4, day: 2, hour: 12) + let planner = ScenarioEPlanner(currentDate: currentDate) + let result = planner.plan(request: request) + + guard case .success(let options) = result else { + if case .failure(let failure) = result { + Issue.record("Expected success but got failure: \(failure.reason) — \(failure.violations.map(\.description))") + } + return + } + + #expect(!options.isEmpty, "Should find trip options for PHI/WSN/BAL") + + let calendar = Calendar.current + var output = "\nRESULT> Fixture: \(fixture.url.path)\n" + output += "RESULT> MLB rows: \(mlbRows.count), skipped malformed MLB rows: \(skippedMLBRows)\n" + output += "RESULT> ========================================\n" + output += "RESULT> PHI + WSN + BAL TRIP OPTIONS (\(options.count) results)\n" + output += "RESULT> ========================================\n\n" + + for option in options { + let cities = option.stops.map { "\($0.city)" }.joined(separator: " → ") + let dates = option.stops.map { stop in + let formatter = DateFormatter() + formatter.dateFormat = "MMM d" + return formatter.string(from: stop.arrivalDate) + }.joined(separator: " → ") + + let gameIds = option.stops.flatMap { $0.games } + let miles = Int(option.totalDistanceMiles) + let hours = String(format: "%.1f", option.totalDrivingHours) + + output += "RESULT> Option #\(option.rank): \(cities)\n" + output += "RESULT> Dates: \(dates)\n" + output += "RESULT> Driving: \(miles) mi, \(hours) hrs\n" + output += "RESULT> Games: \(gameIds.count)\n" + output += "RESULT> Rationale: \(option.geographicRationale)\n" + + // Verify all games are in the future + for stop in option.stops { + #expect(stop.arrivalDate >= calendar.startOfDay(for: currentDate), + "Stop in \(stop.city) on \(stop.arrivalDate) should be after April 2, 2026") + } + + // Verify no spring training stadiums + for stop in option.stops { + let isSpringTraining = stop.city == "Clearwater" || stop.city == "Sarasota" || stop.city == "West Palm Beach" + #expect(!isSpringTraining, "Should not include spring training city: \(stop.city)") + } + + output += "\n" + } + + // Verify temporal spread — results should not all be in April + let months = Set(options.flatMap { $0.stops.map { calendar.component(.month, from: $0.arrivalDate) } }) + output += "RESULT> Months covered: \(months.sorted().map { DateFormatter().monthSymbols[$0 - 1] })\n" + FileHandle.standardOutput.write(Data(output.utf8)) + #expect(months.count >= 2, "Results should span multiple months across the season") + } +} + +// MARK: - JSON Decoding Helpers + +private struct CanonicalGameJSON: Decodable { + let canonical_id: String? + let sport: String? + let season: String? + let game_datetime_utc: String? + let home_team_canonical_id: String? + let away_team_canonical_id: String? + let stadium_canonical_id: String? + let is_playoff: Bool? + + var parsedDate: Date { + guard let game_datetime_utc else { return Date.distantPast } + let formatter = ISO8601DateFormatter() + formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + if let date = formatter.date(from: game_datetime_utc) { return date } + formatter.formatOptions = [.withInternetDateTime] + return formatter.date(from: game_datetime_utc) ?? Date.distantPast + } + + var domainGame: Game? { + guard let canonical_id, + let home_team_canonical_id, + let away_team_canonical_id, + let stadium_canonical_id, + let season else { + return nil + } + + return Game( + id: canonical_id, + homeTeamId: home_team_canonical_id, + awayTeamId: away_team_canonical_id, + stadiumId: stadium_canonical_id, + dateTime: parsedDate, + sport: .mlb, + season: season, + isPlayoff: is_playoff ?? false + ) + } +} + +private enum FixtureLoader { + struct LoadedFixture { + let url: URL + let games: [CanonicalGameJSON] + } + + static func loadCanonicalGames() throws -> LoadedFixture { + let candidateURLs = [ + repositoryRoot.appendingPathComponent("sportstime_export/games_canonical.json"), + Bundle(for: BundleToken.self).url(forResource: "games_canonical", withExtension: "json") + ].compactMap { $0 } + + for url in candidateURLs where FileManager.default.fileExists(atPath: url.path) { + let data = try Data(contentsOf: url) + let games = try JSONDecoder().decode([CanonicalGameJSON].self, from: data) + return LoadedFixture(url: url, games: games) + } + + throw FixtureLoadError.notFound(candidateURLs.map(\.path)) + } + + private static var repositoryRoot: URL { + URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + } +} + +private enum FixtureLoadError: LocalizedError { + case notFound([String]) + + var errorDescription: String? { + switch self { + case .notFound(let paths): + let joined = paths.joined(separator: ", ") + return "Could not find games_canonical.json. Checked: \(joined)" + } + } +} + +private class BundleToken {} diff --git a/SportsTimeUITests/Framework/BaseUITestCase.swift b/SportsTimeUITests/Framework/BaseUITestCase.swift index 8a61817..e6ede77 100644 --- a/SportsTimeUITests/Framework/BaseUITestCase.swift +++ b/SportsTimeUITests/Framework/BaseUITestCase.swift @@ -59,6 +59,33 @@ class BaseUITestCase: XCTestCase { screenshot.lifetime = .keepAlways add(screenshot) } + + /// Polls until the condition becomes true or the timeout expires. + @discardableResult + func waitUntil( + timeout: TimeInterval = BaseUITestCase.defaultTimeout, + pollInterval: TimeInterval = 0.2, + _ message: String? = nil, + file: StaticString = #filePath, + line: UInt = #line, + condition: @escaping () -> Bool + ) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + + while Date() < deadline { + if condition() { + return true + } + + let remaining = deadline.timeIntervalSinceNow + let interval = min(pollInterval, max(0.01, remaining)) + RunLoop.current.run(until: Date().addingTimeInterval(interval)) + } + + let success = condition() + XCTAssertTrue(success, message ?? "Condition was not met within \(timeout)s", file: file, line: line) + return success + } } // MARK: - Wait Helpers diff --git a/SportsTimeUITests/Tests/AppLaunchTests.swift b/SportsTimeUITests/Tests/AppLaunchTests.swift index 4773727..3734c43 100644 --- a/SportsTimeUITests/Tests/AppLaunchTests.swift +++ b/SportsTimeUITests/Tests/AppLaunchTests.swift @@ -45,10 +45,12 @@ final class AppLaunchTests: BaseUITestCase { // Background the app XCUIDevice.shared.press(.home) - sleep(2) // Foreground app.activate() + waitUntil(timeout: BaseUITestCase.longTimeout, "App should return to the foreground") { + self.app.state == .runningForeground + } // Assert: Home still loaded, no re-bootstrap XCTAssertTrue( diff --git a/SportsTimeUITests/Tests/HomeTests.swift b/SportsTimeUITests/Tests/HomeTests.swift index dbe4409..f6f66b1 100644 --- a/SportsTimeUITests/Tests/HomeTests.swift +++ b/SportsTimeUITests/Tests/HomeTests.swift @@ -85,12 +85,10 @@ final class HomeTests: BaseUITestCase { // Tap refresh and verify no crash refreshButton.tap() - // Wait briefly for reload - sleep(2) - // Featured section should still exist after refresh - XCTAssertTrue(section.exists, - "Featured trips section should remain after refresh") + waitUntil(timeout: BaseUITestCase.longTimeout, "Featured trips section should remain after refresh") { + section.exists && refreshButton.exists + } captureScreenshot(named: "F015-FeaturedTripsRefresh") } @@ -307,14 +305,17 @@ final class HomeTests: BaseUITestCase { home.waitForLoad() home.switchToTab(home.myTripsTab) - // Wait briefly for My Trips content to load - sleep(1) + 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) - // Wait for any refresh to complete - sleep(2) + 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"] diff --git a/SportsTimeUITests/Tests/ProgressTests.swift b/SportsTimeUITests/Tests/ProgressTests.swift index 6a477d0..6f70e8f 100644 --- a/SportsTimeUITests/Tests/ProgressTests.swift +++ b/SportsTimeUITests/Tests/ProgressTests.swift @@ -206,22 +206,14 @@ final class ProgressTests: BaseUITestCase { visitSheet.tapSave() visitSheet.navigationBar.waitForNonExistence(timeout: BaseUITestCase.defaultTimeout) - // Wait for data to reload - sleep(2) - // Progress should have updated — verify the progress circle label changed let updatedCircle = app.descendants(matching: .any).matching(NSPredicate( format: "label CONTAINS 'stadiums visited'" )).firstMatch - XCTAssertTrue(updatedCircle.waitForExistence(timeout: BaseUITestCase.longTimeout), - "Progress circle should exist after adding a visit") - - // If we had an initial label, verify it changed - if !initialLabel.isEmpty { - // The new label should have a higher visited count - XCTAssertNotEqual(updatedCircle.label, initialLabel, - "Progress label should update after adding a visit") + 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") diff --git a/SportsTimeUITests/Tests/ScheduleTests.swift b/SportsTimeUITests/Tests/ScheduleTests.swift index bee02cc..df5bca8 100644 --- a/SportsTimeUITests/Tests/ScheduleTests.swift +++ b/SportsTimeUITests/Tests/ScheduleTests.swift @@ -127,9 +127,10 @@ final class ScheduleTests: BaseUITestCase { "Search field should exist") searchField.tap() searchField.typeText("Yankees") - - // Wait for results to filter - sleep(1) + XCTAssertTrue( + ((searchField.value as? String) ?? "").contains("Yankees"), + "Search field should contain the typed team name" + ) captureScreenshot(named: "F089-SearchByTeam") } @@ -150,9 +151,10 @@ final class ScheduleTests: BaseUITestCase { "Search field should exist") searchField.tap() searchField.typeText("Wrigley") - - // Wait for results to filter - sleep(1) + XCTAssertTrue( + ((searchField.value as? String) ?? "").contains("Wrigley"), + "Search field should contain the typed venue name" + ) captureScreenshot(named: "F090-SearchByVenue") } @@ -174,19 +176,15 @@ final class ScheduleTests: BaseUITestCase { searchField.tap() searchField.typeText("ZZZZNONEXISTENTTEAMZZZZ") - // Wait for empty state - sleep(1) - // Empty state or "no results" text should appear let emptyState = schedule.emptyState let noResults = app.staticTexts.matching(NSPredicate( format: "label CONTAINS[c] 'no' AND label CONTAINS[c] 'game'" )).firstMatch - let hasEmptyIndicator = emptyState.waitForExistence(timeout: BaseUITestCase.shortTimeout) - || noResults.waitForExistence(timeout: BaseUITestCase.shortTimeout) - XCTAssertTrue(hasEmptyIndicator, - "Empty state should appear when no games match search") + waitUntil(timeout: BaseUITestCase.shortTimeout, "Empty state should appear when no games match search") { + emptyState.exists || noResults.exists + } captureScreenshot(named: "F092-ScheduleEmptyState") } diff --git a/SportsTimeUITests/Tests/SettingsTests.swift b/SportsTimeUITests/Tests/SettingsTests.swift index 62b3647..11b2942 100644 --- a/SportsTimeUITests/Tests/SettingsTests.swift +++ b/SportsTimeUITests/Tests/SettingsTests.swift @@ -162,10 +162,10 @@ final class SettingsTests: BaseUITestCase { let switchCoord = toggle.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5)) switchCoord.tap() - // Small wait for the toggle animation to complete - sleep(1) - // Value should have changed + waitUntil(timeout: BaseUITestCase.shortTimeout, "Toggle value should change after tapping the switch") { + (toggle.value as? String) != initialValue + } let newValue = toggle.value as? String XCTAssertNotEqual(initialValue, newValue, "Toggle value should change after tap (was '\(initialValue ?? "nil")', now '\(newValue ?? "nil")')") diff --git a/SportsTimeUITests/Tests/TripOptionsTests.swift b/SportsTimeUITests/Tests/TripOptionsTests.swift index fe30177..14cfe3b 100644 --- a/SportsTimeUITests/Tests/TripOptionsTests.swift +++ b/SportsTimeUITests/Tests/TripOptionsTests.swift @@ -157,8 +157,11 @@ final class TripOptionsTests: BaseUITestCase { "'5' cities filter button should exist") fiveCitiesButton.tap() - // Results should update; verify no crash - sleep(1) + let firstTrip = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'tripOptions.trip.'")).firstMatch + XCTAssertTrue( + firstTrip.waitForExistence(timeout: BaseUITestCase.shortTimeout), + "Trip results should remain visible after applying the cities filter" + ) captureScreenshot(named: "F057-CitiesFilter-5") } diff --git a/XCUITest-Authoring.md b/XCUITest-Authoring.md index 4dc0f7e..f09e1b6 100644 --- a/XCUITest-Authoring.md +++ b/XCUITest-Authoring.md @@ -63,7 +63,7 @@ Do not add one-off test-only branching logic unless it removes a real flake. xcodebuild test-without-building \ -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ -parallel-testing-enabled NO \ -only-testing:SportsTimeUITests/TripWizardFlowTests/testF026_DateRangeSelection ``` @@ -74,7 +74,7 @@ xcodebuild test-without-building \ xcodebuild test-without-building \ -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ -parallel-testing-enabled NO \ -only-testing:SportsTimeUITests/TripOptionsTests ``` @@ -85,7 +85,7 @@ xcodebuild test-without-building \ xcodebuild test-without-building \ -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ -parallel-testing-enabled NO \ -only-testing:SportsTimeUITests ``` @@ -96,7 +96,7 @@ xcodebuild test-without-building \ xcodebuild test-without-building \ -project SportsTime.xcodeproj \ -scheme SportsTime \ - -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' \ + -destination 'platform=iOS Simulator,name=iPhone 17,OS=latest' \ -parallel-testing-enabled NO ``` diff --git a/uiTestPrompt.md b/uiTestPrompt.md index eec811f..794df94 100644 --- a/uiTestPrompt.md +++ b/uiTestPrompt.md @@ -66,7 +66,7 @@ If a candidate looks flaky/high-risk, skip it and explain why. Use this destination: -`-destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2'` +`-destination 'platform=iOS Simulator,name=iPhone 17,OS=latest'` Run each selected test explicitly: