ReadViewSDK/ReadViewDemo/ReadViewDemoUITests/Helpers/XCUIApplication+Launch.swift
shen 6f75b083f7 feat: EPUB 阅读器搜索、选中注释、书签 chrome 状态及大量重构优化
- 新增 RDEPUBReaderSearchCoordinator 与 RDEPUBSelectionState 管理搜索和选中状态
- 新增 BookmarkChromeStateTests、NavigationBackwardTests、SelectionAnnotateTests 等 UI 测试
- 新增多个边界测试 epub 样本(损坏结构、空归档、缺失文件、流式外链验证)
- 重构阅读器 chrome 状态管理,统一 tool bar 与 search bar 交互
- 优化大书分页缓存策略(RDEPUBChapterSummaryDiskCache、RDEPUBPageCountCache)
- 移除废弃的 RDEPUBLocationConverter 和 RDEPUBPageBreakPolicy
- 更新 epub-bridge.js 与 JS bridge 通信协议
- 全面更新现有 UI 测试以适配新的 helper 和状态管理
2026-06-13 22:48:56 +08:00

156 lines
5.3 KiB
Swift

import XCTest
extension XCUIApplication {
func launchAndOpenSampleBook(
bookTitleQuery: String = "回归验证样本",
displayType: String? = nil,
pageNumber: Int? = nil,
resetsReaderState: Bool = true,
windowSize: Int? = nil,
concurrency: Int? = nil,
clearsCache: Bool = false,
corruptsCache: Bool = false,
allowsInspectableWebViews: Bool = false,
enablesVerboseWebLogs: Bool = false,
searchKeyword: String? = nil
) {
var args = ["--demo-book-title", bookTitleQuery]
if resetsReaderState {
args.append("--demo-reset-state")
}
if clearsCache {
args.append("--demo-clear-cache")
}
if corruptsCache {
args.append("--demo-corrupt-cache")
}
if allowsInspectableWebViews {
args.append("--demo-allow-inspectable-webviews")
}
if enablesVerboseWebLogs {
args.append("--demo-enable-verbose-weblogs")
}
if let displayType {
args += ["--demo-display-type", displayType]
}
if let pageNumber {
args += ["--demo-page", "\(pageNumber)"]
}
if let windowSize {
args += ["--demo-window-size", "\(windowSize)"]
}
if let concurrency {
args += ["--demo-concurrency", "\(concurrency)"]
}
if let searchKeyword {
args += ["--demo-search-keyword", searchKeyword]
}
launchArguments = args
launch()
}
@discardableResult
func waitForReader(timeout: TimeInterval = 12) -> XCUIElement {
let state = staticTexts[IDs.demoReaderState]
XCTAssertTrue(state.waitForExistence(timeout: timeout), "阅读器状态标签未出现")
let parsed = DemoReaderState(rawValue: state.label)
XCTAssertTrue(parsed.isOpened, "阅读器未进入 opened 状态:\(state.label)")
return state
}
func waitForReaderPage(_ pageNumber: Int, timeout: TimeInterval = 12) {
_ = waitForDemoReaderState(timeout: timeout, description: "page=\(pageNumber)") { state in
state.page == pageNumber
}
}
func showReaderChromeIfNeeded(
requiredButtonIDs: [String] = [IDs.readerBack, IDs.readerSettings],
timeout: TimeInterval = 5
) {
if readerChromeIsVisible(requiredButtonIDs: requiredButtonIDs) {
return
}
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
let content = otherElements[IDs.readerContent]
if content.waitForExistence(timeout: 1) {
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
} else {
windows.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
}
RunLoop.current.run(until: Date().addingTimeInterval(0.35))
if readerChromeIsVisible(requiredButtonIDs: requiredButtonIDs) {
return
}
}
}
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 = "<missing>"
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)")
}
@discardableResult
func requireSelectionText(
expectedPage: Int? = nil,
timeout: TimeInterval = 12
) throws -> XCUIElement {
let selectionText = otherElements[IDs.readerSelectionText].firstMatch
let deadline = Date().addingTimeInterval(timeout)
while Date() < deadline {
if let expectedPage {
let state = currentDemoReaderState()
if state?.page != expectedPage {
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
continue
}
}
if selectionText.exists {
return selectionText
}
let content = otherElements[IDs.readerContent]
if content.exists {
content.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
}
RunLoop.current.run(until: Date().addingTimeInterval(0.2))
}
throw XCTSkip("文本选区元素未出现,当前渲染路径未暴露 \(IDs.readerSelectionText)")
}
private func readerChromeIsVisible(requiredButtonIDs: [String]) -> Bool {
requiredButtonIDs.allSatisfy { buttons[$0].exists }
}
}