import XCTest @MainActor final class LiveReadingFlowUITests: XCTestCase { private let app = XCUIApplication() override func setUp() { super.setUp() // Xcode's runtime-issue callback can crash on Network.framework's log // quarantine with thousands of failing hosts. Keep server testing active. app.launchEnvironment["OS_ACTIVITY_MODE"] = "disable" } func testSearchLatencyAndCachedQuery() throws { guard ProcessInfo.processInfo.environment["LEGADO_SEARCH_UI_BENCHMARK"] == "1" else { throw XCTSkip("Use the LegadoSearchPerformanceUITests scheme with the imported catalog") } continueAfterFailure = false app.launchArguments = ["-uiDemoTab", "search", "-uiAutomationSearch", "极道天魔", "-themeMode", "1"] let started = Date() app.launch() let exact = app.buttons.matching(NSPredicate(format: "label BEGINSWITH %@", "极道天魔, 滚开")).firstMatch XCTAssertTrue(exact.waitForExistence(timeout: 90)) print("[SearchPerformance] launch_to_exact_seconds=\(Date().timeIntervalSince(started))") attach("all-sources-search-latency") app.buttons["搜索选项"].tap() if app.buttons["停止搜索"].exists { app.buttons["停止搜索"].tap() } let search = app.searchFields["书名或作者"] search.tap() search.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: 4) + "极道天") let repeated = Date() search.typeText("\n") XCTAssertTrue(app.staticTexts["“极道天”"].waitForExistence(timeout: 10)) XCTAssertTrue(exact.exists) print("[SearchPerformance] cached_query_to_result_seconds=\(Date().timeIntervalSince(repeated))") attach("cached-search-result") exact.tap() XCTAssertTrue(app.navigationBars["书籍详情"].waitForExistence(timeout: 10)) attach("detail-from-running-search") } func testImportSearchDetailReadSwitchAndResume() throws { guard ProcessInfo.processInfo.environment["LEGADO_LIVE_UI_TESTS"] == "1" else { throw XCTSkip("Use the LegadoLiveReadingUITests scheme for real-server testing") } continueAfterFailure = false defer { if (testRun?.failureCount ?? 0) > 0 { attach("failure") let tree = XCTAttachment(string: app.debugDescription) tree.name = "failure-accessibility-tree" tree.lifetime = .keepAlways add(tree) } } app.launchArguments = ["-uiDemoTab", "search", "-uiAutomationSearch", "极道天魔", "-pageAnim", "0", "-themeMode", "1"] app.launch() XCTAssertTrue(app.navigationBars["搜索"].waitForExistence(timeout: 30)) app.buttons["搜索选项"].tap() app.buttons["书源管理"].tap() XCTAssertTrue(app.navigationBars["书源"].waitForExistence(timeout: 10)) app.buttons["更多"].firstMatch.tap() app.buttons["URL 或粘贴 JSON"].tap() let url = app.textFields["https://example.com/sources.json"] XCTAssertTrue(url.waitForExistence(timeout: 10)) url.tap() url.typeText("https://legado.aoaostar.com/sources/b778fe6b.json") app.navigationBars["导入书源"].buttons["导入"].tap() XCTAssertTrue(app.navigationBars["书源导入预检"].waitForExistence(timeout: 120)) app.navigationBars["书源导入预检"].buttons["选择"].tap() app.buttons["全选"].tap() attach("01-import-preview") XCTAssertTrue(app.navigationBars["书源导入预检"].buttons["导入"].isEnabled) app.navigationBars["书源导入预检"].buttons["导入"].tap() XCTAssertTrue(app.alerts["书源导入完成"].waitForExistence(timeout: 90)) attach("02-import-completed") app.alerts.buttons["好"].tap() app.navigationBars["书源"].buttons.element(boundBy: 0).tap() selectSearchSource("DragonQuestQBall") let search = app.searchFields["书名或作者"] search.tap() search.typeText("\n") let title = app.buttons.matching(NSPredicate(format: "label BEGINSWITH %@", "极道天魔, 滚开")).firstMatch XCTAssertTrue(title.waitForExistence(timeout: 90)) attach("03-search") title.tap() XCTAssertTrue(app.navigationBars["书籍详情"].waitForExistence(timeout: 10)) let start = app.buttons.matching(NSPredicate(format: "label IN %@", ["开始阅读", "继续阅读"])).firstMatch waitUntil("Readable directory", timeout: 90) { start.exists && start.isEnabled } XCTAssertTrue(app.staticTexts["滚开"].exists) attach("04-detail") if app.buttons["继续阅读"].exists { let firstChapter = app.staticTexts.matching(NSPredicate(format: "label CONTAINS %@", "道法无情")).firstMatch XCTAssertTrue(firstChapter.exists) firstChapter.tap() } else { start.tap() } waitForBody(containing: "道法无情") attach("05-reading") showReaderMenu() let currentSourceName = app.buttons["reader.changeSource"].value as? String let replacementID = currentSourceName == "💰QQ阅读[浏览器]" ? "DragonQuestQBall" : "https://bookshelf.html5.qq.com/" let replacementName = currentSourceName == "💰QQ阅读[浏览器]" ? "●QB男生 500 } let value = body.value as? String ?? "" XCTAssertGreaterThan(value.components(separatedBy: "\n").count, 5) XCTAssertFalse(value.contains("

")) XCTAssertFalse(value.contains(#"\r\n"#)) } private func showReaderMenu() { if !app.buttons["换源"].exists { app.textViews["reader.text"].coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } XCTAssertTrue(app.buttons["换源"].waitForExistence(timeout: 10)) } private func waitUntil(_ name: String, timeout: TimeInterval, condition: @escaping () -> Bool) { let ready = expectation(for: NSPredicate { _, _ in condition() }, evaluatedWith: nil) ready.expectationDescription = name wait(for: [ready], timeout: timeout) } private func attach(_ name: String) { let screenshot = XCTAttachment(screenshot: app.screenshot()) screenshot.name = name screenshot.lifetime = .keepAlways add(screenshot) } }