d11cc82fec
Measured: ~half of every authenticated test was fixed setup, dominated by the UI login (typing email+password, keyboard/SecureField dance, ~8-12s). The test already creates the account via API and holds its real Kratos session token — so instead of typing credentials, pass the token as a launch arg and boot the app already authenticated. - App (UITestRuntime + iOSApp): reads --ui-test-session-token; after the --reset-state clear, calls DataManager.setAuthToken(token) and replicates the post-login init the UI login path runs (getCurrentUser + initializeLookups + getMyResidences + getTasks) so owner-gated/data-gated screens (residence detail delete + manage-users, pickers, lists) work on boot. Guarded by UITestRuntime.isEnabled — no effect on production. - AuthenticatedUITestCase: in fresh-account mode, create the account + seed its preconditions BEFORE launch, expose the token via additionalLaunchArguments, and drop the UI login. Legacy (usesFreshAccount=false) suites still UI-login. Measured per-test medians: Contractor 34s -> 25s; Task (uses lookups) ~34s -> 16s. TESTING.md updated. All affected suites pass; 0 leaked accounts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
3.2 KiB
Swift
90 lines
3.2 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import ComposeApp
|
|
|
|
/// Runtime contract between the app and XCUITests.
|
|
enum UITestRuntime {
|
|
// i18n-ignore-begin: launch-argument flag identifiers for XCUITests (non-UI)
|
|
static let uiTestingFlag = "--ui-testing"
|
|
static let disableAnimationsFlag = "--disable-animations"
|
|
static let resetStateFlag = "--reset-state"
|
|
static let mockAuthFlag = "--ui-test-mock-auth"
|
|
static let completeOnboardingFlag = "--complete-onboarding"
|
|
static let sessionTokenFlag = "--ui-test-session-token"
|
|
// i18n-ignore-end
|
|
|
|
static var launchArguments: [String] {
|
|
ProcessInfo.processInfo.arguments
|
|
}
|
|
|
|
static var isEnabled: Bool {
|
|
launchArguments.contains(uiTestingFlag)
|
|
}
|
|
|
|
static var shouldDisableAnimations: Bool {
|
|
isEnabled && launchArguments.contains(disableAnimationsFlag)
|
|
}
|
|
|
|
static var shouldResetState: Bool {
|
|
isEnabled && launchArguments.contains(resetStateFlag)
|
|
}
|
|
|
|
static var shouldMockAuth: Bool {
|
|
isEnabled && launchArguments.contains(mockAuthFlag)
|
|
}
|
|
|
|
static var shouldCompleteOnboarding: Bool {
|
|
isEnabled && launchArguments.contains(completeOnboardingFlag)
|
|
}
|
|
|
|
/// A real Kratos session token supplied by an authenticated UI test so the
|
|
/// app can boot already logged in (skipping the slow/flaky UI login). The
|
|
/// test obtains this token when it creates the account via API.
|
|
static var injectedSessionToken: String? {
|
|
guard isEnabled else { return nil }
|
|
let args = launchArguments
|
|
guard let i = args.firstIndex(of: sessionTokenFlag), i + 1 < args.count else { return nil }
|
|
let token = args[i + 1]
|
|
return token.isEmpty ? nil : token
|
|
}
|
|
|
|
static func configureForLaunch() {
|
|
guard isEnabled else { return }
|
|
|
|
if shouldDisableAnimations {
|
|
UIView.setAnimationsEnabled(false)
|
|
}
|
|
|
|
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() {
|
|
guard shouldResetState else { return }
|
|
|
|
DataManager.shared.clear()
|
|
OnboardingState.shared.reset()
|
|
ThemeManager.shared.currentTheme = .bright
|
|
UserDefaults.standard.removeObject(forKey: "ui_test_user_verified")
|
|
|
|
// Re-apply onboarding completion after reset. Set the flag directly
|
|
// because completeOnboarding() has an auth guard that fails here
|
|
// (DataManager was just cleared, so isAuthenticated is false).
|
|
if shouldCompleteOnboarding {
|
|
OnboardingState.shared.hasCompletedOnboarding = true
|
|
}
|
|
}
|
|
|
|
/// 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()
|
|
}
|
|
}
|