import XCTest /// Page object for the login screen. /// /// Uses accessibility identifiers from `AccessibilityIdentifiers.Authentication` /// to locate elements. Provides typed actions for login flow interactions. class LoginScreen: BaseScreen { // MARK: - Elements var emailField: XCUIElement { app.textFields[AccessibilityIdentifiers.Authentication.usernameField] } var passwordField: XCUIElement { // Password field may be a SecureTextField or regular TextField depending on visibility toggle let secure = app.secureTextFields[AccessibilityIdentifiers.Authentication.passwordField] if secure.exists { return secure } return app.textFields[AccessibilityIdentifiers.Authentication.passwordField] } var loginButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Authentication.loginButton] } var appleSignInButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Authentication.appleSignInButton] } var signUpButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Authentication.signUpButton] } var forgotPasswordButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Authentication.forgotPasswordButton] } var passwordVisibilityToggle: XCUIElement { app.buttons[AccessibilityIdentifiers.Authentication.passwordVisibilityToggle] } var welcomeText: XCUIElement { app.staticTexts["Welcome Back"] } override var isDisplayed: Bool { emailField.waitForExistence(timeout: timeout) } // MARK: - Actions /// Logs in with the provided credentials and returns a MainTabScreen. /// Waits for the email field to appear before typing. @discardableResult func login(email: String, password: String) -> MainTabScreen { let field = waitForHittable(emailField) field.focusAndType(email, app: app) passwordField.focusAndType(password, app: app) // Submit via keyboard Go/Return button (avoids keyboard-covers-button issue) let goButton = app.keyboards.buttons["Go"] let returnButton = app.keyboards.buttons["Return"] if goButton.waitForExistence(timeout: 3) && goButton.isHittable { goButton.tap() } else if returnButton.exists && returnButton.isHittable { returnButton.tap() } else { // Dismiss keyboard, then tap login button app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.15)).tap() _ = app.keyboards.firstMatch.waitForNonExistence(timeout: 3) waitForHittable(loginButton).forceTap() } return MainTabScreen(app: app) } /// Taps the sign up / register link and returns a RegisterScreen. @discardableResult func tapSignUp() -> RegisterScreen { waitForElement(signUpButton).tap() return RegisterScreen(app: app) } /// Taps the forgot password link. func tapForgotPassword() { waitForElement(forgotPasswordButton).tap() } /// Toggles password visibility and returns whether the password is now visible. @discardableResult func togglePasswordVisibility() -> Bool { waitForElement(passwordVisibilityToggle).tap() // If a regular text field with the password identifier exists, password is visible return app.textFields[AccessibilityIdentifiers.Authentication.passwordField].exists } }