From c286294cd3ff62a36e1291ca28468dd41d03710b Mon Sep 17 00:00:00 2001 From: Trey t Date: Tue, 17 Feb 2026 17:43:28 -0600 Subject: [PATCH] Fix remaining 12 UI test failures: subscription state, hittability, tab selection - IAPManager: add resetForTesting() to discard stale cached subscription state - UITestMode: call resetForTesting() after clearing defaults (fixes 5 banner tests) - StabilityTests: use NSPredicate wait for isSelected (iOS 26 Liquid Glass) - SettingsActionTests: use coordinate tap for clear data and analytics toggle - IconPackTests: add horizontal scroll fallback for off-screen icon packs Co-Authored-By: Claude Opus 4.6 --- Shared/IAPManager.swift | 10 ++++++++++ Shared/UITestMode.swift | 4 ++++ Tests iOS/IconPackTests.swift | 6 +++++- Tests iOS/SettingsActionTests.swift | 4 ++-- Tests iOS/StabilityTests.swift | 20 ++++++++++++++------ 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Shared/IAPManager.swift b/Shared/IAPManager.swift index b9eed57..3628c60 100644 --- a/Shared/IAPManager.swift +++ b/Shared/IAPManager.swift @@ -349,6 +349,16 @@ class IAPManager: ObservableObject { return false } + #if DEBUG + /// Reset subscription state for UI testing. Called after group defaults are cleared + /// so that stale cached state from previous test runs is discarded. + func resetForTesting() { + state = .unknown + lastStatusCheckTime = nil + updateTrialState() + } + #endif + private func updateTrialState() { let daysSinceInstall = Calendar.current.dateComponents([.day], from: firstLaunchDate, to: Date()).day ?? 0 let daysRemaining = trialDays - daysSinceInstall diff --git a/Shared/UITestMode.swift b/Shared/UITestMode.swift index f92815f..63b88ad 100644 --- a/Shared/UITestMode.swift +++ b/Shared/UITestMode.swift @@ -77,6 +77,10 @@ enum UITestMode { #if DEBUG IAPManager.shared.bypassSubscription = bypassSubscription + // Reset subscription state to discard stale cached state from previous test runs. + // IAPManager.shared was already initialized (as @StateObject in FeelsApp) before + // configureIfNeeded runs, so it may have restored stale subscription data. + IAPManager.shared.resetForTesting() #endif // Seed fixture data if requested diff --git a/Tests iOS/IconPackTests.swift b/Tests iOS/IconPackTests.swift index a46d3be..7f693d1 100644 --- a/Tests iOS/IconPackTests.swift +++ b/Tests iOS/IconPackTests.swift @@ -36,7 +36,11 @@ final class IconPackTests: BaseUITestCase { for pack in allIconPacks { let button = app.buttons["customize_iconpack_\(pack)"] if !button.exists { - // Scroll more to reveal buttons off-screen + // Icon packs may be in a horizontal scroll — try swipe left first + app.swipeLeft() + } + if !button.exists { + // If still not found, try scrolling the page down app.swipeUp() } if button.waitForExistence(timeout: 3) { diff --git a/Tests iOS/SettingsActionTests.swift b/Tests iOS/SettingsActionTests.swift index 016298a..eb2e74a 100644 --- a/Tests iOS/SettingsActionTests.swift +++ b/Tests iOS/SettingsActionTests.swift @@ -47,7 +47,7 @@ final class SettingsActionTests: BaseUITestCase { return } - clearButton.tap() + clearButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() // Navigate back to Day tab tabBar.tapDay() @@ -94,7 +94,7 @@ final class SettingsActionTests: BaseUITestCase { } // Tap the toggle - analyticsToggle.tap() + analyticsToggle.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() captureScreenshot(name: "analytics_toggled") } diff --git a/Tests iOS/StabilityTests.swift b/Tests iOS/StabilityTests.swift index e771084..72f0fc1 100644 --- a/Tests iOS/StabilityTests.swift +++ b/Tests iOS/StabilityTests.swift @@ -15,7 +15,7 @@ final class StabilityTests: BaseUITestCase { let tabBar = TabBarScreen(app: app) // 1. Day tab (default) - verify loaded - XCTAssertTrue(tabBar.dayTab.isSelected, "Should start on Day tab") + assertTabSelected(tabBar.dayTab, name: "Day (initial)") captureScreenshot(name: "stability_day") // 2. Open entry detail @@ -34,22 +34,22 @@ final class StabilityTests: BaseUITestCase { // 3. Month tab tabBar.tapMonth() - XCTAssertTrue(tabBar.monthTab.isSelected, "Month tab should be selected") + assertTabSelected(tabBar.monthTab, name: "Month") captureScreenshot(name: "stability_month") // 4. Year tab tabBar.tapYear() - XCTAssertTrue(tabBar.yearTab.isSelected, "Year tab should be selected") + assertTabSelected(tabBar.yearTab, name: "Year") captureScreenshot(name: "stability_year") // 5. Insights tab tabBar.tapInsights() - XCTAssertTrue(tabBar.insightsTab.isSelected, "Insights tab should be selected") + assertTabSelected(tabBar.insightsTab, name: "Insights") captureScreenshot(name: "stability_insights") // 6. Settings tab - Customize sub-tab tabBar.tapSettings() - XCTAssertTrue(tabBar.settingsTab.isSelected, "Settings tab should be selected") + assertTabSelected(tabBar.settingsTab, name: "Settings") captureScreenshot(name: "stability_settings_customize") // 7. Settings tab - Settings sub-tab @@ -63,8 +63,16 @@ final class StabilityTests: BaseUITestCase { // 9. Back to Day tabBar.tapDay() - XCTAssertTrue(tabBar.dayTab.isSelected, "Day tab should be selected") + assertTabSelected(tabBar.dayTab, name: "Day") captureScreenshot(name: "stability_full_navigation_complete") } + + /// Wait for a tab to become selected (iOS 26 Liquid Glass may delay state updates). + private func assertTabSelected(_ tab: XCUIElement, name: String, timeout: TimeInterval = 3) { + let predicate = NSPredicate(format: "isSelected == true") + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: tab) + let result = XCTWaiter.wait(for: [expectation], timeout: timeout) + XCTAssertEqual(result, .completed, "\(name) tab should be selected") + } }