import XCTest enum TestFlows { @discardableResult static func navigateToLoginFromOnboarding(app: XCUIApplication) -> LoginScreenObject { let login = LoginScreenObject(app: app) // If already on standalone login screen, return immediately. // Use a generous timeout — the app may still be rendering after launch. if app.textFields[UITestID.Auth.usernameField].waitForExistence(timeout: 10) || app.otherElements[UITestID.Root.login].waitForExistence(timeout: 3) { login.waitForLoad() return login } // Check if onboarding is actually present before trying to navigate from it let onboardingRoot = app.otherElements[UITestID.Root.onboarding] if onboardingRoot.waitForExistence(timeout: 5) { // Navigate from onboarding welcome let welcome = OnboardingWelcomeScreen(app: app) welcome.waitForLoad() welcome.tapAlreadyHaveAccount() login.waitForLoad() return login } // Fallback: use ensureOnLoginScreen which handles all edge cases UITestHelpers.ensureOnLoginScreen(app: app) login.waitForLoad() return login } @discardableResult static func navigateStartFreshToCreateAccount( app: XCUIApplication, residenceName: String = "UI Test Residence" ) -> OnboardingCreateAccountScreen { let welcome = OnboardingWelcomeScreen(app: app) welcome.waitForLoad() welcome.tapStartFresh() let valueProps = OnboardingValuePropsScreen(app: app) valueProps.waitForLoad() valueProps.tapContinue() let nameResidence = OnboardingNameResidenceScreen(app: app) nameResidence.waitForLoad() nameResidence.enterResidenceName(residenceName) nameResidence.tapContinue() let createAccount = OnboardingCreateAccountScreen(app: app) createAccount.waitForLoad() return createAccount } /// Type credentials into the login screen and tap login. /// Assumes the app is already showing the login screen. static func loginWithCredentials(app: XCUIApplication, username: String, password: String) { let login = LoginScreenObject(app: app) login.waitForLoad() login.enterUsername(username) login.enterPassword(password) let loginButton = app.buttons[UITestID.Auth.loginButton] loginButton.waitUntilHittable(timeout: 10).tap() } /// Drive the full forgot password → verify code → reset password flow using the debug code. static func completeForgotPasswordFlow( app: XCUIApplication, email: String, newPassword: String, confirmPassword: String? = nil ) throws { let confirm = confirmPassword ?? newPassword // Step 1: Enter email on forgot password screen let forgotScreen = ForgotPasswordScreen(app: app) forgotScreen.waitForLoad() forgotScreen.enterEmail(email) forgotScreen.tapSendCode() // Step 2: Enter debug verification code let verifyScreen = VerifyResetCodeScreen(app: app) verifyScreen.waitForLoad() verifyScreen.enterCode(TestAccountAPIClient.debugVerificationCode) verifyScreen.tapVerify() // Step 3: Enter new password let resetScreen = ResetPasswordScreen(app: app) try resetScreen.waitForLoad() resetScreen.enterNewPassword(newPassword) resetScreen.enterConfirmPassword(confirm) resetScreen.tapReset() } @discardableResult static func openRegisterFromLogin(app: XCUIApplication) -> RegisterScreenObject { let login: LoginScreenObject // Wait for login screen elements instead of instantaneous .exists checks if app.textFields[UITestID.Auth.usernameField].waitForExistence(timeout: 10) || app.otherElements[UITestID.Root.login].waitForExistence(timeout: 3) { login = LoginScreenObject(app: app) login.waitForLoad() } else { login = navigateToLoginFromOnboarding(app: app) } login.tapSignUp() let register = RegisterScreenObject(app: app) register.waitForLoad() return register } }