import XCTest /// Comprehensive End-to-End Test Suite /// Closely mirrors TestIntegration_ComprehensiveE2E from myCribAPI-go/internal/integration/integration_test.go /// /// This test creates a complete scenario: /// 1. Registers a new user and verifies login /// 2. Creates multiple residences /// 3. Creates multiple tasks in different states /// 4. Verifies task categorization in kanban columns /// 5. Tests task state transitions (in-progress, complete, cancel, archive) /// /// IMPORTANT: These are integration tests requiring network connectivity. /// Run against a test/dev server, NOT production. final class Suite10_ComprehensiveE2ETests: XCTestCase { var app: XCUIApplication! // Test run identifier for unique data - use static so it's shared across test methods private static let testRunId = Int(Date().timeIntervalSince1970) // Test user credentials - unique per test run private var testUsername: String { "e2e_comp_\(Self.testRunId)" } private var testEmail: String { "e2e_comp_\(Self.testRunId)@test.com" } private let testPassword = "TestPass123!" /// Fixed verification code used by Go API when DEBUG=true private let verificationCode = "123456" /// Track if user has been registered for this test run private static var userRegistered = false override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launch() // Register user on first test, then just ensure logged in for subsequent tests if !Self.userRegistered { registerTestUser() Self.userRegistered = true } else { UITestHelpers.ensureLoggedIn(app: app, username: testUsername, password: testPassword) } } /// Register a new test user for this test suite private func registerTestUser() { // Check if already logged in let tabBar = app.tabBars.firstMatch if tabBar.exists { return // Already logged in } // Check if on login screen, navigate to register let welcomeText = app.staticTexts["Welcome Back"] if welcomeText.waitForExistence(timeout: 5) { let signUpButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Sign Up'")).firstMatch if signUpButton.exists { signUpButton.tap() sleep(2) } } // Fill registration form let usernameField = app.textFields[AccessibilityIdentifiers.Authentication.registerUsernameField] if usernameField.waitForExistence(timeout: 5) { usernameField.tap() usernameField.typeText(testUsername) let emailField = app.textFields[AccessibilityIdentifiers.Authentication.registerEmailField] emailField.tap() emailField.typeText(testEmail) let passwordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerPasswordField] passwordField.tap() dismissStrongPasswordSuggestion() passwordField.typeText(testPassword) let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Authentication.registerConfirmPasswordField] confirmPasswordField.tap() dismissStrongPasswordSuggestion() confirmPasswordField.typeText(testPassword) dismissKeyboard() sleep(1) // Submit registration app.swipeUp() sleep(1) var registerButton = app.buttons[AccessibilityIdentifiers.Authentication.registerButton] if !registerButton.exists || !registerButton.isHittable { registerButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Create Account' OR label CONTAINS[c] 'Register'")).firstMatch } if registerButton.exists { registerButton.tap() sleep(3) } // Handle email verification let verifyEmailTitle = app.staticTexts["Verify Your Email"] if verifyEmailTitle.waitForExistence(timeout: 10) { let codeField = app.textFields[AccessibilityIdentifiers.Authentication.verificationCodeField] if codeField.waitForExistence(timeout: 5) { codeField.tap() codeField.typeText(verificationCode) sleep(5) } } // Wait for login to complete _ = tabBar.waitForExistence(timeout: 15) } } /// Dismiss strong password suggestion if shown private func dismissStrongPasswordSuggestion() { let chooseOwnPassword = app.buttons["Choose My Own Password"] if chooseOwnPassword.waitForExistence(timeout: 1) { chooseOwnPassword.tap() return } let notNow = app.buttons["Not Now"] if notNow.exists && notNow.isHittable { notNow.tap() } } override func tearDownWithError() throws { app = nil } // MARK: - Helper Methods private func navigateToTab(_ tabName: String) { let tab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] '\(tabName)'")).firstMatch if tab.waitForExistence(timeout: 5) && !tab.isSelected { tab.tap() sleep(2) } } /// Dismiss keyboard by tapping outside (doesn't submit forms) private func dismissKeyboard() { // Tap on a neutral area to dismiss keyboard without submitting let coordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)) coordinate.tap() Thread.sleep(forTimeInterval: 0.5) } /// Creates a residence with the given name /// Returns true if successful @discardableResult private func createResidence(name: String, streetAddress: String = "123 Test St", city: String = "Austin", state: String = "TX", postalCode: String = "78701") -> Bool { navigateToTab("Residences") sleep(2) let addButton = app.buttons[AccessibilityIdentifiers.Residence.addButton] guard addButton.waitForExistence(timeout: 5) else { XCTFail("Add residence button not found") return false } addButton.tap() sleep(2) // Fill name let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch guard nameField.waitForExistence(timeout: 5) else { XCTFail("Name field not found") return false } nameField.tap() nameField.typeText(name) // Fill address fillTextField(placeholder: "Street", text: streetAddress) fillTextField(placeholder: "City", text: city) fillTextField(placeholder: "State", text: state) fillTextField(placeholder: "Postal", text: postalCode) app.swipeUp() sleep(1) // Save let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch guard saveButton.exists else { XCTFail("Save button not found") return false } saveButton.tap() sleep(3) // Verify created let residenceCard = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(name)'")).firstMatch return residenceCard.waitForExistence(timeout: 10) } /// Creates a task with the given title /// Returns true if successful @discardableResult private func createTask(title: String, description: String? = nil) -> Bool { navigateToTab("Tasks") sleep(2) let addButton = findAddTaskButton() guard addButton.waitForExistence(timeout: 5) && addButton.isEnabled else { XCTFail("Add task button not found or disabled") return false } addButton.tap() sleep(2) // Fill title let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch guard titleField.waitForExistence(timeout: 5) else { XCTFail("Title field not found") return false } titleField.tap() titleField.typeText(title) // Fill description if provided if let desc = description { let descField = app.textViews.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Description'")).firstMatch if descField.exists { descField.tap() descField.typeText(desc) } } app.swipeUp() sleep(1) // Save let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch guard saveButton.exists else { XCTFail("Save button not found") return false } saveButton.tap() sleep(3) // Verify created let taskCard = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(title)'")).firstMatch return taskCard.waitForExistence(timeout: 10) } private func fillTextField(placeholder: String, text: String) { let field = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] '\(placeholder)'")).firstMatch if field.exists { field.tap() field.typeText(text) } } private func findAddTaskButton() -> XCUIElement { // Strategy 1: Accessibility identifier let addButtonById = app.buttons[AccessibilityIdentifiers.Task.addButton].firstMatch if addButtonById.exists && addButtonById.isEnabled { return addButtonById } // Strategy 2: Navigation bar plus button let navBarButtons = app.navigationBars.buttons for i in 0..= 2 let hasListView = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks' OR label CONTAINS[c] 'All Tasks'")).firstMatch.exists XCTAssertTrue(hasKanbanView || hasListView, "Should display tasks in kanban or list view. Found columns: \(foundColumns)") } // MARK: - Test 7: Residence Details Show Tasks // Verifies that residence detail screen shows associated tasks func test07_residenceDetailsShowTasks() { navigateToTab("Residences") sleep(2) // Find any residence let residenceCard = app.cells.firstMatch guard residenceCard.waitForExistence(timeout: 5) else { // No residences - create one with a task createResidence(name: "Detail Test Residence \(Self.testRunId)") createTask(title: "Detail Test Task \(Self.testRunId)") navigateToTab("Residences") sleep(2) let newResidenceCard = app.cells.firstMatch guard newResidenceCard.waitForExistence(timeout: 5) else { XCTFail("Could not find any residence") return } newResidenceCard.tap() sleep(2) return } residenceCard.tap() sleep(2) // Look for tasks section in residence details let tasksSection = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks' OR label CONTAINS[c] 'Maintenance'")).firstMatch let taskCount = app.staticTexts.containing(NSPredicate(format: "label MATCHES '\\\\d+ tasks?' OR label MATCHES '\\\\d+ Tasks?'")).firstMatch // Either tasks section header or task count should be visible let hasTasksInfo = tasksSection.exists || taskCount.exists // Navigate back let backButton = app.navigationBars.buttons.element(boundBy: 0) if backButton.exists && backButton.isHittable { backButton.tap() sleep(1) } // Note: Not asserting because task section visibility depends on UI design } // MARK: - Test 8: Contractor CRUD (Mirrors backend contractor tests) func test08_contractorCRUD() { navigateToTab("Contractors") sleep(2) let contractorName = "E2E Test Contractor \(Self.testRunId)" // Check if Contractors tab exists let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch guard contractorsTab.exists else { // Contractors may not be a main tab - skip this test return } // Try to add contractor let addButton = app.buttons[AccessibilityIdentifiers.Contractor.addButton] guard addButton.waitForExistence(timeout: 5) else { // May need residence first return } addButton.tap() sleep(2) // Fill contractor form let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch if nameField.exists { nameField.tap() nameField.typeText(contractorName) let companyField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Company'")).firstMatch if companyField.exists { companyField.tap() companyField.typeText("Test Company Inc") } let phoneField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Phone'")).firstMatch if phoneField.exists { phoneField.tap() phoneField.typeText("555-123-4567") } app.swipeUp() sleep(1) let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch if saveButton.exists { saveButton.tap() sleep(3) // Verify contractor was created let contractorCard = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(contractorName)'")).firstMatch XCTAssertTrue(contractorCard.waitForExistence(timeout: 10), "Contractor '\(contractorName)' should be created") } } else { // Cancel if form didn't load properly let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch if cancelButton.exists { cancelButton.tap() } } } // MARK: - Test 9: Full Flow Summary func test09_fullFlowSummary() { // This test verifies the overall app state after running previous tests // Check Residences tab navigateToTab("Residences") sleep(2) let residencesList = app.cells let residenceCount = residencesList.count // Check Tasks tab navigateToTab("Tasks") sleep(2) let tasksScreen = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch XCTAssertTrue(tasksScreen.exists, "Tasks screen should be accessible") // Check Profile tab navigateToTab("Profile") sleep(2) let logoutButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout' OR label CONTAINS[c] 'Log Out'")).firstMatch XCTAssertTrue(logoutButton.exists, "User should be logged in with logout option available") print("=== E2E Test Summary ===") print("Residences found: \(residenceCount)") print("Tasks screen accessible: true") print("User logged in: true") print("========================") } }