Files
honeyDueKMP/iosApp/HoneyDueUITests/PageObjects/LoginScreen.swift
treyt 5c360a2796 Rearchitect UI test suite for complete, non-flaky coverage against live API
- Migrate Suite4-10, SmokeTests, NavigationCriticalPathTests to AuthenticatedTestCase
  with seeded admin account and real backend login
- Add 34 accessibility identifiers across 11 app views (task completion, profile,
  notifications, theme, join residence, manage users, forms)
- Create FeatureCoverageTests (14 tests) covering previously untested features:
  profile edit, theme selection, notification prefs, task completion, manage users,
  join residence, task templates
- Create MultiUserSharingTests (18 API tests) and MultiUserSharingUITests (8 XCUI
  tests) for full cross-user residence sharing lifecycle
- Add cleanup infrastructure: SuiteZZ_CleanupTests auto-wipes test data after runs,
  cleanup_test_data.sh script for manual reset via admin API
- Add share code API methods to TestAccountAPIClient (generateShareCode, joinWithCode,
  getShareCode, listResidenceUsers, removeUser)
- Fix app bugs found by tests:
  - ResidencesListView join callback now uses forceRefresh:true
  - APILayer invalidates task cache when residence count changes
  - AllTasksView auto-reloads tasks when residence list changes
- Fix test quality: keyboard focus waits, Save/Add button label matching,
  Documents tab label (Docs), remove API verification from UI tests
- DataLayerTests and PasswordResetTests now verify through UI, not API calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 17:32:13 -05:00

88 lines
2.8 KiB
Swift

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.tap()
field.typeText(email)
let pwField = waitForHittable(passwordField)
pwField.tap()
pwField.typeText(password)
waitForHittable(loginButton).tap()
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
}
}