feat: expand XCUITest coverage to 54 QA scenarios with accessibility IDs and fix test failures
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>
This commit is contained in:
@@ -212,6 +212,7 @@ struct SportProgressButton: View {
|
||||
.accessibilityValue(isSelected ? "Selected" : "Not selected")
|
||||
.accessibilityAddTraits(.isButton)
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
.accessibilityIdentifier("progress.sport.\(sport.rawValue.lowercased())")
|
||||
.simultaneousGesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { _ in
|
||||
|
||||
@@ -45,6 +45,7 @@ struct HomeView: View {
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
.accessibilityLabel("Create new trip")
|
||||
.accessibilityIdentifier("home.createNewTripButton")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,6 +649,7 @@ struct SavedTripsListView: View {
|
||||
SavedTripListRow(trip: trip)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier("myTrips.trip.\(index)")
|
||||
.staggeredAnimation(index: index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,7 @@ struct HomeContent_Classic: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("home.featuredTripsSection")
|
||||
} else if let error = suggestedTripsGenerator.error {
|
||||
// Error state
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
@@ -238,6 +239,7 @@ struct HomeContent_Classic: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("home.recentTripsSection")
|
||||
}
|
||||
|
||||
private func classicTripCard(savedTrip: SavedTrip, trip: Trip) -> some View {
|
||||
@@ -319,6 +321,7 @@ struct HomeContent_Classic: View {
|
||||
.stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("home.tipsSection")
|
||||
}
|
||||
|
||||
private func classicTipRow(icon: String, title: String, subtitle: String) -> some View {
|
||||
|
||||
@@ -170,6 +170,7 @@ struct HomeContent_ClassicAnimated: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("home.featuredTripsSection")
|
||||
} else if let error = suggestedTripsGenerator.error {
|
||||
// Error state
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
@@ -238,6 +239,7 @@ struct HomeContent_ClassicAnimated: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("home.recentTripsSection")
|
||||
}
|
||||
|
||||
private func classicTripCard(savedTrip: SavedTrip, trip: Trip) -> some View {
|
||||
@@ -319,6 +321,7 @@ struct HomeContent_ClassicAnimated: View {
|
||||
.stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("home.tipsSection")
|
||||
}
|
||||
|
||||
private func classicTipRow(icon: String, title: String, subtitle: String) -> some View {
|
||||
|
||||
@@ -30,6 +30,7 @@ struct PaywallView: View {
|
||||
Text("Upgrade to Pro")
|
||||
.font(.largeTitle.bold())
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.accessibilityIdentifier("paywall.title")
|
||||
|
||||
Text("Unlock the full SportsTime experience")
|
||||
.font(.body)
|
||||
|
||||
@@ -27,6 +27,7 @@ struct PollsListView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle("Group Polls")
|
||||
.accessibilityIdentifier("polls.list")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button {
|
||||
|
||||
@@ -107,6 +107,7 @@ struct ProgressTabView: View {
|
||||
VStack(spacing: Theme.Spacing.lg) {
|
||||
// League Selector
|
||||
leagueSelector
|
||||
.accessibilityIdentifier("progress.sportSelector")
|
||||
.staggeredAnimation(index: 0)
|
||||
|
||||
// Progress Summary Card
|
||||
@@ -212,6 +213,7 @@ struct ProgressTabView: View {
|
||||
Text("Stadium Quest")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
.accessibilityIdentifier("progress.stadiumQuest")
|
||||
|
||||
if progress.isComplete {
|
||||
HStack(spacing: 4) {
|
||||
@@ -330,6 +332,7 @@ struct ProgressTabView: View {
|
||||
Text("Achievements")
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.accessibilityIdentifier("progress.achievementsTitle")
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -398,6 +401,7 @@ struct ProgressTabView: View {
|
||||
Text("Recent Visits")
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.accessibilityIdentifier("progress.recentVisitsTitle")
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
@@ -147,8 +147,10 @@ struct ScheduleListView: View {
|
||||
viewModel.resetFilters()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.accessibilityIdentifier("schedule.resetFiltersButton")
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("schedule.emptyState")
|
||||
}
|
||||
|
||||
// MARK: - Loading State
|
||||
@@ -221,6 +223,7 @@ struct SportFilterChip: View {
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier("schedule.sport.\(sport.rawValue.lowercased())")
|
||||
.accessibilityValue(isSelected ? "Selected" : "Not selected")
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ struct SettingsView: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityAddTraits(AppearanceManager.shared.currentMode == mode ? .isSelected : [])
|
||||
.accessibilityIdentifier("settings.appearance.\(mode.rawValue)")
|
||||
}
|
||||
} header: {
|
||||
Text("Appearance")
|
||||
@@ -228,6 +229,7 @@ struct SettingsView: View {
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("settings.animationsToggle")
|
||||
} header: {
|
||||
Text("Home Screen")
|
||||
}
|
||||
@@ -414,6 +416,7 @@ struct SettingsView: View {
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("settings.resetButton")
|
||||
}
|
||||
.listRowBackground(Theme.cardBackground(colorScheme))
|
||||
}
|
||||
@@ -495,6 +498,7 @@ struct SettingsView: View {
|
||||
Label("Sync Now", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
.disabled(isSyncActionInProgress)
|
||||
.accessibilityIdentifier("settings.syncNowButton")
|
||||
|
||||
Button {
|
||||
showSyncLogs = true
|
||||
@@ -784,6 +788,7 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier("settings.upgradeProButton")
|
||||
|
||||
Button {
|
||||
Task {
|
||||
@@ -792,6 +797,7 @@ struct SettingsView: View {
|
||||
} label: {
|
||||
Label("Restore Purchases", systemImage: "arrow.clockwise")
|
||||
}
|
||||
.accessibilityIdentifier("settings.restorePurchasesButton")
|
||||
}
|
||||
} header: {
|
||||
Text("Subscription")
|
||||
|
||||
@@ -170,6 +170,7 @@ struct TripDetailView: View {
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
.accessibilityLabel("Export trip as PDF")
|
||||
.accessibilityIdentifier("tripDetail.pdfExportButton")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +473,7 @@ struct TripDetailView: View {
|
||||
StatPill(icon: "car", value: trip.formattedTotalDriving)
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("tripDetail.statsRow")
|
||||
}
|
||||
|
||||
// MARK: - Score Card
|
||||
|
||||
@@ -68,6 +68,7 @@ struct ReviewStep: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.accessibilityIdentifier("wizard.missingFieldsWarning")
|
||||
}
|
||||
|
||||
Button(action: onPlan) {
|
||||
|
||||
Reference in New Issue
Block a user