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 <stefanc@matrix.org>
This commit is contained in:
Michael Kaye
2023-02-15 05:53:04 +00:00
committed by GitHub
parent 21154c7eef
commit a06365517c
29 changed files with 215 additions and 141 deletions
@@ -21,8 +21,9 @@ import SwiftUI
public extension TextFieldStyle where Self == ElementTextFieldStyle { public extension TextFieldStyle where Self == ElementTextFieldStyle {
static func elementInput(labelText: String? = nil, static func elementInput(labelText: String? = nil,
footerText: String? = nil, footerText: String? = nil,
isError: Bool = false) -> ElementTextFieldStyle { isError: Bool = false,
ElementTextFieldStyle(labelText: labelText, footerText: footerText, isError: isError) 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 labelText: String?
public let footerText: String? public let footerText: String?
public let isError: Bool public let isError: Bool
public let accessibilityIdentifier: String?
/// The color of the text field's border. /// The color of the text field's border.
private var borderColor: Color { private var borderColor: Color {
@@ -91,10 +93,11 @@ public struct ElementTextFieldStyle: TextFieldStyle {
/// - labelText: The text shown in the label above the field. /// - labelText: The text shown in the label above the field.
/// - footerText: The text shown in the footer label below 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. /// - 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.labelText = labelText
self.footerText = footerText self.footerText = footerText
self.isError = isError self.isError = isError
self.accessibilityIdentifier = accessibilityIdentifier
} }
public func _body(configuration: TextField<_Label>) -> some View { public func _body(configuration: TextField<_Label>) -> some View {
@@ -128,8 +131,9 @@ public struct ElementTextFieldStyle: TextFieldStyle {
textField.clearButtonMode = .whileEditing textField.clearButtonMode = .whileEditing
textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "", textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "",
attributes: [NSAttributedString.Key.foregroundColor: UIColor(placeholderColor)]) attributes: [NSAttributedString.Key.foregroundColor: UIColor(placeholderColor)])
textField.accessibilityIdentifier = accessibilityIdentifier
} }
if let footerText { if let footerText {
Text(footerText) Text(footerText)
.font(.element.caption1) .font(.element.caption1)
@@ -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"
}
}
@@ -99,14 +99,12 @@ struct AnalyticsPrompt: View {
.font(.element.bodyBold) .font(.element.bodyBold)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("enableButton")
Button { context.send(viewAction: .disable) } label: { Button { context.send(viewAction: .disable) } label: {
Text(ElementL10n.actionNotNow) Text(ElementL10n.actionNotNow)
.font(.element.bodyBold) .font(.element.bodyBold)
.padding(12) .padding(12)
} }
.accessibilityIdentifier("disableButton")
} }
} }
} }
@@ -77,23 +77,21 @@ struct LoginScreen: View {
TextField(ElementL10n.loginSigninUsernameHint, text: $context.username) TextField(ElementL10n.loginSigninUsernameHint, text: $context.username)
.focused($isUsernameFocused) .focused($isUsernameFocused)
.textFieldStyle(.elementInput()) .textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.emailUsername))
.disableAutocorrection(true) .disableAutocorrection(true)
.textContentType(.username) .textContentType(.username)
.autocapitalization(.none) .autocapitalization(.none)
.submitLabel(.next) .submitLabel(.next)
.onChange(of: isUsernameFocused, perform: usernameFocusChanged) .onChange(of: isUsernameFocused, perform: usernameFocusChanged)
.onSubmit { isPasswordFocused = true } .onSubmit { isPasswordFocused = true }
.accessibilityIdentifier("usernameTextField")
.padding(.bottom, 20) .padding(.bottom, 20)
SecureField(ElementL10n.loginSignupPasswordHint, text: $context.password) SecureField(ElementL10n.loginSignupPasswordHint, text: $context.password)
.focused($isPasswordFocused) .focused($isPasswordFocused)
.textFieldStyle(.elementInput()) .textFieldStyle(.elementInput(accessibilityIdentifier: A11yIdentifiers.loginScreen.password))
.textContentType(.password) .textContentType(.password)
.submitLabel(.done) .submitLabel(.done)
.onSubmit(submit) .onSubmit(submit)
.accessibilityIdentifier("passwordTextField")
Spacer().frame(height: 32) Spacer().frame(height: 32)
@@ -102,7 +100,7 @@ struct LoginScreen: View {
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.disabled(!context.viewState.canSubmit) .disabled(!context.viewState.canSubmit)
.accessibilityIdentifier("nextButton") .accessibilityIdentifier(A11yIdentifiers.loginScreen.continue)
} }
} }
@@ -112,7 +110,7 @@ struct LoginScreen: View {
Text(ElementL10n.loginContinue) Text(ElementL10n.loginContinue)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("oidcButton") .accessibilityIdentifier(A11yIdentifiers.loginScreen.oidc)
} }
/// Text shown if neither password or OIDC login is supported. /// Text shown if neither password or OIDC login is supported.
@@ -122,7 +120,7 @@ struct LoginScreen: View {
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.accessibilityIdentifier("unsupportedServerText") .accessibilityIdentifier(A11yIdentifiers.loginScreen.unsupportedServer)
} }
/// Parses the username for a homeserver. /// Parses the username for a homeserver.
@@ -51,7 +51,7 @@ struct LoginServerInfoSection: View {
} }
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system)) .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system))
} }
.accessibilityIdentifier("editServerButton") .accessibilityIdentifier(A11yIdentifiers.loginScreen.changeServer)
} }
} }
} }
@@ -62,21 +62,21 @@ struct ServerSelectionScreen: View {
TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress) TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress)
.textFieldStyle(.elementInput(labelText: ElementL10n.hsUrl, .textFieldStyle(.elementInput(labelText: ElementL10n.hsUrl,
footerText: context.viewState.footerMessage, footerText: context.viewState.footerMessage,
isError: context.viewState.isShowingFooterError)) isError: context.viewState.isShowingFooterError,
accessibilityIdentifier: A11yIdentifiers.changeServerScreen.server))
.keyboardType(.URL) .keyboardType(.URL)
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.onChange(of: context.homeserverAddress) { _ in context.send(viewAction: .clearFooterError) } .onChange(of: context.homeserverAddress) { _ in context.send(viewAction: .clearFooterError) }
.submitLabel(.done) .submitLabel(.done)
.onSubmit(submit) .onSubmit(submit)
.accessibilityIdentifier("addressTextField")
Button(action: submit) { Button(action: submit) {
Text(context.viewState.buttonTitle) Text(context.viewState.buttonTitle)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.disabled(context.viewState.hasValidationError) .disabled(context.viewState.hasValidationError)
.accessibilityIdentifier("confirmButton") .accessibilityIdentifier(A11yIdentifiers.changeServerScreen.continue)
} }
} }
@@ -87,7 +87,7 @@ struct ServerSelectionScreen: View {
Button { context.send(viewAction: .dismiss) } label: { Button { context.send(viewAction: .dismiss) } label: {
Text(ElementL10n.actionCancel) Text(ElementL10n.actionCancel)
} }
.accessibilityIdentifier("dismissButton") .accessibilityIdentifier(A11yIdentifiers.changeServerScreen.dismiss)
} }
} }
} }
@@ -58,20 +58,19 @@ struct SoftLogoutScreen: View {
.font(.element.title2Bold) .font(.element.title2Bold)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("titleLabel") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.title)
Text(ElementL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userId)) Text(ElementL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userId))
.font(.element.body) .font(.element.body)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("messageLabel1") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.message)
if context.viewState.showRecoverEncryptionKeysMessage { if context.viewState.showRecoverEncryptionKeysMessage {
Text(ElementL10n.softLogoutSigninE2eWarningNotice) Text(ElementL10n.softLogoutSigninE2eWarningNotice)
.font(.element.body) .font(.element.body)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("messageLabel2")
} }
} }
} }
@@ -85,7 +84,7 @@ struct SoftLogoutScreen: View {
.textContentType(.password) .textContentType(.password)
.submitLabel(.done) .submitLabel(.done)
.onSubmit(submit) .onSubmit(submit)
.accessibilityIdentifier("passwordTextField") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.password)
Button { context.send(viewAction: .forgotPassword) } label: { Button { context.send(viewAction: .forgotPassword) } label: {
Text(ElementL10n.ftueAuthForgotPassword) Text(ElementL10n.ftueAuthForgotPassword)
@@ -93,14 +92,14 @@ struct SoftLogoutScreen: View {
} }
.frame(maxWidth: .infinity, alignment: .trailing) .frame(maxWidth: .infinity, alignment: .trailing)
.padding(.bottom, 8) .padding(.bottom, 8)
.accessibilityIdentifier("forgotPasswordButton") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.forgotPassword)
Button(action: submit) { Button(action: submit) {
Text(ElementL10n.loginSignupSubmit) Text(ElementL10n.loginSignupSubmit)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.disabled(!context.viewState.canSubmit) .disabled(!context.viewState.canSubmit)
.accessibilityIdentifier("nextButton") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.next)
} }
} }
@@ -110,7 +109,6 @@ struct SoftLogoutScreen: View {
Text(ElementL10n.loginContinue) Text(ElementL10n.loginContinue)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("oidcButton")
} }
/// Text shown if neither password or OIDC login is supported. /// Text shown if neither password or OIDC login is supported.
@@ -120,7 +118,6 @@ struct SoftLogoutScreen: View {
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.accessibilityIdentifier("unsupportedServerText")
} }
/// The text field and submit button where the user enters an email address. /// The text field and submit button where the user enters an email address.
@@ -130,20 +127,20 @@ struct SoftLogoutScreen: View {
.font(.element.title2Bold) .font(.element.title2Bold)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("clearDataTitleLabel") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataTitle)
Text(ElementL10n.softLogoutClearDataNotice) Text(ElementL10n.softLogoutClearDataNotice)
.font(.element.body) .font(.element.body)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("clearDataMessageLabel") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataMessage)
.padding(.bottom, 12) .padding(.bottom, 12)
Button(action: clearData) { Button(action: clearData) {
Text(ElementL10n.softLogoutClearDataSubmit) Text(ElementL10n.softLogoutClearDataSubmit)
} }
.buttonStyle(.elementAction(.xLarge, color: .element.alert)) .buttonStyle(.elementAction(.xLarge, color: .element.alert))
.accessibilityIdentifier("clearDataButton") .accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearData)
.alert(ElementL10n.softLogoutClearDataDialogTitle, .alert(ElementL10n.softLogoutClearDataDialogTitle,
isPresented: $showingClearDataConfirmation) { isPresented: $showingClearDataConfirmation) {
Button(ElementL10n.actionSignOut, Button(ElementL10n.actionSignOut,
@@ -59,7 +59,7 @@ struct BugReportScreen: View {
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 4) .padding(.vertical, 4)
.cornerRadius(14) .cornerRadius(14)
.accessibilityIdentifier("reportTextView") .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.report)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
if context.reportText.isEmpty { if context.reportText.isEmpty {
@@ -84,7 +84,7 @@ struct BugReportScreen: View {
VStack(spacing: 8) { VStack(spacing: 8) {
Toggle(ElementL10n.bugReportScreenIncludeLogs, isOn: $context.sendingLogsEnabled) Toggle(ElementL10n.bugReportScreenIncludeLogs, isOn: $context.sendingLogsEnabled)
.tint(Color.element.brand) .tint(Color.element.brand)
.accessibilityIdentifier("sendLogsToggle") .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs)
.padding(.horizontal, 16) .padding(.horizontal, 16)
.padding(.vertical, 11) .padding(.vertical, 11)
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.formRowBackground)) .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.formRowBackground))
@@ -104,12 +104,12 @@ struct BugReportScreen: View {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(width: 100) .frame(width: 100)
.accessibilityIdentifier("screenshotImage") .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.screenshot)
Button { context.send(viewAction: .removeScreenshot) } label: { Button { context.send(viewAction: .removeScreenshot) } label: {
Image(uiImage: Asset.Images.closeCircle.image) Image(uiImage: Asset.Images.closeCircle.image)
} }
.offset(x: 10, y: -10) .offset(x: 10, y: -10)
.accessibilityIdentifier("removeScreenshotButton") .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.removeScreenshot)
} }
.padding(.vertical, 16) .padding(.vertical, 16)
.padding(.horizontal, 16) .padding(.horizontal, 16)
@@ -131,7 +131,7 @@ struct BugReportScreen: View {
context.send(viewAction: .submit) context.send(viewAction: .submit)
} }
.disabled(context.reportText.count < 5) .disabled(context.reportText.count < 5)
.accessibilityIdentifier("sendButton") .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.send)
} }
} }
} }
@@ -56,7 +56,6 @@ struct EmojiPickerScreen: View {
Button { context.send(viewAction: .dismiss) } label: { Button { context.send(viewAction: .dismiss) } label: {
Text(ElementL10n.actionCancel) Text(ElementL10n.actionCancel)
} }
.accessibilityIdentifier("dismissButton")
} }
} }
} }
@@ -117,7 +117,7 @@ struct HomeScreen: View {
contentID: context.viewState.userID, contentID: context.viewState.userID,
avatarSize: .user(on: .home), avatarSize: .user(on: .home),
imageProvider: context.imageProvider) imageProvider: context.imageProvider)
.accessibilityIdentifier("userAvatarImage") .accessibilityIdentifier(A11yIdentifiers.homeScreen.userAvatar)
} }
.alert(ElementL10n.actionSignOut, .alert(ElementL10n.actionSignOut,
isPresented: $showingLogoutConfirmation) { isPresented: $showingLogoutConfirmation) {
@@ -38,7 +38,7 @@ struct HomeScreenRoomCell: View {
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
} }
.buttonStyle(HomeScreenRoomCellButtonStyle()) .buttonStyle(HomeScreenRoomCellButtonStyle())
.accessibilityIdentifier("roomName:\(room.name)") .accessibilityIdentifier(A11yIdentifiers.homeScreen.roomName(room.name))
.overlay(alignment: .bottom) { .overlay(alignment: .bottom) {
Divider() Divider()
.frame(height: 0.5) .frame(height: 0.5)
@@ -47,7 +47,7 @@ struct OnboardingScreen: View {
// Add a hidden page at the start of the carousel duplicating the content of the last page // Add a hidden page at the start of the carousel duplicating the content of the last page
OnboardingPageView(content: context.viewState.content[pageCount - 1]) OnboardingPageView(content: context.viewState.content[pageCount - 1])
.frame(width: geometry.size.width) .frame(width: geometry.size.width)
.accessibilityIdentifier("hiddenPage") .accessibilityIdentifier(A11yIdentifiers.onboardingScreen.hidden)
ForEach(0..<pageCount, id: \.self) { index in ForEach(0..<pageCount, id: \.self) { index in
OnboardingPageView(content: context.viewState.content[index]) OnboardingPageView(content: context.viewState.content[index])
@@ -93,7 +93,7 @@ struct OnboardingScreen: View {
Text(ElementL10n.loginContinue) Text(ElementL10n.loginContinue)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("getStartedButton") .accessibilityIdentifier(A11yIdentifiers.onboardingScreen.signIn)
} }
.padding(.horizontal, verticalSizeClass == .compact ? 128 : 24) .padding(.horizontal, verticalSizeClass == .compact ? 128 : 24)
.readableFrame() .readableFrame()
@@ -53,7 +53,6 @@ struct RoomDetailsScreen: View {
contentID: context.viewState.roomId, contentID: context.viewState.roomId,
avatarSize: .room(on: .details), avatarSize: .room(on: .details),
imageProvider: context.imageProvider) imageProvider: context.imageProvider)
.accessibilityIdentifier("roomAvatarImage")
Text(context.viewState.title) Text(context.viewState.title)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
@@ -129,7 +128,6 @@ struct RoomDetailsScreen: View {
} }
.listRowInsets(listRowInsets) .listRowInsets(listRowInsets)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("peopleButton")
.disabled(context.viewState.isLoadingMembers) .disabled(context.viewState.isLoadingMembers)
} header: { } header: {
Text(ElementL10n.roomDetailsAboutSectionTitle) Text(ElementL10n.roomDetailsAboutSectionTitle)
@@ -29,7 +29,7 @@ struct RoomHeaderView: View {
.accessibilityHidden(true) .accessibilityHidden(true)
Text(context.viewState.roomTitle) Text(context.viewState.roomTitle)
.font(.element.headline) .font(.element.headline)
.accessibilityIdentifier("roomNameLabel") .accessibilityIdentifier(A11yIdentifiers.roomScreen.name)
} }
// Leading align whilst using the principal toolbar position. // Leading align whilst using the principal toolbar position.
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
@@ -45,7 +45,7 @@ struct RoomHeaderView: View {
contentID: context.viewState.roomId, contentID: context.viewState.roomId,
avatarSize: .room(on: .timeline), avatarSize: .room(on: .timeline),
imageProvider: context.imageProvider) imageProvider: context.imageProvider)
.accessibilityIdentifier("roomAvatarImage") .accessibilityIdentifier(A11yIdentifiers.roomScreen.avatar)
} }
} }
@@ -79,14 +79,12 @@ struct SessionVerificationScreen: View {
.font(.title2.bold()) .font(.title2.bold())
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent) .foregroundColor(.element.primaryContent)
.accessibilityIdentifier("titleLabel")
.padding(.bottom, 8) .padding(.bottom, 8)
Text(context.viewState.message) Text(context.viewState.message)
.font(.subheadline) .font(.subheadline)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.foregroundColor(.element.tertiaryContent) .foregroundColor(.element.tertiaryContent)
.accessibilityIdentifier("detailLabel")
} }
} }
@@ -129,21 +127,20 @@ struct SessionVerificationScreen: View {
context.send(viewAction: .requestVerification) context.send(viewAction: .requestVerification)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("requestVerificationButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
case .cancelled: case .cancelled:
Button(ElementL10n.globalRetry) { Button(ElementL10n.globalRetry) {
context.send(viewAction: .restart) context.send(viewAction: .restart)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("restartButton")
case .verificationRequestAccepted: case .verificationRequestAccepted:
Button(ElementL10n.sessionVerificationStart) { Button(ElementL10n.sessionVerificationStart) {
context.send(viewAction: .startSasVerification) context.send(viewAction: .startSasVerification)
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("sasVerificationStartButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.startSasVerification)
case .showingChallenge: case .showingChallenge:
VStack(spacing: 30) { VStack(spacing: 30) {
@@ -151,13 +148,13 @@ struct SessionVerificationScreen: View {
Label(ElementL10n.verificationSasMatch, systemImage: "checkmark") Label(ElementL10n.verificationSasMatch, systemImage: "checkmark")
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("challengeAcceptButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
Button(ElementL10n.verificationSasDoNotMatch) { Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline) context.send(viewAction: .decline)
} }
.font(.element.bodyBold) .font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
} }
case .acceptingChallenge: case .acceptingChallenge:
@@ -170,14 +167,14 @@ struct SessionVerificationScreen: View {
} }
} }
.buttonStyle(.elementAction(.xLarge)) .buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("challengeAcceptButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
.disabled(true) .disabled(true)
Button(ElementL10n.verificationSasDoNotMatch) { Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline) context.send(viewAction: .decline)
} }
.font(.element.bodyBold) .font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
.disabled(true) .disabled(true)
} }
@@ -193,7 +190,7 @@ struct SessionVerificationScreen: View {
context.send(viewAction: .close) context.send(viewAction: .close)
} }
.foregroundColor(.element.accent) .foregroundColor(.element.accent)
.accessibilityIdentifier("closeButton") .accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.close)
} }
} }
@@ -86,7 +86,6 @@ struct SettingsScreen: View {
image: Image(systemName: "checkmark.shield")) { image: Image(systemName: "checkmark.shield")) {
context.send(viewAction: .sessionVerification) context.send(viewAction: .sessionVerification)
} }
.accessibilityIdentifier("sessionVerificationButton")
} }
} }
@@ -147,7 +146,6 @@ struct SettingsScreen: View {
private var doneButton: some View { private var doneButton: some View {
Button(ElementL10n.done, action: close) Button(ElementL10n.done, action: close)
.accessibilityIdentifier("closeButton")
} }
private func close() { private func close() {
+9 -14
View File
@@ -38,46 +38,41 @@ class LoginTests: XCTestCase {
private func runLoginLogoutFlow() { private func runLoginLogoutFlow() {
let app = Application.launch() let app = Application.launch()
let getStartedButton = app.buttons["Get started"] let getStartedButton = app.buttons[A11yIdentifiers.onboarding.signIn]
XCTAssertTrue(getStartedButton.waitForExistence(timeout: 5.0)) XCTAssertTrue(getStartedButton.waitForExistence(timeout: 5.0))
getStartedButton.tap() getStartedButton.tap()
let editHomeserverButton = app.buttons["editServerButton"] let editHomeserverButton = app.buttons[A11yIdentifiers.loginScreen.changeServer]
XCTAssertTrue(editHomeserverButton.waitForExistence(timeout: 5.0)) XCTAssertTrue(editHomeserverButton.waitForExistence(timeout: 5.0))
editHomeserverButton.tap() editHomeserverButton.tap()
let homeserverTextField = app.textFields["addressTextField"] let homeserverTextField = app.textFields[A11yIdentifiers.changeServer.server]
XCTAssertTrue(homeserverTextField.waitForExistence(timeout: 5.0)) XCTAssertTrue(homeserverTextField.waitForExistence(timeout: 5.0))
homeserverTextField.clearAndTypeText(app.homeserver) homeserverTextField.clearAndTypeText(app.homeserver)
let slidingSyncTextField = app.textFields["slidingSyncProxyAddressTextField"] let confirmButton = app.buttons[A11yIdentifiers.changeServer.server]
XCTAssertTrue(slidingSyncTextField.waitForExistence(timeout: 5.0))
slidingSyncTextField.clearAndTypeText(app.homeserver)
let confirmButton = app.buttons["confirmButton"]
XCTAssertTrue(confirmButton.exists) XCTAssertTrue(confirmButton.exists)
confirmButton.tap() confirmButton.tap()
let usernameTextField = app.textFields["usernameTextField"] let usernameTextField = app.textFields[A11yIdentifiers.loginScreen.emailUsername]
XCTAssertTrue(usernameTextField.exists) XCTAssertTrue(usernameTextField.exists)
usernameTextField.clearAndTypeText(app.username) usernameTextField.clearAndTypeText(app.username)
let passwordTextField = app.secureTextFields["passwordTextField"] let passwordTextField = app.secureTextFields[A11yIdentifiers.loginScreen.password]
XCTAssertTrue(passwordTextField.exists) XCTAssertTrue(passwordTextField.exists)
passwordTextField.clearAndTypeText(app.password) passwordTextField.clearAndTypeText(app.password)
let nextButton = app.buttons["nextButton"] let nextButton = app.buttons[A11yIdentifiers.loginScreen.continue]
XCTAssertTrue(nextButton.exists) XCTAssertTrue(nextButton.exists)
XCTAssertTrue(nextButton.isEnabled) XCTAssertTrue(nextButton.isEnabled)
nextButton.tap() nextButton.tap()
let profileButton = app.buttons["userAvatarImage"] let profileButton = app.buttons[A11yIdentifiers.homeScreen.userAvatar]
XCTAssertTrue(profileButton.waitForExistence(timeout: expectedDuration)) XCTAssertTrue(profileButton.waitForExistence(timeout: expectedDuration))
profileButton.tap() profileButton.tap()
@@ -58,4 +58,6 @@ targets:
sources: sources:
- path: ../Sources - path: ../Sources
- path: ../SupportingFiles - path: ../SupportingFiles
- path: ../../ElementX/Sources/Other/AccessibilityIdentifiers.swift
- path: ../../ElementX/Sources/Other/Extensions/XCUIElement.swift - path: ../../ElementX/Sources/Other/Extensions/XCUIElement.swift
- path: ../../ElementX/Sources/Other/Extensions/NSRegularExpresion.swift
@@ -25,17 +25,17 @@ class AuthenticationCoordinatorUITests: XCTestCase {
let app = Application.launch(.authenticationFlow) let app = Application.launch(.authenticationFlow)
// Splash Screen: Tap get started button // Splash Screen: Tap get started button
app.buttons["getStartedButton"].tap() app.buttons[A11yIdentifiers.onboardingScreen.signIn].tap()
// Login Screen: Enter valid credentials // Login Screen: Enter valid credentials
app.textFields["usernameTextField"].clearAndTypeText("alice\n") app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("alice\n")
app.secureTextFields["passwordTextField"].clearAndTypeText("12345678") app.secureTextFields[A11yIdentifiers.loginScreen.password].clearAndTypeText("12345678")
app.assertScreenshot(.authenticationFlow) app.assertScreenshot(.authenticationFlow)
// Login Screen: Tap next // Login Screen: Tap next
app.buttons["nextButton"].tap() app.buttons[A11yIdentifiers.loginScreen.continue].tap()
// Then login should succeed. // Then login should succeed.
XCTAssertFalse(app.alerts.element.exists, "No alert should be shown when logging in with valid credentials.") XCTAssertFalse(app.alerts.element.exists, "No alert should be shown when logging in with valid credentials.")
@@ -46,14 +46,14 @@ class AuthenticationCoordinatorUITests: XCTestCase {
let app = Application.launch(.authenticationFlow) let app = Application.launch(.authenticationFlow)
// Splash Screen: Tap get started button // Splash Screen: Tap get started button
app.buttons["getStartedButton"].tap() app.buttons[A11yIdentifiers.onboardingScreen.signIn].tap()
// Login Screen: Enter invalid credentials // Login Screen: Enter invalid credentials
app.textFields["usernameTextField"].clearAndTypeText("alice") app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("alice")
app.secureTextFields["passwordTextField"].clearAndTypeText("87654321") app.secureTextFields[A11yIdentifiers.loginScreen.password].clearAndTypeText("87654321")
// Login Screen: Tap next // Login Screen: Tap next
let nextButton = app.buttons["nextButton"] let nextButton = app.buttons[A11yIdentifiers.loginScreen.continue]
XCTAssertTrue(nextButton.waitForExistence(timeout: 2.0)) XCTAssertTrue(nextButton.waitForExistence(timeout: 2.0))
XCTAssertTrue(nextButton.isEnabled) XCTAssertTrue(nextButton.isEnabled)
nextButton.tap() nextButton.tap()
@@ -67,19 +67,19 @@ class AuthenticationCoordinatorUITests: XCTestCase {
let app = Application.launch(.authenticationFlow) let app = Application.launch(.authenticationFlow)
// Splash Screen: Tap get started button // Splash Screen: Tap get started button
app.buttons["getStartedButton"].tap() app.buttons[A11yIdentifiers.onboardingScreen.signIn].tap()
// Login Screen: Tap edit server button. // Login Screen: Tap edit server button.
XCTAssertFalse(app.buttons["oidcButton"].exists, "The OIDC button shouldn't be shown before entering a supported homeserver.") XCTAssertFalse(app.buttons[A11yIdentifiers.loginScreen.oidc].exists, "The OIDC button shouldn't be shown before entering a supported homeserver.")
app.buttons["editServerButton"].tap() app.buttons[A11yIdentifiers.loginScreen.changeServer].tap()
// Server Selection: Clear the default and enter OIDC server. // Server Selection: Clear the default and enter OIDC server.
app.textFields["addressTextField"].clearAndTypeText("company.com") app.textFields[A11yIdentifiers.changeServerScreen.server].clearAndTypeText("company.com")
// Dismiss server screen. // Dismiss server screen.
app.buttons["confirmButton"].tap() app.buttons[A11yIdentifiers.changeServerScreen.continue].tap()
// Then the login form should be updated for OIDC. // Then the login form should be updated for OIDC.
XCTAssertTrue(app.buttons["oidcButton"].waitForExistence(timeout: 1), "The OIDC button should be shown after selecting a homeserver with OIDC.") XCTAssertTrue(app.buttons[A11yIdentifiers.loginScreen.oidc].waitForExistence(timeout: 1), "The OIDC button should be shown after selecting a homeserver with OIDC.")
} }
} }
+8 -8
View File
@@ -28,9 +28,9 @@ class BugReportUITests: XCTestCase {
func testToggleSendingLogs() { func testToggleSendingLogs() {
let app = Application.launch(.bugReport) let app = Application.launch(.bugReport)
app.switches["sendLogsToggle"].tap() app.switches[A11yIdentifiers.bugReportScreen.sendLogs].tap()
let sendingLogsToggle = app.switches["sendLogsToggle"] let sendingLogsToggle = app.switches[A11yIdentifiers.bugReportScreen.sendLogs]
XCTAssert(sendingLogsToggle.exists) XCTAssert(sendingLogsToggle.exists)
XCTAssertFalse(sendingLogsToggle.isOn) XCTAssertFalse(sendingLogsToggle.isOn)
@@ -41,13 +41,13 @@ class BugReportUITests: XCTestCase {
let app = Application.launch(.bugReport) let app = Application.launch(.bugReport)
// Type 4 characters and the send button should be disabled. // Type 4 characters and the send button should be disabled.
app.textViews["reportTextView"].clearAndTypeText("Text") app.textViews[A11yIdentifiers.bugReportScreen.report].clearAndTypeText("Text")
XCTAssertFalse(app.buttons["sendButton"].isEnabled) XCTAssertFalse(app.buttons[A11yIdentifiers.bugReportScreen.send].isEnabled)
app.assertScreenshot(.bugReport, step: 2) app.assertScreenshot(.bugReport, step: 2)
// Type more than 4 characters and send the button should become enabled. // Type more than 4 characters and send the button should become enabled.
app.textViews["reportTextView"].clearAndTypeText("Longer text") app.textViews[A11yIdentifiers.bugReportScreen.report].clearAndTypeText("Longer text")
XCTAssert(app.buttons["sendButton"].isEnabled) XCTAssert(app.buttons[A11yIdentifiers.bugReportScreen.send].isEnabled)
app.assertScreenshot(.bugReport, step: 3) app.assertScreenshot(.bugReport, step: 3)
} }
@@ -55,8 +55,8 @@ class BugReportUITests: XCTestCase {
let app = Application.launch(.bugReportWithScreenshot) let app = Application.launch(.bugReportWithScreenshot)
// Initial state with a screenshot attached. // Initial state with a screenshot attached.
XCTAssert(app.images["screenshotImage"].exists) XCTAssert(app.images[A11yIdentifiers.bugReportScreen.screenshot].exists)
XCTAssert(app.buttons["removeScreenshotButton"].exists) XCTAssert(app.buttons[A11yIdentifiers.bugReportScreen.removeScreenshot].exists)
app.assertScreenshot(.bugReportWithScreenshot) app.assertScreenshot(.bugReportWithScreenshot)
} }
} }
+4 -4
View File
@@ -25,8 +25,8 @@ class LoginScreenUITests: XCTestCase {
app.assertScreenshot(.login) app.assertScreenshot(.login)
// When typing in a username and password. // When typing in a username and password.
app.textFields["usernameTextField"].clearAndTypeText("@test:matrix.org") app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("@test:matrix.org")
app.secureTextFields["passwordTextField"].clearAndTypeText("12345678") app.secureTextFields[A11yIdentifiers.loginScreen.password].clearAndTypeText("12345678")
// Then the form should be ready to submit. // Then the form should be ready to submit.
app.assertScreenshot(.login, step: 0) app.assertScreenshot(.login, step: 0)
@@ -37,7 +37,7 @@ class LoginScreenUITests: XCTestCase {
let app = Application.launch(.login) let app = Application.launch(.login)
// When entering a username on a homeserver that only supports OIDC. // When entering a username on a homeserver that only supports OIDC.
app.textFields["usernameTextField"].clearAndTypeText("@test:company.com\n") app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("@test:company.com\n")
// Then the screen should be configured for OIDC. // Then the screen should be configured for OIDC.
app.assertScreenshot(.login, step: 1) app.assertScreenshot(.login, step: 1)
@@ -48,7 +48,7 @@ class LoginScreenUITests: XCTestCase {
let app = Application.launch(.login) let app = Application.launch(.login)
// When entering a username on a homeserver with an unsupported flow. // When entering a username on a homeserver with an unsupported flow.
app.textFields["usernameTextField"].clearAndTypeText("@test:server.net\n") app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("@test:server.net\n")
// Then the screen should not allow login to continue. // Then the screen should not allow login to continue.
app.assertScreenshot(.login, step: 2) app.assertScreenshot(.login, step: 2)
+1 -1
View File
@@ -30,7 +30,7 @@ class OnboardingUITests: XCTestCase {
// Given the splash screen in its initial state. // Given the splash screen in its initial state.
let page1TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselSecureTitle] let page1TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselSecureTitle]
let page2TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselControlTitle] let page2TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselControlTitle]
let hiddenPageTitleText = app.staticTexts["hiddenPage"].firstMatch let hiddenPageTitleText = app.staticTexts[A11yIdentifiers.onboardingScreen.hidden].firstMatch
XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.") XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.")
XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.") XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.")
@@ -21,14 +21,14 @@ class RoomDetailsScreenUITests: XCTestCase {
func testInitialStateComponents() { func testInitialStateComponents() {
let app = Application.launch(.roomDetailsScreen) let app = Application.launch(.roomDetailsScreen)
XCTAssert(app.staticTexts["roomAvatarImage"].exists) XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.avatar].exists)
app.assertScreenshot(.roomDetailsScreen) app.assertScreenshot(.roomDetailsScreen)
} }
func testInitialStateComponentsWithRoomAvatar() { func testInitialStateComponentsWithRoomAvatar() {
let app = Application.launch(.roomDetailsScreenWithRoomAvatar) let app = Application.launch(.roomDetailsScreenWithRoomAvatar)
XCTAssert(app.images["roomAvatarImage"].waitForExistence(timeout: 1)) XCTAssert(app.images[A11yIdentifiers.roomScreen.avatar].waitForExistence(timeout: 1))
app.assertScreenshot(.roomDetailsScreenWithRoomAvatar) app.assertScreenshot(.roomDetailsScreenWithRoomAvatar)
} }
} }
+4 -4
View File
@@ -24,8 +24,8 @@ class RoomScreenUITests: XCTestCase {
func testPlainNoAvatar() { func testPlainNoAvatar() {
let app = Application.launch(.roomPlainNoAvatar) let app = Application.launch(.roomPlainNoAvatar)
XCTAssert(app.staticTexts["roomNameLabel"].exists) XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.name].exists)
XCTAssert(app.staticTexts["roomAvatarImage"].exists) XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.avatar].exists)
app.assertScreenshot(.roomPlainNoAvatar) app.assertScreenshot(.roomPlainNoAvatar)
} }
@@ -33,8 +33,8 @@ class RoomScreenUITests: XCTestCase {
func testEncryptedWithAvatar() { func testEncryptedWithAvatar() {
let app = Application.launch(.roomEncryptedWithAvatar) let app = Application.launch(.roomEncryptedWithAvatar)
XCTAssert(app.staticTexts["roomNameLabel"].exists) XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.name].exists)
XCTAssert(app.images["roomAvatarImage"].waitForExistence(timeout: 1)) XCTAssert(app.images[A11yIdentifiers.roomScreen.avatar].waitForExistence(timeout: 1))
app.assertScreenshot(.roomEncryptedWithAvatar) app.assertScreenshot(.roomEncryptedWithAvatar)
} }
+10 -12
View File
@@ -19,16 +19,14 @@ import XCTest
@MainActor @MainActor
class ServerSelectionUITests: XCTestCase { class ServerSelectionUITests: XCTestCase {
let textFieldIdentifier = "addressTextField"
func testNormalState() async { func testNormalState() async {
// Given the initial server selection screen as a modal. // Given the initial server selection screen as a modal.
let app = Application.launch(.serverSelection) let app = Application.launch(.serverSelection)
// Then it should be configured for matrix.org // Then it should be configured for matrix.org
app.assertScreenshot(.serverSelection, step: 0) app.assertScreenshot(.serverSelection, step: 0)
XCTAssertEqual(app.textFields[textFieldIdentifier].value as? String, "matrix.org", "The server shown should be matrix.org with the https scheme hidden.") XCTAssertEqual(app.textFields[A11yIdentifiers.changeServerScreen.server].value as? String, "matrix.org", "The server shown should be matrix.org with the https scheme hidden.")
XCTAssertEqual(app.buttons["confirmButton"].label, ElementL10n.continue, "The confirm button should say Confirm when in modal presentation.") XCTAssertEqual(app.buttons[A11yIdentifiers.changeServerScreen.continue].label, ElementL10n.continue, "The confirm button should say Confirm when in modal presentation.")
} }
func testEmptyAddress() async { func testEmptyAddress() async {
@@ -36,13 +34,13 @@ class ServerSelectionUITests: XCTestCase {
let app = Application.launch(.serverSelection) let app = Application.launch(.serverSelection)
// When clearing the server address text field. // When clearing the server address text field.
app.textFields[textFieldIdentifier].tap() app.textFields[A11yIdentifiers.changeServerScreen.server].tap()
app.textFields[textFieldIdentifier].buttons.element.tap() app.textFields[A11yIdentifiers.changeServerScreen.server].buttons.element.tap()
// Then the screen should not allow the user to continue. // Then the screen should not allow the user to continue.
app.assertScreenshot(.serverSelection, step: 1) app.assertScreenshot(.serverSelection, step: 1)
XCTAssertEqual(app.textFields[textFieldIdentifier].value as? String, ElementL10n.ftueAuthChooseServerEntryHint, "The text field should show placeholder text in this state.") XCTAssertEqual(app.textFields[A11yIdentifiers.changeServerScreen.server].value as? String, ElementL10n.ftueAuthChooseServerEntryHint, "The text field should show placeholder text in this state.")
XCTAssertFalse(app.buttons["confirmButton"].isEnabled, "The confirm button should be disabled when the address is empty.") XCTAssertFalse(app.buttons[A11yIdentifiers.changeServerScreen.continue].isEnabled, "The confirm button should be disabled when the address is empty.")
} }
func testInvalidAddress() { func testInvalidAddress() {
@@ -50,12 +48,12 @@ class ServerSelectionUITests: XCTestCase {
let app = Application.launch(.serverSelection) let app = Application.launch(.serverSelection)
// When typing in an invalid homeserver // When typing in an invalid homeserver
app.textFields[textFieldIdentifier].clearAndTypeText("thisisbad\n") // The tests only accept an address from LoginHomeserver.mockXYZ app.textFields[A11yIdentifiers.changeServerScreen.server].clearAndTypeText("thisisbad\n") // The tests only accept an address from LoginHomeserver.mockXYZ
// Then an error should be shown and the confirmation button disabled. // Then an error should be shown and the confirmation button disabled.
app.assertScreenshot(.serverSelection, step: 2) app.assertScreenshot(.serverSelection, step: 2)
XCTAssertTrue(app.staticTexts[ElementL10n.loginErrorHomeserverNotFound].exists) XCTAssertTrue(app.staticTexts[ElementL10n.loginErrorHomeserverNotFound].exists)
XCTAssertFalse(app.buttons["confirmButton"].isEnabled, "The confirm button should be disabled when there is an error.") XCTAssertFalse(app.buttons[A11yIdentifiers.changeServerScreen.continue].isEnabled, "The confirm button should be disabled when there is an error.")
} }
func testNonModalPresentation() { func testNonModalPresentation() {
@@ -64,7 +62,7 @@ class ServerSelectionUITests: XCTestCase {
// Then the screen should be tweaked slightly to reflect the change of navigation. // Then the screen should be tweaked slightly to reflect the change of navigation.
app.assertScreenshot(.serverSelectionNonModal) app.assertScreenshot(.serverSelectionNonModal)
XCTAssertFalse(app.buttons["dismissButton"].exists, "The dismiss button should be hidden when not in modal presentation.") XCTAssertFalse(app.buttons[A11yIdentifiers.changeServerScreen.dismiss].exists, "The dismiss button should be hidden when not in modal presentation.")
XCTAssertEqual(app.buttons["confirmButton"].label, ElementL10n.actionNext, "The confirm button should say Next when not in modal presentation.") XCTAssertEqual(app.buttons[A11yIdentifiers.changeServerScreen.continue].label, ElementL10n.actionNext, "The confirm button should say Next when not in modal presentation.")
} }
} }
@@ -34,68 +34,68 @@ class SessionVerificationUITests: XCTestCase {
let app = Application.launch(.sessionVerification) let app = Application.launch(.sessionVerification)
app.assertScreenshot(.sessionVerification, step: Step.initialState) app.assertScreenshot(.sessionVerification, step: Step.initialState)
app.buttons["requestVerificationButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.requestVerification].tap()
app.assertScreenshot(.sessionVerification, step: Step.waitingForOtherDevice) app.assertScreenshot(.sessionVerification, step: Step.waitingForOtherDevice)
XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.useEmojiComparisonPrompt) app.assertScreenshot(.sessionVerification, step: Step.useEmojiComparisonPrompt)
app.buttons["sasVerificationStartButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].tap()
app.assertScreenshot(.sessionVerification, step: Step.waitingForEmojis) app.assertScreenshot(.sessionVerification, step: Step.waitingForEmojis)
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons[A11yIdentifiers.sessionVerificationScreen.acceptChallenge].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.compareEmojis) app.assertScreenshot(.sessionVerification, step: Step.compareEmojis)
app.buttons["challengeAcceptButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.acceptChallenge].tap()
app.assertScreenshot(.sessionVerification, step: Step.acceptingEmojis) app.assertScreenshot(.sessionVerification, step: Step.acceptingEmojis)
XCTAssert(app.staticTexts[ElementL10n.verificationConclusionOkSelfNoticeTitle].waitForExistence(timeout: 5.0)) XCTAssert(app.staticTexts[ElementL10n.verificationConclusionOkSelfNoticeTitle].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.verificationComplete) app.assertScreenshot(.sessionVerification, step: Step.verificationComplete)
app.buttons["closeButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
} }
func testChallengeDoesNotMatch() { func testChallengeDoesNotMatch() {
let app = Application.launch(.sessionVerification) let app = Application.launch(.sessionVerification)
app.assertScreenshot(.sessionVerification, step: Step.initialState) app.assertScreenshot(.sessionVerification, step: Step.initialState)
app.buttons["requestVerificationButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.requestVerification].tap()
app.assertScreenshot(.sessionVerification, step: Step.waitingForOtherDevice) app.assertScreenshot(.sessionVerification, step: Step.waitingForOtherDevice)
XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.useEmojiComparisonPrompt) app.assertScreenshot(.sessionVerification, step: Step.useEmojiComparisonPrompt)
app.buttons["sasVerificationStartButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].tap()
app.assertScreenshot(.sessionVerification, step: Step.waitingForEmojis) app.assertScreenshot(.sessionVerification, step: Step.waitingForEmojis)
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons[A11yIdentifiers.sessionVerificationScreen.acceptChallenge].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.compareEmojis) app.assertScreenshot(.sessionVerification, step: Step.compareEmojis)
app.buttons["challengeDeclineButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.declineChallenge].tap()
app.assertScreenshot(.sessionVerification, step: Step.verificationCancelled) app.assertScreenshot(.sessionVerification, step: Step.verificationCancelled)
app.buttons["closeButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
} }
func testSessionVerificationCancelation() { func testSessionVerificationCancelation() {
let app = Application.launch(.sessionVerification) let app = Application.launch(.sessionVerification)
app.assertScreenshot(.sessionVerification, step: Step.initialState) app.assertScreenshot(.sessionVerification, step: Step.initialState)
app.buttons["requestVerificationButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.requestVerification].tap()
app.assertScreenshot(.sessionVerification, step: Step.waitingForOtherDevice) app.assertScreenshot(.sessionVerification, step: Step.waitingForOtherDevice)
XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.useEmojiComparisonPrompt) app.assertScreenshot(.sessionVerification, step: Step.useEmojiComparisonPrompt)
app.buttons["sasVerificationStartButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].tap()
app.assertScreenshot(.sessionVerification, step: Step.waitingForEmojis) app.assertScreenshot(.sessionVerification, step: Step.waitingForEmojis)
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0)) XCTAssert(app.buttons[A11yIdentifiers.sessionVerificationScreen.acceptChallenge].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.compareEmojis) app.assertScreenshot(.sessionVerification, step: Step.compareEmojis)
app.buttons["closeButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
app.assertScreenshot(.sessionVerification, step: Step.verificationCancelled) app.assertScreenshot(.sessionVerification, step: Step.verificationCancelled)
app.buttons["closeButton"].tap() app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
} }
} }
+8 -8
View File
@@ -29,14 +29,14 @@ class SoftLogoutUITests: XCTestCase {
func testInitialState() { func testInitialState() {
app = Application.launch(.softLogout) app = Application.launch(.softLogout)
XCTAssertTrue(app.staticTexts["titleLabel"].exists, "The title should be shown.") XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.title].exists, "The title should be shown.")
XCTAssertTrue(app.staticTexts["messageLabel1"].exists, "The message 1 should be shown.") XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.message].exists, "The message 1 should be shown.")
XCTAssertTrue(app.staticTexts["clearDataTitleLabel"].exists, "The clear data title should be shown.") XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.clearDataTitle].exists, "The clear data title should be shown.")
XCTAssertTrue(app.staticTexts["clearDataMessageLabel"].exists, "The clear data message should be shown.") XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.clearDataMessage].exists, "The clear data message should be shown.")
XCTAssertTrue(app.secureTextFields["passwordTextField"].exists, "The password text field should be shown.") XCTAssertTrue(app.secureTextFields[A11yIdentifiers.softLogoutScreen.password].exists, "The password text field should be shown.")
XCTAssertTrue(app.buttons["nextButton"].exists, "The next button should be shown.") XCTAssertTrue(app.buttons[A11yIdentifiers.softLogoutScreen.next].exists, "The next button should be shown.")
XCTAssertTrue(app.buttons["forgotPasswordButton"].exists, "The forgot password button should be shown.") XCTAssertTrue(app.buttons[A11yIdentifiers.softLogoutScreen.forgotPassword].exists, "The forgot password button should be shown.")
XCTAssertTrue(app.buttons["clearDataButton"].exists, "The clear data button should be shown.") XCTAssertTrue(app.buttons[A11yIdentifiers.softLogoutScreen.clearData].exists, "The clear data button should be shown.")
app.assertScreenshot(.softLogout) app.assertScreenshot(.softLogout)
} }
+1 -1
View File
@@ -26,7 +26,7 @@ class UserSessionScreenTests: XCTestCase {
app.assertScreenshot(.userSessionScreen, step: 1) app.assertScreenshot(.userSessionScreen, step: 1)
app.buttons["roomName:\(roomName)"].tap() app.buttons[A11yIdentifiers.homeScreen.roomName(roomName)].tap()
XCTAssert(app.staticTexts[roomName].waitForExistence(timeout: 5.0)) XCTAssert(app.staticTexts[roomName].waitForExistence(timeout: 5.0))
+1
View File
@@ -63,5 +63,6 @@ targets:
- path: ../../ElementX/Sources/Generated/Strings.swift - path: ../../ElementX/Sources/Generated/Strings.swift
- path: ../../ElementX/Sources/Generated/Strings+Untranslated.swift - path: ../../ElementX/Sources/Generated/Strings+Untranslated.swift
- path: ../../ElementX/Resources - path: ../../ElementX/Resources
- path: ../../ElementX/Sources/Other/AccessibilityIdentifiers.swift
- path: ../../ElementX/Sources/Other/Extensions/Bundle.swift - path: ../../ElementX/Sources/Other/Extensions/Bundle.swift
- path: ../../ElementX/Sources/Other/Extensions/XCUIElement.swift - path: ../../ElementX/Sources/Other/Extensions/XCUIElement.swift