// // LoginFlow.swift // suixinkanUITests // // Created by Codex on 2026/6/26. // import XCTest /// 登录与多账号选择流程封装。 enum LoginFlow { /// 如当前在登录页则执行完整登录,否则直接返回。 static func loginIfNeeded( app: SuixinkanApp, credentials: UITestCredentials, file: StaticString = #filePath, line: UInt = #line ) { guard app.isOnLoginScreen else { return } app.application.activate() app.waitForGlobalLoadingToDisappear() fillLoginForm(app: app, credentials: credentials) submitLogin(app: app) handleAccountSelectionIfNeeded(app: app, credentials: credentials) app.waitForGlobalLoadingToDisappear() XCTAssertTrue( app.isOnMainTabs, "登录后应进入主 Tab。", file: file, line: line ) } /// 填写登录表单并勾选协议。 static func fillLoginForm(app: SuixinkanApp, credentials: UITestCredentials) { let usernameField = app.application.textFields["login.username"] XCTAssertTrue(usernameField.waitForExistence(timeout: 12)) let currentPhone = usernameField.value as? String if currentPhone != credentials.phone { tapWhenReady(usernameField) usernameField.clearAndType(credentials.phone) } let passwordField = preferredPasswordField(in: app.application) XCTAssertTrue(passwordField.waitForExistence(timeout: 4)) tapWhenReady(passwordField) passwordField.clearAndType(credentials.password) let privacyToggle = app.application.buttons["login.privacy"] if privacyToggle.exists, privacyToggle.isHittable { privacyToggle.tap() } } /// 点击登录按钮。 static func submitLogin(app: SuixinkanApp) { let submitButton = app.application.buttons["login.submit"] XCTAssertTrue(submitButton.waitForExistence(timeout: 4)) tapWhenReady(submitButton) app.waitForGlobalLoadingToDisappear() } /// 处理 v9 多账号选择页。 static func handleAccountSelectionIfNeeded( app: SuixinkanApp, credentials: UITestCredentials ) { let accountSelectionTitle = app.application.navigationBars["选择账号"] guard accountSelectionTitle.waitForExistence(timeout: 6) else { return } if let accountName = credentials.accountName { let accountCell = app.application.staticTexts[accountName] if accountCell.waitForExistence(timeout: 4) { accountCell.tap() } } let confirmButton = app.application.buttons["进入系统"] XCTAssertTrue(confirmButton.waitForExistence(timeout: 4)) confirmButton.tap() app.waitForGlobalLoadingToDisappear() } /// 优先读取安全输入框,兼容调试构建下的普通文本框。 private static func preferredPasswordField(in app: XCUIApplication) -> XCUIElement { let secureField = app.secureTextFields["login.password"] if secureField.exists { return secureField } return app.textFields["login.password"] } /// 等待元素可点后点击,不可点时退化为坐标点击。 private static func tapWhenReady(_ element: XCUIElement) { let deadline = Date().addingTimeInterval(8) while Date() < deadline { if element.isHittable { element.tap() return } RunLoop.current.run(until: Date().addingTimeInterval(0.2)) } element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } } private extension XCUIElement { /// 清空并重新输入文本。 func clearAndType(_ text: String) { guard let currentValue = value as? String, !currentValue.isEmpty else { typeText(text) return } tap() let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: currentValue.count) typeText(deleteString) typeText(text) } }