import XCTest struct UITestID { struct Root { static let ready = "ui.app.ready" static let onboarding = "ui.root.onboarding" static let login = "ui.root.login" static let mainTabs = "ui.root.mainTabs" } struct Onboarding { static let welcomeTitle = "Onboarding.WelcomeTitle" static let startFreshButton = "Onboarding.StartFreshButton" static let joinExistingButton = "Onboarding.JoinExistingButton" static let loginButton = "Onboarding.LoginButton" static let valuePropsContainer = "Onboarding.ValuePropsTitle" static let valuePropsNextButton = "Onboarding.ValuePropsNextButton" static let nameResidenceTitle = "Onboarding.NameResidenceTitle" static let residenceNameField = "Onboarding.ResidenceNameField" static let nameResidenceContinueButton = "Onboarding.NameResidenceContinueButton" static let createAccountTitle = "Onboarding.CreateAccountTitle" static let emailSignUpExpandButton = "Onboarding.EmailSignUpExpandButton" static let createAccountButton = "Onboarding.CreateAccountButton" static let backButton = "Onboarding.BackButton" static let skipButton = "Onboarding.SkipButton" static let progressIndicator = "Onboarding.ProgressIndicator" } struct PasswordReset { static let emailField = "PasswordReset.EmailField" static let sendCodeButton = "PasswordReset.SendCodeButton" static let backToLoginButton = "PasswordReset.BackToLoginButton" static let codeField = "PasswordReset.CodeField" static let verifyCodeButton = "PasswordReset.VerifyCodeButton" static let resendCodeButton = "PasswordReset.ResendCodeButton" static let newPasswordField = "PasswordReset.NewPasswordField" static let confirmPasswordField = "PasswordReset.ConfirmPasswordField" static let resetButton = "PasswordReset.ResetButton" static let returnToLoginButton = "PasswordReset.ReturnToLoginButton" } struct Auth { static let usernameField = "Login.UsernameField" static let passwordField = "Login.PasswordField" static let passwordVisibilityToggle = "Login.PasswordVisibilityToggle" static let loginButton = "Login.LoginButton" static let signUpButton = "Login.SignUpButton" static let forgotPasswordButton = "Login.ForgotPasswordButton" static let registerUsernameField = "Register.UsernameField" static let registerEmailField = "Register.EmailField" static let registerPasswordField = "Register.PasswordField" static let registerConfirmPasswordField = "Register.ConfirmPasswordField" static let registerButton = "Register.RegisterButton" static let registerCancelButton = "Register.CancelButton" } } struct RootScreen { let app: XCUIApplication func waitForReady(timeout: TimeInterval = 15) { app.otherElements[UITestID.Root.ready].waitForExistenceOrFail(timeout: timeout) } } struct OnboardingWelcomeScreen { let app: XCUIApplication private var onboardingRoot: XCUIElement { app.otherElements[UITestID.Root.onboarding] } private var startFreshButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.startFreshButton).firstMatch } private var joinExistingButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.joinExistingButton).firstMatch } private var loginButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.loginButton).firstMatch } private var backButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.backButton).firstMatch } func waitForLoad(timeout: TimeInterval = 15) { onboardingRoot.waitForExistenceOrFail(timeout: timeout) if startFreshButton.waitForExistence(timeout: 2) { return } for _ in 0..<4 { if backButton.exists && backButton.isHittable { backButton.tap() } if startFreshButton.waitForExistence(timeout: 2) { return } } if !startFreshButton.waitForExistence(timeout: timeout) { XCTFail("Expected onboarding welcome entry point. Debug tree:\n\(app.debugDescription)") } } func tapStartFresh() { startFreshButton.waitUntilHittable(timeout: 10).tap() } func tapJoinExisting() { joinExistingButton.waitUntilHittable(timeout: 10).tap() } func tapAlreadyHaveAccount() { loginButton.waitForExistenceOrFail(timeout: 10) if loginButton.isHittable { loginButton.tap() } else { loginButton.forceTap() } } } struct OnboardingValuePropsScreen { let app: XCUIApplication private var container: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.valuePropsContainer).firstMatch } private var continueButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.valuePropsNextButton).firstMatch } private var backButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.backButton).firstMatch } func waitForLoad(timeout: TimeInterval = 15) { container.waitForExistenceOrFail(timeout: timeout) } func tapContinue() { continueButton.waitUntilHittable(timeout: 10).tap() } func tapBack() { backButton.waitForExistenceOrFail(timeout: 10) backButton.forceTap() } } struct OnboardingNameResidenceScreen { let app: XCUIApplication private var title: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.nameResidenceTitle).firstMatch } private var nameField: XCUIElement { app.textFields[UITestID.Onboarding.residenceNameField] } private var continueButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.nameResidenceContinueButton).firstMatch } private var backButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.backButton).firstMatch } func waitForLoad(timeout: TimeInterval = 15) { title.waitForExistenceOrFail(timeout: timeout) } func enterResidenceName(_ value: String) { nameField.waitUntilHittable(timeout: 10).tap() nameField.typeText(value) } func tapContinue() { continueButton.waitUntilHittable(timeout: 10).tap() } func tapBack() { backButton.waitForExistenceOrFail(timeout: 10) backButton.forceTap() } } struct OnboardingCreateAccountScreen { let app: XCUIApplication private var title: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.createAccountTitle).firstMatch } private var expandEmailButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.emailSignUpExpandButton).firstMatch } private var createAccountButton: XCUIElement { app.descendants(matching: .any).matching(identifier: UITestID.Onboarding.createAccountButton).firstMatch } func waitForLoad(timeout: TimeInterval = 15) { title.waitForExistenceOrFail(timeout: timeout) } func expandEmailSignup() { expandEmailButton.waitUntilHittable(timeout: 10).tap() } func waitForCreateAccountButton(timeout: TimeInterval = 10) { createAccountButton.waitForExistenceOrFail(timeout: timeout) } } struct LoginScreenObject { let app: XCUIApplication private var usernameField: XCUIElement { app.textFields[UITestID.Auth.usernameField] } private var passwordSecureField: XCUIElement { app.secureTextFields[UITestID.Auth.passwordField] } private var passwordVisibleField: XCUIElement { app.textFields[UITestID.Auth.passwordField] } private var loginButton: XCUIElement { app.buttons[UITestID.Auth.loginButton] } private var signUpButton: XCUIElement { app.buttons[UITestID.Auth.signUpButton] } private var forgotPasswordButton: XCUIElement { app.buttons[UITestID.Auth.forgotPasswordButton] } private var visibilityToggle: XCUIElement { app.buttons[UITestID.Auth.passwordVisibilityToggle] } func waitForLoad(timeout: TimeInterval = 15) { usernameField.waitForExistenceOrFail(timeout: timeout) loginButton.waitForExistenceOrFail(timeout: timeout) } func enterUsername(_ username: String) { usernameField.waitUntilHittable(timeout: 10).tap() usernameField.typeText(username) } func enterPassword(_ password: String) { if passwordSecureField.exists { passwordSecureField.tap() passwordSecureField.typeText(password) } else { passwordVisibleField.waitUntilHittable(timeout: 10).tap() passwordVisibleField.typeText(password) } } func tapPasswordVisibilityToggle() { visibilityToggle.waitUntilHittable(timeout: 10).tap() } func tapSignUp() { signUpButton.waitUntilHittable(timeout: 10).tap() } func tapForgotPassword() { forgotPasswordButton.waitUntilHittable(timeout: 10).tap() } func assertPasswordFieldVisible() { XCTAssertTrue(passwordVisibleField.waitForExistence(timeout: 5), "Expected visible password text field after toggle") } } struct RegisterScreenObject { let app: XCUIApplication private var usernameField: XCUIElement { app.textFields[UITestID.Auth.registerUsernameField] } private var emailField: XCUIElement { app.textFields[UITestID.Auth.registerEmailField] } private var passwordField: XCUIElement { app.secureTextFields[UITestID.Auth.registerPasswordField] } private var confirmPasswordField: XCUIElement { app.secureTextFields[UITestID.Auth.registerConfirmPasswordField] } private var registerButton: XCUIElement { app.buttons[UITestID.Auth.registerButton] } private var cancelButton: XCUIElement { app.buttons[UITestID.Auth.registerCancelButton] } func waitForLoad(timeout: TimeInterval = 15) { usernameField.waitForExistenceOrFail(timeout: timeout) registerButton.waitForExistenceOrFail(timeout: timeout) } func fill(username: String, email: String, password: String) { func advanceToNextField() { let keys = ["Next", "Return", "return", "Done", "done"] for key in keys { let button = app.keyboards.buttons[key] if button.waitForExistence(timeout: 1) && button.isHittable { button.tap() return } } } usernameField.waitForExistenceOrFail(timeout: 10) usernameField.forceTap() usernameField.typeText(username) advanceToNextField() emailField.waitForExistenceOrFail(timeout: 10) if !emailField.hasKeyboardFocus { emailField.forceTap() if !emailField.hasKeyboardFocus { advanceToNextField() emailField.forceTap() } } emailField.typeText(email) advanceToNextField() passwordField.waitForExistenceOrFail(timeout: 10) if !passwordField.hasKeyboardFocus { passwordField.forceTap() } passwordField.typeText(password) advanceToNextField() confirmPasswordField.waitForExistenceOrFail(timeout: 10) if !confirmPasswordField.hasKeyboardFocus { confirmPasswordField.forceTap() } confirmPasswordField.typeText(password) } func tapCancel() { cancelButton.waitUntilHittable(timeout: 10).tap() } } // MARK: - Password Reset Screens struct ForgotPasswordScreen { let app: XCUIApplication private var emailField: XCUIElement { app.textFields[UITestID.PasswordReset.emailField] } private var sendCodeButton: XCUIElement { app.buttons[UITestID.PasswordReset.sendCodeButton] } private var backToLoginButton: XCUIElement { app.buttons[UITestID.PasswordReset.backToLoginButton] } func waitForLoad(timeout: TimeInterval = 15) { // Wait for the email field or the "Forgot Password?" title let emailLoaded = emailField.waitForExistence(timeout: timeout) if !emailLoaded { let title = app.staticTexts.containing( NSPredicate(format: "label CONTAINS[c] 'Forgot Password'") ).firstMatch XCTAssertTrue(title.waitForExistence(timeout: 5), "Expected forgot password screen to load") } } func enterEmail(_ email: String) { emailField.waitUntilHittable(timeout: 10).tap() emailField.typeText(email) } func tapSendCode() { sendCodeButton.waitUntilHittable(timeout: 10).tap() } func tapBackToLogin() { backToLoginButton.waitUntilHittable(timeout: 10).tap() } } struct VerifyResetCodeScreen { let app: XCUIApplication private var codeField: XCUIElement { app.textFields[UITestID.PasswordReset.codeField] } private var verifyCodeButton: XCUIElement { app.buttons[UITestID.PasswordReset.verifyCodeButton] } private var resendCodeButton: XCUIElement { app.buttons[UITestID.PasswordReset.resendCodeButton] } func waitForLoad(timeout: TimeInterval = 15) { let codeLoaded = codeField.waitForExistence(timeout: timeout) if !codeLoaded { let title = app.staticTexts.containing( NSPredicate(format: "label CONTAINS[c] 'Check Your Email'") ).firstMatch XCTAssertTrue(title.waitForExistence(timeout: 5), "Expected verify reset code screen to load") } } func enterCode(_ code: String) { codeField.waitUntilHittable(timeout: 10).tap() codeField.typeText(code) } func tapVerify() { verifyCodeButton.waitUntilHittable(timeout: 10).tap() } func tapResendCode() { resendCodeButton.waitUntilHittable(timeout: 10).tap() } } struct ResetPasswordScreen { let app: XCUIApplication // The new password field may be a SecureField or TextField depending on visibility toggle private var newPasswordSecureField: XCUIElement { app.secureTextFields[UITestID.PasswordReset.newPasswordField] } private var newPasswordVisibleField: XCUIElement { app.textFields[UITestID.PasswordReset.newPasswordField] } private var confirmPasswordSecureField: XCUIElement { app.secureTextFields[UITestID.PasswordReset.confirmPasswordField] } private var confirmPasswordVisibleField: XCUIElement { app.textFields[UITestID.PasswordReset.confirmPasswordField] } private var resetButton: XCUIElement { app.buttons[UITestID.PasswordReset.resetButton] } private var returnToLoginButton: XCUIElement { app.buttons[UITestID.PasswordReset.returnToLoginButton] } func waitForLoad(timeout: TimeInterval = 15) { let loaded = newPasswordSecureField.waitForExistence(timeout: timeout) || newPasswordVisibleField.waitForExistence(timeout: 3) if !loaded { let title = app.staticTexts.containing( NSPredicate(format: "label CONTAINS[c] 'Set New Password'") ).firstMatch XCTAssertTrue(title.waitForExistence(timeout: 5), "Expected reset password screen to load") } } func enterNewPassword(_ password: String) { if newPasswordSecureField.exists { newPasswordSecureField.waitUntilHittable(timeout: 10).tap() newPasswordSecureField.typeText(password) } else { newPasswordVisibleField.waitUntilHittable(timeout: 10).tap() newPasswordVisibleField.typeText(password) } } func enterConfirmPassword(_ password: String) { if confirmPasswordSecureField.exists { confirmPasswordSecureField.waitUntilHittable(timeout: 10).tap() confirmPasswordSecureField.typeText(password) } else { confirmPasswordVisibleField.waitUntilHittable(timeout: 10).tap() confirmPasswordVisibleField.typeText(password) } } func tapReset() { resetButton.waitUntilHittable(timeout: 10).tap() } func tapReturnToLogin() { returnToLoginButton.waitUntilHittable(timeout: 10).tap() } var isResetButtonEnabled: Bool { resetButton.waitForExistenceOrFail(timeout: 10) return resetButton.isEnabled } }