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 e4b6ba6596
commit 23ce41ec11
29 changed files with 215 additions and 141 deletions

View File

@@ -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)

View File

@@ -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"
}
}

View File

@@ -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")
}
}
}

View File

@@ -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.

View File

@@ -51,7 +51,7 @@ struct LoginServerInfoSection: View {
}
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system))
}
.accessibilityIdentifier("editServerButton")
.accessibilityIdentifier(A11yIdentifiers.loginScreen.changeServer)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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,

View File

@@ -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)
}
}
}

View File

@@ -56,7 +56,6 @@ struct EmojiPickerScreen: View {
Button { context.send(viewAction: .dismiss) } label: {
Text(ElementL10n.actionCancel)
}
.accessibilityIdentifier("dismissButton")
}
}
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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..<pageCount, id: \.self) { index in
OnboardingPageView(content: context.viewState.content[index])
@@ -93,7 +93,7 @@ struct OnboardingScreen: View {
Text(ElementL10n.loginContinue)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("getStartedButton")
.accessibilityIdentifier(A11yIdentifiers.onboardingScreen.signIn)
}
.padding(.horizontal, verticalSizeClass == .compact ? 128 : 24)
.readableFrame()

View File

@@ -53,7 +53,6 @@ struct RoomDetailsScreen: View {
contentID: context.viewState.roomId,
avatarSize: .room(on: .details),
imageProvider: context.imageProvider)
.accessibilityIdentifier("roomAvatarImage")
Text(context.viewState.title)
.foregroundColor(.element.primaryContent)
@@ -129,7 +128,6 @@ struct RoomDetailsScreen: View {
}
.listRowInsets(listRowInsets)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("peopleButton")
.disabled(context.viewState.isLoadingMembers)
} header: {
Text(ElementL10n.roomDetailsAboutSectionTitle)

View File

@@ -29,7 +29,7 @@ struct RoomHeaderView: View {
.accessibilityHidden(true)
Text(context.viewState.roomTitle)
.font(.element.headline)
.accessibilityIdentifier("roomNameLabel")
.accessibilityIdentifier(A11yIdentifiers.roomScreen.name)
}
// Leading align whilst using the principal toolbar position.
.frame(maxWidth: .infinity, alignment: .leading)
@@ -45,7 +45,7 @@ struct RoomHeaderView: View {
contentID: context.viewState.roomId,
avatarSize: .room(on: .timeline),
imageProvider: context.imageProvider)
.accessibilityIdentifier("roomAvatarImage")
.accessibilityIdentifier(A11yIdentifiers.roomScreen.avatar)
}
}

View File

@@ -79,14 +79,12 @@ struct SessionVerificationScreen: View {
.font(.title2.bold())
.multilineTextAlignment(.center)
.foregroundColor(.element.primaryContent)
.accessibilityIdentifier("titleLabel")
.padding(.bottom, 8)
Text(context.viewState.message)
.font(.subheadline)
.multilineTextAlignment(.center)
.foregroundColor(.element.tertiaryContent)
.accessibilityIdentifier("detailLabel")
}
}
@@ -129,21 +127,20 @@ struct SessionVerificationScreen: View {
context.send(viewAction: .requestVerification)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("requestVerificationButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.requestVerification)
case .cancelled:
Button(ElementL10n.globalRetry) {
context.send(viewAction: .restart)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("restartButton")
case .verificationRequestAccepted:
Button(ElementL10n.sessionVerificationStart) {
context.send(viewAction: .startSasVerification)
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("sasVerificationStartButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.startSasVerification)
case .showingChallenge:
VStack(spacing: 30) {
@@ -151,13 +148,13 @@ struct SessionVerificationScreen: View {
Label(ElementL10n.verificationSasMatch, systemImage: "checkmark")
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("challengeAcceptButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline)
}
.font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
}
case .acceptingChallenge:
@@ -170,14 +167,14 @@ struct SessionVerificationScreen: View {
}
}
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("challengeAcceptButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.acceptChallenge)
.disabled(true)
Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline)
}
.font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.declineChallenge)
.disabled(true)
}
@@ -193,7 +190,7 @@ struct SessionVerificationScreen: View {
context.send(viewAction: .close)
}
.foregroundColor(.element.accent)
.accessibilityIdentifier("closeButton")
.accessibilityIdentifier(A11yIdentifiers.sessionVerificationScreen.close)
}
}

