import XCTest #if os(macOS) import Foundation #endif /// Comprehensive registration flow tests /// Tests the complete registration and email verification flow /// /// NOTE: Tests that require database access (fetchVerificationCode, cleanupTestUser) /// use shell scripts that run on the host machine. These tests are designed to run /// in the iOS Simulator with the backend running locally. final class RegistrationTests: XCTestCase { var app: XCUIApplication! // Test user credentials - using timestamp to ensure unique users private var testUsername: String { return "testuser_\(Int(Date().timeIntervalSince1970))" } private var testEmail: String { return "test_\(Int(Date().timeIntervalSince1970))@example.com" } private let testPassword = "TestPass123!" override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launch() ensureLoggedOut() } override func tearDownWithError() throws { // Clean up: logout if logged in ensureLoggedOut() app = nil } // MARK: - Helper Methods private func ensureLoggedOut() { UITestHelpers.ensureLoggedOut(app: app) } private func navigateToRegistration() { let signUpButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Sign Up'")).firstMatch XCTAssertTrue(signUpButton.waitForExistence(timeout: 5), "Sign Up button should exist on login screen") signUpButton.tap() sleep(1) } /// Dismisses the iOS Strong Password suggestion overlay if it appears private func dismissStrongPasswordSuggestion() { // Look for "Choose My Own Password" or similar button in the password suggestion let chooseOwnPassword = app.buttons["Choose My Own Password"] if chooseOwnPassword.waitForExistence(timeout: 2) { chooseOwnPassword.tap() sleep(1) return } // Try alternate labels let notNowButton = app.buttons["Not Now"] if notNowButton.exists { notNowButton.tap() sleep(1) return } // Try tapping outside the suggestion to dismiss it let strongPasswordText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Strong Password'")).firstMatch if strongPasswordText.exists { // Tap somewhere else to dismiss app.tap() sleep(1) } } /// Fixed test verification code - Django uses this code for emails starting with "test_" in DEBUG mode /// This matches ConfirmationCode.TEST_VERIFICATION_CODE in the Django backend private let testVerificationCode = "123456" /// Note: cleanupTestUser should be called from command line after tests complete /// Run: cd /Users/treyt/Desktop/code/Casera/myCribAPI && python manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.filter(email__startswith='test_').delete()" private func cleanupTestUser(email: String) { print("Cleanup test user: \(email)") print("Run manually if needed: cd /Users/treyt/Desktop/code/Casera/myCribAPI && python manage.py shell -c \"from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.filter(email='\(email)').delete()\"") } // MARK: - Registration Form Tests func testRegistrationScreenElements() { // Given: User is on login screen navigateToRegistration() // Then: Registration form should have all expected elements let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] let cancelButton = app.buttons[AccessibilityIdentifiers.Authentication.registerCancelButton] XCTAssertTrue(usernameField.waitForExistence(timeout: 5), "Username field should exist") XCTAssertTrue(emailField.exists, "Email field should exist") XCTAssertTrue(passwordField.exists, "Password field should exist") XCTAssertTrue(confirmPasswordField.exists, "Confirm password field should exist") XCTAssertTrue(createAccountButton.exists, "Create Account button should exist") XCTAssertTrue(cancelButton.exists, "Cancel button should exist") } func testRegistrationWithEmptyFields() { // Given: User is on registration screen navigateToRegistration() // When: User taps Create Account without filling fields let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] XCTAssertTrue(createAccountButton.waitForExistence(timeout: 5)) createAccountButton.tap() // Then: Error message should appear sleep(2) let errorExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'required' OR label CONTAINS[c] 'error' OR label CONTAINS[c] 'invalid'")).firstMatch.waitForExistence(timeout: 3) XCTAssertTrue(errorExists, "Error message should appear for empty fields") } func testRegistrationWithInvalidEmail() { // Given: User is on registration screen navigateToRegistration() // When: User fills form with invalid email let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText("testuser") emailField.tap() emailField.typeText("invalid-email") // Invalid email format passwordField.tap() passwordField.typeText(testPassword) confirmPasswordField.tap() confirmPasswordField.typeText(testPassword) let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Then: Error message for invalid email should appear sleep(2) let errorExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'email' OR label CONTAINS[c] 'invalid'")).firstMatch.waitForExistence(timeout: 3) XCTAssertTrue(errorExists, "Error message should appear for invalid email") } func testRegistrationWithMismatchedPasswords() { // Given: User is on registration screen navigateToRegistration() // When: User fills form with mismatched passwords let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText("testuser") emailField.tap() emailField.typeText("test@example.com") passwordField.tap() passwordField.typeText("Password123!") confirmPasswordField.tap() confirmPasswordField.typeText("DifferentPassword123!") // Mismatched password let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Then: Error message for mismatched passwords should appear sleep(2) let errorExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'match' OR label CONTAINS[c] 'password'")).firstMatch.waitForExistence(timeout: 3) XCTAssertTrue(errorExists, "Error message should appear for mismatched passwords") } func testRegistrationWithWeakPassword() { // Given: User is on registration screen navigateToRegistration() // When: User fills form with weak password let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText("testuser") emailField.tap() emailField.typeText("test@example.com") passwordField.tap() passwordField.typeText("weak") // Too short/weak password confirmPasswordField.tap() confirmPasswordField.typeText("weak") let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Then: Error message for weak password should appear sleep(2) let errorExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'password' OR label CONTAINS[c] 'character' OR label CONTAINS[c] 'strong'")).firstMatch.waitForExistence(timeout: 3) XCTAssertTrue(errorExists, "Error message should appear for weak password") } func testCancelRegistration() { // Given: User is on registration screen navigateToRegistration() // When: User taps Cancel button let cancelButton = app.buttons[AccessibilityIdentifiers.Authentication.registerCancelButton] XCTAssertTrue(cancelButton.waitForExistence(timeout: 5)) cancelButton.tap() // Then: Should return to login screen sleep(1) let welcomeText = app.staticTexts["Welcome Back"] XCTAssertTrue(welcomeText.waitForExistence(timeout: 5), "Should return to login screen") } // MARK: - Full Registration Flow Tests func testSuccessfulRegistrationAndVerification() { // Use unique credentials for this test let username = testUsername let email = testEmail // Given: User is on registration screen navigateToRegistration() // When: User fills in valid registration details let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText(username) emailField.tap() emailField.typeText(email) // Handle strong password suggestion by dismissing it passwordField.tap() sleep(1) // Dismiss the strong password suggestion if it appears dismissStrongPasswordSuggestion() // Now type the password passwordField.tap() passwordField.typeText(testPassword) confirmPasswordField.tap() sleep(1) dismissStrongPasswordSuggestion() confirmPasswordField.tap() confirmPasswordField.typeText(testPassword) // Submit registration let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Wait for verification screen to appear let verifyEmailTitle = app.staticTexts["Verify Your Email"] XCTAssertTrue(verifyEmailTitle.waitForExistence(timeout: 10), "Should navigate to email verification screen") // Use the fixed test verification code // Django uses "123456" for emails starting with "test_" when DEBUG=True let verificationCode = testVerificationCode print("Using test verification code: \(verificationCode)") // Enter the verification code let codeField = app.textFields[AccessibilityIdentifiers.Authentication.verificationCodeField] XCTAssertTrue(codeField.waitForExistence(timeout: 5), "Verification code field should exist") codeField.tap() codeField.typeText(verificationCode) // Submit verification let verifyButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")).firstMatch XCTAssertTrue(verifyButton.exists, "Verify button should exist") verifyButton.tap() // Then: Should navigate to main app screen (home/residences tab) let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch XCTAssertTrue(residencesTab.waitForExistence(timeout: 10), "Should navigate to main app after successful verification") // Verify we're on the home screen by checking for residences-related content let homeScreenVisible = residencesTab.isSelected || app.navigationBars.containing(NSPredicate(format: "identifier CONTAINS[c] 'Residences' OR identifier CONTAINS[c] 'Home' OR identifier CONTAINS[c] 'Properties'")).firstMatch.exists XCTAssertTrue(homeScreenVisible || residencesTab.exists, "Should be on home/residences screen after registration") // Navigate to Profile tab and log out let profileTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Profile'")).firstMatch XCTAssertTrue(profileTab.waitForExistence(timeout: 5), "Profile tab should exist") profileTab.tap() sleep(1) // Tap logout button let logoutButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout' OR label CONTAINS[c] 'Log Out' OR label CONTAINS[c] 'Sign Out'")).firstMatch XCTAssertTrue(logoutButton.waitForExistence(timeout: 5), "Logout button should exist on profile screen") logoutButton.tap() sleep(1) // Confirm logout in alert if present let alertLogoutButton = app.alerts.buttons["Log Out"] if alertLogoutButton.waitForExistence(timeout: 3) { alertLogoutButton.tap() sleep(1) } // Verify we're back on login screen let welcomeText = app.staticTexts["Welcome Back"] XCTAssertTrue(welcomeText.waitForExistence(timeout: 5), "Should return to login screen after logout") // Cleanup test user cleanupTestUser(email: email) } func testRegistrationWithInvalidVerificationCode() { // Use unique credentials for this test let username = testUsername let email = testEmail // Given: User is on registration screen navigateToRegistration() // Register a new user let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText(username) emailField.tap() emailField.typeText(email) passwordField.tap() sleep(1) dismissStrongPasswordSuggestion() passwordField.tap() passwordField.typeText(testPassword) confirmPasswordField.tap() sleep(1) dismissStrongPasswordSuggestion() confirmPasswordField.tap() confirmPasswordField.typeText(testPassword) let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Wait for verification screen let verifyEmailTitle = app.staticTexts["Verify Your Email"] XCTAssertTrue(verifyEmailTitle.waitForExistence(timeout: 10), "Should navigate to email verification screen") // Enter an invalid verification code let codeField = app.textFields[AccessibilityIdentifiers.Authentication.verificationCodeField] XCTAssertTrue(codeField.waitForExistence(timeout: 5)) codeField.tap() codeField.typeText("000000") // Invalid code // Submit verification let verifyButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")).firstMatch verifyButton.tap() // Then: Error message should appear sleep(3) let errorExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'invalid' OR label CONTAINS[c] 'error' OR label CONTAINS[c] 'incorrect'")).firstMatch.waitForExistence(timeout: 5) XCTAssertTrue(errorExists, "Error message should appear for invalid verification code") // Cleanup: Logout and delete test user let logoutButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout'")).firstMatch if logoutButton.exists { logoutButton.tap() sleep(2) } cleanupTestUser(email: email) } func testLogoutFromVerificationScreen() { // Use unique credentials for this test let username = testUsername let email = testEmail // Given: User is on registration screen navigateToRegistration() // Register a new user let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText(username) emailField.tap() emailField.typeText(email) passwordField.tap() sleep(1) dismissStrongPasswordSuggestion() passwordField.tap() passwordField.typeText(testPassword) confirmPasswordField.tap() sleep(1) dismissStrongPasswordSuggestion() confirmPasswordField.tap() confirmPasswordField.typeText(testPassword) let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Wait for verification screen let verifyEmailTitle = app.staticTexts["Verify Your Email"] XCTAssertTrue(verifyEmailTitle.waitForExistence(timeout: 10), "Should navigate to email verification screen") // When: User taps Logout button let logoutButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout'")).firstMatch XCTAssertTrue(logoutButton.waitForExistence(timeout: 5), "Logout button should exist on verification screen") logoutButton.tap() // Then: Should return to login screen sleep(2) let welcomeText = app.staticTexts["Welcome Back"] XCTAssertTrue(welcomeText.waitForExistence(timeout: 5), "Should return to login screen after logout") // Cleanup cleanupTestUser(email: email) } func testVerificationCodeFieldValidation() { // Use unique credentials for this test let username = testUsername let email = testEmail // Given: User is on registration screen and registers navigateToRegistration() let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText(username) emailField.tap() emailField.typeText(email) passwordField.tap() sleep(1) dismissStrongPasswordSuggestion() passwordField.tap() passwordField.typeText(testPassword) confirmPasswordField.tap() sleep(1) dismissStrongPasswordSuggestion() confirmPasswordField.tap() confirmPasswordField.typeText(testPassword) let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Wait for verification screen let verifyEmailTitle = app.staticTexts["Verify Your Email"] XCTAssertTrue(verifyEmailTitle.waitForExistence(timeout: 10)) // When: User tries to verify with incomplete code let codeField = app.textFields[AccessibilityIdentifiers.Authentication.verificationCodeField] XCTAssertTrue(codeField.waitForExistence(timeout: 5)) codeField.tap() codeField.typeText("123") // Only 3 digits // Then: Verify button should be disabled or show error let verifyButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")).firstMatch XCTAssertTrue(verifyButton.exists) // The button might be disabled or tapping it shows an error // Check that verification doesn't proceed with incomplete code verifyButton.tap() sleep(1) // Should still be on verification screen XCTAssertTrue(verifyEmailTitle.exists, "Should still be on verification screen with incomplete code") // Cleanup let logoutButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout'")).firstMatch if logoutButton.exists { logoutButton.tap() sleep(2) } cleanupTestUser(email: email) } func testRegistrationWithExistingUsername() { // This test requires an existing user in the database // First, we need to ensure testuser exists // Given: User is on registration screen navigateToRegistration() // When: User tries to register with existing username let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) usernameField.tap() usernameField.typeText("testuser") // Assuming this user already exists emailField.tap() emailField.typeText("newemail_\(Int(Date().timeIntervalSince1970))@example.com") passwordField.tap() passwordField.typeText(testPassword) confirmPasswordField.tap() confirmPasswordField.typeText(testPassword) let createAccountButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] createAccountButton.tap() // Then: Error message for existing username should appear sleep(3) let errorExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'exists' OR label CONTAINS[c] 'already' OR label CONTAINS[c] 'taken'")).firstMatch.waitForExistence(timeout: 5) XCTAssertTrue(errorExists, "Error message should appear for existing username") } func testKeyboardNavigationDuringRegistration() { // Given: User is on registration screen navigateToRegistration() // When: User navigates through fields using keyboard let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] XCTAssertTrue(usernameField.waitForExistence(timeout: 5)) // Start with username field usernameField.tap() XCTAssertTrue(usernameField.hasKeyboardFocus, "Username field should have keyboard focus") usernameField.typeText("testuser\n") // Press return to move to next field sleep(1) // Email field should now be focused (or at least exist) XCTAssertTrue(emailField.exists, "Email field should exist") emailField.tap() emailField.typeText("test@example.com\n") sleep(1) // Password field should now be accessible XCTAssertTrue(passwordField.exists, "Password field should exist") } } // MARK: - XCUIElement Extension for keyboard focus extension XCUIElement { var hasKeyboardFocus: Bool { return (value(forKey: "hasKeyboardFocus") as? Bool) ?? false } }