// // 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 dayTodayButton: XCUIElement { app.element(UITestID.Onboarding.dayToday) } var dayYesterdayButton: XCUIElement { app.element(UITestID.Onboarding.dayYesterday) } var skipButton: XCUIElement { app.element(UITestID.Onboarding.skip) } var nextButton: XCUIElement { app.element(UITestID.Onboarding.next) } // MARK: - Actions /// Tap the "Continue" / "Get Started" next button to advance one page. func tapNext(file: StaticString = #filePath, line: UInt = #line) { nextButton.waitForExistenceOrFail( timeout: navigationTimeout, message: "Onboarding next button should exist", file: file, line: line ) // Tap via coordinate — the page indicator may overlap the button's hittable area nextButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() // Allow page transition to settle _ = app.waitForExistence(timeout: 0.5) } /// Complete the full onboarding flow by tapping through all screens. func completeOnboarding(file: StaticString = #filePath, line: UInt = #line) { // Welcome -> tap next welcomeScreen.waitForExistenceOrFail( timeout: navigationTimeout, message: "Onboarding welcome screen should appear", file: file, line: line ) tapNext(file: file, line: line) // Day -> select Today, tap next dayTodayButton.waitForExistenceOrFail( timeout: navigationTimeout, message: "Day 'Today' button should appear", file: file, line: line ) dayTodayButton.forceTap(file: file, line: line) tapNext(file: file, line: line) // Time -> tap next tapNext(file: file, line: line) // Style -> tap next tapNext(file: file, line: line) // Subscription -> tap skip ("Maybe Later") // The subscription page may not expose children via accessibility IDs on iOS 26. // Try multiple strategies. let strategies: [() -> Bool] = [ { self.skipButton.waitForExistence(timeout: 2) }, { self.app.buttons["Maybe Later"].waitForExistence(timeout: 2) }, { self.app.staticTexts["Maybe Later"].waitForExistence(timeout: 2) }, ] for strategy in strategies { if strategy() { break } } if skipButton.exists { skipButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } else if app.buttons["Maybe Later"].exists { app.buttons["Maybe Later"].tap() } else if app.staticTexts["Maybe Later"].exists { app.staticTexts["Maybe Later"].tap() } else { // Last resort: tap at the expected button location (below center) app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.88)).tap() } } // 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 ) } }