import XCTest /// Onboarding flow tests /// /// SETUP REQUIREMENTS: /// This test suite requires the app to be UNINSTALLED before running. /// Add a Pre-action script to the CaseraUITests scheme (Edit Scheme → Test → Pre-actions): /// /usr/bin/xcrun simctl uninstall booted com.tt.casera.CaseraDev /// exit 0 /// /// There is ONE fresh-install test that runs the complete onboarding flow. /// Additional tests for returning users (login screen) can run without fresh install. final class Suite0_OnboardingTests: BaseUITestCase { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") override func setUpWithError() throws { try super.setUpWithError() sleep(2) } override func tearDownWithError() throws { app.terminate() try super.tearDownWithError() } private func typeText(_ text: String, into field: XCUIElement) { field.waitForExistenceOrFail(timeout: 10) for _ in 0..<3 { if !field.isHittable { app.swipeUp() } field.forceTap() if !field.hasKeyboardFocus { field.coordinate(withNormalizedOffset: CGVector(dx: 0.8, dy: 0.5)).tap() } if !field.hasKeyboardFocus { continue } app.typeText(text) if let value = field.value as? String { if value.contains(text) || value.count >= text.count { return } } } XCTFail("Unable to enter text into \(field)") } private func dismissStrongPasswordSuggestionIfPresent() { 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() } } private func focusField(_ field: XCUIElement, name: String) { field.waitForExistenceOrFail(timeout: 10) for _ in 0..<4 { if field.hasKeyboardFocus { return } field.forceTap() if field.hasKeyboardFocus { return } field.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5)).tap() if field.hasKeyboardFocus { return } } XCTFail("Failed to focus \(name) field") } func test_onboarding() { app.activate() sleep(2) let springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard") let allowButton = springboardApp.buttons["Allow"].firstMatch if allowButton.waitForExistence(timeout: 2) { allowButton.tap() } let welcome = OnboardingWelcomeScreen(app: app) welcome.waitForLoad() welcome.tapStartFresh() let valuePropsTitle = app.descendants(matching: .any).matching(identifier: AccessibilityIdentifiers.Onboarding.valuePropsTitle).firstMatch if valuePropsTitle.waitForExistence(timeout: 5) { let valueProps = OnboardingValuePropsScreen(app: app) valueProps.tapContinue() } let nameResidenceTitle = app.descendants(matching: .any).matching(identifier: AccessibilityIdentifiers.Onboarding.nameResidenceTitle).firstMatch if nameResidenceTitle.waitForExistence(timeout: 5) { let residenceField = app.textFields[AccessibilityIdentifiers.Onboarding.residenceNameField] residenceField.waitUntilHittable(timeout: 8).tap() residenceField.typeText("xcuitest") app.descendants(matching: .any).matching(identifier: AccessibilityIdentifiers.Onboarding.nameResidenceContinueButton).firstMatch.waitUntilHittable(timeout: 8).tap() } let emailExpandButton = app.buttons[AccessibilityIdentifiers.Onboarding.emailSignUpExpandButton].firstMatch if emailExpandButton.waitForExistence(timeout: 10) && emailExpandButton.isHittable { emailExpandButton.tap() } let unique = Int(Date().timeIntervalSince1970) let onboardingUsername = "xcuitest\(unique)" let onboardingEmail = "xcuitest_\(unique)@treymail.com" let usernameField = app.textFields[AccessibilityIdentifiers.Onboarding.usernameField].firstMatch focusField(usernameField, name: "username") usernameField.typeText(onboardingUsername) XCTAssertTrue((usernameField.value as? String)?.contains(onboardingUsername) == true, "Username should be populated") let emailField = app.textFields[AccessibilityIdentifiers.Onboarding.emailField].firstMatch emailField.waitForExistenceOrFail(timeout: 10) var didEnterEmail = false for _ in 0..<5 { app.swipeUp() emailField.forceTap() if emailField.hasKeyboardFocus { emailField.typeText(onboardingEmail) didEnterEmail = true break } } XCTAssertTrue(didEnterEmail, "Email field must become focused for typing") let strongPassword = "TestPass123!" let passwordField = app.secureTextFields[AccessibilityIdentifiers.Onboarding.passwordField].firstMatch dismissStrongPasswordSuggestionIfPresent() focusField(passwordField, name: "password") passwordField.typeText(strongPassword) XCTAssertFalse((passwordField.value as? String)?.isEmpty ?? true, "Password should be populated") let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Onboarding.confirmPasswordField].firstMatch dismissStrongPasswordSuggestionIfPresent() if !confirmPasswordField.hasKeyboardFocus { app.swipeUp() focusField(confirmPasswordField, name: "confirm password") } confirmPasswordField.typeText(strongPassword) let createAccountButtonByID = app.buttons[AccessibilityIdentifiers.Onboarding.createAccountButton] let createAccountButtonByLabel = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Create Account'")).firstMatch let createAccountButton = createAccountButtonByID.exists ? createAccountButtonByID : createAccountButtonByLabel createAccountButton.waitForExistenceOrFail(timeout: 10) if !createAccountButton.isHittable { app.swipeUp() sleep(1) } if !createAccountButton.isEnabled { // Retry confirm-password input once when validation hasn't propagated. let confirmPasswordField = app.secureTextFields[AccessibilityIdentifiers.Onboarding.confirmPasswordField].firstMatch if confirmPasswordField.waitForExistence(timeout: 3) { focusField(confirmPasswordField, name: "confirm password retry") confirmPasswordField.typeText(strongPassword) } sleep(1) } XCTAssertTrue(createAccountButton.isEnabled, "Create account button should be enabled after valid form entry") createAccountButton.forceTap() let verifyCodeField = app.textFields[AccessibilityIdentifiers.Onboarding.verificationCodeField] verifyCodeField.waitForExistenceOrFail(timeout: 12) verifyCodeField.forceTap() app.typeText("123456") let verifyButtonByID = app.buttons[AccessibilityIdentifiers.Onboarding.verifyButton] let verifyButtonByLabel = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")).firstMatch let verifyButton = verifyButtonByID.exists ? verifyButtonByID : verifyButtonByLabel verifyButton.waitForExistenceOrFail(timeout: 10) if !verifyButton.isHittable { app.swipeUp() sleep(1) } verifyButton.forceTap() let addPopular = app.buttons[AccessibilityIdentifiers.Onboarding.addPopularTasksButton].firstMatch if addPopular.waitForExistence(timeout: 10) { addPopular.tap() } else { app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add Most Popular'")).firstMatch.tap() } let addTasksContinue = app.buttons[AccessibilityIdentifiers.Onboarding.addTasksContinueButton].firstMatch if addTasksContinue.waitForExistence(timeout: 10) { addTasksContinue.tap() } else { app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks & Continue'")).firstMatch.tap() } let continueWithFree = app.buttons[AccessibilityIdentifiers.Onboarding.continueWithFreeButton].firstMatch if continueWithFree.waitForExistence(timeout: 10) { continueWithFree.tap() } else { app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Continue with Free'")).firstMatch.tap() } let residencesHeader = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Your Properties' OR label CONTAINS[c] 'My Properties' OR label CONTAINS[c] 'Residences'")).firstMatch XCTAssertTrue(residencesHeader.waitForExistence(timeout: 5), "Residences list screen must be visible") let xcuitestResidence = app.staticTexts["xcuitest"].waitForExistence(timeout: 10) XCTAssertTrue(xcuitestResidence, "Residence should appear in list") app/*@START_MENU_TOKEN@*/.images["checkmark.circle.fill"]/*[[".buttons[\"checkmark.circle.fill\"].images",".buttons",".images[\"selected\"]",".images[\"checkmark.circle.fill\"]"],[[[-1,3],[-1,2],[-1,1,1],[-1,0]],[[-1,3],[-1,2]]],[0]]@END_MENU_TOKEN@*/.firstMatch.tap() let taskOne = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "HVAC")).firstMatch XCTAssertTrue(taskOne.waitForExistence(timeout: 10), "HVAC task should appear in list") let taskTwo = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "Leaks")).firstMatch XCTAssertTrue(taskTwo.waitForExistence(timeout: 10), "Leaks task should appear in list") let taskThree = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] %@", "Coils")).firstMatch XCTAssertTrue(taskThree.waitForExistence(timeout: 10), "Coils task should appear in list") // Try profile tab logout let profileTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Profile'")).firstMatch if profileTab.exists && profileTab.isHittable { profileTab.tap() let logoutButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout' OR label CONTAINS[c] 'Log Out'")).firstMatch if logoutButton.waitForExistence(timeout: 3) && logoutButton.isHittable { logoutButton.tap() // Handle confirmation alert let alertLogout = app.alerts.buttons["Log Out"] if alertLogout.waitForExistence(timeout: 2) { alertLogout.tap() } } } // Try verification screen logout let verifyLogout = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Logout'")).firstMatch if verifyLogout.exists && verifyLogout.isHittable { verifyLogout.tap() } // Wait for login screen _ = app.textFields[AccessibilityIdentifiers.Authentication.usernameField].waitForExistence(timeout: 8) } }