Add 22 new UI tests across 8 test files covering Home, Schedule, Progress, Settings, TabNavigation, TripSaving, and TripOptions. Add accessibility identifiers to 11 view files for test element discovery. Fix sport chip assertion logic (all sports start selected, tap deselects), scroll container issues on iOS 26 nested ScrollViews, toggle interaction, and delete trip flow. Update QA coverage map from 32 to 54 automated test cases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
241 lines
8.6 KiB
Swift
241 lines
8.6 KiB
Swift
//
|
|
// SettingsTests.swift
|
|
// SportsTimeUITests
|
|
//
|
|
// Verifies the Settings screen loads, displays version, and shows all sections.
|
|
// QA Sheet: F-123, F-124, F-125, F-126, F-127, F-128, F-135, F-138, F-139
|
|
//
|
|
|
|
import XCTest
|
|
|
|
final class SettingsTests: BaseUITestCase {
|
|
|
|
/// F-062: Settings shows app version.
|
|
@MainActor
|
|
func testF062_SettingsShowsVersion() {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
settings.assertVersionDisplayed()
|
|
|
|
captureScreenshot(named: "F062-Settings-Version")
|
|
}
|
|
|
|
/// F-063: Settings key sections are present (Subscription, Privacy, About).
|
|
@MainActor
|
|
func testF063_SettingsSectionsPresent() {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
|
|
// Assert: Key sections exist
|
|
XCTAssertTrue(settings.subscriptionSection.waitForExistence(
|
|
timeout: BaseUITestCase.shortTimeout),
|
|
"Subscription section should exist")
|
|
|
|
// SwiftUI List renders as UICollectionView on iOS 26
|
|
settings.privacySection.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(settings.privacySection.exists, "Privacy section should exist")
|
|
|
|
settings.aboutSection.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(settings.aboutSection.exists, "About section should exist")
|
|
}
|
|
|
|
/// F-075: Subscription section shows correct content for current user tier.
|
|
@MainActor
|
|
func testF075_SubscriptionSectionContent() {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
|
|
// Subscription section header should exist
|
|
XCTAssertTrue(settings.subscriptionSection.waitForExistence(
|
|
timeout: BaseUITestCase.shortTimeout),
|
|
"Subscription section should exist")
|
|
|
|
// In debug/UI testing mode, debugProOverride is true → Pro user
|
|
// So we expect "SportsTime Pro" text and no "Upgrade to Pro" button
|
|
let proLabel = app.staticTexts["SportsTime Pro"]
|
|
if proLabel.exists {
|
|
// Pro user path
|
|
XCTAssertTrue(proLabel.exists, "Pro label should be visible for Pro user")
|
|
} else {
|
|
// Free user path — "Upgrade to Pro" and "Restore Purchases" should exist
|
|
settings.upgradeProButton.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(settings.upgradeProButton.exists,
|
|
"Upgrade to Pro button should exist for free user")
|
|
|
|
settings.restorePurchasesButton.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(settings.restorePurchasesButton.exists,
|
|
"Restore Purchases button should exist for free user")
|
|
}
|
|
|
|
captureScreenshot(named: "F075-Settings-Subscription")
|
|
}
|
|
|
|
// MARK: - Appearance Mode (F-125, F-126, F-127)
|
|
|
|
/// Helper: navigates to Settings and scrolls to Appearance section.
|
|
@MainActor
|
|
private func navigateToAppearance() -> SettingsScreen {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
|
|
settings.appearanceSection.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(settings.appearanceSection.exists, "Appearance section should exist")
|
|
return settings
|
|
}
|
|
|
|
/// F-125: Appearance - Light mode can be selected.
|
|
@MainActor
|
|
func testF125_AppearanceLightMode() {
|
|
let settings = navigateToAppearance()
|
|
|
|
let lightBtn = settings.appearanceButton("Light")
|
|
lightBtn.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(lightBtn.exists, "Light mode button should exist")
|
|
lightBtn.tap()
|
|
|
|
captureScreenshot(named: "F125-Appearance-Light")
|
|
}
|
|
|
|
/// F-126: Appearance - Dark mode can be selected.
|
|
@MainActor
|
|
func testF126_AppearanceDarkMode() {
|
|
let settings = navigateToAppearance()
|
|
|
|
let darkBtn = settings.appearanceButton("Dark")
|
|
darkBtn.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(darkBtn.exists, "Dark mode button should exist")
|
|
darkBtn.tap()
|
|
|
|
captureScreenshot(named: "F126-Appearance-Dark")
|
|
}
|
|
|
|
/// F-127: Appearance - System mode can be selected.
|
|
@MainActor
|
|
func testF127_AppearanceSystemMode() {
|
|
let settings = navigateToAppearance()
|
|
|
|
let systemBtn = settings.appearanceButton("System")
|
|
systemBtn.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(systemBtn.exists, "System mode button should exist")
|
|
systemBtn.tap()
|
|
|
|
captureScreenshot(named: "F127-Appearance-System")
|
|
}
|
|
|
|
// MARK: - Toggle Animations (F-128)
|
|
|
|
/// F-128: Toggle animations on/off in Settings.
|
|
@MainActor
|
|
func testF128_ToggleAnimations() {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
|
|
let toggle = settings.animationsToggle
|
|
toggle.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(toggle.exists, "Animations toggle should exist")
|
|
|
|
// Capture initial state
|
|
let initialValue = toggle.value as? String
|
|
|
|
// On iOS 26, switches in List rows need a coordinate-based tap
|
|
// to ensure the tap lands on the switch control itself
|
|
let switchCoord = toggle.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5))
|
|
switchCoord.tap()
|
|
|
|
// Small wait for the toggle animation to complete
|
|
sleep(1)
|
|
|
|
// Value should have changed
|
|
let newValue = toggle.value as? String
|
|
XCTAssertNotEqual(initialValue, newValue,
|
|
"Toggle value should change after tap (was '\(initialValue ?? "nil")', now '\(newValue ?? "nil")')")
|
|
|
|
captureScreenshot(named: "F128-AnimationsToggled")
|
|
}
|
|
|
|
// MARK: - Reset to Defaults (F-138, F-139)
|
|
|
|
/// F-138: Reset to Defaults triggers confirmation and resets settings.
|
|
@MainActor
|
|
func testF138_ResetToDefaults() {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
|
|
settings.resetButton.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
XCTAssertTrue(settings.resetButton.exists, "Reset button should exist")
|
|
settings.resetButton.tap()
|
|
|
|
// Confirmation alert should appear
|
|
let alert = app.alerts.firstMatch
|
|
XCTAssertTrue(alert.waitForExistence(timeout: BaseUITestCase.shortTimeout),
|
|
"Reset confirmation alert should appear")
|
|
|
|
// Confirm the reset
|
|
let confirmButton = alert.buttons["Reset"]
|
|
if confirmButton.exists {
|
|
confirmButton.tap()
|
|
} else {
|
|
// Fallback: tap the destructive action (could be "Reset" or "OK")
|
|
alert.buttons.element(boundBy: 1).tap()
|
|
}
|
|
|
|
captureScreenshot(named: "F138-ResetToDefaults")
|
|
}
|
|
|
|
/// F-139: Reset to Defaults - cancel leaves settings unchanged.
|
|
@MainActor
|
|
func testF139_ResetToDefaultsCancel() {
|
|
let home = HomeScreen(app: app)
|
|
home.waitForLoad()
|
|
home.switchToTab(home.settingsTab)
|
|
|
|
let settings = SettingsScreen(app: app)
|
|
settings.assertLoaded()
|
|
|
|
settings.resetButton.scrollIntoView(in: app.collectionViews.firstMatch)
|
|
settings.resetButton.tap()
|
|
|
|
// Confirmation alert should appear
|
|
let alert = app.alerts.firstMatch
|
|
XCTAssertTrue(alert.waitForExistence(timeout: BaseUITestCase.shortTimeout),
|
|
"Reset confirmation alert should appear")
|
|
|
|
// Cancel the reset
|
|
let cancelButton = alert.buttons["Cancel"]
|
|
XCTAssertTrue(cancelButton.exists, "Cancel button should exist on alert")
|
|
cancelButton.tap()
|
|
|
|
// Settings screen should still be visible — check reset button
|
|
// (it's already in view since we just tapped it)
|
|
XCTAssertTrue(settings.resetButton.waitForExistence(
|
|
timeout: BaseUITestCase.shortTimeout),
|
|
"Settings should still be displayed after cancelling reset")
|
|
|
|
captureScreenshot(named: "F139-ResetCancel")
|
|
}
|
|
}
|