From 23ce41ec119eaa9592bb5c7600a18a334506baf2 Mon Sep 17 00:00:00 2001 From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com> Date: Wed, 15 Feb 2023 05:53:04 +0000 Subject: [PATCH] Align accessibility IDs with elementX android (#567) * Rename onboarding and login user/pass accessibility ids. * Refactor remaining server change accessibility IDs * Avoid accessibilityIdentifier spreading to all decorations on the textBox. Required because otherwise there are multiple items tagged with the accessibilityId which means we can't click on "the" item. * Move all accessibility identifiers to `AccessibilityIdentifiers.swft` - use same naming convention on all of them - remove the unused ones - fix build errors in integration tests and invalid identifiers (still broken until autodiscovery lands on rosa) --------- Co-authored-by: Stefan Ceriu --- .../TextFields/ElementTextFieldStyle.swift | 12 ++- .../Other/AccessibilityIdentifiers.swift | 89 +++++++++++++++++++ .../View/AnalyticsPrompt.swift | 2 - .../LoginScreen/View/LoginScreen.swift | 12 ++- .../View/LoginServerInfoSection.swift | 2 +- .../View/ServerSelectionScreen.swift | 8 +- .../SoftLogout/View/SoftLogoutScreen.swift | 19 ++-- .../BugReport/View/BugReportScreen.swift | 10 +-- .../View/EmojiPickerScreen.swift | 1 - .../Screens/HomeScreen/View/HomeScreen.swift | 2 +- .../HomeScreen/View/HomeScreenRoomCell.swift | 2 +- .../View/OnboardingScreen.swift | 4 +- .../RoomDetails/View/RoomDetailsScreen.swift | 2 - .../RoomScreen/View/RoomHeaderView.swift | 4 +- .../View/SessionVerificationScreen.swift | 17 ++-- .../Settings/View/SettingsScreen.swift | 2 - IntegrationTests/Sources/LoginTests.swift | 23 ++--- IntegrationTests/SupportingFiles/target.yml | 2 + .../AuthenticationCoordinatorUITests.swift | 28 +++--- UITests/Sources/BugReportUITests.swift | 16 ++-- UITests/Sources/LoginScreenUITests.swift | 8 +- UITests/Sources/OnboardingUITests.swift | 2 +- .../Sources/RoomDetailsScreenUITests.swift | 4 +- UITests/Sources/RoomScreenUITests.swift | 8 +- UITests/Sources/ServerSelectionUITests.swift | 22 +++-- .../Sources/SessionVerificationUITests.swift | 36 ++++---- UITests/Sources/SoftLogoutUITests.swift | 16 ++-- UITests/Sources/UserSessionScreenTests.swift | 2 +- UITests/SupportingFiles/target.yml | 1 + 29 files changed, 215 insertions(+), 141 deletions(-) create mode 100644 ElementX/Sources/Other/AccessibilityIdentifiers.swift diff --git a/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift b/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift index d40d76af6..8c4af6fb3 100644 --- a/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift +++ b/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift @@ -21,8 +21,9 @@ import SwiftUI public extension TextFieldStyle where Self == ElementTextFieldStyle { static func elementInput(labelText: String? = nil, footerText: String? = nil, - isError: Bool = false) -> ElementTextFieldStyle { - ElementTextFieldStyle(labelText: labelText, footerText: footerText, isError: isError) + isError: Bool = false, + accessibilityIdentifier: String? = nil) -> ElementTextFieldStyle { + ElementTextFieldStyle(labelText: labelText, footerText: footerText, isError: isError, accessibilityIdentifier: accessibilityIdentifier) } } @@ -38,6 +39,7 @@ public struct ElementTextFieldStyle: TextFieldStyle { public let labelText: String? public let footerText: String? public let isError: Bool + public let accessibilityIdentifier: String? /// The color of the text field's border. private var borderColor: Color { @@ -91,10 +93,11 @@ public struct ElementTextFieldStyle: TextFieldStyle { /// - labelText: The text shown in the label above the field. /// - footerText: The text shown in the footer label below the field. /// - isError: Whether or not the text field is currently in the error state. - public init(labelText: String? = nil, footerText: String? = nil, isError: Bool = false) { + public init(labelText: String? = nil, footerText: String? = nil, isError: Bool = false, accessibilityIdentifier: String? = nil) { self.labelText = labelText self.footerText = footerText self.isError = isError + self.accessibilityIdentifier = accessibilityIdentifier } public func _body(configuration: TextField<_Label>) -> some View { @@ -128,8 +131,9 @@ public struct ElementTextFieldStyle: TextFieldStyle { textField.clearButtonMode = .whileEditing textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor: UIColor(placeholderColor)]) + textField.accessibilityIdentifier = accessibilityIdentifier } - + if let footerText { Text(footerText) .font(.element.caption1) diff --git a/ElementX/Sources/Other/AccessibilityIdentifiers.swift b/ElementX/Sources/Other/AccessibilityIdentifiers.swift new file mode 100644 index 000000000..8fe0fbfd5 --- /dev/null +++ b/ElementX/Sources/Other/AccessibilityIdentifiers.swift @@ -0,0 +1,89 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +struct A11yIdentifiers { + static let bugReportScreen = BugReportScreen() + static let changeServerScreen = ChangeServer() + static let homeScreen = HomeScreen() + static let loginScreen = LoginScreen() + static let onboardingScreen = OnboardingScreen() + static let roomScreen = RoomScreen() + static let sessionVerificationScreen = SessionVerificationScreen() + static let softLogoutScreen = SoftLogoutScreen() + + struct BugReportScreen { + let report = "bug_report-report" + let sendLogs = "bug_report-send_logs" + let screenshot = "bug_report-screenshot" + let removeScreenshot = "bug_report-remove_screenshot" + let send = "bug_report-send" + } + + struct ChangeServer { + let server = "change_server-server" + let `continue` = "change_server-continue" + let dismiss = "change_server-dismiss" + } + + struct HomeScreen { + let userAvatar = "home_screen-user_avatar" + + func roomName(_ name: String) -> String { + "home_screen-room_name:\(name)" + } + } + + struct LoginScreen { + let emailUsername = "login-email_username" + let password = "login-password" + let `continue` = "login-continue" + let changeServer = "login-change_server" + let oidc = "login-oidc" + let unsupportedServer = "login-unsupported_server" + } + + struct OnboardingScreen { + let signIn = "onboarding-sign_in" + let hidden = "onboarding-hidden" + } + + struct RoomScreen { + let name = "room_name" + let avatar = "room_avatar" + } + + struct SessionVerificationScreen { + let requestVerification = "session_verification-request_verification" + let startSasVerification = "session_verification-start_sas_verification" + let acceptChallenge = "session_verification-accept_challenge" + let declineChallenge = "session_verification-accept_challenge" + let close = "session_verification-close" + } + + struct SoftLogoutScreen { + let title = "soft_logout-title" + let message = "soft_logout-message" + let password = "soft_logout-password" + let forgotPassword = "soft_logout-forgot_password" + let next = "soft_logout-next" + let unsupportedServer = "soft_logout-unsupported_server" + let clearDataTitle = "soft_logout-clear_data_title" + let clearDataMessage = "soft_logout-clear_data_message" + let clearData = "soft_logout-clear_data" + } +} diff --git a/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift b/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift index c157f6f3b..301779a68 100644 --- a/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift +++ b/ElementX/Sources/Screens/AnalyticsPrompt/View/AnalyticsPrompt.swift @@ -99,14 +99,12 @@ struct AnalyticsPrompt: View { .font(.element.bodyBold) } .buttonStyle(.elementAction(.xLarge)) - .accessibilityIdentifier("enableButton") Button { context.send(viewAction: .disable) } label: { Text(ElementL10n.actionNotNow) .font(.element.bodyBold) .padding(12) } - .accessibilityIdentifier("disableButton") } } } diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift index 3079c2ebc..c4cc8638b 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift @@ -77,23 +77,21 @@ struct LoginScreen: View { TextField(ElementL10n.loginSigninUsernameHint, text: $context.username) .focused($isUsernameFocused) - .textFieldStyle(.elementInput()) + .textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.emailUsername)) .disableAutocorrection(true) .textContentType(.username) .autocapitalization(.none) .submitLabel(.next) .onChange(of: isUsernameFocused, perform: usernameFocusChanged) .onSubmit { isPasswordFocused = true } - .accessibilityIdentifier("usernameTextField") .padding(.bottom, 20) SecureField(ElementL10n.loginSignupPasswordHint, text: $context.password) .focused($isPasswordFocused) - .textFieldStyle(.elementInput()) + .textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.password)) .textContentType(.password) .submitLabel(.done) .onSubmit(submit) - .accessibilityIdentifier("passwordTextField") Spacer().frame(height: 32) @@ -102,7 +100,7 @@ struct LoginScreen: View { } .buttonStyle(.elementAction(.xLarge)) .disabled(!context.viewState.canSubmit) - .accessibilityIdentifier("nextButton") + .accessibilityIdentifier(A11yIdentifiers.loginScreen.continue) } } @@ -112,7 +110,7 @@ struct LoginScreen: View { Text(ElementL10n.loginContinue) } .buttonStyle(.elementAction(.xLarge)) - .accessibilityIdentifier("oidcButton") + .accessibilityIdentifier(A11yIdentifiers.loginScreen.oidc) } /// Text shown if neither password or OIDC login is supported. @@ -122,7 +120,7 @@ struct LoginScreen: View { .multilineTextAlignment(.center) .foregroundColor(.element.primaryContent) .frame(maxWidth: .infinity) - .accessibilityIdentifier("unsupportedServerText") + .accessibilityIdentifier(A11yIdentifiers.loginScreen.unsupportedServer) } /// Parses the username for a homeserver. diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift index 7a282edce..82bcce40d 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift @@ -51,7 +51,7 @@ struct LoginServerInfoSection: View { } .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system)) } - .accessibilityIdentifier("editServerButton") + .accessibilityIdentifier(A11yIdentifiers.loginScreen.changeServer) } } } diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift index 3532cf869..4d976adda 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift @@ -62,21 +62,21 @@ struct ServerSelectionScreen: View { TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress) .textFieldStyle(.elementInput(labelText: ElementL10n.hsUrl, footerText: context.viewState.footerMessage, - isError: context.viewState.isShowingFooterError)) + isError: context.viewState.isShowingFooterError, + accessibilityIdentifier: A11yIdentifiers.changeServerScreen.server)) .keyboardType(.URL) .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: context.homeserverAddress) { _ in context.send(viewAction: .clearFooterError) } .submitLabel(.done) .onSubmit(submit) - .accessibilityIdentifier("addressTextField") Button(action: submit) { Text(context.viewState.buttonTitle) } .buttonStyle(.elementAction(.xLarge)) .disabled(context.viewState.hasValidationError) - .accessibilityIdentifier("confirmButton") + .accessibilityIdentifier(A11yIdentifiers.changeServerScreen.continue) } } @@ -87,7 +87,7 @@ struct ServerSelectionScreen: View { Button { context.send(viewAction: .dismiss) } label: { Text(ElementL10n.actionCancel) } - .accessibilityIdentifier("dismissButton") + .accessibilityIdentifier(A11yIdentifiers.changeServerScreen.dismiss) } } } diff --git a/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift b/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift index 1ed175981..196e4cf32 100644 --- a/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift +++ b/ElementX/Sources/Screens/Authentication/SoftLogout/View/SoftLogoutScreen.swift @@ -58,20 +58,19 @@ struct SoftLogoutScreen: View { .font(.element.title2Bold) .multilineTextAlignment(.leading) .foregroundColor(.element.primaryContent) - .accessibilityIdentifier("titleLabel") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.title) Text(ElementL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userId)) .font(.element.body) .multilineTextAlignment(.leading) .foregroundColor(.element.primaryContent) - .accessibilityIdentifier("messageLabel1") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.message) if context.viewState.showRecoverEncryptionKeysMessage { Text(ElementL10n.softLogoutSigninE2eWarningNotice) .font(.element.body) .multilineTextAlignment(.leading) .foregroundColor(.element.primaryContent) - .accessibilityIdentifier("messageLabel2") } } } @@ -85,7 +84,7 @@ struct SoftLogoutScreen: View { .textContentType(.password) .submitLabel(.done) .onSubmit(submit) - .accessibilityIdentifier("passwordTextField") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.password) Button { context.send(viewAction: .forgotPassword) } label: { Text(ElementL10n.ftueAuthForgotPassword) @@ -93,14 +92,14 @@ struct SoftLogoutScreen: View { } .frame(maxWidth: .infinity, alignment: .trailing) .padding(.bottom, 8) - .accessibilityIdentifier("forgotPasswordButton") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.forgotPassword) Button(action: submit) { Text(ElementL10n.loginSignupSubmit) } .buttonStyle(.elementAction(.xLarge)) .disabled(!context.viewState.canSubmit) - .accessibilityIdentifier("nextButton") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.next) } } @@ -110,7 +109,6 @@ struct SoftLogoutScreen: View { Text(ElementL10n.loginContinue) } .buttonStyle(.elementAction(.xLarge)) - .accessibilityIdentifier("oidcButton") } /// Text shown if neither password or OIDC login is supported. @@ -120,7 +118,6 @@ struct SoftLogoutScreen: View { .multilineTextAlignment(.center) .foregroundColor(.element.primaryContent) .frame(maxWidth: .infinity) - .accessibilityIdentifier("unsupportedServerText") } /// The text field and submit button where the user enters an email address. @@ -130,20 +127,20 @@ struct SoftLogoutScreen: View { .font(.element.title2Bold) .multilineTextAlignment(.leading) .foregroundColor(.element.primaryContent) - .accessibilityIdentifier("clearDataTitleLabel") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataTitle) Text(ElementL10n.softLogoutClearDataNotice) .font(.element.body) .multilineTextAlignment(.leading) .foregroundColor(.element.primaryContent) - .accessibilityIdentifier("clearDataMessageLabel") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataMessage) .padding(.bottom, 12) Button(action: clearData) { Text(ElementL10n.softLogoutClearDataSubmit) } .buttonStyle(.elementAction(.xLarge, color: .element.alert)) - .accessibilityIdentifier("clearDataButton") + .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearData) .alert(ElementL10n.softLogoutClearDataDialogTitle, isPresented: $showingClearDataConfirmation) { Button(ElementL10n.actionSignOut, diff --git a/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift b/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift index e2c9c392b..bd8d58813 100644 --- a/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift +++ b/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift @@ -59,7 +59,7 @@ struct BugReportScreen: View { .padding(.horizontal, 10) .padding(.vertical, 4) .cornerRadius(14) - .accessibilityIdentifier("reportTextView") + .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.report) .scrollContentBackground(.hidden) if context.reportText.isEmpty { @@ -84,7 +84,7 @@ struct BugReportScreen: View { VStack(spacing: 8) { Toggle(ElementL10n.bugReportScreenIncludeLogs, isOn: $context.sendingLogsEnabled) .tint(Color.element.brand) - .accessibilityIdentifier("sendLogsToggle") + .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs) .padding(.horizontal, 16) .padding(.vertical, 11) .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.formRowBackground)) @@ -104,12 +104,12 @@ struct BugReportScreen: View { .resizable() .scaledToFit() .frame(width: 100) - .accessibilityIdentifier("screenshotImage") + .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.screenshot) Button { context.send(viewAction: .removeScreenshot) } label: { Image(uiImage: Asset.Images.closeCircle.image) } .offset(x: 10, y: -10) - .accessibilityIdentifier("removeScreenshotButton") + .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.removeScreenshot) } .padding(.vertical, 16) .padding(.horizontal, 16) @@ -131,7 +131,7 @@ struct BugReportScreen: View { context.send(viewAction: .submit) } .disabled(context.reportText.count < 5) - .accessibilityIdentifier("sendButton") + .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.send) } } } diff --git a/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift b/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift index d5db15585..6436a346d 100644 --- a/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift +++ b/ElementX/Sources/Screens/EmojiPickerScreen/View/EmojiPickerScreen.swift @@ -56,7 +56,6 @@ struct EmojiPickerScreen: View { Button { context.send(viewAction: .dismiss) } label: { Text(ElementL10n.actionCancel) } - .accessibilityIdentifier("dismissButton") } } } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index b95836dec..403cb0fcf 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -117,7 +117,7 @@ struct HomeScreen: View { contentID: context.viewState.userID, avatarSize: .user(on: .home), imageProvider: context.imageProvider) - .accessibilityIdentifier("userAvatarImage") + .accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar) } .alert(ElementL10n.actionSignOut, isPresented: $showingLogoutConfirmation) { diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 15d8cdef8..cc5a58ac4 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -38,7 +38,7 @@ struct HomeScreenRoomCell: View { .accessibilityElement(children: .combine) } .buttonStyle(HomeScreenRoomCellButtonStyle()) - .accessibilityIdentifier("roomName:\(room.name)") + .accessibilityIdentifier(A11yIdentifiers.homeScreen.roomName(room.name)) .overlay(alignment: .bottom) { Divider() .frame(height: 0.5) diff --git a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift index 6e5e7f125..1e03d8865 100644 --- a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift +++ b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift @@ -47,7 +47,7 @@ struct OnboardingScreen: View { // Add a hidden page at the start of the carousel duplicating the content of the last page OnboardingPageView(content: context.viewState.content[pageCount - 1]) .frame(width: geometry.size.width) - .accessibilityIdentifier("hiddenPage") + .accessibilityIdentifier(A11yIdentifiers.onboardingScreen.hidden) ForEach(0..