UI test infrastructure overhaul — 58% to 96% pass rate (231/241)

Major infrastructure changes:
- BaseUITestCase: per-suite app termination via class setUp() prevents
  stale state when parallel clones share simulators
- relaunchBetweenTests override for suites that modify login/onboarding state
- focusAndType: dedicated SecureTextField path handles iOS strong password
  autofill suggestions (Choose My Own Password / Not Now dialogs)
- LoginScreenObject: tapSignUp/tapForgotPassword use scrollIntoView for
  offscreen buttons instead of simple swipeUp
- Removed all coordinate taps from ForgotPasswordScreen, VerifyResetCodeScreen,
  ResetPasswordScreen (Rule 3 compliance)
- Removed all usleep calls from screen objects (Rule 14 compliance)

App fixes exposed by tests:
- ContractorsListView: added onDismiss to sheet for list refresh after save
- AllTasksView: added Task.RefreshButton accessibility identifier
- AccessibilityIdentifiers: added Task.refreshButton
- DocumentsWarrantiesView: onDismiss handler for document list refresh
- Various form views: textContentType, submitLabel, onSubmit for keyboard flow

Test fixes:
- PasswordResetTests: handle auto-login after reset (app skips success screen)
- AuthenticatedUITestCase: refreshTasks() helper for kanban toolbar button
- All pre-login suites use relaunchBetweenTests for test independence
- Deleted dead code: AuthenticatedTestCase, SeededTestData, SeedTests,
  CleanupTests, old Suite0/2/3, Suite1_RegistrationRebuildTests

10 remaining failures: 5 iOS strong password autofill (simulator env),
3 pull-to-refresh gesture on empty lists, 2 feature coverage edge cases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-03-23 15:05:37 -05:00
parent 0ca4a44bac
commit 4df8707b92
67 changed files with 3085 additions and 4853 deletions
@@ -82,6 +82,7 @@ struct AccessibilityIdentifiers {
struct Task {
// List/Kanban
static let addButton = "Task.AddButton"
static let refreshButton = "Task.RefreshButton"
static let tasksList = "Task.List"
static let taskCard = "Task.Card"
static let emptyStateView = "Task.EmptyState"
@@ -164,6 +165,13 @@ struct AccessibilityIdentifiers {
static let filePicker = "DocumentForm.FilePicker"
static let notesField = "DocumentForm.NotesField"
static let expirationDatePicker = "DocumentForm.ExpirationDatePicker"
static let itemNameField = "DocumentForm.ItemNameField"
static let modelNumberField = "DocumentForm.ModelNumberField"
static let serialNumberField = "DocumentForm.SerialNumberField"
static let providerField = "DocumentForm.ProviderField"
static let providerContactField = "DocumentForm.ProviderContactField"
static let tagsField = "DocumentForm.TagsField"
static let locationField = "DocumentForm.LocationField"
static let saveButton = "DocumentForm.SaveButton"
static let formCancelButton = "DocumentForm.CancelButton"
+24
View File
@@ -8,6 +8,7 @@ enum UITestRuntime {
static let disableAnimationsFlag = "--disable-animations"
static let resetStateFlag = "--reset-state"
static let mockAuthFlag = "--ui-test-mock-auth"
static let completeOnboardingFlag = "--complete-onboarding"
static var launchArguments: [String] {
ProcessInfo.processInfo.arguments
@@ -29,6 +30,10 @@ enum UITestRuntime {
isEnabled && launchArguments.contains(mockAuthFlag)
}
static var shouldCompleteOnboarding: Bool {
isEnabled && launchArguments.contains(completeOnboardingFlag)
}
static func configureForLaunch() {
guard isEnabled else { return }
@@ -37,6 +42,12 @@ enum UITestRuntime {
}
UserDefaults.standard.set(true, forKey: "ui_testing_mode")
// Mark onboarding complete synchronously before SwiftUI renders,
// so RootView routes to the standalone LoginView instead of OnboardingCoordinator.
if shouldCompleteOnboarding {
UserDefaults.standard.set(true, forKey: "hasCompletedOnboarding")
}
}
@MainActor static func resetStateIfRequested() {
@@ -45,5 +56,18 @@ enum UITestRuntime {
DataManager.shared.clear()
OnboardingState.shared.reset()
ThemeManager.shared.currentTheme = .bright
// Re-apply onboarding completion after reset so tests that need
// both --reset-state and --complete-onboarding work correctly.
if shouldCompleteOnboarding {
OnboardingState.shared.completeOnboarding()
}
}
/// Mark onboarding as complete so the app shows the standalone login
/// instead of the onboarding coordinator. Called after resetState (if any).
@MainActor static func completeOnboardingIfRequested() {
guard shouldCompleteOnboarding else { return }
OnboardingState.shared.completeOnboarding()
}
}