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:
Trey T
2026-04-15 08:38:31 -05:00
parent 9ececfa48a
commit a4d66c6ed1
17 changed files with 388 additions and 59 deletions

View File

@@ -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
}