// // UITestLaunchConfiguration.swift // suixinkanUITests // // Created by Codex on 2026/6/26. // import XCTest /// UI Test 启动参数与环境变量,需与 App 侧 `AppUITestLaunchState` 保持同名。 enum UITestLaunchConfiguration { static let uiTestsArgument = "-suixinkan-ui-tests" static let resetArgument = "-suixinkan-ui-tests-reset-state" static let openMenuArgument = "-suixinkan-ui-tests-open-menu" static let openProfileArgument = "-suixinkan-ui-tests-open-profile" /// 生成 UI Test 启动参数;可用 openMenu / openProfile 直达目标页。 static func launchArguments( resetState: Bool = false, openMenu: String? = nil, openProfile: String? = nil ) -> [String] { var arguments = [uiTestsArgument] if resetState { arguments.append(resetArgument) } if let openMenu, !openMenu.isEmpty { arguments.append(contentsOf: [openMenuArgument, openMenu]) } else if let openProfile, !openProfile.isEmpty { arguments.append(contentsOf: [openProfileArgument, openProfile]) } return arguments } /// 为 XCUIApplication 注入 UI Test 启动参数;仅在需要冷启动空白态时传入 resetState。 static func apply( to app: XCUIApplication, resetState: Bool = false, openMenu: String? = nil, openProfile: String? = nil ) { app.launchArguments = launchArguments( resetState: resetState, openMenu: openMenu, openProfile: openProfile ) } static let phoneEnvironmentKey = "SUI_TEST_PHONE" static let passwordEnvironmentKey = "SUI_TEST_PASSWORD" static let accountNameEnvironmentKey = "SUI_TEST_ACCOUNT_NAME" /// 从 Scheme / xcodebuild 环境变量或本地 plist 读取测试账号。 static var credentials: UITestCredentials? { if let environmentCredentials = credentialsFromEnvironment() { return environmentCredentials } return credentialsFromLocalPlist() } /// 缺少测试账号时跳过需要真实登录的用例。 static func requireCredentials(file: StaticString = #filePath, line: UInt = #line) throws -> UITestCredentials { guard let credentials else { throw XCTSkip( "缺少 UI Test 账号。请配置环境变量 \(phoneEnvironmentKey) / \(passwordEnvironmentKey),或在本机创建 UITestCredentials.local.plist。" ) } return credentials } /// 从 Scheme / xcodebuild 环境变量读取测试账号。 private static func credentialsFromEnvironment() -> UITestCredentials? { let environment = ProcessInfo.processInfo.environment guard let phone = environment[phoneEnvironmentKey]?.trimmingCharacters(in: .whitespacesAndNewlines), !phone.isEmpty, let password = environment[passwordEnvironmentKey]?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty else { return nil } let accountName = environment[accountNameEnvironmentKey]? .trimmingCharacters(in: .whitespacesAndNewlines) return UITestCredentials( phone: phone, password: password, accountName: accountName?.isEmpty == false ? accountName : nil ) } /// 从 gitignore 的本地 plist 读取测试账号,便于本机开发配置。 private static func credentialsFromLocalPlist() -> UITestCredentials? { let plistURL = localCredentialsURL guard FileManager.default.fileExists(atPath: plistURL.path), let dictionary = NSDictionary(contentsOf: plistURL) as? [String: String] else { return nil } guard let phone = dictionary[phoneEnvironmentKey]?.trimmingCharacters(in: .whitespacesAndNewlines), !phone.isEmpty, let password = dictionary[passwordEnvironmentKey]?.trimmingCharacters(in: .whitespacesAndNewlines), !password.isEmpty else { return nil } let accountName = dictionary[accountNameEnvironmentKey]? .trimmingCharacters(in: .whitespacesAndNewlines) return UITestCredentials( phone: phone, password: password, accountName: accountName?.isEmpty == false ? accountName : nil ) } /// 本地测试账号 plist 路径。 private static var localCredentialsURL: URL { URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .deletingLastPathComponent() .appendingPathComponent("UITestCredentials.local.plist") } } /// UI Test 登录账号实体。 struct UITestCredentials: Equatable { let phone: String let password: String let accountName: String? }