// // OnboardingTests.swift // Tests iOS // // Onboarding flow completion and non-repetition tests. // import XCTest final class OnboardingTests: BaseUITestCase { override var seedFixture: String? { "empty" } override var skipOnboarding: Bool { false } /// TC-120: Complete the full onboarding flow. func testOnboarding_CompleteFlow() { // Welcome screen should appear let welcomeText = app.staticTexts.matching( NSPredicate(format: "label CONTAINS[cd] %@", "Welcome to Feels") ).firstMatch XCTAssertTrue( welcomeText.waitForExistence(timeout: 10), "Welcome screen should appear on first launch" ) captureScreenshot(name: "onboarding_welcome") // Swipe through screens with waits to ensure page transitions complete swipeAndWait() // Welcome → Time captureScreenshot(name: "onboarding_time") swipeAndWait() // Time → Day // Select "Today" if the button exists let todayButton = app.descendants(matching: .any) .matching(identifier: "onboarding_day_today") .firstMatch if todayButton.waitForExistence(timeout: 3) { todayButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } captureScreenshot(name: "onboarding_day") swipeAndWait() // Day → Style captureScreenshot(name: "onboarding_style") swipeAndWait() // Style → Subscription captureScreenshot(name: "onboarding_subscription") // Tap "Maybe Later" to complete onboarding 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) { swipeAndWait() } XCTAssertTrue( skipButton.waitForExistence(timeout: 5), "Skip/Maybe Later button should exist on subscription screen" ) skipButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() // After onboarding, the tab bar should appear let tabBar = app.tabBars.firstMatch XCTAssertTrue( tabBar.waitForExistence(timeout: 10), "Tab bar should be visible after completing onboarding" ) captureScreenshot(name: "onboarding_complete") } /// TC-121: After completing onboarding, relaunch should go directly to Day view. func testOnboarding_DoesNotRepeatAfterCompletion() { // First, complete onboarding let welcomeText = app.staticTexts.matching( NSPredicate(format: "label CONTAINS[cd] %@", "Welcome to Feels") ).firstMatch if welcomeText.waitForExistence(timeout: 5) { // Swipe through all screens swipeAndWait() // → Time swipeAndWait() // → Day swipeAndWait() // → Style swipeAndWait() // → Subscription let skipButton = app.descendants(matching: .any) .matching(identifier: "onboarding_skip_button") .firstMatch if !skipButton.waitForExistence(timeout: 5) { swipeAndWait() } if skipButton.waitForExistence(timeout: 5) { skipButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } } // Wait for main app to load let tabBar = app.tabBars.firstMatch XCTAssertTrue( tabBar.waitForExistence(timeout: 10), "Tab bar should appear after onboarding" ) // Terminate and relaunch (keeping --reset-state OUT to preserve onboarding completion) app.terminate() // Relaunch WITHOUT reset-state so onboarding completion is preserved let freshApp = XCUIApplication() freshApp.launchArguments = ["--ui-testing", "--disable-animations", "--bypass-subscription", "--skip-onboarding"] freshApp.launch() // Tab bar should appear immediately (no onboarding) let freshTabBar = freshApp.tabBars.firstMatch XCTAssertTrue( freshTabBar.waitForExistence(timeout: 10), "Tab bar should appear immediately on relaunch (no onboarding)" ) // Welcome screen should NOT appear let welcomeAgain = freshApp.staticTexts.matching( NSPredicate(format: "label CONTAINS[cd] %@", "Welcome to Feels") ).firstMatch XCTAssertFalse( welcomeAgain.waitForExistence(timeout: 2), "Onboarding should not appear on second launch" ) captureScreenshot(name: "no_onboarding_on_relaunch") } /// Swipe left with a brief wait for the page transition to settle. /// Uses a coordinate-based swipe for more reliable page advancement in paged TabView. private func swipeAndWait() { // Use a wide swipe from right to left for reliable page advancement let start = app.coordinate(withNormalizedOffset: CGVector(dx: 0.85, dy: 0.5)) let end = app.coordinate(withNormalizedOffset: CGVector(dx: 0.15, dy: 0.5)) start.press(forDuration: 0.05, thenDragTo: end) // Allow the paged TabView animation to settle _ = app.waitForExistence(timeout: 1.0) } }