View File

@@ -86,7 +86,6 @@ struct SettingsScreen: View {
image: Image(systemName: "checkmark.shield")) {
context.send(viewAction: .sessionVerification)
}
.accessibilityIdentifier("sessionVerificationButton")
}
}
@@ -147,7 +146,6 @@ struct SettingsScreen: View {
private var doneButton: some View {
Button(ElementL10n.done, action: close)
.accessibilityIdentifier("closeButton")
}
private func close() {

View File

@@ -38,46 +38,41 @@ class LoginTests: XCTestCase {
private func runLoginLogoutFlow() {
let app = Application.launch()
let getStartedButton = app.buttons["Get started"]
let getStartedButton = app.buttons[A11yIdentifiers.onboarding.signIn]
XCTAssertTrue(getStartedButton.waitForExistence(timeout: 5.0))
getStartedButton.tap()
let editHomeserverButton = app.buttons["editServerButton"]
let editHomeserverButton = app.buttons[A11yIdentifiers.loginScreen.changeServer]
XCTAssertTrue(editHomeserverButton.waitForExistence(timeout: 5.0))
editHomeserverButton.tap()
let homeserverTextField = app.textFields["addressTextField"]
let homeserverTextField = app.textFields[A11yIdentifiers.changeServer.server]
XCTAssertTrue(homeserverTextField.waitForExistence(timeout: 5.0))
homeserverTextField.clearAndTypeText(app.homeserver)
let slidingSyncTextField = app.textFields["slidingSyncProxyAddressTextField"]
XCTAssertTrue(slidingSyncTextField.waitForExistence(timeout: 5.0))
slidingSyncTextField.clearAndTypeText(app.homeserver)
let confirmButton = app.buttons["confirmButton"]
let confirmButton = app.buttons[A11yIdentifiers.changeServer.server]
XCTAssertTrue(confirmButton.exists)
confirmButton.tap()
let usernameTextField = app.textFields["usernameTextField"]
let usernameTextField = app.textFields[A11yIdentifiers.loginScreen.emailUsername]
XCTAssertTrue(usernameTextField.exists)
usernameTextField.clearAndTypeText(app.username)
let passwordTextField = app.secureTextFields["passwordTextField"]
let passwordTextField = app.secureTextFields[A11yIdentifiers.loginScreen.password]
XCTAssertTrue(passwordTextField.exists)
passwordTextField.clearAndTypeText(app.password)
let nextButton = app.buttons["nextButton"]
let nextButton = app.buttons[A11yIdentifiers.loginScreen.continue]
XCTAssertTrue(nextButton.exists)
XCTAssertTrue(nextButton.isEnabled)
nextButton.tap()
let profileButton = app.buttons["userAvatarImage"]
let profileButton = app.buttons[A11yIdentifiers.homeScreen.userAvatar]
XCTAssertTrue(profileButton.waitForExistence(timeout: expectedDuration))
profileButton.tap()

View File

@@ -58,4 +58,6 @@ targets:
sources:
- path: ../Sources
- path: ../SupportingFiles
- path: ../../ElementX/Sources/Other/AccessibilityIdentifiers.swift
- path: ../../ElementX/Sources/Other/Extensions/XCUIElement.swift
- path: ../../ElementX/Sources/Other/Extensions/NSRegularExpresion.swift

View File

@@ -25,17 +25,17 @@ class AuthenticationCoordinatorUITests: XCTestCase {
let app = Application.launch(.authenticationFlow)
// Splash Screen: Tap get started button
app.buttons["getStartedButton"].tap()
app.buttons[A11yIdentifiers.onboardingScreen.signIn].tap()
// Login Screen: Enter valid credentials
app.textFields["usernameTextField"].clearAndTypeText("alice\n")
app.secureTextFields["passwordTextField"].clearAndTypeText("12345678")
app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("alice\n")
app.secureTextFields[A11yIdentifiers.loginScreen.password].clearAndTypeText("12345678")
app.assertScreenshot(.authenticationFlow)
// Login Screen: Tap next
app.buttons["nextButton"].tap()
app.buttons[A11yIdentifiers.loginScreen.continue].tap()
// Then login should succeed.
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)
// Splash Screen: Tap get started button
app.buttons["getStartedButton"].tap()
app.buttons[A11yIdentifiers.onboardingScreen.signIn].tap()
// Login Screen: Enter invalid credentials
app.textFields["usernameTextField"].clearAndTypeText("alice")
app.secureTextFields["passwordTextField"].clearAndTypeText("87654321")
app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("alice")
app.secureTextFields[A11yIdentifiers.loginScreen.password].clearAndTypeText("87654321")
// Login Screen: Tap next
let nextButton = app.buttons["nextButton"]
let nextButton = app.buttons[A11yIdentifiers.loginScreen.continue]
XCTAssertTrue(nextButton.waitForExistence(timeout: 2.0))
XCTAssertTrue(nextButton.isEnabled)
nextButton.tap()
@@ -67,19 +67,19 @@ class AuthenticationCoordinatorUITests: XCTestCase {
let app = Application.launch(.authenticationFlow)
// Splash Screen: Tap get started button
app.buttons["getStartedButton"].tap()
app.buttons[A11yIdentifiers.onboardingScreen.signIn].tap()
// Login Screen: Tap edit server button.
XCTAssertFalse(app.buttons["oidcButton"].exists, "The OIDC button shouldn't be shown before entering a supported homeserver.")
app.buttons["editServerButton"].tap()
XCTAssertFalse(app.buttons[A11yIdentifiers.loginScreen.oidc].exists, "The OIDC button shouldn't be shown before entering a supported homeserver.")
app.buttons[A11yIdentifiers.loginScreen.changeServer].tap()
// 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.
app.buttons["confirmButton"].tap()
app.buttons[A11yIdentifiers.changeServerScreen.continue].tap()
// 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.")
}
}

View File

@@ -28,9 +28,9 @@ class BugReportUITests: XCTestCase {
func testToggleSendingLogs() {
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)
XCTAssertFalse(sendingLogsToggle.isOn)
@@ -41,13 +41,13 @@ class BugReportUITests: XCTestCase {
let app = Application.launch(.bugReport)
// Type 4 characters and the send button should be disabled.
app.textViews["reportTextView"].clearAndTypeText("Text")
XCTAssertFalse(app.buttons["sendButton"].isEnabled)
app.textViews[A11yIdentifiers.bugReportScreen.report].clearAndTypeText("Text")
XCTAssertFalse(app.buttons[A11yIdentifiers.bugReportScreen.send].isEnabled)
app.assertScreenshot(.bugReport, step: 2)
// Type more than 4 characters and send the button should become enabled.
app.textViews["reportTextView"].clearAndTypeText("Longer text")
XCTAssert(app.buttons["sendButton"].isEnabled)
app.textViews[A11yIdentifiers.bugReportScreen.report].clearAndTypeText("Longer text")
XCTAssert(app.buttons[A11yIdentifiers.bugReportScreen.send].isEnabled)
app.assertScreenshot(.bugReport, step: 3)
}
@@ -55,8 +55,8 @@ class BugReportUITests: XCTestCase {
let app = Application.launch(.bugReportWithScreenshot)
// Initial state with a screenshot attached.
XCTAssert(app.images["screenshotImage"].exists)
XCTAssert(app.buttons["removeScreenshotButton"].exists)
XCTAssert(app.images[A11yIdentifiers.bugReportScreen.screenshot].exists)
XCTAssert(app.buttons[A11yIdentifiers.bugReportScreen.removeScreenshot].exists)
app.assertScreenshot(.bugReportWithScreenshot)
}
}

View File

@@ -25,8 +25,8 @@ class LoginScreenUITests: XCTestCase {
app.assertScreenshot(.login)
// When typing in a username and password.
app.textFields["usernameTextField"].clearAndTypeText("@test:matrix.org")
app.secureTextFields["passwordTextField"].clearAndTypeText("12345678")
app.textFields[A11yIdentifiers.loginScreen.emailUsername].clearAndTypeText("@test:matrix.org")
app.secureTextFields[A11yIdentifiers.loginScreen.password].clearAndTypeText("12345678")
// Then the form should be ready to submit.
app.assertScreenshot(.login, step: 0)
@@ -37,7 +37,7 @@ class LoginScreenUITests: XCTestCase {
let app = Application.launch(.login)
// 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.
app.assertScreenshot(.login, step: 1)
@@ -48,7 +48,7 @@ class LoginScreenUITests: XCTestCase {
let app = Application.launch(.login)
// 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.
app.assertScreenshot(.login, step: 2)

View File

@@ -30,7 +30,7 @@ class OnboardingUITests: XCTestCase {
// Given the splash screen in its initial state.
let page1TitleText = app.staticTexts[ElementL10n.ftueAuthCarouselSecureTitle]
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.")
XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.")

View File

@@ -21,14 +21,14 @@ class RoomDetailsScreenUITests: XCTestCase {
func testInitialStateComponents() {
let app = Application.launch(.roomDetailsScreen)
XCTAssert(app.staticTexts["roomAvatarImage"].exists)
XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.avatar].exists)
app.assertScreenshot(.roomDetailsScreen)
}
func testInitialStateComponentsWithRoomAvatar() {
let app = Application.launch(.roomDetailsScreenWithRoomAvatar)
XCTAssert(app.images["roomAvatarImage"].waitForExistence(timeout: 1))
XCTAssert(app.images[A11yIdentifiers.roomScreen.avatar].waitForExistence(timeout: 1))
app.assertScreenshot(.roomDetailsScreenWithRoomAvatar)
}
}

View File

@@ -24,8 +24,8 @@ class RoomScreenUITests: XCTestCase {
func testPlainNoAvatar() {
let app = Application.launch(.roomPlainNoAvatar)
XCTAssert(app.staticTexts["roomNameLabel"].exists)
XCTAssert(app.staticTexts["roomAvatarImage"].exists)
XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.name].exists)
XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.avatar].exists)
app.assertScreenshot(.roomPlainNoAvatar)
}
@@ -33,8 +33,8 @@ class RoomScreenUITests: XCTestCase {
func testEncryptedWithAvatar() {
let app = Application.launch(.roomEncryptedWithAvatar)
XCTAssert(app.staticTexts["roomNameLabel"].exists)
XCTAssert(app.images["roomAvatarImage"].waitForExistence(timeout: 1))
XCTAssert(app.staticTexts[A11yIdentifiers.roomScreen.name].exists)
XCTAssert(app.images[A11yIdentifiers.roomScreen.avatar].waitForExistence(timeout: 1))
app.assertScreenshot(.roomEncryptedWithAvatar)
}

View File

@@ -19,16 +19,14 @@ import XCTest
@MainActor
class ServerSelectionUITests: XCTestCase {
let textFieldIdentifier = "addressTextField"
func testNormalState() async {
// Given the initial server selection screen as a modal.
let app = Application.launch(.serverSelection)
// Then it should be configured for matrix.org
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.buttons["confirmButton"].label, ElementL10n.continue, "The confirm button should say Confirm when in modal presentation.")
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[A11yIdentifiers.changeServerScreen.continue].label, ElementL10n.continue, "The confirm button should say Confirm when in modal presentation.")
}
func testEmptyAddress() async {
@@ -36,13 +34,13 @@ class ServerSelectionUITests: XCTestCase {
let app = Application.launch(.serverSelection)
// When clearing the server address text field.
app.textFields[textFieldIdentifier].tap()
app.textFields[textFieldIdentifier].buttons.element.tap()
app.textFields[A11yIdentifiers.changeServerScreen.server].tap()
app.textFields[A11yIdentifiers.changeServerScreen.server].buttons.element.tap()
// Then the screen should not allow the user to continue.
app.assertScreenshot(.serverSelection, step: 1)
XCTAssertEqual(app.textFields[textFieldIdentifier].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.")
XCTAssertEqual(app.textFields[A11yIdentifiers.changeServerScreen.server].value as? String, ElementL10n.ftueAuthChooseServerEntryHint, "The text field should show placeholder text in this state.")
XCTAssertFalse(app.buttons[A11yIdentifiers.changeServerScreen.continue].isEnabled, "The confirm button should be disabled when the address is empty.")
}
func testInvalidAddress() {
@@ -50,12 +48,12 @@ class ServerSelectionUITests: XCTestCase {
let app = Application.launch(.serverSelection)
// 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.
app.assertScreenshot(.serverSelection, step: 2)
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() {
@@ -64,7 +62,7 @@ class ServerSelectionUITests: XCTestCase {
// Then the screen should be tweaked slightly to reflect the change of navigation.
app.assertScreenshot(.serverSelectionNonModal)
XCTAssertFalse(app.buttons["dismissButton"].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.")
XCTAssertFalse(app.buttons[A11yIdentifiers.changeServerScreen.dismiss].exists, "The dismiss button should be hidden 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.")
}
}

View File

@@ -34,68 +34,68 @@ class SessionVerificationUITests: XCTestCase {
let app = Application.launch(.sessionVerification)
app.assertScreenshot(.sessionVerification, step: Step.initialState)
app.buttons["requestVerificationButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.requestVerification].tap()
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.buttons["sasVerificationStartButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].tap()
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.buttons["challengeAcceptButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.acceptChallenge].tap()
app.assertScreenshot(.sessionVerification, step: Step.acceptingEmojis)
XCTAssert(app.staticTexts[ElementL10n.verificationConclusionOkSelfNoticeTitle].waitForExistence(timeout: 5.0))
app.assertScreenshot(.sessionVerification, step: Step.verificationComplete)
app.buttons["closeButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
}
func testChallengeDoesNotMatch() {
let app = Application.launch(.sessionVerification)
app.assertScreenshot(.sessionVerification, step: Step.initialState)
app.buttons["requestVerificationButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.requestVerification].tap()
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.buttons["sasVerificationStartButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].tap()
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.buttons["challengeDeclineButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.declineChallenge].tap()
app.assertScreenshot(.sessionVerification, step: Step.verificationCancelled)
app.buttons["closeButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
}
func testSessionVerificationCancelation() {
let app = Application.launch(.sessionVerification)
app.assertScreenshot(.sessionVerification, step: Step.initialState)
app.buttons["requestVerificationButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.requestVerification].tap()
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.buttons["sasVerificationStartButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.startSasVerification].tap()
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.buttons["closeButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
app.assertScreenshot(.sessionVerification, step: Step.verificationCancelled)
app.buttons["closeButton"].tap()
app.buttons[A11yIdentifiers.sessionVerificationScreen.close].tap()
}
}

View File

@@ -29,14 +29,14 @@ class SoftLogoutUITests: XCTestCase {
func testInitialState() {
app = Application.launch(.softLogout)
XCTAssertTrue(app.staticTexts["titleLabel"].exists, "The title should be shown.")
XCTAssertTrue(app.staticTexts["messageLabel1"].exists, "The message 1 should be shown.")
XCTAssertTrue(app.staticTexts["clearDataTitleLabel"].exists, "The clear data title should be shown.")
XCTAssertTrue(app.staticTexts["clearDataMessageLabel"].exists, "The clear data message should be shown.")
XCTAssertTrue(app.secureTextFields["passwordTextField"].exists, "The password text field should be shown.")
XCTAssertTrue(app.buttons["nextButton"].exists, "The next button should be shown.")
XCTAssertTrue(app.buttons["forgotPasswordButton"].exists, "The forgot password button should be shown.")
XCTAssertTrue(app.buttons["clearDataButton"].exists, "The clear data button should be shown.")
XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.title].exists, "The title should be shown.")
XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.message].exists, "The message 1 should be shown.")
XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.clearDataTitle].exists, "The clear data title should be shown.")
XCTAssertTrue(app.staticTexts[A11yIdentifiers.softLogoutScreen.clearDataMessage].exists, "The clear data message should be shown.")
XCTAssertTrue(app.secureTextFields[A11yIdentifiers.softLogoutScreen.password].exists, "The password text field should be shown.")
XCTAssertTrue(app.buttons[A11yIdentifiers.softLogoutScreen.next].exists, "The next button should be shown.")
XCTAssertTrue(app.buttons[A11yIdentifiers.softLogoutScreen.forgotPassword].exists, "The forgot password button should be shown.")
XCTAssertTrue(app.buttons[A11yIdentifiers.softLogoutScreen.clearData].exists, "The clear data button should be shown.")
app.assertScreenshot(.softLogout)
}

View File

@@ -26,7 +26,7 @@ class UserSessionScreenTests: XCTestCase {
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))

View File

@@ -63,5 +63,6 @@ targets:
- path: ../../ElementX/Sources/Generated/Strings.swift
- path: ../../ElementX/Sources/Generated/Strings+Untranslated.swift
- path: ../../ElementX/Resources
- path: ../../ElementX/Sources/Other/AccessibilityIdentifiers.swift
- path: ../../ElementX/Sources/Other/Extensions/Bundle.swift
- path: ../../ElementX/Sources/Other/Extensions/XCUIElement.swift