test: add accessibility identifiers along the onboarding-to-residence-detail path

Scaffolding for the gitea#2 regression XCUITest. No user-visible
change — pure metadata for UI automation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-25 09:33:47 -05:00
parent cb4806b423
commit f5f02145a2
5 changed files with 35 additions and 0 deletions
@@ -48,6 +48,9 @@ struct AccessibilityIdentifiers {
static let addButton = "Residence.AddButton" static let addButton = "Residence.AddButton"
static let residencesList = "Residence.List" static let residencesList = "Residence.List"
static let residenceCard = "Residence.Card" static let residenceCard = "Residence.Card"
/// Prefix for individual residence cells in the list. Suffix with the
/// residence id to address a specific cell (e.g. "Residence.Cell.42").
static let cellPrefix = "Residence.Cell"
static let emptyStateView = "Residence.EmptyState" static let emptyStateView = "Residence.EmptyState"
static let emptyStateButton = "Residence.EmptyState.AddButton" static let emptyStateButton = "Residence.EmptyState.AddButton"
@@ -87,7 +90,15 @@ struct AccessibilityIdentifiers {
static let refreshButton = "Task.RefreshButton" static let refreshButton = "Task.RefreshButton"
static let tasksList = "Task.List" static let tasksList = "Task.List"
static let taskCard = "Task.Card" static let taskCard = "Task.Card"
/// Prefix for individual task rows. Suffix with the task id to
/// address a specific row (e.g. "Task.Row.42"). Use `BEGINSWITH`
/// in tests to detect "any task row exists".
static let rowPrefix = "Task.Row"
static let emptyStateView = "Task.EmptyState" static let emptyStateView = "Task.EmptyState"
/// Label rendered when a residence-detail tasks section has no tasks
/// in any kanban column. Asserted ABSENT after onboarding bulk-create
/// in the gitea#2 regression test.
static let noTasksLabel = "Task.NoTasksLabel"
static let kanbanView = "Task.KanbanView" static let kanbanView = "Task.KanbanView"
static let overdueColumn = "Task.Column.Overdue" static let overdueColumn = "Task.Column.Overdue"
static let upcomingColumn = "Task.Column.Upcoming" static let upcomingColumn = "Task.Column.Upcoming"
@@ -229,8 +240,24 @@ struct AccessibilityIdentifiers {
static let taskSelectionCounter = "Onboarding.TaskSelectionCounter" static let taskSelectionCounter = "Onboarding.TaskSelectionCounter"
static let addPopularTasksButton = "Onboarding.AddPopularTasksButton" static let addPopularTasksButton = "Onboarding.AddPopularTasksButton"
static let addTasksContinueButton = "Onboarding.AddTasksContinueButton" static let addTasksContinueButton = "Onboarding.AddTasksContinueButton"
/// Submit/continue button at the bottom of the First-Task screen.
/// Triggers `POST /api/tasks/bulk/` for the selected templates.
static let submitTasksButton = "Onboarding.SubmitTasksButton"
/// Tab bar control above the task list. The "Browse All" segment is
/// addressed via `app.buttons["Browse All"]` from the segmented
/// picker once this identifier is set.
static let firstTaskTabBar = "Onboarding.FirstTaskTabBar"
/// Tab segment that shows the full template catalog.
/// Tap from a test by addressing the Picker's segment label
/// "Browse All" within the element identified above.
static let browseAllTab = "Onboarding.BrowseAllTab"
static let taskCategorySection = "Onboarding.TaskCategorySection" static let taskCategorySection = "Onboarding.TaskCategorySection"
static let taskTemplateRow = "Onboarding.TaskTemplateRow" static let taskTemplateRow = "Onboarding.TaskTemplateRow"
/// Prefix for individual template rows on the First-Task screen
/// (Browse All tab). Suffix with the backend template id
/// e.g. `"Onboarding.TemplateRow.123"`. Tests use `BEGINSWITH` to
/// pick the first N rows deterministically without knowing ids.
static let templateRowPrefix = "Onboarding.TemplateRow"
// Subscription Screen // Subscription Screen
static let subscriptionTitle = "Onboarding.SubscriptionTitle" static let subscriptionTitle = "Onboarding.SubscriptionTitle"
@@ -136,6 +136,7 @@ struct OnboardingFirstTaskContent: View {
OnboardingTaskTabBar(selectedTab: $selectedTab) OnboardingTaskTabBar(selectedTab: $selectedTab)
.padding(.horizontal, OrganicSpacing.comfortable) .padding(.horizontal, OrganicSpacing.comfortable)
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.firstTaskTabBar)
switch selectedTab { switch selectedTab {
case .forYou: case .forYou:
@@ -362,6 +363,7 @@ struct OnboardingFirstTaskContent: View {
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.naturalShadow(selectedCount > 0 ? .medium : .subtle) .naturalShadow(selectedCount > 0 ? .medium : .subtle)
} }
.accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.submitTasksButton)
.disabled(vm.isSubmitting) .disabled(vm.isSubmitting)
.animation(.easeInOut(duration: 0.2), value: selectedCount) .animation(.easeInOut(duration: 0.2), value: selectedCount)
} }
@@ -631,6 +633,7 @@ private struct OnboardingSuggestionRow: View {
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier("\(AccessibilityIdentifiers.Onboarding.templateRowPrefix).\(suggestion.template.id)")
.accessibilityLabel("\(suggestion.template.title), \(suggestion.template.frequencyDisplay), \(relevancePercent)% match") .accessibilityLabel("\(suggestion.template.title), \(suggestion.template.frequencyDisplay), \(relevancePercent)% match")
.accessibilityValue(isSelected ? "selected" : "not selected") .accessibilityValue(isSelected ? "selected" : "not selected")
} }
@@ -776,6 +779,7 @@ private struct OnboardingTemplateRow: View {
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.accessibilityIdentifier("\(AccessibilityIdentifiers.Onboarding.templateRowPrefix).\(template.id)")
.accessibilityLabel("\(template.title), \(template.frequencyLabel)") .accessibilityLabel("\(template.title), \(template.frequencyLabel)")
.accessibilityValue(isSelected ? "selected" : "not selected") .accessibilityValue(isSelected ? "selected" : "not selected")
} }
@@ -231,6 +231,7 @@ private struct ResidencesContent: View {
.padding(.horizontal, 16) .padding(.horizontal, 16)
} }
.buttonStyle(OrganicCardButtonStyle()) .buttonStyle(OrganicCardButtonStyle())
.accessibilityIdentifier("\(AccessibilityIdentifiers.Residence.cellPrefix).\(residence.id)")
.transition(.asymmetric( .transition(.asymmetric(
insertion: .opacity.combined(with: .move(edge: .bottom)), insertion: .opacity.combined(with: .move(edge: .bottom)),
removal: .opacity removal: .opacity
@@ -122,6 +122,7 @@ struct DynamicTaskCard: View {
.cornerRadius(12) .cornerRadius(12)
.shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2) .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)
.simultaneousGesture(TapGesture(), including: .subviews) .simultaneousGesture(TapGesture(), including: .subviews)
.accessibilityIdentifier("\(AccessibilityIdentifiers.Task.rowPrefix).\(task.id)")
.sheet(isPresented: $showCompletionHistory) { .sheet(isPresented: $showCompletionHistory) {
CompletionHistorySheet( CompletionHistorySheet(
taskTitle: task.title, taskTitle: task.title,
@@ -22,6 +22,8 @@ struct EmptyTasksView: View {
.background(Color.appBackgroundSecondary) .background(Color.appBackgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.naturalShadow(.subtle) .naturalShadow(.subtle)
.accessibilityElement(children: .combine)
.accessibilityIdentifier(AccessibilityIdentifiers.Task.noTasksLabel)
} }
} }