Refactor iOS UI tests to blueprint architecture
This commit is contained in:
@@ -1,8 +1,247 @@
|
||||
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 {
|
||||
func testSuite0_StartFreshToCreateAccount() {
|
||||
let createAccount = TestFlows.navigateStartFreshToCreateAccount(app: app, residenceName: "Suite0 House")
|
||||
createAccount.waitForLoad(timeout: defaultTimeout)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user