mirror of
https://github.com/Aminark/legado-ios.git
synced 2026-10-20 19:21:22 +08:00
feat: open-source 小浣熊阅读 for iOS
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
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男生<qq♛" : "💰QQ阅读[浏览器]"
|
||||
app.buttons["下一章"].tap()
|
||||
waitForBody(containing: "作弊器")
|
||||
attach("06-next-chapter")
|
||||
showReaderMenu()
|
||||
app.buttons["换源"].tap()
|
||||
XCTAssertTrue(app.navigationBars["为《极道天魔》换源"].waitForExistence(timeout: 10))
|
||||
let replacement = app.buttons["sourceSwitch.candidate." + replacementID]
|
||||
XCTAssertTrue(replacement.waitForExistence(timeout: 240))
|
||||
attach("07-source-candidates")
|
||||
replacement.tap()
|
||||
waitUntil("Source switch sheet dismissed", timeout: 90) {
|
||||
!self.app.navigationBars["为《极道天魔》换源"].exists
|
||||
}
|
||||
waitForBody(containing: "作弊器")
|
||||
showReaderMenu()
|
||||
waitUntil("Replacement source active", timeout: 20) {
|
||||
self.app.buttons["reader.changeSource"].value as? String == replacementName
|
||||
}
|
||||
attach("08-switched-reading")
|
||||
app.buttons["返回"].tap()
|
||||
app.terminate()
|
||||
app.launchArguments = ["-pageAnim", "0", "-themeMode", "1"]
|
||||
app.launch()
|
||||
let resume = app.buttons["bookshelf.continue"]
|
||||
XCTAssertTrue(resume.waitForExistence(timeout: 30))
|
||||
resume.tap()
|
||||
waitForBody(containing: "作弊器")
|
||||
showReaderMenu()
|
||||
XCTAssertEqual(app.buttons["reader.changeSource"].value as? String, replacementName)
|
||||
attach("09-resumed-reading")
|
||||
}
|
||||
|
||||
private func selectSearchSource(_ sourceID: String) {
|
||||
app.buttons["搜索选项"].tap()
|
||||
if app.buttons["停止搜索"].exists {
|
||||
app.buttons["停止搜索"].tap()
|
||||
app.buttons["搜索选项"].tap()
|
||||
}
|
||||
app.buttons["分组或书源"].tap()
|
||||
app.segmentedControls.buttons["书源"].tap()
|
||||
let filter = app.searchFields["筛选书源"]
|
||||
filter.tap()
|
||||
filter.typeText(sourceID)
|
||||
let source = app.staticTexts[sourceID].firstMatch
|
||||
XCTAssertTrue(source.waitForExistence(timeout: 10))
|
||||
source.tap()
|
||||
app.buttons["确定"].tap()
|
||||
}
|
||||
|
||||
private func waitForBody(containing chapter: String) {
|
||||
let body = app.textViews["reader.text"]
|
||||
waitUntil("Body of \(chapter)", timeout: 90) {
|
||||
guard body.exists, body.isHittable else { return false }
|
||||
let value = body.value as? String ?? ""
|
||||
return value.contains(chapter) && value.count > 500
|
||||
}
|
||||
let value = body.value as? String ?? ""
|
||||
XCTAssertGreaterThan(value.components(separatedBy: "\n").count, 5)
|
||||
XCTAssertFalse(value.contains("<p>"))
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class ReaderScrollGestureTests: XCTestCase {
|
||||
func testFailedChaptersAllowRetryMenuAndSourceSwitchInBothReadingModes() throws {
|
||||
continueAfterFailure = false
|
||||
for mode in ["3", "0"] {
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoReader", "-uiReaderFailure", "-pageAnim", mode, "-themeMode", "0"]
|
||||
app.launch()
|
||||
let retry = app.buttons["reader.retryChapter"]
|
||||
XCTAssertTrue(retry.waitForExistence(timeout: 20))
|
||||
retry.tap()
|
||||
XCTAssertTrue(app.buttons["reader.failureChangeSource"].waitForExistence(timeout: 10))
|
||||
app.buttons["reader.failureChangeSource"].tap()
|
||||
XCTAssertTrue(app.navigationBars["为《长夜里的灯》换源"].waitForExistence(timeout: 10))
|
||||
app.buttons["取消"].tap()
|
||||
XCTAssertTrue(retry.waitForExistence(timeout: 10))
|
||||
if mode == "3" {
|
||||
app.collectionViews["reader.continuous"].coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.4)).tap()
|
||||
} else {
|
||||
app.buttons["reader.failureMenu"].tap()
|
||||
}
|
||||
let changeSource = app.buttons["reader.changeSource"]
|
||||
XCTAssertTrue(changeSource.waitForExistence(timeout: 5))
|
||||
changeSource.tap()
|
||||
XCTAssertTrue(app.navigationBars["为《长夜里的灯》换源"].waitForExistence(timeout: 10))
|
||||
attachScreenshot(app)
|
||||
app.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
func testPagedReadingReportsPageCountAndTurnsWithinTheSameChapter() throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoReader", "-uiScrollRegression", "-uiPagingDiagnostics", "-pageAnim", "4", "-themeMode", "1", "-tipFooterRight", "6", "-textSize", "28"]
|
||||
app.launch()
|
||||
let body = app.textViews["reader.text"]
|
||||
XCTAssertTrue(body.waitForExistence(timeout: 20))
|
||||
let page = app.staticTexts.matching(NSPredicate(format: "label MATCHES %@", "[0-9]+/[0-9]+.*")).firstMatch
|
||||
let ready = expectation(for: NSPredicate { _, _ in
|
||||
print("[PagedReader] \(body.label) footer=\(page.label)")
|
||||
let total = page.label.split(separator: "/").last?.split(separator: " ").first.flatMap { Int($0) } ?? 0
|
||||
return page.exists && page.label.hasPrefix("1/") && total > 1
|
||||
}, evaluatedWith: nil)
|
||||
wait(for: [ready], timeout: 10)
|
||||
let chapter = body.value as? String
|
||||
app.coordinate(withNormalizedOffset: CGVector(dx: 0.8, dy: 0.5)).tap()
|
||||
let next = expectation(for: NSPredicate { _, _ in page.label.hasPrefix("2/") }, evaluatedWith: nil)
|
||||
wait(for: [next], timeout: 5)
|
||||
XCTAssertEqual(body.value as? String, chapter)
|
||||
app.coordinate(withNormalizedOffset: CGVector(dx: 0.2, dy: 0.5)).tap()
|
||||
let previous = expectation(for: NSPredicate { _, _ in page.label.hasPrefix("1/") }, evaluatedWith: nil)
|
||||
wait(for: [previous], timeout: 5)
|
||||
XCTAssertEqual(body.value as? String, chapter)
|
||||
attachScreenshot(app)
|
||||
}
|
||||
|
||||
func testPagedReadingChapterNavigationDirectorySearchAndSettings() throws {
|
||||
try exerciseChapterNavigationAndSettings(usesLegacyUI: false)
|
||||
}
|
||||
|
||||
func testLegacyReadingChapterNavigationDirectorySearchAndPhotoPicker() throws {
|
||||
try exerciseChapterNavigationAndSettings(usesLegacyUI: true)
|
||||
}
|
||||
|
||||
private func exerciseChapterNavigationAndSettings(usesLegacyUI: Bool) throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoReader", "-pageAnim", "0", "-themeMode", "1"]
|
||||
if usesLegacyUI { app.launchArguments.append("-uiLegacyCompatibility") }
|
||||
app.launch()
|
||||
let body = app.textViews["reader.text"]
|
||||
XCTAssertTrue(body.waitForExistence(timeout: 20))
|
||||
func waitForChapter(_ title: String) {
|
||||
let ready = expectation(for: NSPredicate { _, _ in
|
||||
(body.value as? String ?? "").contains(title)
|
||||
}, evaluatedWith: nil)
|
||||
wait(for: [ready], timeout: 15)
|
||||
}
|
||||
func showMenu() {
|
||||
if !app.buttons["目录"].exists {
|
||||
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||
}
|
||||
XCTAssertTrue(app.buttons["目录"].waitForExistence(timeout: 5))
|
||||
}
|
||||
waitForChapter("第一章 · 雨夜")
|
||||
showMenu()
|
||||
XCTAssertFalse(app.buttons["上一章"].isEnabled)
|
||||
app.buttons["下一章"].tap()
|
||||
waitForChapter("第二章 · 灯塔")
|
||||
showMenu()
|
||||
app.buttons["上一章"].tap()
|
||||
waitForChapter("第一章 · 雨夜")
|
||||
|
||||
showMenu()
|
||||
app.buttons["目录"].tap()
|
||||
XCTAssertTrue(app.navigationBars["目录"].waitForExistence(timeout: 5))
|
||||
let chapterSearch = app.searchFields["搜索章节"]
|
||||
chapterSearch.tap()
|
||||
chapterSearch.typeText("天明")
|
||||
app.staticTexts["第五章 · 天明"].tap()
|
||||
waitForChapter("第五章 · 天明")
|
||||
showMenu()
|
||||
XCTAssertFalse(app.buttons["下一章"].isEnabled)
|
||||
|
||||
app.buttons["更多"].tap()
|
||||
app.buttons["搜索正文"].tap()
|
||||
XCTAssertTrue(app.navigationBars["正文搜索"].waitForExistence(timeout: 5))
|
||||
let contentSearch = app.searchFields["搜索本章"]
|
||||
contentSearch.tap()
|
||||
contentSearch.typeText("灯塔")
|
||||
let match = app.buttons.matching(NSPredicate(format: "label CONTAINS %@", "林深推开阁楼的窗")).firstMatch
|
||||
XCTAssertTrue(match.waitForExistence(timeout: 5))
|
||||
match.tap()
|
||||
XCTAssertFalse(app.navigationBars["正文搜索"].exists)
|
||||
|
||||
showMenu()
|
||||
app.buttons["书签"].tap()
|
||||
app.buttons["更多"].tap()
|
||||
app.buttons["书签列表"].tap()
|
||||
XCTAssertTrue(app.navigationBars["书签"].waitForExistence(timeout: 5))
|
||||
XCTAssertTrue(app.staticTexts["第五章 · 天明"].firstMatch.waitForExistence(timeout: 5))
|
||||
app.buttons["关闭"].tap()
|
||||
|
||||
showMenu()
|
||||
app.buttons["外观"].tap()
|
||||
XCTAssertTrue(app.navigationBars["阅读样式"].waitForExistence(timeout: 5))
|
||||
if usesLegacyUI {
|
||||
app.buttons["选择背景图片"].tap()
|
||||
let cancel = app.buttons.matching(NSPredicate(format: "label IN %@", ["Cancel", "取消"])).firstMatch
|
||||
XCTAssertTrue(cancel.waitForExistence(timeout: 10))
|
||||
cancel.tap()
|
||||
XCTAssertTrue(app.navigationBars["阅读样式"].waitForExistence(timeout: 5))
|
||||
}
|
||||
app.buttons["完成"].tap()
|
||||
showMenu()
|
||||
app.buttons["设置"].tap()
|
||||
XCTAssertTrue(app.navigationBars["阅读设置"].waitForExistence(timeout: 5))
|
||||
app.buttons["完成"].tap()
|
||||
waitForChapter("第五章 · 天明")
|
||||
attachScreenshot(app)
|
||||
}
|
||||
|
||||
func testForwardAndReverseFlicksKeepMomentumAcrossChapterInsertion() throws {
|
||||
let (app, reader) = launchReader(shortChapters: false)
|
||||
let initial = try value("chapter", in: reader)
|
||||
for _ in 0..<6 { flick(reader, forward: true) }
|
||||
let forward = try value("chapter", in: reader)
|
||||
XCTAssertGreaterThan(forward, initial + 2)
|
||||
for _ in 0..<6 { flick(reader, forward: false) }
|
||||
XCTAssertLessThan(try value("chapter", in: reader), forward)
|
||||
XCTAssertGreaterThan(try value("decelInsertions", in: reader), 0)
|
||||
XCTAssertGreaterThan(try value("continued", in: reader), 0)
|
||||
XCTAssertEqual(try value("interrupted", in: reader), 0)
|
||||
attachScreenshot(app)
|
||||
}
|
||||
|
||||
func testManyShortChaptersFlowPastTheInitialWindow() throws {
|
||||
let (app, reader) = launchReader(shortChapters: true)
|
||||
let initial = try value("chapter", in: reader)
|
||||
for _ in 0..<4 { flick(reader, forward: true) }
|
||||
let forward = try value("chapter", in: reader)
|
||||
XCTAssertGreaterThan(forward, initial + 5)
|
||||
for _ in 0..<4 { flick(reader, forward: false) }
|
||||
XCTAssertLessThan(try value("chapter", in: reader), forward)
|
||||
XCTAssertEqual(try value("interrupted", in: reader), 0)
|
||||
attachScreenshot(app)
|
||||
}
|
||||
|
||||
private func launchReader(shortChapters: Bool) -> (XCUIApplication, XCUIElement) {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoReader", "-uiScrollRegression", "-pageAnim", "3", "-themeMode", "0"]
|
||||
if shortChapters { app.launchArguments.append("-uiShortChapters") }
|
||||
app.launch()
|
||||
let reader = app.collectionViews["reader.continuous"]
|
||||
XCTAssertTrue(reader.waitForExistence(timeout: 20))
|
||||
let ready = expectation(for: NSPredicate { _, _ in
|
||||
let text = reader.value as? String ?? ""
|
||||
return text.contains("chapter=20 ") && !text.contains("chapters=1 ")
|
||||
}, evaluatedWith: nil)
|
||||
wait(for: [ready], timeout: 20)
|
||||
return (app, reader)
|
||||
}
|
||||
|
||||
private func flick(_ reader: XCUIElement, forward: Bool) {
|
||||
let start = reader.coordinate(withNormalizedOffset: CGVector(dx: 0.65, dy: forward ? 0.8 : 0.2))
|
||||
let end = reader.coordinate(withNormalizedOffset: CGVector(dx: 0.65, dy: forward ? 0.2 : 0.8))
|
||||
start.press(forDuration: 0.01, thenDragTo: end, withVelocity: .fast, thenHoldForDuration: 0)
|
||||
}
|
||||
|
||||
private func value(_ name: String, in reader: XCUIElement) throws -> Int {
|
||||
let fields = (reader.value as? String ?? "").split(separator: " ")
|
||||
let field = try XCTUnwrap(fields.first { $0.hasPrefix(name + "=") })
|
||||
let value = try XCTUnwrap(Int(field.dropFirst(name.count + 1)))
|
||||
print("[ContinuousReader] \(reader.value ?? "")")
|
||||
return value
|
||||
}
|
||||
|
||||
private func attachScreenshot(_ app: XCUIApplication) {
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
final class RuleEditorUITests: XCTestCase {
|
||||
func testSourceFormJSONSwitchKeepsHighlightedTextAndSaves() throws {
|
||||
try exerciseSourceEditor(usesLegacyUI: false)
|
||||
}
|
||||
|
||||
func testLegacySourceFormJSONSwitchKeepsHighlightedTextAndSaves() throws {
|
||||
try exerciseSourceEditor(usesLegacyUI: true)
|
||||
}
|
||||
|
||||
private func exerciseSourceEditor(usesLegacyUI: Bool) throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoTab", "sources"]
|
||||
if usesLegacyUI { app.launchArguments.append("-uiLegacyCompatibility") }
|
||||
app.launch()
|
||||
XCTAssertTrue(app.navigationBars["书源"].waitForExistence(timeout: 20))
|
||||
app.buttons["更多"].firstMatch.tap()
|
||||
app.buttons["新建书源"].tap()
|
||||
XCTAssertTrue(app.navigationBars["新建书源"].waitForExistence(timeout: 10))
|
||||
app.buttons.matching(NSPredicate(format: "label CONTAINS %@", "书源名称")).firstMatch.tap()
|
||||
let editor = app.textViews["syntaxCodeEditor"]
|
||||
XCTAssertTrue(editor.waitForExistence(timeout: 5))
|
||||
editor.tap(); editor.typeText("UI Source")
|
||||
attach(app, name: "Typed source name")
|
||||
XCTAssertEqual(editor.value as? String, "UI Source")
|
||||
app.navigationBars.buttons.element(boundBy: 0).tap()
|
||||
app.buttons.matching(NSPredicate(format: "label CONTAINS %@", "书源地址")).firstMatch.tap()
|
||||
editor.tap(); editor.typeText("https://ui-editor.test")
|
||||
XCTAssertEqual(editor.value as? String, "https://ui-editor.test")
|
||||
app.navigationBars.buttons.element(boundBy: 0).tap()
|
||||
app.segmentedControls.buttons["JSON"].tap()
|
||||
attach(app, name: "Source JSON editor")
|
||||
let json = editor.value as? String ?? ""
|
||||
XCTAssertTrue(json.contains("UI Source"), "Actual JSON: \(json)")
|
||||
XCTAssertTrue(json.contains("https://ui-editor.test"), "Actual JSON: \(json)")
|
||||
app.segmentedControls.buttons["字段编辑"].tap()
|
||||
app.buttons["保存"].tap()
|
||||
if app.alerts.firstMatch.waitForExistence(timeout: 5) { app.alerts.buttons["好"].tap() }
|
||||
XCTAssertTrue(app.staticTexts["UI Source"].waitForExistence(timeout: 10))
|
||||
}
|
||||
|
||||
func testPurificationDraftRunsThroughReaderPipeline() throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoTab", "settings", "-uiDemoRules"]
|
||||
app.launch()
|
||||
XCTAssertTrue(app.buttons["新建规则"].waitForExistence(timeout: 20))
|
||||
app.buttons["新建规则"].tap()
|
||||
app.textFields["名称"].tap(); app.textFields["名称"].typeText("UI Rule")
|
||||
app.buttons.matching(NSPredicate(format: "label CONTAINS %@", "匹配内容")).firstMatch.tap()
|
||||
let editor = app.textViews["syntaxCodeEditor"]
|
||||
XCTAssertTrue(editor.waitForExistence(timeout: 5))
|
||||
editor.tap(); editor.typeText(".")
|
||||
app.navigationBars.buttons.element(boundBy: 0).tap()
|
||||
app.segmentedControls.buttons["测试规则"].tap()
|
||||
app.buttons["运行当前草稿"].tap()
|
||||
XCTAssertTrue(app.staticTexts["规则已生效"].waitForExistence(timeout: 10))
|
||||
XCTAssertTrue(app.staticTexts["(正文为空)"].exists)
|
||||
attach(app, name: "Purification test result")
|
||||
app.buttons["保存"].tap()
|
||||
XCTAssertTrue(app.navigationBars["净化规则"].waitForExistence(timeout: 10))
|
||||
XCTAssertTrue(app.staticTexts["UI Rule"].exists)
|
||||
}
|
||||
|
||||
func testLegacySearchSubmitsAndFiltersSources() throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoTab", "search", "-uiLegacyCompatibility"]
|
||||
app.launch()
|
||||
let search = app.textFields["书名或作者"]
|
||||
XCTAssertTrue(search.waitForExistence(timeout: 20))
|
||||
app.buttons["搜索选项"].tap()
|
||||
app.buttons["分组或书源"].tap()
|
||||
XCTAssertTrue(app.navigationBars["分组或书源"].waitForExistence(timeout: 10))
|
||||
app.segmentedControls.buttons["书源"].tap()
|
||||
let filter = app.textFields["筛选书源"]
|
||||
XCTAssertTrue(filter.waitForExistence(timeout: 5))
|
||||
filter.tap()
|
||||
filter.typeText("no-match")
|
||||
XCTAssertFalse(app.staticTexts["经典文学馆"].exists)
|
||||
app.navigationBars["分组或书源"].buttons["取消"].tap()
|
||||
XCTAssertTrue(search.waitForExistence(timeout: 5))
|
||||
search.tap()
|
||||
search.typeText("legacy-probe\n")
|
||||
XCTAssertTrue(app.buttons["修改关键词"].waitForExistence(timeout: 30))
|
||||
app.buttons["修改关键词"].tap()
|
||||
XCTAssertTrue(app.keyboards.firstMatch.waitForExistence(timeout: 5))
|
||||
attach(app, name: "Legacy search after keyboard submission")
|
||||
}
|
||||
|
||||
func testLegacyAboutCanPresentSystemShareSheet() throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoTab", "settings", "-uiDemoAbout", "-uiLegacyCompatibility"]
|
||||
app.launch()
|
||||
let share = app.buttons["分享版本信息"]
|
||||
XCTAssertTrue(share.waitForExistence(timeout: 20))
|
||||
share.tap()
|
||||
let copy = app.descendants(matching: .any).matching(NSPredicate(format: "label IN %@", ["Copy", "拷贝", "复制"])).firstMatch
|
||||
let appeared = copy.waitForExistence(timeout: 10)
|
||||
if !appeared { print("[Share sheet hierarchy] \(app.debugDescription)") }
|
||||
XCTAssertTrue(appeared)
|
||||
attach(app, name: "Legacy system share sheet")
|
||||
}
|
||||
|
||||
func testLegacyRuleMenuCanExportThroughSystemShareSheet() throws {
|
||||
continueAfterFailure = false
|
||||
let app = XCUIApplication()
|
||||
app.launchArguments = ["-uiDemo", "-uiDemoTab", "settings", "-uiDemoRules", "-uiLegacyCompatibility"]
|
||||
app.launch()
|
||||
XCTAssertTrue(app.navigationBars["净化规则"].waitForExistence(timeout: 20))
|
||||
app.buttons["更多"].tap()
|
||||
app.buttons["导出全部规则"].tap()
|
||||
let copy = app.descendants(matching: .any).matching(NSPredicate(format: "label IN %@", ["Copy", "拷贝", "复制"])).firstMatch
|
||||
let appeared = copy.waitForExistence(timeout: 10)
|
||||
if !appeared { print("[Share sheet hierarchy] \(app.debugDescription)") }
|
||||
XCTAssertTrue(appeared)
|
||||
attach(app, name: "Legacy rule menu export")
|
||||
}
|
||||
|
||||
private func attach(_ app: XCUIApplication, name: String) {
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.name = name
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user