diff --git a/damus/AppAccessibilityIdentifiers.swift b/damus/AppAccessibilityIdentifiers.swift index b5eceee0..dfbd0b11 100644 --- a/damus/AppAccessibilityIdentifiers.swift +++ b/damus/AppAccessibilityIdentifiers.swift @@ -25,6 +25,23 @@ enum AppAccessibilityIdentifiers: String { case sign_in_confirm_button + // MARK: Sign Up / Create Account + // Prefix: `sign_up` + + /// Button to navigate to create account view + case sign_up_option_button + /// Text field for entering name during account creation + case sign_up_name_field + /// Text field for entering bio during account creation + case sign_up_bio_field + /// Button to proceed to the next step after entering profile info + case sign_up_next_button + /// Button to save keys after account creation + case sign_up_save_keys_button + /// Button to skip saving keys + case sign_up_skip_save_keys_button + + // MARK: Onboarding // Prefix: `onboarding` @@ -60,6 +77,12 @@ enum AppAccessibilityIdentifiers: String { /// The profile option in the side menu case side_menu_profile_button + /// The logout button in the side menu + case side_menu_logout_button + + /// The logout confirmation button in the alert dialog + case side_menu_logout_confirm_button + // MARK: Items specific to the user's own profile // Prefix: `own_profile` diff --git a/damus/Features/Onboarding/Views/CreateAccountView.swift b/damus/Features/Onboarding/Views/CreateAccountView.swift index 7a9f81e1..22e3c632 100644 --- a/damus/Features/Onboarding/Views/CreateAccountView.swift +++ b/damus/Features/Onboarding/Views/CreateAccountView.swift @@ -55,10 +55,12 @@ struct CreateAccountView: View, KeyboardReadable { .foregroundColor(DamusColors.neutral6) FormTextInput(NSLocalizedString("Satoshi Nakamoto", comment: "Name of Bitcoin creator(s)."), text: $account.name) .textInputAutocapitalization(.words) + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_name_field.rawValue) FormLabel(NSLocalizedString("Bio", comment: "Label to prompt bio entry for user to describe themself."), optional: true) .foregroundColor(DamusColors.neutral6) FormTextInput(NSLocalizedString("Absolute legend.", comment: "Example Bio"), text: $account.about) + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_bio_field.rawValue) } .padding(.top, 25) @@ -75,6 +77,7 @@ struct CreateAccountView: View, KeyboardReadable { .disabled(profileUploadObserver.isLoading || account.name.isEmpty) .opacity(profileUploadObserver.isLoading || account.name.isEmpty ? 0.5 : 1) .padding(.top, 20) + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_next_button.rawValue) LoginPrompt() .padding(.top) diff --git a/damus/Features/Onboarding/Views/LoginView.swift b/damus/Features/Onboarding/Views/LoginView.swift index b9bd5b33..cdb8f380 100644 --- a/damus/Features/Onboarding/Views/LoginView.swift +++ b/damus/Features/Onboarding/Views/LoginView.swift @@ -452,6 +452,7 @@ struct CreateAccountPrompt: View { Button(NSLocalizedString("Create account", comment: "Button to navigate to create account view.")) { nav.push(route: Route.CreateAccount) } + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_option_button.rawValue) Spacer() } diff --git a/damus/Features/Onboarding/Views/SaveKeysView.swift b/damus/Features/Onboarding/Views/SaveKeysView.swift index 6ec5dc3d..bd142392 100644 --- a/damus/Features/Onboarding/Views/SaveKeysView.swift +++ b/damus/Features/Onboarding/Views/SaveKeysView.swift @@ -99,6 +99,7 @@ struct SaveKeysView: View { } .buttonStyle(GradientButtonStyle()) .padding(.top, 20) + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_save_keys_button.rawValue) Button(action: { Task { await complete_account_creation(account) } @@ -111,6 +112,7 @@ struct SaveKeysView: View { } .buttonStyle(NeutralButtonStyle(padding: EdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15), cornerRadius: 12)) .padding(.top, 20) + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_skip_save_keys_button.rawValue) } } .padding(20) diff --git a/damus/Features/Onboarding/Views/SetupView.swift b/damus/Features/Onboarding/Views/SetupView.swift index 92cbf34d..6e8f2499 100644 --- a/damus/Features/Onboarding/Views/SetupView.swift +++ b/damus/Features/Onboarding/Views/SetupView.swift @@ -44,6 +44,7 @@ struct SetupView: View { .frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center) } .buttonStyle(GradientButtonStyle()) + .accessibilityIdentifier(AppAccessibilityIdentifiers.sign_up_option_button.rawValue) .padding(.horizontal) Button(action: { diff --git a/damus/Features/Timeline/Views/SideMenuView.swift b/damus/Features/Timeline/Views/SideMenuView.swift index d001e9a3..9b462450 100644 --- a/damus/Features/Timeline/Views/SideMenuView.swift +++ b/damus/Features/Timeline/Views/SideMenuView.swift @@ -87,6 +87,7 @@ struct SideMenuView: View { }, label: { navLabel(title: NSLocalizedString("Logout", comment: "Sidebar menu label to sign out of the account."), img: "logout") }) + .accessibilityIdentifier(AppAccessibilityIdentifiers.side_menu_logout_button.rawValue) } } @@ -211,6 +212,7 @@ struct SideMenuView: View { Button(NSLocalizedString("Logout", comment: "Button for logging out the user."), role: .destructive) { logout(damus_state) } + .accessibilityIdentifier(AppAccessibilityIdentifiers.side_menu_logout_confirm_button.rawValue) } message: { Text("Make sure your nsec account key is saved before you logout or you will lose access to this account", comment: "Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out.") } diff --git a/damusUITests/damusUITests.swift b/damusUITests/damusUITests.swift index d1edb9e0..46e07bb6 100644 --- a/damusUITests/damusUITests.swift +++ b/damusUITests/damusUITests.swift @@ -57,6 +57,116 @@ class damusUITests: XCTestCase { guard app.buttons[AID.own_profile_banner_image_edit_from_url.rawValue].waitForExistence(timeout: 5) else { throw DamusUITestError.timeout_waiting_for_element } } + /// Tests the sign up flow to ensure users can successfully create a new account. + /// This test verifies: + /// 1. The "Create account" button is accessible + /// 2. Users can enter their name and bio + /// 3. The "Next" button becomes enabled after entering required information + /// 4. Users reach the save keys screen + /// 5. Users can skip saving keys and complete onboarding + func testSignUpFlow() throws { + try logoutIfNotAlready() + + // Verify we're on the initial screen with sign up option + guard app.buttons[AID.sign_up_option_button.rawValue].waitForExistence(timeout: 5) else { + throw DamusUITestError.timeout_waiting_for_element + } + + // Tap the create account button + app.buttons[AID.sign_up_option_button.rawValue].tap() + + // Wait for the create account screen to appear + guard app.textFields[AID.sign_up_name_field.rawValue].waitForExistence(timeout: 5) else { + throw DamusUITestError.timeout_waiting_for_element + } + + // Enter name (required field) + let nameField = app.textFields[AID.sign_up_name_field.rawValue] + nameField.tap() + nameField.typeText("Test User") + + // Enter bio (optional field) + let bioField = app.textFields[AID.sign_up_bio_field.rawValue] + bioField.tap() + bioField.typeText("This is a test bio") + + // Verify the Next button is present and enabled + let nextButton = app.buttons[AID.sign_up_next_button.rawValue] + guard nextButton.waitForExistence(timeout: 5) else { + throw DamusUITestError.timeout_waiting_for_element + } + + // Tap Next to proceed to save keys screen + nextButton.tap() + + // Verify we reached the save keys screen by checking for the save button + guard app.buttons[AID.sign_up_save_keys_button.rawValue].waitForExistence(timeout: 10) else { + throw DamusUITestError.timeout_waiting_for_element + } + + // Verify both save options are present + XCTAssertTrue(app.buttons[AID.sign_up_skip_save_keys_button.rawValue].exists, + "Skip save keys button should be visible") + + // Tap "Not now" to skip saving keys and continue to onboarding + app.buttons[AID.sign_up_skip_save_keys_button.rawValue].tap() + + // Go through onboarding flow (similar to loginIfNotAlready) + // Select an interest if the interests page appears + app.buttons[AID.onboarding_interest_option_button.rawValue].firstMatch.tapIfExists(timeout: 5) + app.buttons[AID.onboarding_interest_page_next_page.rawValue].tapIfExists(timeout: 5) + + // Continue through content settings page + app.buttons[AID.onboarding_content_settings_page_next_page.rawValue].tapIfExists(timeout: 5) + + // Skip any remaining onboarding sheets + app.buttons[AID.onboarding_sheet_skip_button.rawValue].tapIfExists(timeout: 5) + + // Cancel post composer if it appears + app.buttons[AID.post_composer_cancel_button.rawValue].tapIfExists(timeout: 5) + + // Verify we've reached the main app interface by checking for the side menu button + guard app.buttons[AID.main_side_menu_button.rawValue].waitForExistence(timeout: 10) else { + throw DamusUITestError.timeout_waiting_for_element + } + } + + func logoutIfNotAlready() throws { + // First, check if user is already logged in and logout if needed + if app.buttons[AID.main_side_menu_button.rawValue].waitForExistence(timeout: 5) { + // User is already logged in, need to logout first + try logout() + } + } + + func logout() throws { + app.buttons[AID.main_side_menu_button.rawValue].tap() + + guard app.buttons[AID.side_menu_logout_button.rawValue].waitForExistence(timeout: 5) else { + throw DamusUITestError.timeout_waiting_for_element + } + + app.buttons[AID.side_menu_logout_button.rawValue].tap() + + // Handle logout confirmation dialog (system alert) + // Wait for the alert to appear + let alert = app.alerts.firstMatch + guard alert.waitForExistence(timeout: 5) else { + throw DamusUITestError.timeout_waiting_for_element + } + + // Tap the confirm button in the alert + let confirmButton = alert.buttons[AID.side_menu_logout_confirm_button.rawValue].firstMatch + guard confirmButton.waitForExistence(timeout: 5) else { + throw DamusUITestError.timeout_waiting_for_element + } + + confirmButton.tap() + + // Wait a moment for logout to complete + sleep(2) + } + func loginIfNotAlready() throws { if app.buttons[AID.sign_in_option_button.rawValue].waitForExistence(timeout: 5) { try self.login()