From a0df8e1df29bb0ba31cbe5d7f26a2c6f452345ba Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Wed, 27 May 2026 19:01:26 +0200 Subject: [PATCH 1/4] test: verify new username is synced across the devices TC-1938 --- .../pageManager/webapp/pages/account.page.ts | 10 ++++++++++ .../AccountSettings/accountSettings.spec.ts | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts index 16762a9e8f3..3669210b197 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts @@ -36,8 +36,10 @@ export class AccountPage { readonly usernameDisplay: Locator; readonly editEmailButton: Locator; readonly editDisplayNameButton: Locator; + readonly editUserNameButton: Locator; readonly emailInput: Locator; readonly displayNameInput: Locator; + readonly userNameInput: Locator; readonly resetPasswordButton: Locator; readonly receiveNewsletterCheckbox: Locator; readonly typingIndicator: Locator; @@ -56,8 +58,10 @@ export class AccountPage { this.logoutButton = page.getByTestId('do-logout'); this.editEmailButton = page.getByTestId('go-edit-email'); this.editDisplayNameButton = page.getByTestId('go-edit-email'); + this.editUserNameButton = page.getByRole('button', {name: 'Username'}); this.emailInput = page.getByTestId('enter-email-input'); this.displayNameInput = page.getByTestId('enter-displayname-input'); + this.userNameInput = page.getByTestId('enter-username-input'); this.emailDisplay = page.getByTestId('email-display'); this.nameDisplay = page.getByTestId('displayname-display'); this.domainDisplay = page.getByTestId('item-enriched-value'); @@ -141,6 +145,12 @@ export class AccountPage { await this.displayNameInput.press('Enter'); } + async changeUserName(newUserName: string) { + await this.editUserNameButton.click(); + await this.userNameInput.fill(newUserName); + await this.userNameInput.press('Enter'); + } + statusOption(status: 'Away' | 'Busy' | 'Available' | 'None') { return this.page.getByRole('button', {name: new RegExp(`${status}`, 'i')}); } diff --git a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts index bf5ee6c0555..43c541bcf65 100644 --- a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts @@ -200,6 +200,26 @@ test.describe('account settings', () => { .toEqual(expect.arrayContaining([expect.stringContaining(expectedLog)])); }); + test('Verify new username is synced across the devices', {tag: ['@TC-1938', '@regression']}, async ({createPage}) => { + const {components, pages} = PageManager.from(await createPage(withLogin(memberA))).webapp; + await components.conversationSidebar().clickPreferencesButton(); + // Verify initial username + await expect(pages.account().usernameDisplay).toContainText(memberA.username); + + // Change username to a unique name + const newUserName = `user_${Date.now()}`; + await pages.account().changeUserName(newUserName); + await expect(pages.account().usernameDisplay).toContainText(newUserName); + + // Setup second device session + const {components: userA2DeviceComponents} = PageManager.from( + await createPage(withLogin(memberA, {confirmNewHistory: true})), + ).webapp; + + // Verify sync on second device + await expect(userA2DeviceComponents.conversationSidebar().personalUserName).toContainText(newUserName); + }); + test( 'I want to see the Full Name wherever my name gets displayed', {tag: ['@TC-1948', '@regression']}, From a6ea070531c8b214e7274eacb5a7fbb5865d1770 Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Thu, 28 May 2026 16:49:02 +0200 Subject: [PATCH 2/4] test: verify username is unique TC-1942 --- .../webapp/pages/setUsername.page.ts | 2 + .../AccountSettings/accountSettings.spec.ts | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/setUsername.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/setUsername.page.ts index 24f4f7c5051..ca354d5cc93 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/setUsername.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/setUsername.page.ts @@ -22,10 +22,12 @@ import {Page, Locator} from '@playwright/test'; export class SetUsernamePage { readonly handleInput: Locator; readonly nextButton: Locator; + readonly errorMessage: Locator; constructor(page: Page) { this.handleInput = page.locator('[data-uie-name="enter-handle"]'); this.nextButton = page.locator('[data-uie-name="do-send-handle"]'); + this.errorMessage = page.getByTestId('error-message'); } async setUsername(username: string) { diff --git a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts index 43c541bcf65..369fc4eab1d 100644 --- a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts @@ -220,6 +220,72 @@ test.describe('account settings', () => { await expect(userA2DeviceComponents.conversationSidebar().personalUserName).toContainText(newUserName); }); + test('Verify username is unique', {tag: ['@TC-1942', '@regression']}, async ({createPage, createUser, api}) => { + const userA = await createUser(); + + const userB = getUser(); + const userPage = await createPage(); + const userPageManager = PageManager.from(userPage); + const {pages, components, modals} = userPageManager.webapp; + + await test.step('Register User B before username selection', async () => { + await userPageManager.openMainPage(); + await pages.singleSignOn().enterEmailOnSSOPage(userB.email); + await pages.welcome().clickCreateAccountButton(); + await pages.welcome().clickCreatePersonalAccountButton(); + await expect(pages.registration().passwordPolicyInfo).toContainText( + 'Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.', + ); + + await pages.registration().fillInUserInfo(userB); + await expect(pages.registration().submitButton).toBeDisabled(); + + await pages.registration().toggleTermsCheckbox(); + await expect(pages.registration().submitButton).toBeEnabled(); + await pages.registration().clickSubmitButton(); + await expect(pages.emailVerification().verificationCodeInputLabel).toBeVisible(); + + const verificationCode = await api.brig.getActivationCodeForEmail(userB.email); + await pages.emailVerification().enterVerificationCode(verificationCode); + await modals.marketingConsent().clickConfirmButton(); + }); + + await test.step('Validate username uniqueness error during account creating', async () => { + await pages.setUsername().setUsername(userA.username); + await pages.setUsername().clickNextButton(); + await expect(pages.setUsername().errorMessage).toContainText('This username is already taken'); + + // Proceed successfully with User B's unique username + await pages.setUsername().setUsername(userB.username); + await pages.setUsername().clickNextButton(); + await pages.registerSuccess().clickOpenWireWebButton(); + await modals.confirm().cancelButton.click(); + }); + + await test.step('Validate username uniqueness error inside Profile Settings', async () => { + await components.conversationSidebar().clickPreferencesButton(); + await expect(pages.account().usernameDisplay).toContainText(userB.username); + await pages.account().changeUserName(userA.username); + await pages.account().editUserNameButton.click(); + await expect(userPage.getByText('Already taken')).toBeVisible(); + await pages.account().userNameInput.press('Enter'); + await expect(pages.account().usernameDisplay).toContainText(userB.username); + }); + + await test.step('Delete user', async () => { + await components.conversationSidebar().clickPreferencesButton(); + await pages.account().clickDeleteAccountButton(); + await expect(modals.confirm().modal).toBeVisible(); + await modals.confirm().actionButton.click(); + const url = await api.inbucket.getAccountDeletionURL(userB.email); + + await userPageManager.openNewTab(url, async tab => { + await tab.webapp.pages.deleteAccount().deleteAccountButton.click(); + await expect(tab.webapp.pages.deleteAccount().accountDeletedHeadline).toContainText('Account deleted'); + }); + }); + }); + test( 'I want to see the Full Name wherever my name gets displayed', {tag: ['@TC-1948', '@regression']}, From 970c8b27b76c1be3394f0eefb349f2ce68db8b85 Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Thu, 28 May 2026 17:45:07 +0200 Subject: [PATCH 3/4] test: verify autogeneration of a username for a user TC-1939 --- .../AccountSettings/accountSettings.spec.ts | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts index 369fc4eab1d..30cfc2d30b9 100644 --- a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts @@ -220,6 +220,78 @@ test.describe('account settings', () => { await expect(userA2DeviceComponents.conversationSidebar().personalUserName).toContainText(newUserName); }); + test( + 'Verify autogeneration of a username for a user', + {tag: ['@TC-1939', '@regression']}, + async ({createPage, api}, testInfo) => { + test.setTimeout(testInfo.timeout + 90_000); + + const usernameScenarios = [ + {firstName: 'Jack', expectedUsername: 'jack'}, + {firstName: 'Jack.Wireson', expectedUsername: 'jackwireson'}, + {firstName: 'Æéÿüíøšłźçñ', expectedUsername: 'aeeyueioslzcn'}, + {firstName: 'Даша', expectedUsername: 'dasha'}, + {firstName: 'داريا', expectedUsername: 'darya'}, + {firstName: 'Jack😼', expectedUsername: 'jack'}, + ]; + + for (const {firstName, expectedUsername} of usernameScenarios) { + await test.step(`Scenario: ${firstName} -> ${expectedUsername}`, async () => { + const userA = getUser({firstName}); + const userPage = await createPage(); + const userPageManager = PageManager.from(userPage); + const {pages, components, modals} = userPageManager.webapp; + + await test.step('Register user and handle verification', async () => { + await userPageManager.openMainPage(); + await pages.singleSignOn().enterEmailOnSSOPage(userA.email); + await pages.welcome().clickCreateAccountButton(); + await pages.welcome().clickCreatePersonalAccountButton(); + + await pages.registration().fillInUserInfo(userA); + await expect(pages.registration().submitButton).toBeDisabled(); + await pages.registration().toggleTermsCheckbox(); + await expect(pages.registration().submitButton).toBeEnabled(); + await pages.registration().clickSubmitButton(); + + await expect(pages.emailVerification().verificationCodeInputLabel).toBeVisible(); + const verificationCode = await api.brig.getActivationCodeForEmail(userA.email); + + await pages.emailVerification().enterVerificationCode(verificationCode); + await modals.marketingConsent().clickConfirmButton(); + }); + + await test.step('Verify autogenerated username on registration and profile', async () => { + const defaultUserName = await pages.setUsername().getHandleInputValue(); + expect(defaultUserName).toContain(expectedUsername); + + await pages.setUsername().clickNextButton(); + await pages.registerSuccess().clickOpenWireWebButton(); + await modals.confirm().cancelButton.click(); + + await components.conversationSidebar().clickPreferencesButton(); + await expect(pages.account().usernameDisplay).toContainText(expectedUsername); + }); + + await test.step('Cleanup: delete account and close page', async () => { + await pages.account().clickDeleteAccountButton(); + await expect(modals.confirm().modal).toBeVisible(); + await modals.confirm().actionButton.click(); + + const deletionUrl = await api.inbucket.getAccountDeletionURL(userA.email); + + await userPageManager.openNewTab(deletionUrl, async tab => { + await tab.webapp.pages.deleteAccount().deleteAccountButton.click(); + await expect(tab.webapp.pages.deleteAccount().accountDeletedHeadline).toContainText('Account deleted'); + }); + + await userPage.close(); + }); + }); + } + }, + ); + test('Verify username is unique', {tag: ['@TC-1942', '@regression']}, async ({createPage, createUser, api}) => { const userA = await createUser(); From aa78828141b7ffca2a0563efaacfd29074cf5699 Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Fri, 29 May 2026 19:01:54 +0200 Subject: [PATCH 4/4] refactor: parallelize usernameScenarios execution TC-1939 --- .../pageManager/webapp/pages/account.page.ts | 2 +- .../AccountSettings/accountSettings.spec.ts | 128 +++++++++--------- 2 files changed, 64 insertions(+), 66 deletions(-) diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts index 3669210b197..24e8e84552a 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/account.page.ts @@ -61,7 +61,7 @@ export class AccountPage { this.editUserNameButton = page.getByRole('button', {name: 'Username'}); this.emailInput = page.getByTestId('enter-email-input'); this.displayNameInput = page.getByTestId('enter-displayname-input'); - this.userNameInput = page.getByTestId('enter-username-input'); + this.userNameInput = page.getByLabel('Username'); this.emailDisplay = page.getByTestId('email-display'); this.nameDisplay = page.getByTestId('displayname-display'); this.domainDisplay = page.getByTestId('item-enriched-value'); diff --git a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts index 30cfc2d30b9..08aed1f3b22 100644 --- a/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/AccountSettings/accountSettings.spec.ts @@ -223,9 +223,7 @@ test.describe('account settings', () => { test( 'Verify autogeneration of a username for a user', {tag: ['@TC-1939', '@regression']}, - async ({createPage, api}, testInfo) => { - test.setTimeout(testInfo.timeout + 90_000); - + async ({createPage, api}) => { const usernameScenarios = [ {firstName: 'Jack', expectedUsername: 'jack'}, {firstName: 'Jack.Wireson', expectedUsername: 'jackwireson'}, @@ -235,60 +233,63 @@ test.describe('account settings', () => { {firstName: 'Jack😼', expectedUsername: 'jack'}, ]; - for (const {firstName, expectedUsername} of usernameScenarios) { - await test.step(`Scenario: ${firstName} -> ${expectedUsername}`, async () => { - const userA = getUser({firstName}); - const userPage = await createPage(); - const userPageManager = PageManager.from(userPage); - const {pages, components, modals} = userPageManager.webapp; - - await test.step('Register user and handle verification', async () => { - await userPageManager.openMainPage(); - await pages.singleSignOn().enterEmailOnSSOPage(userA.email); - await pages.welcome().clickCreateAccountButton(); - await pages.welcome().clickCreatePersonalAccountButton(); - - await pages.registration().fillInUserInfo(userA); - await expect(pages.registration().submitButton).toBeDisabled(); - await pages.registration().toggleTermsCheckbox(); - await expect(pages.registration().submitButton).toBeEnabled(); - await pages.registration().clickSubmitButton(); - - await expect(pages.emailVerification().verificationCodeInputLabel).toBeVisible(); - const verificationCode = await api.brig.getActivationCodeForEmail(userA.email); - - await pages.emailVerification().enterVerificationCode(verificationCode); - await modals.marketingConsent().clickConfirmButton(); - }); - - await test.step('Verify autogenerated username on registration and profile', async () => { - const defaultUserName = await pages.setUsername().getHandleInputValue(); - expect(defaultUserName).toContain(expectedUsername); - - await pages.setUsername().clickNextButton(); - await pages.registerSuccess().clickOpenWireWebButton(); - await modals.confirm().cancelButton.click(); - - await components.conversationSidebar().clickPreferencesButton(); - await expect(pages.account().usernameDisplay).toContainText(expectedUsername); - }); - - await test.step('Cleanup: delete account and close page', async () => { - await pages.account().clickDeleteAccountButton(); - await expect(modals.confirm().modal).toBeVisible(); - await modals.confirm().actionButton.click(); - - const deletionUrl = await api.inbucket.getAccountDeletionURL(userA.email); - - await userPageManager.openNewTab(deletionUrl, async tab => { - await tab.webapp.pages.deleteAccount().deleteAccountButton.click(); - await expect(tab.webapp.pages.deleteAccount().accountDeletedHeadline).toContainText('Account deleted'); + await Promise.all( + usernameScenarios.map(async ({firstName, expectedUsername}) => { + return test.step(`Scenario: ${firstName} -> ${expectedUsername}`, async () => { + const userA = getUser({firstName}); + const userPage = await createPage(); + const userPageManager = PageManager.from(userPage); + const {pages, components, modals} = userPageManager.webapp; + + await test.step('Register user and handle verification', async () => { + await userPageManager.openMainPage(); + await pages.singleSignOn().enterEmailOnSSOPage(userA.email); + await pages.welcome().clickCreateAccountButton(); + await pages.welcome().clickCreatePersonalAccountButton(); + + await pages.registration().fillInUserInfo(userA); + await pages.registration().toggleTermsCheckbox(); + await pages.registration().clickSubmitButton(); + + await expect(pages.emailVerification().verificationCodeInputLabel).toBeVisible(); + + const verificationCode = await api.brig.getActivationCodeForEmail(userA.email); + await pages.emailVerification().enterVerificationCode(verificationCode); + await modals.marketingConsent().clickConfirmButton(); }); - await userPage.close(); + try { + await test.step('Verify autogenerated username on registration and profile', async () => { + await expect(pages.setUsername().handleInput).toHaveValue(new RegExp(expectedUsername)); + + await pages.setUsername().clickNextButton(); + await pages.registerSuccess().clickOpenWireWebButton(); + await modals.confirm().cancelButton.click({timeout: LOGIN_TIMEOUT}); + + await components.conversationSidebar().clickPreferencesButton(); + await expect(pages.account().usernameDisplay).toContainText(expectedUsername); + }); + } finally { + await test.step('Cleanup: delete account and close page', async () => { + await pages.account().clickDeleteAccountButton(); + await expect(modals.confirm().modal).toBeVisible(); + await modals.confirm().actionButton.click(); + + const deletionUrl = await api.inbucket.getAccountDeletionURL(userA.email); + + await userPageManager.openNewTab(deletionUrl, async tab => { + await tab.webapp.pages.deleteAccount().deleteAccountButton.click(); + await expect(tab.webapp.pages.deleteAccount().accountDeletedHeadline).toContainText( + 'Account deleted', + ); + }); + + await userPage.close(); + }); + } }); - }); - } + }), + ); }, ); @@ -305,15 +306,9 @@ test.describe('account settings', () => { await pages.singleSignOn().enterEmailOnSSOPage(userB.email); await pages.welcome().clickCreateAccountButton(); await pages.welcome().clickCreatePersonalAccountButton(); - await expect(pages.registration().passwordPolicyInfo).toContainText( - 'Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.', - ); await pages.registration().fillInUserInfo(userB); - await expect(pages.registration().submitButton).toBeDisabled(); - await pages.registration().toggleTermsCheckbox(); - await expect(pages.registration().submitButton).toBeEnabled(); await pages.registration().clickSubmitButton(); await expect(pages.emailVerification().verificationCodeInputLabel).toBeVisible(); @@ -331,23 +326,26 @@ test.describe('account settings', () => { await pages.setUsername().setUsername(userB.username); await pages.setUsername().clickNextButton(); await pages.registerSuccess().clickOpenWireWebButton(); - await modals.confirm().cancelButton.click(); + await modals.confirm().cancelButton.click({timeout: LOGIN_TIMEOUT}); }); await test.step('Validate username uniqueness error inside Profile Settings', async () => { await components.conversationSidebar().clickPreferencesButton(); await expect(pages.account().usernameDisplay).toContainText(userB.username); await pages.account().changeUserName(userA.username); - await pages.account().editUserNameButton.click(); - await expect(userPage.getByText('Already taken')).toBeVisible(); - await pages.account().userNameInput.press('Enter'); + + // TODO(WPB-25926): Currently the error message isn't shown correctly in the UI + // await expect(userPage.getByText('Already taken')).toBeVisible(); + // await pages.account().userNameInput.fill(userB.username); + // await pages.account().userNameInput.press('Enter'); + await expect(pages.account().usernameDisplay).toContainText(userB.username); }); await test.step('Delete user', async () => { await components.conversationSidebar().clickPreferencesButton(); await pages.account().clickDeleteAccountButton(); - await expect(modals.confirm().modal).toBeVisible(); + await expect(modals.confirm().modalTitle).toContainText('Delete account'); await modals.confirm().actionButton.click(); const url = await api.inbucket.getAccountDeletionURL(userB.email);