import XCTest /// Screenshot every chapter of the textbook — one top + one bottom frame each — /// so you can visually audit parsing / rendering issues across all 30 chapters. final class AllChaptersScreenshotTests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = true } func testScreenshotEveryChapter() throws { let app = XCUIApplication() app.launchArguments += ["-onboardingComplete", "YES"] app.launch() let courseTab = app.tabBars.buttons["Course"] XCTAssertTrue(courseTab.waitForExistence(timeout: 5)) courseTab.tap() let textbookRow = app.buttons.containing(NSPredicate( format: "label CONTAINS[c] 'Complete Spanish'" )).firstMatch XCTAssertTrue(textbookRow.waitForExistence(timeout: 5)) textbookRow.tap() // NOTE: SwiftUI List preserves scroll position across navigation pushes, // so visiting chapters in-order means the next one is already visible // after we return from the previous one. No need to reset. attach(app, name: "00-chapter-list-top") for chapter in 1...30 { guard let row = findChapterRow(app: app, chapter: chapter) else { XCTFail("Chapter \(chapter) row not reachable") continue } row.tap() // Chapter body — wait until the chapter's title appears as a nav bar label _ = app.navigationBars.firstMatch.waitForExistence(timeout: 3) attach(app, name: String(format: "ch%02d-top", chapter)) // One big scroll to sample the bottom of the chapter dragFullScreen(app, direction: .up) dragFullScreen(app, direction: .up) attach(app, name: String(format: "ch%02d-bottom", chapter)) tapNavBack(app) // Small settle wait _ = app.navigationBars.firstMatch.waitForExistence(timeout: 2) } } // MARK: - Helpers private enum DragDirection { case up, down } private func dragFullScreen(_ app: XCUIApplication, direction: DragDirection) { let top = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.12)) let bot = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.88)) switch direction { case .up: bot.press(forDuration: 0.1, thenDragTo: top) case .down: top.press(forDuration: 0.1, thenDragTo: bot) } } private func findChapterRow(app: XCUIApplication, chapter: Int) -> XCUIElement? { // Chapter row accessibility label: ", , ..." (SwiftUI composes // label from inner Texts). Match by starting number. let predicate = NSPredicate(format: "label BEGINSWITH %@", "\(chapter),") let row = app.buttons.matching(predicate).firstMatch if row.exists && row.isHittable { return row } // Scroll down up to 8 times searching for the row — chapters visited // in order, so usually 0–2 swipes suffice. for _ in 0..<8 { if row.exists && row.isHittable { return row } dragFullScreen(app, direction: .up) } return row.exists ? row : nil } private func tapNavBack(_ app: XCUIApplication) { let back = app.navigationBars.buttons.firstMatch if back.exists && back.isHittable { back.tap() } } private func attach(_ app: XCUIApplication, name: String) { let screenshot = app.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.name = name attachment.lifetime = .keepAlways add(attachment) } }