Files
honeyDueKMP/iosApp/HoneyDueUITests/Suite2_AuthenticationTests.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

157 lines
6.6 KiB
Swift

import XCTest
/// Authentication flow tests
/// Based on working SimpleLoginTest pattern
final class Suite2_AuthenticationTests: BaseUITestCase {
override var completeOnboarding: Bool { true }
override var includeResetStateLaunchArgument: Bool { false }
override func setUpWithError() throws {
try super.setUpWithError()
// Wait for app to stabilize, then ensure we're on the login screen
sleep(2)
ensureOnLoginScreen()
}
override func tearDownWithError() throws {
try super.tearDownWithError()
}
// MARK: - Helper Methods
private func ensureOnLoginScreen() {
let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.usernameField]
// Already on login screen
if usernameField.waitForExistence(timeout: 3) {
return
}
// If on main tabs, log out first
let tabBar = app.tabBars.firstMatch
if tabBar.exists {
UITestHelpers.logout(app: app)
// After logout, wait for login screen
if usernameField.waitForExistence(timeout: 15) {
return
}
}
// Fallback: use ensureOnLoginScreen which handles onboarding state too
UITestHelpers.ensureOnLoginScreen(app: app)
}
private func login(username: String, password: String) {
UITestHelpers.login(app: app, username: username, password: password)
}
// MARK: - 1. Error/Validation Tests
func test01_loginWithInvalidCredentials() {
// Given: User is on login screen
let welcomeText = app.textFields[AccessibilityIdentifiers.Authentication.usernameField]
XCTAssertTrue(welcomeText.exists, "Should be on login screen")
// When: User logs in with invalid credentials
login(username: "wronguser", password: "wrongpass")
// Then: User should see error message and stay on login screen
sleep(3) // Wait for API response
// Should still be on login screen
XCTAssertTrue(welcomeText.exists, "Should still be on login screen")
// Sign In button should still be visible (not logged in)
let signInButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Sign In'")).firstMatch
XCTAssertTrue(signInButton.exists, "Should still see Sign In button")
}
// MARK: - 2. Creation Tests (Login/Session)
func test02_loginWithValidCredentials() {
// Given: User is on login screen
let welcomeText = app.textFields[AccessibilityIdentifiers.Authentication.usernameField]
XCTAssertTrue(welcomeText.exists, "Should be on login screen")
// When: User logs in with valid credentials
login(username: "testuser", password: "TestPass123!")
// Then: User should see main tab view
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
let didNavigate = residencesTab.waitForExistence(timeout: 10)
XCTAssertTrue(didNavigate, "Should navigate to main app after successful login")
}
// MARK: - 3. View/UI Tests
func test03_passwordVisibilityToggle() {
// Given: User is on login screen
let passwordField = app.secureTextFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'password'")).firstMatch
XCTAssertTrue(passwordField.waitForExistence(timeout: 5), "Password field should exist")
// When: User types password
passwordField.tap()
passwordField.typeText("secret123")
// Then: Find and tap the eye icon (visibility toggle)
let eyeButton = app.buttons[AccessibilityIdentifiers.Authentication.passwordVisibilityToggle].firstMatch
XCTAssertTrue(eyeButton.waitForExistence(timeout: 5), "Password visibility toggle button must exist")
eyeButton.tap()
sleep(1)
// Password should now be visible in a regular text field
let visiblePasswordField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'password'")).firstMatch
XCTAssertTrue(visiblePasswordField.exists, "Password should be visible after toggle")
}
// MARK: - 4. Navigation Tests
func test04_navigationToSignUp() {
// Given: User is on login screen
let welcomeText = app.textFields[AccessibilityIdentifiers.Authentication.usernameField]
XCTAssertTrue(welcomeText.exists, "Should be on login screen")
// When: User taps Sign Up button
let signUpButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Sign Up'")).firstMatch
XCTAssertTrue(signUpButton.exists, "Sign Up button should exist")
signUpButton.tap()
// Then: Registration screen should appear
sleep(2)
let registerButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Register' OR label CONTAINS[c] 'Create Account'")).firstMatch
XCTAssertTrue(registerButton.waitForExistence(timeout: 5), "Should navigate to registration screen")
}
func test05_forgotPasswordNavigation() {
// Given: User is on login screen
let welcomeText = app.textFields[AccessibilityIdentifiers.Authentication.usernameField]
XCTAssertTrue(welcomeText.exists, "Should be on login screen")
// When: User taps Forgot Password button
let forgotPasswordButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Forgot Password'")).firstMatch
XCTAssertTrue(forgotPasswordButton.exists, "Forgot Password button should exist")
forgotPasswordButton.tap()
// Then: Password reset screen should appear
sleep(2)
// Look for email field or reset button
let emailField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'email'")).firstMatch
let resetButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Reset' OR label CONTAINS[c] 'Send'")).firstMatch
let passwordResetScreenAppeared = emailField.exists || resetButton.exists
XCTAssertTrue(passwordResetScreenAppeared, "Should navigate to password reset screen")
}
// MARK: - 5. Delete/Logout Tests
func test06_logout() {
// Given: User is logged in
login(username: "testuser", password: "TestPass123!")
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
XCTAssertTrue(residencesTab.waitForExistence(timeout: 10), "Should be logged in")
// When: User logs out
UITestHelpers.logout(app: app)
// Then: User should be back on login screen (verified by UITestHelpers.logout)
}
}