import XCTest /// Walks the whole onboarding on a clean install. /// /// This exists because of a bug that no unit test could see: the flow used to /// write `onboardingCompleted` at the dishes step, which swapped the root view /// for the home and silently skipped the last two steps — the Week Ready "aha /// moment" and the paywall. Shipped in 2.0 and live for a week before GA4 /// (no step 5/6 events despite completions) gave it away. final class OnboardingFlowUITests: XCTestCase { private var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() // Pin the language so the run doesn't depend on the simulator's locale. app.launchArguments += ["-AppleLanguages", "(en)", "-AppleLocale", "en_US"] // Keep synthetic runs out of the production GA4 property. Literal on // purpose: a UI test bundle doesn't link the app target, so it can't // reference AnalyticsService.launchArgumentDisablingAnalytics — keep the // two in sync by hand. app.launchArguments.append("UITEST_DISABLE_ANALYTICS") } func testOnboardingReachesWeekReadyAndPaywallBeforeTheHome() { app.launch() dismissSystemAlertIfPresent() tap("onboarding_cta_welcome", "welcome") tap("onboarding_cta_meal_windows", "meal windows") tap("onboarding_cta_weekends", "weekends") tap("onboarding_cta_calendar", "calendar") tap("onboarding_cta_first_dishes", "first dishes") // The two steps the bug used to skip. let weekReady = app.staticTexts["onboarding_week_ready_title"] XCTAssertTrue(weekReady.waitForExistence(timeout: 10), "Week Ready (the aha moment) never appeared — the flow skipped step 5") tap("onboarding_cta_week_ready_skip", "week ready") let paywallSkip = app.buttons["onboarding_cta_paywall_skip"] XCTAssertTrue(paywallSkip.waitForExistence(timeout: 10), "The paywall never appeared — the flow skipped step 6") paywallSkip.tap() // Only now should onboarding hand over to the home. XCTAssertTrue(app.otherElements["home_root"].waitForExistence(timeout: 15), "Onboarding finished but the home never came up") } // MARK: Helpers private func tap(_ identifier: String, _ step: String, file: StaticString = #filePath, line: UInt = #line) { let button = app.buttons[identifier] XCTAssertTrue(button.waitForExistence(timeout: 15), "Never reached the \(step) step", file: file, line: line) button.tap() dismissSystemAlertIfPresent() } /// The simulator can raise an Apple Account / iCloud prompt on launch, which /// would swallow the taps that follow. private func dismissSystemAlertIfPresent() { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") for label in ["Not Now", "Cancel", "Don't Allow", "Allow"] { let button = springboard.buttons[label] if button.exists && button.isHittable { button.tap() return } } } } /// Regression: tapping "next week" must land on an editable week with slots — /// the plan for a not-yet-visited week has to be created on navigation. final class NextWeekNavigationUITests: XCTestCase { func testNextWeekShowsSlotsToPlan() throws { let app = XCUIApplication() app.launchArguments += ["-AppleLanguages", "(en)", "-AppleLocale", "en_US", "UITEST_DISABLE_ANALYTICS"] app.launch() // Reach the home, walking onboarding if this is a clean install. if !app.otherElements["home_root"].waitForExistence(timeout: 5) { for id in ["onboarding_cta_welcome", "onboarding_cta_meal_windows", "onboarding_cta_weekends", "onboarding_cta_calendar", "onboarding_cta_first_dishes", "onboarding_cta_week_ready_skip", "onboarding_cta_paywall_skip"] { let button = app.buttons[id] if button.waitForExistence(timeout: 10) { button.tap() } } XCTAssertTrue(app.otherElements["home_root"].waitForExistence(timeout: 15), "Home never appeared") } let next = app.buttons["Next"] XCTAssertTrue(next.waitForExistence(timeout: 10), "Next-week chevron not found") next.tap() sleep(2) let attachment = XCTAttachment(screenshot: app.screenshot()) attachment.name = "next-week" attachment.lifetime = .keepAlways add(attachment) // The paywall must NOT appear (next week is free for everyone) and the // home must still be there with content, not a blank screen. XCTAssertFalse(app.staticTexts["premium_title"].exists, "Paywall appeared navigating to next week") XCTAssertTrue(app.otherElements["home_root"].exists, "Home disappeared after navigating to next week") } }