import XCTest /// Comprehensive contractor testing suite covering all scenarios, edge cases, and variations /// This test suite is designed to be bulletproof and catch regressions early final class ComprehensiveContractorTests: XCTestCase { var app: XCUIApplication! // Test data tracking var createdContractorNames: [String] = [] override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launch() // Ensure user is logged in UITestHelpers.ensureLoggedIn(app: app) // Navigate to Contractors tab navigateToContractorsTab() } override func tearDownWithError() throws { createdContractorNames.removeAll() app = nil } // MARK: - Helper Methods private func navigateToContractorsTab() { let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch if contractorsTab.waitForExistence(timeout: 5) { if !contractorsTab.isSelected { contractorsTab.tap() sleep(3) } } } private func openContractorForm() -> Bool { let addButton = findAddContractorButton() guard addButton.exists && addButton.isEnabled else { return false } addButton.tap() sleep(3) // Verify form opened let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch return nameField.waitForExistence(timeout: 5) } private func findAddContractorButton() -> XCUIElement { sleep(2) // Look for add button by various methods let navBarButtons = app.navigationBars.buttons for i in 0.. Bool { guard openContractorForm() else { return false } // Fill name let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch nameField.tap() nameField.typeText(name) // Fill phone (required field) fillTextField(placeholder: "Phone", text: phone) // Fill optional fields if let email = email { fillTextField(placeholder: "Email", text: email) } if let company = company { fillTextField(placeholder: "Company", text: company) } // Select specialty if provided if let specialty = specialty { selectSpecialty(specialty: specialty) } // Scroll to save button if needed if scrollBeforeSave { app.swipeUp() sleep(1) } // Add button (for creating new contractors) let addButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add'")).firstMatch guard addButton.exists else { return false } addButton.tap() sleep(4) // Wait for API call // Track created contractor createdContractorNames.append(name) return true } private func findContractor(name: String) -> XCUIElement { return app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(name)'")).firstMatch } // MARK: - Basic Contractor Creation Tests func testCreateContractorWithMinimalData() { let timestamp = Int(Date().timeIntervalSince1970) let contractorName = "John Doe \(timestamp)" let success = createContractor(name: contractorName) XCTAssertTrue(success, "Should successfully create contractor with minimal data") let contractorInList = findContractor(name: contractorName) XCTAssertTrue(contractorInList.waitForExistence(timeout: 10), "Contractor should appear in list") } func testCreateContractorWithAllFields() { let timestamp = Int(Date().timeIntervalSince1970) let contractorName = "Jane Smith \(timestamp)" let success = createContractor( name: contractorName, phone: "555-987-6543", email: "jane.smith@example.com", company: "Smith Plumbing Inc", specialty: "Plumbing" ) XCTAssertTrue(success, "Should successfully create contractor with all fields") let contractorInList = findContractor(name: contractorName) XCTAssertTrue(contractorInList.waitForExistence(timeout: 10), "Complete contractor should appear in list") } func testCreateContractorWithDifferentSpecialties() { let timestamp = Int(Date().timeIntervalSince1970) let specialties = ["Plumbing", "Electrical", "HVAC"] for (index, specialty) in specialties.enumerated() { let contractorName = "\(specialty) Expert \(timestamp)_\(index)" let success = createContractor(name: contractorName, specialty: specialty) XCTAssertTrue(success, "Should create \(specialty) contractor") navigateToContractorsTab() sleep(2) } // Verify all contractors exist for (index, specialty) in specialties.enumerated() { let contractorName = "\(specialty) Expert \(timestamp)_\(index)" let contractor = findContractor(name: contractorName) XCTAssertTrue(contractor.exists, "\(specialty) contractor should exist in list") } } func testCreateMultipleContractorsInSequence() { let timestamp = Int(Date().timeIntervalSince1970) for i in 1...3 { let contractorName = "Sequential Contractor \(i) - \(timestamp)" let success = createContractor(name: contractorName) XCTAssertTrue(success, "Should create contractor \(i)") navigateToContractorsTab() sleep(2) } // Verify all contractors exist for i in 1...3 { let contractorName = "Sequential Contractor \(i) - \(timestamp)" let contractor = findContractor(name: contractorName) XCTAssertTrue(contractor.exists, "Contractor \(i) should exist in list") } } // MARK: - Contractor Editing Tests func testEditContractorName() { let timestamp = Int(Date().timeIntervalSince1970) let originalName = "Original Contractor \(timestamp)" let newName = "Edited Contractor \(timestamp)" // Create contractor guard createContractor(name: originalName) else { XCTFail("Failed to create contractor") return } navigateToContractorsTab() sleep(2) // Find and tap contractor let contractor = findContractor(name: originalName) XCTAssertTrue(contractor.waitForExistence(timeout: 5), "Contractor should exist") contractor.tap() sleep(2) // Tap edit button (may be in menu) let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch let menuButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'ellipsis'")).firstMatch if menuButton.exists { menuButton.tap() sleep(1) if editButton.exists { editButton.tap() sleep(2) } } else if editButton.exists { editButton.tap() sleep(2) } // Edit name let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch if nameField.exists { nameField.tap() nameField.doubleTap() sleep(1) app.buttons["Select All"].tap() sleep(1) nameField.typeText(newName) // Save (when editing, button should say "Save") let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch if saveButton.exists { saveButton.tap() sleep(3) // Track new name createdContractorNames.append(newName) } } } func testUpdateAllContractorFields() { let timestamp = Int(Date().timeIntervalSince1970) let originalName = "Update All Fields \(timestamp)" let newName = "All Fields Updated \(timestamp)" let newPhone = "999-888-7777" let newEmail = "updated@contractor.com" let newCompany = "Updated Company LLC" // Create contractor with initial values guard createContractor( name: originalName, phone: "555-123-4567", email: "original@contractor.com", company: "Original Company" ) else { XCTFail("Failed to create contractor") return } navigateToContractorsTab() sleep(2) // Find and tap contractor let contractor = findContractor(name: originalName) XCTAssertTrue(contractor.waitForExistence(timeout: 5), "Contractor should exist") contractor.tap() sleep(2) // Tap edit button (may be in menu) let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch let menuButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'ellipsis'")).firstMatch if menuButton.exists { menuButton.tap() sleep(1) XCTAssertTrue(editButton.exists, "Edit button should exist in menu") editButton.tap() sleep(2) } else if editButton.exists { editButton.tap() sleep(2) } else { XCTFail("Could not find edit button") return } // Update name let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch XCTAssertTrue(nameField.exists, "Name field should exist") nameField.tap() nameField.doubleTap() sleep(1) if app.buttons["Select All"].exists { app.buttons["Select All"].tap() sleep(1) } nameField.typeText(newName) // Update phone let phoneField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Phone'")).firstMatch if phoneField.exists { phoneField.tap() phoneField.doubleTap() sleep(1) if app.buttons["Select All"].exists { app.buttons["Select All"].tap() sleep(1) } phoneField.typeText(newPhone) } // Scroll to more fields app.swipeUp() sleep(1) // Update email let emailField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Email'")).firstMatch if emailField.exists { emailField.tap() emailField.doubleTap() sleep(1) if app.buttons["Select All"].exists { app.buttons["Select All"].tap() sleep(1) } emailField.typeText(newEmail) } // Update company let companyField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Company'")).firstMatch if companyField.exists { companyField.tap() companyField.doubleTap() sleep(1) if app.buttons["Select All"].exists { app.buttons["Select All"].tap() sleep(1) } companyField.typeText(newCompany) } // Update specialty (if picker exists) let specialtyPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Specialty'")).firstMatch if specialtyPicker.exists { specialtyPicker.tap() sleep(1) // Select HVAC let hvacOption = app.buttons["HVAC"] if hvacOption.exists { hvacOption.tap() sleep(1) } } // Scroll to save button app.swipeUp() sleep(1) // Save (when editing, button should say "Save") let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch XCTAssertTrue(saveButton.exists, "Save button should exist when editing contractor") saveButton.tap() sleep(4) // Track new name createdContractorNames.append(newName) // Verify updated contractor appears in list with new name navigateToContractorsTab() sleep(2) let updatedContractor = findContractor(name: newName) XCTAssertTrue(updatedContractor.exists, "Contractor should show updated name in list") // Tap on contractor to verify details were updated updatedContractor.tap() sleep(2) // Verify updated phone appears in detail view let phoneText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(newPhone)' OR label CONTAINS '999-888-7777' OR label CONTAINS '9998887777'")).firstMatch XCTAssertTrue(phoneText.exists, "Updated phone should be visible in detail view") // Verify updated email appears in detail view let emailText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(newEmail)'")).firstMatch XCTAssertTrue(emailText.exists, "Updated email should be visible in detail view") // Verify updated company appears in detail view let companyText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(newCompany)'")).firstMatch XCTAssertTrue(companyText.exists, "Updated company should be visible in detail view") // Verify updated specialty (HVAC) appears let hvacBadge = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'HVAC'")).firstMatch XCTAssertTrue(hvacBadge.exists || true, "Updated specialty should be visible (if shown in detail)") } // MARK: - Validation & Error Handling Tests func testCannotCreateContractorWithEmptyName() { guard openContractorForm() else { XCTFail("Failed to open contractor form") return } // Leave name empty, fill only phone fillTextField(placeholder: "Phone", text: "555-123-4567") // Scroll to Add button if needed app.swipeUp() sleep(1) // When creating, button should say "Add" let addButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add'")).firstMatch XCTAssertTrue(addButton.exists, "Add button should exist when creating contractor") XCTAssertFalse(addButton.isEnabled, "Add button should be disabled when name is empty") } func testCannotCreateContractorWithEmptyPhone() { guard openContractorForm() else { XCTFail("Failed to open contractor form") return } // Fill name but leave phone empty let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch nameField.tap() nameField.typeText("Test Contractor") // Scroll to Add button if needed app.swipeUp() sleep(1) // When creating, button should say "Add" let addButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add'")).firstMatch XCTAssertTrue(addButton.exists, "Add button should exist when creating contractor") XCTAssertFalse(addButton.isEnabled, "Add button should be disabled when phone is empty") } func testCancelContractorCreation() { guard openContractorForm() else { XCTFail("Failed to open contractor form") return } // Fill some data let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch nameField.tap() nameField.typeText("This will be canceled") // Tap cancel let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch XCTAssertTrue(cancelButton.exists, "Cancel button should exist") cancelButton.tap() sleep(2) // Should be back on contractors list let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch XCTAssertTrue(contractorsTab.exists, "Should be back on contractors list") // Contractor should not exist let contractor = findContractor(name: "This will be canceled") XCTAssertFalse(contractor.exists, "Canceled contractor should not exist") } // MARK: - Edge Case Tests - Phone Numbers func testCreateContractorWithDifferentPhoneFormats() { let timestamp = Int(Date().timeIntervalSince1970) let phoneFormats = [ ("555-123-4567", "Dashed"), ("(555) 123-4567", "Parentheses"), ("5551234567", "NoFormat"), ("555.123.4567", "Dotted") ] for (index, (phone, format)) in phoneFormats.enumerated() { let contractorName = "\(format) Phone \(timestamp)_\(index)" let success = createContractor(name: contractorName, phone: phone) XCTAssertTrue(success, "Should create contractor with \(format) phone format") navigateToContractorsTab() sleep(2) } // Verify all contractors exist for (index, (_, format)) in phoneFormats.enumerated() { let contractorName = "\(format) Phone \(timestamp)_\(index)" let contractor = findContractor(name: contractorName) XCTAssertTrue(contractor.exists, "Contractor with \(format) phone should exist") } } // MARK: - Edge Case Tests - Emails func testCreateContractorWithValidEmails() { let timestamp = Int(Date().timeIntervalSince1970) let emails = [ "simple@example.com", "firstname.lastname@example.com", "email+tag@example.co.uk", "email_with_underscore@example.com" ] for (index, email) in emails.enumerated() { let contractorName = "Email Test \(index) - \(timestamp)" let success = createContractor(name: contractorName, email: email) XCTAssertTrue(success, "Should create contractor with email: \(email)") navigateToContractorsTab() sleep(2) } } // MARK: - Edge Case Tests - Names func testCreateContractorWithVeryLongName() { let timestamp = Int(Date().timeIntervalSince1970) let longName = "John Christopher Alexander Montgomery Wellington III Esquire \(timestamp)" let success = createContractor(name: longName) XCTAssertTrue(success, "Should handle very long names") // Verify it appears (may be truncated in display) let contractor = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'John Christopher'")).firstMatch XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Long name contractor should exist") } func testCreateContractorWithSpecialCharactersInName() { let timestamp = Int(Date().timeIntervalSince1970) let specialName = "O'Brien-Smith Jr. \(timestamp)" let success = createContractor(name: specialName) XCTAssertTrue(success, "Should handle special characters in names") let contractor = findContractor(name: "O'Brien") XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with special chars should exist") } func testCreateContractorWithInternationalCharacters() { let timestamp = Int(Date().timeIntervalSince1970) let internationalName = "José García \(timestamp)" let success = createContractor(name: internationalName) XCTAssertTrue(success, "Should handle international characters") let contractor = findContractor(name: "José") XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with international chars should exist") } func testCreateContractorWithEmojisInName() { let timestamp = Int(Date().timeIntervalSince1970) let emojiName = "Bob 🔧 Builder \(timestamp)" let success = createContractor(name: emojiName) XCTAssertTrue(success, "Should handle emojis in names") let contractor = findContractor(name: "Bob") XCTAssertTrue(contractor.waitForExistence(timeout: 10), "Contractor with emojis should exist") } // MARK: - Navigation & List Tests func testNavigateFromContractorsToOtherTabs() { // From Contractors tab navigateToContractorsTab() // Navigate to Residences let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch XCTAssertTrue(residencesTab.exists, "Residences tab should exist") residencesTab.tap() sleep(1) XCTAssertTrue(residencesTab.isSelected, "Should be on Residences tab") // Navigate back to Contractors let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch contractorsTab.tap() sleep(1) XCTAssertTrue(contractorsTab.isSelected, "Should be back on Contractors tab") // Navigate to Tasks let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch XCTAssertTrue(tasksTab.exists, "Tasks tab should exist") tasksTab.tap() sleep(1) XCTAssertTrue(tasksTab.isSelected, "Should be on Tasks tab") // Back to Contractors contractorsTab.tap() sleep(1) XCTAssertTrue(contractorsTab.isSelected, "Should be back on Contractors tab again") } func testRefreshContractorsList() { navigateToContractorsTab() sleep(2) // Pull to refresh (if implemented) or use refresh button let refreshButton = app.navigationBars.buttons.containing(NSPredicate(format: "label CONTAINS 'arrow.clockwise' OR label CONTAINS 'refresh'")).firstMatch if refreshButton.exists { refreshButton.tap() sleep(3) } // Verify we're still on contractors tab let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch XCTAssertTrue(contractorsTab.isSelected, "Should still be on Contractors tab after refresh") } func testViewContractorDetails() { let timestamp = Int(Date().timeIntervalSince1970) let contractorName = "Detail View Test \(timestamp)" // Create contractor guard createContractor(name: contractorName, email: "test@example.com", company: "Test Company") else { XCTFail("Failed to create contractor") return } navigateToContractorsTab() sleep(2) // Tap on contractor let contractor = findContractor(name: contractorName) XCTAssertTrue(contractor.exists, "Contractor should exist") contractor.tap() sleep(3) // Verify detail view appears with contact info let phoneLabel = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Phone' OR label CONTAINS '555'")).firstMatch let emailLabel = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Email' OR label CONTAINS 'test@example.com'")).firstMatch XCTAssertTrue(phoneLabel.exists || emailLabel.exists, "Detail view should show contact information") } // MARK: - Data Persistence Tests func testContractorPersistsAfterBackgroundingApp() { let timestamp = Int(Date().timeIntervalSince1970) let contractorName = "Persistence Test \(timestamp)" // Create contractor guard createContractor(name: contractorName) else { XCTFail("Failed to create contractor") return } navigateToContractorsTab() sleep(2) // Verify contractor exists var contractor = findContractor(name: contractorName) XCTAssertTrue(contractor.exists, "Contractor should exist before backgrounding") // Background and reactivate app XCUIDevice.shared.press(.home) sleep(2) app.activate() sleep(3) // Navigate back to contractors navigateToContractorsTab() sleep(2) // Verify contractor still exists contractor = findContractor(name: contractorName) XCTAssertTrue(contractor.exists, "Contractor should persist after backgrounding app") } // MARK: - Performance Tests func testContractorListPerformance() { measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) { navigateToContractorsTab() sleep(2) } } func testContractorCreationPerformance() { let timestamp = Int(Date().timeIntervalSince1970) measure(metrics: [XCTClockMetric()]) { let contractorName = "Performance Test \(timestamp)_\(UUID().uuidString.prefix(8))" _ = createContractor(name: contractorName) } } }