// // OnboardingScreen.swift // Tests iOS // // Screen object for the onboarding flow — welcome, time, day, style, and subscription screens. // import XCTest struct OnboardingScreen { let app: XCUIApplication private let defaultTimeout: TimeInterval = 2 private let navigationTimeout: TimeInterval = 5 // MARK: - Elements var welcomeScreen: XCUIElement { app.element(UITestID.Onboarding.welcome) } var subscriptionScreen: XCUIElement { app.element(UITestID.Onboarding.subscription) } var dayTodayButton: XCUIElement { app.element(UITestID.Onboarding.dayToday) } var dayYesterdayButton: XCUIElement { app.element(UITestID.Onboarding.dayYesterday) } var skipButton: XCUIElement { app.element(UITestID.Onboarding.skip) } // MARK: - Actions /// Swipe to next onboarding page. Uses a coordinate-based drag at the top /// of the screen to avoid DatePicker/ScrollView gesture conflicts on inner pages. /// This is the only reliable way to advance a paged TabView in XCUITest. func swipeToNext() { // Use slow velocity for reliable paged TabView advancement on iOS 26. app.swipeLeft(velocity: .slow) // Allow transition animation to settle _ = app.waitForExistence(timeout: 0.8) } /// Complete the full onboarding flow: swipe through all screens and skip subscription. func completeOnboarding(file: StaticString = #filePath, line: UInt = #line) { // Welcome -> swipe welcomeScreen.waitForExistenceOrFail( timeout: navigationTimeout, message: "Onboarding welcome screen should appear", file: file, line: line ) swipeToNext() // Time -> swipe. The wheel DatePicker can absorb gestures, so retry if needed. swipeToNext() if !dayTodayButton.waitForExistence(timeout: 2) { // Retry — the DatePicker may have absorbed the first swipe swipeToNext() } // Day -> select Today, then swipe dayTodayButton.waitForExistenceOrFail( timeout: navigationTimeout, message: "Day 'Today' button should appear", file: file, line: line ) dayTodayButton.forceTap(file: file, line: line) swipeToNext() // Style -> swipe swipeToNext() // Subscription -> tap skip skipButton.waitForExistenceOrFail( timeout: navigationTimeout, message: "Skip button should appear on subscription screen", file: file, line: line ) skipButton.forceTap(file: file, line: line) } // MARK: - Assertions @discardableResult func assertVisible(file: StaticString = #filePath, line: UInt = #line) -> OnboardingScreen { welcomeScreen.waitForExistenceOrFail( timeout: navigationTimeout, message: "Onboarding welcome screen should be visible", file: file, line: line ) return self } func assertDismissed(file: StaticString = #filePath, line: UInt = #line) { app.tabBars.firstMatch.waitForExistenceOrFail( timeout: navigationTimeout, message: "Tab bar should be visible after onboarding completes", file: file, line: line ) } }