import XCTest extension XCUIApplication { func launchAndOpenSampleBook( bookTitleQuery: String = "回归验证样本", displayType: String? = nil, pageNumber: Int? = nil, resetsReaderState: Bool = true ) { var args = ["--demo-book-title", bookTitleQuery] if resetsReaderState { args.append("--demo-reset-state") } if let displayType { args += ["--demo-display-type", displayType] } if let pageNumber { args += ["--demo-page", "\(pageNumber)"] } launchArguments = args launch() } @discardableResult func waitForReader(timeout: TimeInterval = 12) -> XCUIElement { let state = staticTexts[IDs.demoReaderState] XCTAssertTrue(state.waitForExistence(timeout: timeout), "阅读器状态标签未出现") XCTAssertTrue(state.label.contains("reader=opened"), "阅读器未进入 opened 状态:\(state.label)") return state } func waitForReaderPage(_ pageNumber: Int, timeout: TimeInterval = 12) { waitForReaderState(containing: "page=\(pageNumber)", timeout: timeout) } func showReaderChromeIfNeeded() { if buttons[IDs.readerBack].exists && buttons[IDs.readerSettings].exists { return } let content = otherElements[IDs.readerContent] if content.waitForExistence(timeout: 3) { content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } else { windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } } func hideReaderChromeIfNeeded() { for _ in 0..<3 { guard buttons[IDs.readerBack].exists else { return } let content = otherElements[IDs.readerContent] if content.waitForExistence(timeout: 2) { content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.6)).tap() } RunLoop.current.run(until: Date().addingTimeInterval(0.5)) } } func waitForReaderState(containing expectedText: String, timeout: TimeInterval = 8) { let state = staticTexts[IDs.demoReaderState] let deadline = Date().addingTimeInterval(timeout) var lastLabel = "" while Date() < deadline { if state.exists { lastLabel = state.label if lastLabel.contains(expectedText) { return } } RunLoop.current.run(until: Date().addingTimeInterval(0.1)) } XCTFail("阅读器状态未包含 \(expectedText),当前:\(lastLabel)") } }