Fix UI test failures: registration dismiss cascade, onboarding reset, test stability
- Fix registration flow dismiss cascade: chain fullScreenCover → sheet onDismiss so auth state is set only after all UIKit presentations are removed, preventing RootView from swapping LoginView→MainTabView behind a stale sheet - Fix onboarding reset: set hasCompletedOnboarding directly instead of calling completeOnboarding() which has an auth guard that fails after DataManager.clear() - Stabilize Suite1 registration tests, Suite6 task tests, Suite7 contractor tests - Add clean-slate-per-suite via AuthenticatedUITestCase reset state - Improve test account seeding and screen object reliability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,8 +15,6 @@ class AuthenticatedUITestCase: BaseUITestCase {
|
||||
("admin", "test1234")
|
||||
}
|
||||
|
||||
override var includeResetStateLaunchArgument: Bool { false }
|
||||
|
||||
// MARK: - API Session
|
||||
|
||||
private(set) var session: TestSession!
|
||||
@@ -24,11 +22,21 @@ class AuthenticatedUITestCase: BaseUITestCase {
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override class func setUp() {
|
||||
super.setUp()
|
||||
guard TestAccountAPIClient.isBackendReachable() else { return }
|
||||
// Ensure both known test accounts exist (covers all subclass credential overrides)
|
||||
if TestAccountAPIClient.login(username: "testuser", password: "TestPass123!") == nil {
|
||||
_ = TestAccountAPIClient.createVerifiedAccount(username: "testuser", email: "testuser@honeydue.com", password: "TestPass123!")
|
||||
}
|
||||
if TestAccountAPIClient.login(username: "admin", password: "test1234") == nil {
|
||||
_ = TestAccountAPIClient.createVerifiedAccount(username: "admin", email: "admin@honeydue.com", password: "test1234")
|
||||
}
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
if needsAPISession {
|
||||
guard TestAccountAPIClient.isBackendReachable() else {
|
||||
throw XCTSkip("Backend not reachable at \(TestAccountAPIClient.baseURL)")
|
||||
}
|
||||
guard TestAccountAPIClient.isBackendReachable() else {
|
||||
throw XCTSkip("Backend not reachable at \(TestAccountAPIClient.baseURL)")
|
||||
}
|
||||
|
||||
try super.setUpWithError()
|
||||
|
||||
@@ -191,19 +191,35 @@ extension XCUIElement {
|
||||
// SecureTextFields may trigger iOS strong password suggestion dialog
|
||||
// which blocks the regular keyboard. Handle them with a dedicated path.
|
||||
if elementType == .secureTextField {
|
||||
// Dismiss any open keyboard first — iOS 26 fails to transfer focus
|
||||
// from a TextField to a SecureTextField if the keyboard is already up.
|
||||
if app.keyboards.firstMatch.exists {
|
||||
let navBar = app.navigationBars.firstMatch
|
||||
if navBar.exists {
|
||||
navBar.tap()
|
||||
}
|
||||
_ = app.keyboards.firstMatch.waitForNonExistence(timeout: 2)
|
||||
}
|
||||
|
||||
tap()
|
||||
// Dismiss "Choose My Own Password" or "Not Now" if iOS suggests a strong password
|
||||
let chooseOwn = app.buttons["Choose My Own Password"]
|
||||
if chooseOwn.waitForExistence(timeout: 1) {
|
||||
if chooseOwn.waitForExistence(timeout: 0.5) {
|
||||
chooseOwn.tap()
|
||||
} else {
|
||||
let notNow = app.buttons["Not Now"]
|
||||
if notNow.exists && notNow.isHittable { notNow.tap() }
|
||||
}
|
||||
if app.keyboards.firstMatch.waitForExistence(timeout: 2) {
|
||||
// Wait for keyboard after tapping SecureTextField
|
||||
if !app.keyboards.firstMatch.waitForExistence(timeout: 5) {
|
||||
// Retry tap — first tap may not have acquired focus
|
||||
tap()
|
||||
_ = app.keyboards.firstMatch.waitForExistence(timeout: 3)
|
||||
}
|
||||
if app.keyboards.firstMatch.exists {
|
||||
typeText(text)
|
||||
} else {
|
||||
app.typeText(text)
|
||||
XCTFail("Keyboard did not appear after tapping SecureTextField: \(self)", file: file, line: line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -257,8 +257,6 @@ struct RegisterScreenObject {
|
||||
|
||||
private var usernameField: XCUIElement { app.textFields[UITestID.Auth.registerUsernameField] }
|
||||
private var emailField: XCUIElement { app.textFields[UITestID.Auth.registerEmailField] }
|
||||
private var passwordField: XCUIElement { app.secureTextFields[UITestID.Auth.registerPasswordField] }
|
||||
private var confirmPasswordField: XCUIElement { app.secureTextFields[UITestID.Auth.registerConfirmPasswordField] }
|
||||
private var registerButton: XCUIElement { app.buttons[UITestID.Auth.registerButton] }
|
||||
private var cancelButton: XCUIElement { app.buttons[UITestID.Auth.registerCancelButton] }
|
||||
|
||||
@@ -268,30 +266,32 @@ struct RegisterScreenObject {
|
||||
}
|
||||
|
||||
func fill(username: String, email: String, password: String) {
|
||||
func advanceToNextField() {
|
||||
let keys = ["Next", "Return", "return", "Done", "done"]
|
||||
for key in keys {
|
||||
let button = app.keyboards.buttons[key]
|
||||
if button.waitForExistence(timeout: 1) && button.isHittable {
|
||||
button.tap()
|
||||
return
|
||||
}
|
||||
// iOS 26 bug: SecureTextField won't gain keyboard focus when tapped directly.
|
||||
// Workaround: toggle password visibility first to convert SecureField → TextField,
|
||||
// then use focusAndType() on all regular TextFields.
|
||||
usernameField.waitForExistenceOrFail(timeout: 10)
|
||||
|
||||
// Scroll down to reveal the password toggle buttons (they're below the fold)
|
||||
let scrollView = app.scrollViews.firstMatch
|
||||
if scrollView.exists { scrollView.swipeUp() }
|
||||
|
||||
// Toggle both password visibility buttons (converts SecureField → TextField)
|
||||
let toggleButtons = app.buttons.matching(NSPredicate(format: "label == 'Toggle password visibility'"))
|
||||
for i in 0..<toggleButtons.count {
|
||||
let toggle = toggleButtons.element(boundBy: i)
|
||||
if toggle.exists && toggle.isHittable {
|
||||
toggle.tap()
|
||||
}
|
||||
}
|
||||
|
||||
usernameField.waitForExistenceOrFail(timeout: 10)
|
||||
// After toggling, password fields are regular TextFields.
|
||||
// Don't swipeDown — it dismisses the sheet. focusAndType() auto-scrolls via tap().
|
||||
let passwordField = app.textFields[UITestID.Auth.registerPasswordField]
|
||||
let confirmPasswordField = app.textFields[UITestID.Auth.registerConfirmPasswordField]
|
||||
|
||||
usernameField.focusAndType(username, app: app)
|
||||
advanceToNextField()
|
||||
|
||||
emailField.waitForExistenceOrFail(timeout: 10)
|
||||
emailField.focusAndType(email, app: app)
|
||||
advanceToNextField()
|
||||
|
||||
passwordField.waitForExistenceOrFail(timeout: 10)
|
||||
passwordField.focusAndType(password, app: app)
|
||||
advanceToNextField()
|
||||
|
||||
confirmPasswordField.waitForExistenceOrFail(timeout: 10)
|
||||
confirmPasswordField.focusAndType(password, app: app)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user