新增启动参数直达首页与个人中心页面,引入 AppUITestRouteDriver 与 UITestSession 共享登录,并重构各模块用例的导航断言逻辑。 Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.8 KiB
Swift
135 lines
4.8 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 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?
|
||
}
|