c52ce4d497
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>
101 lines
4.6 KiB
Swift
101 lines
4.6 KiB
Swift
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")
|
|
}
|
|
}
|