按模块拆分 XCUITest 用例与 Support 基础设施,UI Test 运行时跳过推送与排队 WebSocket,并将 Podfile 最低版本对齐 iOS 16。 Co-authored-by: Cursor <cursoragent@cursor.com>
116 lines
4.2 KiB
Swift
116 lines
4.2 KiB
Swift
//
|
||
// 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 phoneEnvironmentKey = "SUI_TEST_PHONE"
|
||
static let passwordEnvironmentKey = "SUI_TEST_PASSWORD"
|
||
static let accountNameEnvironmentKey = "SUI_TEST_ACCOUNT_NAME"
|
||
|
||
/// 为 XCUIApplication 注入 UI Test 启动参数;仅在需要冷启动空白态时传入 resetState。
|
||
static func apply(to app: XCUIApplication, resetState: Bool = false) {
|
||
if !app.launchArguments.contains(uiTestsArgument) {
|
||
app.launchArguments.append(uiTestsArgument)
|
||
}
|
||
|
||
if resetState {
|
||
if !app.launchArguments.contains(resetArgument) {
|
||
app.launchArguments.append(resetArgument)
|
||
}
|
||
} else {
|
||
app.launchArguments.removeAll { $0 == resetArgument }
|
||
}
|
||
}
|
||
|
||
/// 从 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?
|
||
}
|