// // SettingsScreen.swift // Tests iOS // // Screen object for the Settings tab (Customize + Settings sub-tabs). // import XCTest struct SettingsScreen { let app: XCUIApplication private let defaultTimeout: TimeInterval = 2 private let navigationTimeout: TimeInterval = 5 // MARK: - Elements var settingsHeader: XCUIElement { app.element(UITestID.Settings.header) } var customizeSegment: XCUIElement { app.element(UITestID.Settings.customizeTab) } var settingsSegment: XCUIElement { app.element(UITestID.Settings.settingsTab) } var upgradeBanner: XCUIElement { app.element(UITestID.Settings.upgradeBanner) } var subscribeButton: XCUIElement { app.element(UITestID.Settings.subscribeButton) } var whyUpgradeButton: XCUIElement { app.element(UITestID.Settings.whyUpgradeButton) } var browseThemesButton: XCUIElement { app.element(UITestID.Settings.browseThemesButton) } var clearDataButton: XCUIElement { app.element(UITestID.Settings.clearDataButton) } var analyticsToggle: XCUIElement { app.element(UITestID.Settings.analyticsToggle) } var eulaButton: XCUIElement { app.element(UITestID.Settings.eulaButton) } var privacyPolicyButton: XCUIElement { app.element(UITestID.Settings.privacyPolicyButton) } // MARK: - Actions func tapCustomizeTab(file: StaticString = #filePath, line: UInt = #line) { tapSegment(identifier: UITestID.Settings.customizeTab, fallbackLabel: "Customize", file: file, line: line) } func tapSettingsTab(file: StaticString = #filePath, line: UInt = #line) { tapSegment(identifier: UITestID.Settings.settingsTab, fallbackLabel: "Settings", file: file, line: line) } private func tapSegment(identifier: String, fallbackLabel: String, file: StaticString, line: UInt) { // Try accessibility ID on the descendant element (SwiftUI puts IDs on Text inside Picker) let byID = app.element(identifier) if byID.waitForExistence(timeout: defaultTimeout) { if byID.isHittable { byID.tap() return } // Element exists but not hittable — try coordinate tap byID.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() return } // Fallback: segmented control button by label let segButton = app.segmentedControls.buttons[fallbackLabel] if segButton.waitForExistence(timeout: defaultTimeout) { segButton.tap() return } // Last fallback: find buttons matching label that are NOT in tab bar let allButtons = app.buttons.matching(NSPredicate(format: "label == %@", fallbackLabel)).allElementsBoundByIndex let tabBarButton = app.tabBars.buttons[fallbackLabel] for button in allButtons { if button.frame != tabBarButton.frame && button.isHittable { button.tap() return } } XCTFail("Could not find segment '\(fallbackLabel)' by ID or label", file: file, line: line) } func tapClearData(file: StaticString = #filePath, line: UInt = #line) { clearDataButton.scrollIntoView(in: app, direction: .up, maxSwipes: 5, file: file, line: line) clearDataButton.forceTap(file: file, line: line) } func tapAnalyticsToggle(file: StaticString = #filePath, line: UInt = #line) { analyticsToggle.scrollIntoView(in: app, direction: .up, maxSwipes: 5, file: file, line: line) analyticsToggle.forceTap(file: file, line: line) } // MARK: - Assertions @discardableResult func assertVisible(file: StaticString = #filePath, line: UInt = #line) -> SettingsScreen { settingsHeader.waitForExistenceOrFail(timeout: navigationTimeout, message: "Settings header should be visible", file: file, line: line) return self } func assertUpgradeBannerVisible(file: StaticString = #filePath, line: UInt = #line) { upgradeBanner.waitForExistenceOrFail(timeout: defaultTimeout, message: "Upgrade banner should be visible", file: file, line: line) } func assertUpgradeBannerHidden(file: StaticString = #filePath, line: UInt = #line) { upgradeBanner.waitForNonExistence(timeout: navigationTimeout, message: "Upgrade banner should be hidden (subscribed)", file: file, line: line) } }