补充 Auth/Tasks/ScenicPermission/Live 单元测试,并完善 UI Test 登录与会话稳定性。

移除 LoginViewModel 硬编码账号,扩展登录流程测试覆盖;调整 ZLoginSmokeUITests 执行顺序与路由直达逻辑,避免清空 Keychain 影响其它用例。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 12:44:10 +08:00
parent aac8458b04
commit d27438b264
17 changed files with 1104 additions and 36 deletions

View File

@ -16,7 +16,11 @@ enum HomeRouteTestRunner {
file: StaticString = #filePath,
line: UInt = #line
) throws -> SuixinkanApp {
let app = try UITestSession.relaunch(openMenu: entry.menuTitle, file: file, line: line)
let app = try UITestSession.relaunch(
openMenu: UITestMenuTitleAliases.resolve(entry.menuTitle),
file: file,
line: line
)
UITestExpectationVerifier.verify(entry.expectation, app: app, file: file, line: line)
return app
}

View File

@ -18,6 +18,8 @@ enum LoginFlow {
) {
guard app.isOnLoginScreen else { return }
app.application.activate()
app.waitForGlobalLoadingToDisappear()
fillLoginForm(app: app, credentials: credentials)
submitLogin(app: app)
handleAccountSelectionIfNeeded(app: app, credentials: credentials)
@ -34,17 +36,21 @@ enum LoginFlow {
///
static func fillLoginForm(app: SuixinkanApp, credentials: UITestCredentials) {
let usernameField = app.application.textFields["login.username"]
XCTAssertTrue(usernameField.waitForExistence(timeout: 8))
usernameField.tap()
usernameField.clearAndType(credentials.phone)
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))
passwordField.tap()
tapWhenReady(passwordField)
passwordField.clearAndType(credentials.password)
let privacyToggle = app.application.buttons["login.privacy"]
if privacyToggle.exists {
if privacyToggle.exists, privacyToggle.isHittable {
privacyToggle.tap()
}
}
@ -53,7 +59,7 @@ enum LoginFlow {
static func submitLogin(app: SuixinkanApp) {
let submitButton = app.application.buttons["login.submit"]
XCTAssertTrue(submitButton.waitForExistence(timeout: 4))
submitButton.tap()
tapWhenReady(submitButton)
app.waitForGlobalLoadingToDisappear()
}
@ -86,12 +92,26 @@ enum LoginFlow {
}
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 else {
guard let currentValue = value as? String, !currentValue.isEmpty else {
typeText(text)
return
}

View File

@ -0,0 +1,21 @@
//
// UITestMenuTitleAliases.swift
// suixinkanUITests
//
// Created by Codex on 2026/6/26.
//
import Foundation
/// `HomeMenuRouter.titleMap`
enum UITestMenuTitleAliases {
///
static func resolve(_ menuTitle: String) -> String {
switch menuTitle {
case "系统设置":
return "设置中心"
default:
return menuTitle
}
}
}

View File

@ -33,6 +33,7 @@ enum UITestSession {
line: UInt = #line
) throws -> SuixinkanApp {
sharedApp?.application.terminate()
Thread.sleep(forTimeInterval: 1.0)
let relaunched = try UITestAuthenticatedSession.launchLoggedIn(
openMenuTitle: menuTitle,
@ -51,6 +52,7 @@ enum UITestSession {
line: UInt = #line
) throws -> SuixinkanApp {
sharedApp?.application.terminate()
Thread.sleep(forTimeInterval: 1.0)
let relaunched = try UITestAuthenticatedSession.launchLoggedIn(
openProfileRoute: route,