import XCTest /// UI tests for authentication flows: login, signup, forgot password, toggle. final class AuthFlowUITests: UITestBase { /// Test class overrides scenario to unauthenticated override class var scenario: UITestScenario { .unauthenticated } // MARK: - Helpers /// Relaunch the app with a specific scenario and reassign self.app private func relaunch(scenario: UITestScenario) { app.terminate() app = XCUIApplication() app.launchArguments = ["-UITesting"] app.launchEnvironment["UITestScenario"] = scenario.rawValue app.launch() } // MARK: - Launch & Branding /// Verify the app launches to the auth screen when unauthenticated func testLaunchAppShowsOnboardingScreen() { // Verify branding elements are visible XCTAssertTrue(text("Kordant").exists, "Brand name should be visible") XCTAssertTrue(text("Protect what matters most").exists, "Tagline should be visible") XCTAssertTrue(button("Continue with Google").exists, "Google sign-in button should exist") } // MARK: - Login / Signup Toggle /// Verify user can toggle between login and signup forms func testToggleBetweenLoginAndSignup() { // Should start on login view XCTAssertTrue(button("Sign In").exists, "Sign In button should be visible on login form") // Tap the toggle link to show signup button("Don't have an account? Sign up").tap() XCTAssertTrue(button("Create Account").exists, "Create Account button should be visible on signup form") // Tap back to login button("Already have an account? Sign in").tap() XCTAssertTrue(button("Sign In").exists, "Sign In button should be visible after toggling back") } // MARK: - Login with Valid Credentials /// Test successful login navigates to the dashboard func testLoginWithValidCredentialsNavigatesToDashboard() { // Re-launch with authenticated scenario relaunch(scenario: .authenticated) // When already authenticated, the app should skip auth and show the main tab view XCTAssertTrue(app.tabBars.buttons["Dashboard"].waitForExistence(timeout: 5), "Dashboard tab should be visible when authenticated") } // MARK: - Login with Invalid Credentials /// Test login with invalid credentials shows error state func testLoginWithInvalidCredentialsShowsError() { // Re-launch with authError scenario relaunch(scenario: .authError) // In .authError scenario, the mock API will fail let emailField = app.textFields["Email"] guard emailField.waitForExistence(timeout: 3) else { XCTFail("Email field not found") return } emailField.tap() emailField.typeText("wrong@email.com") let passwordField = app.secureTextFields["Password"] passwordField.tap() passwordField.typeText("wrongpassword") button("Sign In").tap() // Should show an error (either as inline text or alert) let errorExists = app.staticTexts.containing( NSPredicate(format: "label CONTAINS[c] 'error' OR label CONTAINS[c] 'Invalid' OR label CONTAINS[c] 'Unauthorized'") ).element.exists || app.alerts.element.exists XCTAssertTrue(errorExists, "Error should be shown for invalid credentials") captureScreen(name: "LoginInvalidCredentials") } // MARK: - Signup Form Validation /// Test signup form shows validation errors for invalid input func testSignupFormValidationShowsErrors() { // Switch to signup let toggleButton = button("Don't have an account? Sign up") guard toggleButton.waitForExistence(timeout: 2) else { XCTFail("Toggle to signup button not found") return } toggleButton.tap() // Wait for signup form to appear XCTAssertTrue(button("Create Account").waitForExistence(timeout: 2), "Create Account button should be visible on signup form") // Try to submit empty form - tap Create Account with empty fields button("Create Account").tap() // Should show validation errors let errorExists = app.staticTexts.containing( NSPredicate(format: "label CONTAINS[c] 'required' OR label CONTAINS[c] 'must'") ).element.exists XCTAssertTrue(errorExists, "Validation errors should be shown for empty form") captureScreen(name: "SignupValidationErrors") } // MARK: - Forgot Password Flow /// Test the forgot password flow shows confirmation func testForgotPasswordFlowShowsConfirmation() { // Re-launch with forgotPasswordSuccess scenario relaunch(scenario: .forgotPasswordSuccess) // Tap "Forgot password?" link let forgotButton = button("Forgot password?") guard forgotButton.waitForExistence(timeout: 3) else { XCTFail("Forgot password link not found") return } forgotButton.tap() // Forgot password sheet should appear let emailField = app.textFields["Email"] guard emailField.waitForExistence(timeout: 3) else { XCTFail("Email field in forgot password sheet not found") return } emailField.tap() emailField.typeText("test@kordant.com") button("Send Reset Link").tap() // Should see success state let successExists = text("Check your email").waitForExistence(timeout: 3) XCTAssertTrue(successExists, "Forgot password success state should be shown") captureScreen(name: "ForgotPasswordSuccess") } // MARK: - Authenticated Scenario /// Test that the authenticated scenario loads the dashboard func testAuthenticatedScenarioLoadsDashboard() { relaunch(scenario: .authenticated) // Verify we're on the dashboard XCTAssertTrue(app.navigationBars["Dashboard"].waitForExistence(timeout: 5), "Dashboard navigation bar should appear after authentication") } // MARK: - Biometric Prompt /// Test auth flow completes successfully (biometric prompt is shown when applicable) func testAuthFlowCompletesSuccessfully() { relaunch(scenario: .authenticated) // Verify we reach the main app with tab bar let dashboardTab = app.tabBars.buttons["Dashboard"] XCTAssertTrue(dashboardTab.waitForExistence(timeout: 5), "App should show tab bar after authentication") } // MARK: - Email Field Accessibility /// Verify email and password fields are accessible in login form func testLoginFormFieldsAreAccessible() { // Verify fields exist on the login form // With `.unauthenticated` scenario (current), should see login form let emailField = app.textFields["Email"] XCTAssertTrue(emailField.waitForExistence(timeout: 3), "Email text field should exist on login form") XCTAssertTrue(emailField.isEnabled, "Email field should be enabled") let passwordField = app.secureTextFields["Password"] XCTAssertTrue(passwordField.exists, "Password secure field should exist") XCTAssertTrue(passwordField.isEnabled, "Password field should be enabled") } }