// // CustomizeScreen.swift // Tests iOS // // Screen object for the Customize sub-tab — theme, voting layout, and day view style pickers. // import XCTest struct CustomizeScreen { let app: XCUIApplication private let defaultTimeout: TimeInterval = 2 private let navigationTimeout: TimeInterval = 5 // MARK: - Elements func themeButton(named name: String) -> XCUIElement { app.buttons[UITestID.Customize.themeButton(name)] } func votingLayoutButton(named name: String) -> XCUIElement { app.buttons[UITestID.Customize.votingLayoutButton(name)] } func dayViewStyleButton(named name: String) -> XCUIElement { app.buttons[UITestID.Customize.dayStyleButton(name)] } func iconPackButton(named name: String) -> XCUIElement { app.buttons[UITestID.Customize.iconPackButton(name)] } func personalityPackButton(named name: String) -> XCUIElement { app.element(UITestID.Customize.personalityPackButton(name)) } func appThemeCard(named name: String) -> XCUIElement { app.element(UITestID.Customize.appThemeCard(name)) } // MARK: - Actions /// Select a button in a horizontal picker. Scrolls vertically to reveal /// the section, then scrolls horizontally to find the button. private func selectHorizontalPickerButton( _ button: XCUIElement, file: StaticString = #filePath, line: UInt = #line ) { // Already visible and hittable if button.waitForExistence(timeout: 1) && button.isHittable { button.forceTap(file: file, line: line) return } // Phase 1: Scroll settings page vertically to reveal the section for _ in 0..<5 { if button.exists && button.isHittable { button.forceTap(file: file, line: line) return } app.swipeUp() } // Phase 2: Button exists in tree but is off-screen in a horizontal ScrollView. // Simple left swipes on the app to scroll horizontally. if button.exists { for _ in 0..<8 { if button.isHittable { button.forceTap(file: file, line: line) return } app.swipeLeft() } } // Phase 3: Try scrolling right (button may be before current position) for _ in 0..<4 { if button.exists && button.isHittable { button.forceTap(file: file, line: line) return } app.swipeRight() } XCTFail("Could not find or tap button: \(button)", file: file, line: line) } func selectTheme(_ name: String, file: StaticString = #filePath, line: UInt = #line) { selectHorizontalPickerButton(themeButton(named: name), file: file, line: line) } func selectVotingLayout(_ name: String, file: StaticString = #filePath, line: UInt = #line) { selectHorizontalPickerButton(votingLayoutButton(named: name), file: file, line: line) } func selectDayViewStyle(_ name: String, file: StaticString = #filePath, line: UInt = #line) { selectHorizontalPickerButton(dayViewStyleButton(named: name), file: file, line: line) } func selectIconPack(_ name: String, file: StaticString = #filePath, line: UInt = #line) { let button = iconPackButton(named: name) button.scrollIntoView(in: app.scrollViews.firstMatch, direction: .up, maxSwipes: 5, file: file, line: line) button.forceTap(file: file, line: line) } func selectPersonalityPack(_ name: String, file: StaticString = #filePath, line: UInt = #line) { let button = personalityPackButton(named: name) button.scrollIntoView(in: app.scrollViews.firstMatch, direction: .up, maxSwipes: 5, file: file, line: line) button.forceTap(file: file, line: line) } @discardableResult func openThemePicker(file: StaticString = #filePath, line: UInt = #line) -> CustomizeScreen { let browseButton = app.element(UITestID.Settings.browseThemesButton) browseButton .waitUntilHittableOrFail(timeout: defaultTimeout, message: "Browse Themes button should be hittable", file: file, line: line) .forceTap(file: file, line: line) appThemeCard(named: "Zen Garden") .waitForExistenceOrFail(timeout: navigationTimeout, message: "Theme picker should show cards", file: file, line: line) return self } // MARK: - Assertions func assertThemeButtonExists(_ name: String, file: StaticString = #filePath, line: UInt = #line) { themeButton(named: name) .waitForExistenceOrFail(timeout: defaultTimeout, message: "Theme button '\(name)' should exist", file: file, line: line) } }