Stabilize UI test suite — 39% → 98%+ pass rate
Fix root causes uncovered across repeated parallel runs: - Admin seed password "test1234" failed backend complexity (needs uppercase). Bumped to "Test1234" across every hard-coded reference (AuthenticatedUITestCase default, TestAccountManager seeded-login default, Tests/*Integration suites, Tests/DataLayer, OnboardingTests). - dismissKeyboard() tapped the Return key first, which races SwiftUI's TextField binding on numeric keyboards (postal, year built) and complex forms. KeyboardDismisser now prefers the keyboard-toolbar Done button, falls back to tap-above-keyboard, then keyboard Return. BaseUITestCase.clearAndEnterText uses the same helper. - Form page-object save() helpers (task / residence / contractor / document) now dismiss the keyboard and scroll the submit button into view before tapping, eliminating Suite4/6/7/8 "save button stayed visible" timeouts. - Suite6 createTask was producing a disabled-save race: under parallel contention the SwiftUI title binding lagged behind XCUITest typing. Rewritten to inline Suite5's proven pattern with a retry that nudges the title binding via a no-op edit when Add is disabled, and an explicit refreshTasks after creation. - Suite8 selectProperty now picks the residence by name (works with menu, list, or wheel picker variants) — avoids bad form-cell taps when the picker hasn't fully rendered. - run_ui_tests.sh uses 2 workers instead of 4 (4-worker contention caused XCUITest typing races across Suite5/7/8) and isolates Suite6 in its own 2-worker phase after the main parallel phase. - Add AAA_SeedTests / SuiteZZ_CleanupTests: the runner's Phase 1 (seed) and Phase 3 (cleanup) depend on these and they were missing from version control.
This commit is contained in:
@@ -107,15 +107,52 @@ final class Suite6_ComprehensiveTaskTests: AuthenticatedUITestCase {
|
||||
description: String? = nil,
|
||||
scrollToFindFields: Bool = true
|
||||
) -> Bool {
|
||||
guard openTaskForm() else { return false }
|
||||
// Mirror Suite5's proven-working inline flow to avoid page-object drift.
|
||||
// Page-object `save()` was producing a disabled-save race where the form
|
||||
// stayed open; this sequence matches the one that consistently passes.
|
||||
let addButton = app.buttons[AccessibilityIdentifiers.Task.addButton].firstMatch
|
||||
guard addButton.waitForExistence(timeout: defaultTimeout) && addButton.isEnabled else { return false }
|
||||
addButton.tap()
|
||||
|
||||
taskForm.enterTitle(title)
|
||||
let titleField = app.textFields[AccessibilityIdentifiers.Task.titleField].firstMatch
|
||||
guard titleField.waitForExistence(timeout: defaultTimeout) else { return false }
|
||||
fillTextField(identifier: AccessibilityIdentifiers.Task.titleField, text: title)
|
||||
|
||||
if let desc = description {
|
||||
taskForm.enterDescription(desc)
|
||||
dismissKeyboard()
|
||||
app.swipeUp()
|
||||
let descField = app.textViews[AccessibilityIdentifiers.Task.descriptionField].firstMatch
|
||||
if descField.waitForExistence(timeout: 5) {
|
||||
descField.focusAndType(desc, app: app)
|
||||
}
|
||||
}
|
||||
|
||||
taskForm.save()
|
||||
dismissKeyboard()
|
||||
app.swipeUp()
|
||||
|
||||
let saveButton = app.buttons[AccessibilityIdentifiers.Task.saveButton].firstMatch
|
||||
guard saveButton.waitForExistence(timeout: defaultTimeout) else { return false }
|
||||
|
||||
saveButton.tap()
|
||||
|
||||
// If the first tap is a no-op (canSave=false because SwiftUI's title
|
||||
// binding hasn't caught up with XCUITest typing under parallel load),
|
||||
// nudge the form so the binding flushes, then re-tap. Up to 2 retries.
|
||||
if !saveButton.waitForNonExistence(timeout: navigationTimeout) {
|
||||
for _ in 0..<2 {
|
||||
let stillOpenTitle = app.textFields[AccessibilityIdentifiers.Task.titleField].firstMatch
|
||||
if stillOpenTitle.exists && stillOpenTitle.isHittable {
|
||||
stillOpenTitle.tap()
|
||||
_ = app.keyboards.firstMatch.waitForExistence(timeout: 2)
|
||||
app.typeText(" ")
|
||||
app.typeText(XCUIKeyboardKey.delete.rawValue)
|
||||
dismissKeyboard()
|
||||
app.swipeUp()
|
||||
}
|
||||
saveButton.tap()
|
||||
if saveButton.waitForNonExistence(timeout: navigationTimeout) { break }
|
||||
}
|
||||
}
|
||||
|
||||
createdTaskTitles.append(title)
|
||||
|
||||
@@ -125,8 +162,11 @@ final class Suite6_ComprehensiveTaskTests: AuthenticatedUITestCase {
|
||||
cleaner.trackTask(created.id)
|
||||
}
|
||||
|
||||
// Navigate to tasks tab to trigger list refresh and reset scroll position
|
||||
// Navigate to tasks tab to trigger list refresh and reset scroll position.
|
||||
// Explicit refresh catches cases where the kanban list lags behind the
|
||||
// just-created task (matches Suite5's proven pattern).
|
||||
navigateToTasks()
|
||||
refreshTasks()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user