test: 增强 UI 测试基础设施与阅读器自动化测试

- 新增 BookmarkTests/PageNavigationTests/TableOfContentsTests 等阅读器功能测试
- 扩展 AccessibilityIdentifiers 支持更多 UI 元素定位
- XCUIApplication+Launch: 增加启动参数支持(reset state、自定义书籍)
- ReaderAnnotationTests: 完善标注测试覆盖
- ViewController: 支持 --demo-reset-state 参数清理持久化状态
- 新增凡人修仙传测试样本

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-06-02 21:17:04 +08:00
co-authored by Claude Opus 4.7
parent c76beed03c
commit 9801af05d3
11 changed files with 907 additions and 14 deletions
@@ -0,0 +1,82 @@
import XCTest
final class BookmarkTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testBookmarkButtonExistsInToolbar() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let bookmarkButton = app.buttons[IDs.readerBookmark]
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
}
func testToggleBookmark() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let bookmarkButton = app.buttons[IDs.readerBookmark]
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
bookmarkButton.tap()
Thread.sleep(forTimeInterval: 1)
app.showReaderChromeIfNeeded()
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮再次点击后未出现")
bookmarkButton.tap()
}
func testBookmarksButtonVisibleAfterBookmarkAdded() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let bookmarkButton = app.buttons[IDs.readerBookmark]
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
bookmarkButton.tap()
Thread.sleep(forTimeInterval: 1)
app.showReaderChromeIfNeeded()
let bookmarksListButton = app.buttons[IDs.readerBookmarks]
XCTAssertTrue(bookmarksListButton.waitForExistence(timeout: 5), "书签列表按钮未出现")
}
func testBookmarksListButtonExists() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let bookmarksListButton = app.buttons[IDs.readerBookmarks]
XCTAssertTrue(bookmarksListButton.waitForExistence(timeout: 5), "书签列表按钮未出现")
}
func testBookmarksPanelIsAccessible() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let bookmarkButton = app.buttons[IDs.readerBookmark]
XCTAssertTrue(bookmarkButton.waitForExistence(timeout: 5), "书签按钮未出现")
bookmarkButton.tap()
app.showReaderChromeIfNeeded()
let bookmarksListButton = app.buttons[IDs.readerBookmarks]
XCTAssertTrue(bookmarksListButton.waitForExistence(timeout: 5), "书签列表按钮未出现")
let enabledPredicate = NSPredicate(format: "isEnabled == true")
expectation(for: enabledPredicate, evaluatedWith: bookmarksListButton)
waitForExpectations(timeout: 10)
XCTAssertTrue(bookmarksListButton.isEnabled, "书签列表按钮未启用")
bookmarksListButton.tap()
let bookmarksPanel = app.tables[IDs.readerBookmarksTable]
XCTAssertTrue(bookmarksPanel.waitForExistence(timeout: 5), "书签面板未出现")
XCTAssertTrue(app.navigationBars[IDs.readerBookmarksNavBar].waitForExistence(timeout: 2), "书签导航栏未暴露")
}
}
@@ -0,0 +1,55 @@
import XCTest
final class PageNavigationTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testContentAreaExistsForNavigation() {
app.launchAndOpenSampleBook()
app.waitForReader()
let content = app.otherElements[IDs.readerContent]
XCTAssertTrue(content.waitForExistence(timeout: 5), "阅读内容区域未出现")
}
func testPagingViewExistsInScrollMode() {
app.launchAndOpenSampleBook(displayType: "scroll")
app.waitForReader()
app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5)
let paging = app.collectionViews[IDs.readerPaging]
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页滚动视图未出现")
}
func testPagingViewExistsInVerticalScrollMode() {
app.launchAndOpenSampleBook(displayType: "vertical")
app.waitForReader()
app.waitForReaderState(containing: "display=verticalScroll", timeout: 5)
let paging = app.collectionViews[IDs.readerPaging]
XCTAssertTrue(paging.waitForExistence(timeout: 5), "分页滚动视图未出现")
}
func testSwipeInHorizontalScrollMode() {
app.launchAndOpenSampleBook(displayType: "scroll")
app.waitForReader()
app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5)
let paging = app.collectionViews[IDs.readerPaging]
XCTAssertTrue(paging.waitForExistence(timeout: 5))
let state = app.staticTexts[IDs.demoReaderState]
let beforeLabel = state.label
paging.swipeLeft()
let deadline = Date().addingTimeInterval(5)
while Date() < deadline {
if state.exists && state.label != beforeLabel { break }
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
}
XCTAssertNotEqual(beforeLabel, state.label, "水平滑动后页码未变化")
}
}
@@ -0,0 +1,67 @@
import XCTest
final class ReaderAnnotationExtendedTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testBottomToolbarHasAddHighlightButton() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let addHighlightButton = app.buttons[IDs.readerAddHighlight]
XCTAssertTrue(addHighlightButton.waitForExistence(timeout: 5), "底部工具栏标注按钮未出现")
}
func testBottomToolbarHasHighlightsButton() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
let highlightsButton = app.buttons[IDs.readerHighlights]
XCTAssertTrue(highlightsButton.waitForExistence(timeout: 5), "底部工具栏高亮列表按钮未出现")
}
func testBottomToolbarButtonsLayout() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.buttons[IDs.readerToc].waitForExistence(timeout: 5), "目录按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerBookmarks].exists, "书签按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerHighlights].exists, "高亮按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerAddHighlight].exists, "标注按钮未出现")
XCTAssertTrue(app.buttons[IDs.readerSettings].exists, "设置按钮未出现")
}
func testSelectionTextElementExists() {
app.launchAndOpenSampleBook()
app.waitForReader()
let selectionText = app.otherElements[IDs.readerSelectionText].firstMatch
XCTAssertTrue(selectionText.waitForExistence(timeout: 5), "文本选区元素未出现")
}
func testReaderStateReportsPageInfo() {
app.launchAndOpenSampleBook()
app.waitForReader()
let state = app.staticTexts[IDs.demoReaderState]
XCTAssertTrue(state.waitForExistence(timeout: 5))
XCTAssertTrue(state.label.contains("reader=opened"), "状态未包含 reader=opened")
XCTAssertTrue(state.label.contains("page="), "状态未包含 page 信息")
XCTAssertTrue(state.label.contains("display="), "状态未包含 display 信息")
}
func testReaderStateReportsHighlightCount() {
app.launchAndOpenSampleBook()
app.waitForReader()
let state = app.staticTexts[IDs.demoReaderState]
XCTAssertTrue(state.waitForExistence(timeout: 5))
XCTAssertTrue(state.label.contains("highlights=0"), "初始高亮数应为 0")
}
}
@@ -10,13 +10,17 @@ final class ReaderAnnotationTests: XCTestCase {
func testSelectionMenuCreatesHighlight() {
app.launchAndOpenSampleBook(pageNumber: 2)
app.waitForReader()
app.waitForReaderPage(2)
app.hideReaderChromeIfNeeded()
let content = app.otherElements[IDs.readerSelectionText].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 5), "阅读内容区域未出现")
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.42))
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.42))
start.press(forDuration: 0.6, thenDragTo: end)
app.waitForReaderState(containing: "toolbar=hidden", timeout: 5)
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.45))
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.45))
start.press(forDuration: 0.8, thenDragTo: end)
app.waitForReaderState(containing: "selection=1", timeout: 5)
@@ -36,15 +40,19 @@ final class ReaderAnnotationTests: XCTestCase {
func testLongPressSelectionDoesNotShowToolbars() {
app.launchAndOpenSampleBook(pageNumber: 2)
app.waitForReader()
app.waitForReaderPage(2)
app.hideReaderChromeIfNeeded()
let content = app.otherElements[IDs.readerSelectionText].firstMatch
XCTAssertTrue(content.waitForExistence(timeout: 5), "阅读内容区域未出现")
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.42))
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.42))
start.press(forDuration: 0.6, thenDragTo: end)
app.waitForReaderState(containing: "selection=1", timeout: 5)
app.waitForReaderState(containing: "toolbar=hidden", timeout: 5)
let start = content.coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.45))
let end = content.coordinate(withNormalizedOffset: CGVector(dx: 0.68, dy: 0.45))
start.press(forDuration: 0.8, thenDragTo: end)
app.waitForReaderState(containing: "selection=1", timeout: 8)
app.waitForReaderState(containing: "toolbar=hidden", timeout: 3)
}
}
@@ -0,0 +1,79 @@
import XCTest
final class SettingsExtendedTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
private func openSettings() {
app.launchAndOpenSampleBook()
app.waitForReader()
app.showReaderChromeIfNeeded()
XCTAssertTrue(app.buttons[IDs.readerSettings].waitForExistence(timeout: 5))
app.buttons[IDs.readerSettings].tap()
XCTAssertTrue(app.scrollViews[IDs.settingsScroll].waitForExistence(timeout: 5))
}
func testBrightnessSliderExists() {
openSettings()
let slider = app.sliders[IDs.settingsBrightness]
XCTAssertTrue(slider.waitForExistence(timeout: 3), "亮度滑块未出现")
}
func testLineHeightControl() {
openSettings()
let lineHeight = app.segmentedControls[IDs.settingsLineHeight]
XCTAssertTrue(lineHeight.waitForExistence(timeout: 3), "行距控件未出现")
lineHeight.buttons["紧凑"].tap()
lineHeight.buttons["标准"].tap()
lineHeight.buttons["宽松"].tap()
app.buttons[IDs.settingsDone].tap()
XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 5))
}
func testColumnCountControl() {
openSettings()
let columns = app.segmentedControls[IDs.settingsColumns]
XCTAssertTrue(columns.waitForExistence(timeout: 3), "分栏控件未出现")
columns.buttons["单栏"].tap()
columns.buttons["双栏"].tap()
app.buttons[IDs.settingsDone].tap()
XCTAssertTrue(app.buttons[IDs.readerBack].waitForExistence(timeout: 5))
}
func testDisplayTypeFromSettings() {
openSettings()
let displayType = app.segmentedControls[IDs.settingsDisplayType]
XCTAssertTrue(displayType.waitForExistence(timeout: 3), "翻页方式控件未出现")
displayType.buttons["横滑"].tap()
app.buttons[IDs.settingsDone].tap()
app.waitForReaderState(containing: "display=horizontalScroll", timeout: 5)
}
func testAllThemeButtonsExist() {
openSettings()
for index in 0...5 {
let themeButton = app.buttons[IDs.settingsTheme(index)]
XCTAssertTrue(themeButton.waitForExistence(timeout: 3), "主题按钮 \(index) 未出现")
}
}
func testScrollSettingsToReachAllControls() {
openSettings()
let scroll = app.scrollViews[IDs.settingsScroll]
XCTAssertTrue(scroll.waitForExistence(timeout: 3))
XCTAssertTrue(app.sliders[IDs.settingsBrightness].waitForExistence(timeout: 3))
XCTAssertTrue(app.segmentedControls[IDs.settingsLineHeight].waitForExistence(timeout: 3))
XCTAssertTrue(app.segmentedControls[IDs.settingsColumns].waitForExistence(timeout: 3))
XCTAssertTrue(app.segmentedControls[IDs.settingsDisplayType].waitForExistence(timeout: 3))
}
}
@@ -0,0 +1,33 @@
import XCTest
final class TableOfContentsTests: XCTestCase {
private let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
}
func testTocButtonExists() {
app.launchAndOpenSampleBook(bookTitleQuery: "宝山辽墓材料与释读")
app.waitForReader()
app.showReaderChromeIfNeeded()
let tocButton = app.buttons[IDs.readerToc]
XCTAssertTrue(tocButton.waitForExistence(timeout: 5), "目录按钮未出现")
}
func testTocButtonTappable() {
app.launchAndOpenSampleBook(bookTitleQuery: "宝山辽墓材料与释读")
app.waitForReader()
app.showReaderChromeIfNeeded()
let tocButton = app.buttons[IDs.readerToc]
XCTAssertTrue(tocButton.waitForExistence(timeout: 5))
XCTAssertTrue(tocButton.isEnabled, "目录按钮不可点击")
tocButton.tap()
let tocPanel = app.tables[IDs.readerTocTable]
XCTAssertTrue(tocPanel.waitForExistence(timeout: 5), "目录面板未出现")
XCTAssertTrue(app.navigationBars[IDs.readerTocNavBar].waitForExistence(timeout: 2), "目录导航栏未暴露")
}
}