Re-architect iOS XCUITest suite: per-test isolation + domain organization
Migrate the XCUITest suite off the legacy shared-account model (and the prior Django-style auth assumptions) to a parallel-safe, domain-organized architecture, validated end-to-end against the live Kratos stack. Isolation (parallel-safe by construction): - Core/Fixtures/TestAccount.swift: each test mints its own pre-verified Kratos identity (uit_<domain>_<uuid>@test.honeydue.local), logs in, seeds under its own token, and deletes the identity in teardown (cascading all data + clearing Kratos). No shared testuser; parallel workers no longer race. - AuthenticatedUITestCase rewritten to that model (member surface preserved); adds requiresResidence / seedAccountPreconditions to seed UI-gated data BEFORE login (a fresh account is empty at login). Organization (255 tests preserved, none dropped): - 21 domain suites under Auth/ Onboarding/ Residence/ Task/ Contractor/ Document/ Sharing/ Navigation/ Smoke/ CrossCutting/ E2E/, consistent <Domain>UITests naming. Removes the Suite1..11 / AAA_ / ZZ_ / Tests/Rebuild naming chaos and the overlapping task/residence/auth suites. Runner + test plans: - run_ui_tests.sh: Smoke gate -> Seed -> Parallel(8 workers) -> Sweep. The parallel phase runs the whole target minus phase-managed suites via -skip-testing, so new suites auto-include (no hand-maintained list to drift). Drops the 2-worker cap and Suite6 isolation (isolation made them moot). - HoneyDueUITests.xctestplan skips the 4 phase-managed suites; adds Smoke.xctestplan. Kratos auth fixes folded in (login/verify/reset endpoints removed under Kratos): real Mailpit verification codes replace the obsolete fixed "123456"; teardown deletes Kratos identities; admin-panel login uses the correct seeded password. Build green; isolation, parallelism, and the precondition/sharing migrations validated against the live stack (0 leaked accounts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import XCTest
|
||||
|
||||
/// Smoke tests - run on every PR. Must complete in <2 minutes.
|
||||
///
|
||||
/// Tests that the app launches successfully, the auth screen renders correctly,
|
||||
/// and core navigation is functional. These are the minimum-viability tests
|
||||
/// that must pass before any PR can merge.
|
||||
///
|
||||
/// These run logged-IN (via AuthenticatedUITestCase). Logged-OUT launch-surface
|
||||
/// checks live in `AppLaunchUITests` (BaseUITestCase) in this same folder.
|
||||
///
|
||||
/// Zero sleep() calls -- all waits are condition-based.
|
||||
final class SmokeUITests: AuthenticatedUITestCase {
|
||||
|
||||
// MARK: - App Launch
|
||||
|
||||
func testAppLaunches() {
|
||||
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
|
||||
let onboarding = app.descendants(matching: .any)
|
||||
.matching(identifier: UITestID.Onboarding.startFreshButton).firstMatch
|
||||
let loginField = app.textFields[UITestID.Auth.usernameField]
|
||||
|
||||
let mainAppeared = residencesTab.waitForExistence(timeout: 10)
|
||||
let loginAppeared = loginField.waitForExistence(timeout: 3)
|
||||
let onboardingAppeared = onboarding.waitForExistence(timeout: 3)
|
||||
|
||||
XCTAssertTrue(loginAppeared || mainAppeared || onboardingAppeared, "App should show login, main, or onboarding screen on launch")
|
||||
}
|
||||
|
||||
// MARK: - Login Screen Elements
|
||||
|
||||
func testLoginScreenElements() {
|
||||
let tabBar = app.tabBars.firstMatch
|
||||
if tabBar.exists {
|
||||
return // Already logged in, skip login screen element checks
|
||||
}
|
||||
|
||||
let emailField = app.textFields[UITestID.Auth.usernameField]
|
||||
let passwordField = app.secureTextFields[UITestID.Auth.passwordField].exists
|
||||
? app.secureTextFields[UITestID.Auth.passwordField]
|
||||
: app.textFields[UITestID.Auth.passwordField]
|
||||
let loginButton = app.buttons[UITestID.Auth.loginButton]
|
||||
|
||||
guard emailField.exists else {
|
||||
return // Already logged in, skip
|
||||
}
|
||||
|
||||
XCTAssertTrue(emailField.exists, "Email field should exist")
|
||||
XCTAssertTrue(passwordField.exists, "Password field should exist")
|
||||
XCTAssertTrue(loginButton.exists, "Login button should exist")
|
||||
}
|
||||
|
||||
// MARK: - Login Flow
|
||||
|
||||
func testLoginWithExistingCredentials() {
|
||||
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
|
||||
XCTAssertTrue(residencesTab.waitForExistence(timeout: 15), "Should be on main screen after login")
|
||||
}
|
||||
|
||||
// MARK: - Tab Navigation
|
||||
|
||||
func testMainTabsExistAfterLogin() {
|
||||
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
|
||||
guard residencesTab.waitForExistence(timeout: 15) else {
|
||||
XCTFail("Main screen did not appear")
|
||||
return
|
||||
}
|
||||
|
||||
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
|
||||
let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
|
||||
let documentsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Doc'")).firstMatch
|
||||
|
||||
XCTAssertTrue(residencesTab.exists, "Residences tab should exist")
|
||||
XCTAssertTrue(tasksTab.exists, "Tasks tab should exist")
|
||||
XCTAssertTrue(contractorsTab.exists, "Contractors tab should exist")
|
||||
XCTAssertTrue(documentsTab.exists, "Documents tab should exist")
|
||||
}
|
||||
|
||||
func testTabNavigation() {
|
||||
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
|
||||
guard residencesTab.waitForExistence(timeout: 15) else {
|
||||
XCTFail("Main screen did not appear")
|
||||
return
|
||||
}
|
||||
|
||||
// Navigate through all tabs — verify each by checking that navigation didn't crash
|
||||
// and the tab bar remains visible (proving the screen loaded)
|
||||
navigateToTasks()
|
||||
XCTAssertTrue(app.tabBars.firstMatch.waitForExistence(timeout: navigationTimeout), "Tab bar should remain after navigating to Tasks")
|
||||
|
||||
navigateToContractors()
|
||||
XCTAssertTrue(app.tabBars.firstMatch.waitForExistence(timeout: navigationTimeout), "Tab bar should remain after navigating to Contractors")
|
||||
|
||||
navigateToDocuments()
|
||||
XCTAssertTrue(app.tabBars.firstMatch.waitForExistence(timeout: navigationTimeout), "Tab bar should remain after navigating to Documents")
|
||||
|
||||
navigateToResidences()
|
||||
XCTAssertTrue(app.tabBars.firstMatch.waitForExistence(timeout: navigationTimeout), "Tab bar should remain after navigating to Residences")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user