From 8845ccfd1b02b522078f816f5de35b43c83e844b Mon Sep 17 00:00:00 2001 From: Trey t Date: Tue, 17 Feb 2026 19:57:03 -0600 Subject: [PATCH] Fix cascading crash and remaining UI test failures - Revert key-by-key UserDefaults iteration that removed system keys causing kAXErrorServerNotFound crashes; restore removePersistentDomain with explicit subscription key clearing - Add .accessibilityElement(children: .contain) to UpgradeBannerView so subscribe button is discoverable by XCUITest - Fix AllDayViewStylesTests to use coordinate-based tapping instead of button.isHittable/button.tap() for iOS 26 Liquid Glass compatibility - Improve OnboardingTests with multiple swipe retries and label-based fallback for skip button Co-Authored-By: Claude Opus 4.6 --- Shared/UITestMode.swift | 13 ++++++----- .../Views/SettingsView/SettingsTabView.swift | 1 + Tests iOS/AllDayViewStylesTests.swift | 17 +++++++------- Tests iOS/OnboardingTests.swift | 22 ++++++++++++++++--- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/Shared/UITestMode.swift b/Shared/UITestMode.swift index 3445d9b..3289504 100644 --- a/Shared/UITestMode.swift +++ b/Shared/UITestMode.swift @@ -92,12 +92,15 @@ enum UITestMode { /// Reset all user defaults and persisted state for a clean test run @MainActor private static func resetAppState() { - // Clear group user defaults by iterating all keys. - // removePersistentDomain(forName:) is unreliable on app group suites. let defaults = GroupUserDefaults.groupDefaults - for key in defaults.dictionaryRepresentation().keys { - defaults.removeObject(forKey: key) - } + // Clear group user defaults using the suite domain name + defaults.removePersistentDomain(forName: Constants.currentGroupShareId) + + // Explicitly clear subscription cache keys that may survive removePersistentDomain + // on app group suites (known reliability issue). + defaults.removeObject(forKey: UserDefaultsStore.Keys.cachedSubscriptionExpiration.rawValue) + defaults.removeObject(forKey: UserDefaultsStore.Keys.hasActiveSubscription.rawValue) + defaults.removeObject(forKey: UserDefaultsStore.Keys.firstLaunchDate.rawValue) // Reset key defaults explicitly (true = fresh install state where onboarding is needed) defaults.set(true, forKey: UserDefaultsStore.Keys.needsOnboarding.rawValue) diff --git a/Shared/Views/SettingsView/SettingsTabView.swift b/Shared/Views/SettingsView/SettingsTabView.swift index c9a1656..b8be95a 100644 --- a/Shared/Views/SettingsView/SettingsTabView.swift +++ b/Shared/Views/SettingsView/SettingsTabView.swift @@ -148,6 +148,7 @@ struct UpgradeBannerView: View { RoundedRectangle(cornerRadius: 14) .fill(colorScheme == .dark ? Color(.systemGray6) : Color(.systemGray6).opacity(0.5)) ) + .accessibilityElement(children: .contain) .accessibilityIdentifier(AccessibilityID.Settings.upgradeBanner) } } diff --git a/Tests iOS/AllDayViewStylesTests.swift b/Tests iOS/AllDayViewStylesTests.swift index fa85b3b..f23f293 100644 --- a/Tests iOS/AllDayViewStylesTests.swift +++ b/Tests iOS/AllDayViewStylesTests.swift @@ -29,22 +29,21 @@ final class AllDayViewStylesTests: BaseUITestCase { settingsScreen.assertVisible() settingsScreen.tapCustomizeTab() - // Try to find and tap the style button, scrolling if needed + // Try to find the style button, scrolling if needed let button = customizeScreen.dayViewStyleButton(named: style) - if !button.waitForExistence(timeout: 2) || !button.isHittable { - // Scroll left multiple times to find styles further right + if !button.waitForExistence(timeout: 2) { + // Scroll left multiple times to find styles further right in horizontal scroll for _ in 0..<5 { app.swipeLeft() - if button.isHittable { break } + if button.waitForExistence(timeout: 1) { break } } } - if button.isHittable { - button.tap() - } else { - // Style button not found after scrolling — skip but don't fail, - // as the main assertion is no-crash on the Day tab + if button.waitForExistence(timeout: 2) { + // Use coordinate tap for iOS 26 Liquid Glass compatibility + button.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } + // Skip but don't fail if button not found — main assertion is no-crash // Navigate to Day tab and verify the entry row still renders tabBar.tapDay() diff --git a/Tests iOS/OnboardingTests.swift b/Tests iOS/OnboardingTests.swift index 9f37355..bd49125 100644 --- a/Tests iOS/OnboardingTests.swift +++ b/Tests iOS/OnboardingTests.swift @@ -46,16 +46,32 @@ final class OnboardingTests: BaseUITestCase { swipeAndWait() // Style → Subscription captureScreenshot(name: "onboarding_subscription") - // Tap "Maybe Later" to complete onboarding + // Find the "Maybe Later" skip button on the subscription screen. + // Try multiple approaches in case the page transition didn't complete. let skipButton = app.descendants(matching: .any) .matching(identifier: "onboarding_skip_button") .firstMatch - // If skip button isn't visible, try one more swipe (in case a page was added) - if !skipButton.waitForExistence(timeout: 5) { + // If skip button isn't visible, try additional swipes + for _ in 0..<3 { + if skipButton.waitForExistence(timeout: 3) { break } swipeAndWait() } + // Also try finding by label as a fallback + if !skipButton.exists { + let maybeLater = app.buttons.matching( + NSPredicate(format: "label CONTAINS[cd] %@", "Maybe Later") + ).firstMatch + if maybeLater.waitForExistence(timeout: 3) { + maybeLater.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + let tabBar = app.tabBars.firstMatch + XCTAssertTrue(tabBar.waitForExistence(timeout: 10), "Tab bar should appear after onboarding") + captureScreenshot(name: "onboarding_complete") + return + } + } + XCTAssertTrue( skipButton.waitForExistence(timeout: 5), "Skip/Maybe Later button should exist on subscription screen"