Rearchitect UI test suite for complete, non-flaky coverage against live API
- Migrate Suite4-10, SmokeTests, NavigationCriticalPathTests to AuthenticatedTestCase with seeded admin account and real backend login - Add 34 accessibility identifiers across 11 app views (task completion, profile, notifications, theme, join residence, manage users, forms) - Create FeatureCoverageTests (14 tests) covering previously untested features: profile edit, theme selection, notification prefs, task completion, manage users, join residence, task templates - Create MultiUserSharingTests (18 API tests) and MultiUserSharingUITests (8 XCUI tests) for full cross-user residence sharing lifecycle - Add cleanup infrastructure: SuiteZZ_CleanupTests auto-wipes test data after runs, cleanup_test_data.sh script for manual reset via admin API - Add share code API methods to TestAccountAPIClient (generateShareCode, joinWithCode, getShareCode, listResidenceUsers, removeUser) - Fix app bugs found by tests: - ResidencesListView join callback now uses forceRefresh:true - APILayer invalidates task cache when residence count changes - AllTasksView auto-reloads tasks when residence list changes - Fix test quality: keyboard focus waits, Save/Add button label matching, Documents tab label (Docs), remove API verification from UI tests - DataLayerTests and PasswordResetTests now verify through UI, not API calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,28 +44,51 @@ struct UITestHelpers {
|
||||
sleep(1)
|
||||
}
|
||||
|
||||
// Find and tap logout button
|
||||
// Find and tap logout button — the profile sheet uses a lazy
|
||||
// SwiftUI List so the button may not exist until scrolled into view
|
||||
let logoutButton = app.buttons[AccessibilityIdentifiers.Profile.logoutButton]
|
||||
if logoutButton.waitForExistence(timeout: 3) {
|
||||
logoutButton.tap()
|
||||
sleep(1)
|
||||
|
||||
// Confirm logout in alert if present - specifically target the alert's button
|
||||
if !logoutButton.waitForExistence(timeout: 3) {
|
||||
// Scroll down in the profile sheet's CollectionView
|
||||
let collectionView = app.collectionViews.firstMatch
|
||||
if collectionView.exists {
|
||||
for _ in 0..<5 {
|
||||
collectionView.swipeUp()
|
||||
if logoutButton.waitForExistence(timeout: 1) { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if logoutButton.waitForExistence(timeout: 3) {
|
||||
if logoutButton.isHittable {
|
||||
logoutButton.tap()
|
||||
} else {
|
||||
logoutButton.forceTap()
|
||||
}
|
||||
|
||||
// Confirm logout in alert if present
|
||||
let alert = app.alerts.firstMatch
|
||||
if alert.waitForExistence(timeout: 2) {
|
||||
if alert.waitForExistence(timeout: 3) {
|
||||
let confirmLogout = alert.buttons["Log Out"]
|
||||
if confirmLogout.exists {
|
||||
if confirmLogout.waitForExistence(timeout: 2) {
|
||||
confirmLogout.tap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sleep(2)
|
||||
// Wait for the app to transition back to login screen after logout
|
||||
let loginRoot = app.otherElements[UITestID.Root.login]
|
||||
let loggedOut = usernameField.waitForExistence(timeout: 15)
|
||||
|| loginRoot.waitForExistence(timeout: 5)
|
||||
|
||||
XCTAssertTrue(
|
||||
usernameField.waitForExistence(timeout: 8),
|
||||
"Failed to log out - login username field should appear"
|
||||
)
|
||||
if !loggedOut {
|
||||
// Check if we landed on onboarding instead (when onboarding state was reset)
|
||||
let onboardingRoot = app.otherElements[UITestID.Root.onboarding]
|
||||
if onboardingRoot.waitForExistence(timeout: 3) {
|
||||
return // Logout succeeded, landed on onboarding
|
||||
}
|
||||
XCTFail("Failed to log out - login username field should appear. App state:\n\(app.debugDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
/// Logs in a user with the provided credentials
|
||||
@@ -132,7 +155,7 @@ struct UITestHelpers {
|
||||
|
||||
static func ensureOnLoginScreen(app: XCUIApplication) {
|
||||
let usernameField = loginUsernameField(app: app)
|
||||
if usernameField.waitForExistence(timeout: 2) {
|
||||
if usernameField.waitForExistence(timeout: 5) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -140,7 +163,7 @@ struct UITestHelpers {
|
||||
let mainTabsRoot = app.otherElements[UITestID.Root.mainTabs]
|
||||
if mainTabsRoot.exists || app.tabBars.firstMatch.exists {
|
||||
logout(app: app)
|
||||
if usernameField.waitForExistence(timeout: 8) {
|
||||
if usernameField.waitForExistence(timeout: 10) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -148,9 +171,11 @@ struct UITestHelpers {
|
||||
// Wait for a stable root state before interacting.
|
||||
let loginRoot = app.otherElements[UITestID.Root.login]
|
||||
let onboardingRoot = app.otherElements[UITestID.Root.onboarding]
|
||||
_ = loginRoot.waitForExistence(timeout: 5) || onboardingRoot.waitForExistence(timeout: 5)
|
||||
|
||||
if onboardingRoot.exists {
|
||||
// Check for standalone login screen first (when --complete-onboarding is active)
|
||||
if loginRoot.waitForExistence(timeout: 8) {
|
||||
_ = usernameField.waitForExistence(timeout: 10)
|
||||
} else if onboardingRoot.waitForExistence(timeout: 5) {
|
||||
// Handle both pure onboarding and onboarding + login sheet.
|
||||
let onboardingLoginButton = app.buttons[AccessibilityIdentifiers.Onboarding.loginButton]
|
||||
if onboardingLoginButton.waitForExistence(timeout: 5) {
|
||||
|
||||
Reference in New Issue
Block a user