diff --git a/apps/webapp/playwright.config.ts b/apps/webapp/playwright.config.ts
index bdd8646139d..375423ee952 100644
--- a/apps/webapp/playwright.config.ts
+++ b/apps/webapp/playwright.config.ts
@@ -64,7 +64,7 @@ module.exports = defineConfig({
name: 'Chromium',
use: {
...devices['Desktop Chrome'],
- permissions: ['notifications'],
+ permissions: ['notifications', 'clipboard-read', 'clipboard-write'],
launchOptions: {
args: [
'--use-fake-device-for-media-stream', // Provide fake devices for audio & video device input
diff --git a/apps/webapp/src/script/components/UserList/UserList.tsx b/apps/webapp/src/script/components/UserList/UserList.tsx
index 6a61c2177f0..daaf6de4457 100644
--- a/apps/webapp/src/script/components/UserList/UserList.tsx
+++ b/apps/webapp/src/script/components/UserList/UserList.tsx
@@ -17,7 +17,7 @@
*
*/
-import {ChangeEvent, useCallback, useMemo, useState} from 'react';
+import {ChangeEvent, useCallback, useId, useMemo, useState} from 'react';
import cx from 'classnames';
import {container} from 'tsyringe';
@@ -157,6 +157,8 @@ export const UserList = ({
[highlightedUserIds, isSelectable, isSelfVerified, mode, noSelfInteraction, selectedUsers, teamState],
);
+ const adminsHeaderId = useId();
+ const membersHeaderId = useId();
let content;
const showRoles = !!conversation;
@@ -188,12 +190,16 @@ export const UserList = ({
<>
{(admins.length > 0 || showEmptyAdmin) && (
<>
-
+
{admins.length > 0 && (
-
+
{admins.slice(0, maxShownUsers).map(user => renderListItem(user))}
)}
@@ -208,11 +214,15 @@ export const UserList = ({
{members.length > 0 && maxShownUsers > admins.length && (
<>
-
+
-
+
{members.slice(0, maxShownUsers - admins.length).map(user => renderListItem(user))}
>
diff --git a/apps/webapp/src/script/components/calling/CallingCell/CallingCell.tsx b/apps/webapp/src/script/components/calling/CallingCell/CallingCell.tsx
index f657850da05..1694646d66c 100644
--- a/apps/webapp/src/script/components/calling/CallingCell/CallingCell.tsx
+++ b/apps/webapp/src/script/components/calling/CallingCell/CallingCell.tsx
@@ -419,7 +419,6 @@ export const CallingCell = ({
answerCall={answerCall}
call={call}
callActions={callActions}
- call1To1StartedAlert={call1To1StartedAlert}
isFullUi={isFullUi}
isMuted={isMuted}
isConnecting={isConnecting}
diff --git a/apps/webapp/src/script/components/calling/CallingCell/CallingControls/CallingControls.tsx b/apps/webapp/src/script/components/calling/CallingCell/CallingControls/CallingControls.tsx
index 75d9f137576..680a47e3427 100644
--- a/apps/webapp/src/script/components/calling/CallingCell/CallingControls/CallingControls.tsx
+++ b/apps/webapp/src/script/components/calling/CallingCell/CallingControls/CallingControls.tsx
@@ -39,7 +39,6 @@ interface CallingControlsProps {
answerCall: () => void;
call: Call;
callActions: CallActions;
- call1To1StartedAlert: string;
isDetachedWindow: boolean;
isFullUi?: boolean;
isMuted?: boolean;
@@ -61,7 +60,6 @@ export const CallingControls = ({
answerCall,
call,
callActions,
- call1To1StartedAlert,
isFullUi,
isMuted,
isConnecting,
@@ -176,8 +174,8 @@ export const CallingControls = ({
className="call-ui__button call-ui__button--red call-ui__button--large"
onClick={() => (isIncoming ? callActions.reject(call) : callActions.leave(call))}
onBlur={() => clearShowAlert()}
- title={!isGroup && showAlert ? call1To1StartedAlert : t('videoCallOverlayHangUp')}
- aria-label={!isGroup && showAlert ? call1To1StartedAlert : t('videoCallOverlayHangUp')}
+ title={t('videoCallOverlayHangUp')}
+ aria-label={t('videoCallOverlayHangUp')}
type="button"
data-uie-name="do-call-controls-call-decline"
>
diff --git a/apps/webapp/test/e2e_tests/pageManager/index.ts b/apps/webapp/test/e2e_tests/pageManager/index.ts
index 11eea2a8314..7ef35260046 100644
--- a/apps/webapp/test/e2e_tests/pageManager/index.ts
+++ b/apps/webapp/test/e2e_tests/pageManager/index.ts
@@ -72,6 +72,8 @@ import {SettingsPage} from './webapp/pages/settings.page';
import {SingleSignOnPage} from './webapp/pages/singleSignOn.page';
import {StartUIPage} from './webapp/pages/startUI.page';
import {WelcomePage} from './webapp/pages/welcome.page';
+import {GuestLinkPasswordModal} from './webapp/modals/guestLinkPassword.modal';
+import {ConversationJoinPage} from './webapp/pages/conversationJoin.page';
export const webAppPath = process.env.WEBAPP_URL ?? '';
@@ -166,7 +168,7 @@ export class PageManager {
this.getOrCreate('webapp.pages.audioVideoSettings', () => new AudioVideoSettingsPage(this.page)),
outgoingConnection: () =>
this.getOrCreate('webapp.pages.outgoingConnection', () => new OutgoingConnectionPage(this.page)),
- guestOptions: () => this.getOrCreate('webapp.pages.guestOptions', () => new GuestOptionsPage(this.page)),
+ guestOptions: () => this.getOrCreate('webapp.pages.guestOptions', () => GuestOptionsPage(this.page)),
deleteAccount: () => this.getOrCreate('webapp.pages.deleteAccount', () => new DeleteAccountPage(this.page)),
groupCreation: () => this.getOrCreate('webapp.pages.groupCreation', () => new GroupCreationPage(this.page)),
historyInfo: () => this.getOrCreate('webapp.pages.infoHostory', () => new HistoryInfoPage(this.page)),
@@ -182,6 +184,7 @@ export class PageManager {
emailVerification: () =>
this.getOrCreate('webapp.pages.verification', () => new EmailVerificationPage(this.page)),
setUsername: () => this.getOrCreate('webapp.pages.setUsername', () => new SetUsernamePage(this.page)),
+ conversationJoin: () => this.getOrCreate('webapp.pages.conversationJoin', () => ConversationJoinPage(this.page)),
},
modals: {
dataShareConsent: () =>
@@ -207,6 +210,8 @@ export class PageManager {
cellsFileDetailView: () =>
this.getOrCreate('webapp.modals.cellsFileDetailView', () => new CellsFileDetailViewModal(this.page)),
optionModal: () => this.getOrCreate('webapp.modals.optionModal', () => new OptionModal(this.page)),
+ guestLinkPassword: () =>
+ this.getOrCreate('webapp.modals.guestLinkPassword', () => new GuestLinkPasswordModal(this.page)),
},
components: {
contactList: () => this.getOrCreate('webapp.components.ContactList', () => new ContactList(this.page)),
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/modals/guestLinkPassword.modal.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/modals/guestLinkPassword.modal.ts
new file mode 100644
index 00000000000..4d5c40dfe27
--- /dev/null
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/modals/guestLinkPassword.modal.ts
@@ -0,0 +1,35 @@
+/*
+ * Wire
+ * Copyright (C) 2025 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ *
+ */
+
+import {Locator, Page} from '@playwright/test';
+
+import {BaseModal} from './base.modal';
+
+/** Modal shown when a link for guests to join a group conversation with a password is created */
+export class GuestLinkPasswordModal extends BaseModal {
+ readonly setPasswordInput: Locator;
+ readonly confirmPasswordInput: Locator;
+
+ constructor(page: Page) {
+ super(page, 'modal-template-guest-link-password');
+
+ this.setPasswordInput = this.modal.getByRole('textbox', {name: 'Set password'});
+ this.confirmPasswordInput = this.modal.getByRole('textbox', {name: 'Confirm password'});
+ }
+}
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversation.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversation.page.ts
index ff4e2b5e609..fc59c15a484 100644
--- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversation.page.ts
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversation.page.ts
@@ -59,6 +59,7 @@ export class ConversationPage {
readonly itemPendingRequest: Locator;
readonly ignoreButton: Locator;
readonly cancelRequest: Locator;
+ readonly mentionSuggestions: Locator;
readonly getImageAltText = (user: User) => `Image from ${user.fullName}`;
@@ -104,6 +105,7 @@ export class ConversationPage {
this.itemPendingRequest = page.getByTestId('item-pending-requests');
this.ignoreButton = page.getByTestId('do-ignore');
this.cancelRequest = page.getByTestId('do-cancel-request');
+ this.mentionSuggestions = page.getByRole('listbox').getByTestId('item-mention-suggestion');
}
getImageLocator(user: User): Locator {
@@ -158,6 +160,12 @@ export class ConversationPage {
await this.messageInput.pressSequentially(message, {delay: 100});
}
+ async mentionUser(userFullName: string, searchQuery?: string) {
+ const textToType = searchQuery ? `@${searchQuery}` : `@${userFullName.slice(0, 3)}`;
+ await this.messageInput.pressSequentially(textToType);
+ await this.mentionSuggestions.filter({hasText: userFullName}).click({timeout: 5000});
+ }
+
async replyToMessage(message: Locator) {
await message.hover();
await message.getByRole('group').getByTestId('do-reply-message').click();
@@ -174,12 +182,8 @@ export class ConversationPage {
}
async sendMessageWithUserMention(userFullName: string, messageText?: string) {
- await this.messageInput.fill(`@`);
- await this.page
- .getByTestId('item-mention-suggestion')
- .getByTestId('status-name')
- .filter({hasText: userFullName})
- .click({timeout: 1000});
+ await this.messageInput.fill(''); // Clear the input initially
+ await this.mentionUser(userFullName);
if (messageText) {
await this.messageInput.pressSequentially(messageText);
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationDetails.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationDetails.page.ts
index b47b48aae03..461c13f677c 100644
--- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationDetails.page.ts
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationDetails.page.ts
@@ -18,10 +18,14 @@
*/
import {Locator, Page} from '@playwright/test';
+import {GuestOptionsPage} from './guestOptions.page';
export class ConversationDetailsPage {
readonly page: Page;
+ readonly groupAdmins: Locator;
+ readonly groupMembers: Locator;
+
readonly addPeopleButton: Locator;
readonly conversationDetails: Locator;
readonly guestOptionsButton: Locator;
@@ -38,9 +42,12 @@ export class ConversationDetailsPage {
constructor(page: Page) {
this.page = page;
+ this.conversationDetails = page.locator('#conversation-details');
+
+ this.groupAdmins = this.conversationDetails.getByRole('list', {name: 'Group Admins'}).getByRole('listitem');
+ this.groupMembers = this.conversationDetails.getByRole('list', {name: 'Group Members'}).getByRole('listitem');
this.addPeopleButton = page.getByTestId('go-add-people');
- this.conversationDetails = page.locator('#conversation-details');
this.guestOptionsButton = this.conversationDetails.locator('[data-uie-name="go-guest-options"]');
this.selfDeletingMessageButton = this.conversationDetails.getByRole('button', {name: 'Self-deleting messages'});
this.archiveButton = this.conversationDetails.getByTestId('do-archive');
@@ -135,6 +142,17 @@ export class ConversationDetailsPage {
await selfDeletingMessagesPanel.getByRole('button', {name: 'Go back'}).click();
}
+ /** Opens the guests panel, creates a link for guests to join the group, closes the panel and returns the created link */
+ async createGuestLink(options?: Parameters['createLink']>[0]) {
+ await this.guestOptionsButton.click();
+
+ const guestOptionsPage = GuestOptionsPage(this.page);
+ const link = await guestOptionsPage.createLink(options);
+
+ await guestOptionsPage.backButton.click();
+ return link;
+ }
+
async addServiceToConversation(serviceName: string) {
// Click on the Services/Apps tab
const servicesTab = this.page.locator('#add-participants').getByTestId('do-add-services');
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationJoin.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationJoin.page.ts
new file mode 100644
index 00000000000..1083bf57add
--- /dev/null
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationJoin.page.ts
@@ -0,0 +1,15 @@
+import {Page} from 'playwright/test';
+
+/** Page shown to users using a guest link to join a group conversation */
+export const ConversationJoinPage = (page: Page) => {
+ const joinAsGuest = async (name: string) => {
+ await page.getByRole('textbox', {name: 'Your name'}).fill(name);
+ // It's necessary to specify a position since the text contains a link which would be clicked instead of the checkbox
+ await page.getByText(/I accept .* terms of use/i).click({position: {x: 0, y: 8}});
+ await page.getByRole('button', {name: 'Join as Temporary Guest'}).click();
+ };
+
+ return {
+ joinAsGuest,
+ };
+};
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts
index bf62c915f20..740e1eab0d6 100644
--- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationList.page.ts
@@ -111,13 +111,17 @@ export class ConversationListPage {
* @param options.protocol Only locate conversations matching this protocol (mls only works for 1on1 conversations as groups still use proteus) - Default: "mls"
*/
getConversationLocator(conversationName: string, options?: {protocol?: 'mls' | 'proteus'}) {
- const conversation = this.page.getByTestId('item-conversation').filter({hasText: conversationName});
+ let conversation = this.page.getByTestId('item-conversation').filter({hasText: conversationName});
if (options?.protocol) {
- return conversation.and(this.page.locator(`[data-protocol="${options.protocol}"]`));
+ conversation = conversation.and(this.page.locator(`[data-protocol="${options.protocol}"]`));
}
- return conversation;
+ return Object.assign(conversation, {
+ unreadIndicator: conversation.getByTitle('Unread message'),
+ mutedIndicator: conversation.getByTitle('Muted conversation'),
+ mentionIndicator: conversation.getByTitle('Unread mention'),
+ });
}
async openContextMenu(conversationName: string) {
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/guestOptions.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/guestOptions.page.ts
index b867b9b381c..2ddf3e7de18 100644
--- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/guestOptions.page.ts
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/guestOptions.page.ts
@@ -17,32 +17,44 @@
*
*/
-import {Page, Locator} from '@playwright/test';
-
-export class GuestOptionsPage {
- readonly page: Page;
-
- readonly createLinkButton: Locator;
- readonly inviteLink: Locator;
- readonly copyLinkButton: Locator;
-
- constructor(page: Page) {
- this.page = page;
-
- this.createLinkButton = page.locator('[data-uie-name="do-create-link"]');
- this.inviteLink = page.locator('[data-uie-name="status-invite-link"]');
- this.copyLinkButton = page.locator('[data-uie-name="do-copy-link"]');
- }
-
- async clickCreateLinkButton() {
- await this.createLinkButton.click();
- }
-
- async getInviteLink() {
- return this.inviteLink.textContent();
- }
-
- async clickCopyLinkButton() {
- await this.copyLinkButton.click();
- }
-}
+import {Page} from '@playwright/test';
+import {GuestLinkPasswordModal} from '../modals/guestLinkPassword.modal';
+import {ConfirmModal} from '../modals/confirm.modal';
+
+export const GuestOptionsPage = (page: Page) => {
+ const panel = page.getByRole('complementary').filter({has: page.getByRole('heading', {name: 'Guests'})});
+ const createPasswordModal = new GuestLinkPasswordModal(page);
+
+ const backButton = panel.getByRole('button', {name: 'Go back'});
+ const passwordSecuredRadioButton = panel.getByRole('radiogroup').getByText('Password secured');
+ const notPasswordSecuredRadioButton = panel.getByRole('radiogroup').getByText('Not password secured');
+ const createLinkButton = panel.getByRole('button', {name: 'Create link'});
+
+ const guestLink = panel.getByRole('button', {name: /https:\/\/.+\/conversation-join\//});
+
+ const createLink = async (options?: {password?: string}) => {
+ if (options?.password) {
+ await passwordSecuredRadioButton.click();
+ } else {
+ await notPasswordSecuredRadioButton.click();
+ }
+
+ await createLinkButton.click();
+
+ if (options?.password) {
+ await createPasswordModal.setPasswordInput.fill(options.password);
+ await createPasswordModal.confirmPasswordInput.fill(options.password);
+ await createPasswordModal.actionButton.click();
+
+ // After the link was created a second modal to copy the password will open
+ await new ConfirmModal(page).actionButton.click();
+ }
+
+ return await guestLink.textContent();
+ };
+
+ return {
+ backButton,
+ createLink,
+ };
+};
diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/participantDetails.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/participantDetails.page.ts
index ebbaa7f221d..c325bf9f262 100644
--- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/participantDetails.page.ts
+++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/participantDetails.page.ts
@@ -18,6 +18,7 @@
*/
import {Locator, Page} from '@playwright/test';
+import {ConfirmModal} from '../modals/confirm.modal';
export class ParticipantDetails {
readonly page: Page;
@@ -30,7 +31,7 @@ export class ParticipantDetails {
readonly closeButton: Locator;
readonly cancelRequest: Locator;
readonly unblockButton: Locator;
- readonly removeFromGroup: Locator;
+ readonly removeFromGroupButton: Locator;
constructor(page: Page) {
this.page = page;
@@ -45,7 +46,7 @@ export class ParticipantDetails {
this.closeButton = this.page.getByRole('button', {name: 'Close conversation info'});
this.cancelRequest = this.page.getByRole('button', {name: 'Cancel request'});
this.unblockButton = this.page.getByRole('button', {name: 'Unblock'});
- this.removeFromGroup = this.page.getByRole('button', {name: 'Remove from group'});
+ this.removeFromGroupButton = this.page.getByRole('button', {name: 'Remove from group'});
}
async blockUser() {
@@ -63,4 +64,9 @@ export class ParticipantDetails {
async sendConnectRequest() {
await this.page.getByRole('button', {name: 'Connect'}).click();
}
+
+ async removeFromGroup() {
+ await this.removeFromGroupButton.click();
+ await new ConfirmModal(this.page).actionButton.click();
+ }
}
diff --git a/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts b/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts
index 59a9f94c6b9..73cd23d3037 100644
--- a/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts
+++ b/apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts
@@ -144,11 +144,11 @@ test.describe('Edit', () => {
await test.step('Check user B has a unread conversation with A containing the sent message', async () => {
const conversation = userBPages.conversationList().getConversationLocator(userA.fullName);
- await expect(conversation.getByTestId('status-unread')).toBeVisible();
+ await expect(conversation.unreadIndicator).toBeVisible();
await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
await expect(conversation).toContainText('Test Message');
- await expect(conversation.getByTestId('status-unread')).not.toBeVisible();
+ await expect(conversation.unreadIndicator).not.toBeVisible();
});
await test.step("Open group conversation to ensure new messages won't be read immediately", async () => {
@@ -165,7 +165,7 @@ test.describe('Edit', () => {
await test.step('Check B received the updated message without marking the conversation as unread', async () => {
const conversation = userBPages.conversationList().getConversationLocator(userA.fullName);
- await expect(conversation.getByTestId('status-unread')).not.toBeVisible();
+ await expect(conversation.unreadIndicator).not.toBeVisible();
await userBPages.conversationList().openConversation(userA.fullName);
await expect(userBPages.conversation().getMessage({sender: userA})).toContainText('Edited Message');
diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts
new file mode 100644
index 00000000000..a14b3b497df
--- /dev/null
+++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts
@@ -0,0 +1,570 @@
+import {
+ test,
+ expect,
+ withLogin,
+ withConnectedUser,
+ Team,
+ withGuestUser,
+ withConnectionRequest,
+} from 'test/e2e_tests/test.fixtures';
+import {User} from 'test/e2e_tests/data/user';
+import {PageManager} from 'test/e2e_tests/pageManager';
+import {createGroup} from 'test/e2e_tests/utils/userActions';
+
+test.describe('Mention', () => {
+ let team: Team;
+ let userA: User;
+ let userB: User;
+ let userC: User;
+
+ test.beforeEach(async ({createUser, createTeam}) => {
+ [userB, userC] = await Promise.all([createUser(), createUser()]);
+ team = await createTeam('Test Team', {users: [userB, userC]});
+ userA = team.owner;
+ });
+
+ test('I want to be able to write a mention in a group', {tag: ['@TC-3487', '@regression']}, async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+ await createGroup(userAPages, 'Mention Group', [userB]);
+
+ // User A sends a message with a mention
+ await userAPages.conversationList().openConversation('Mention Group');
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey');
+
+ // User B receives the message
+ await userBPages.conversationList().openConversation('Mention Group');
+ const messageOnUserB = userBPages.conversation().getMessage({content: 'Hey', sender: userA});
+ await expect(messageOnUserB).toBeVisible();
+ const mentionOnUserB = messageOnUserB.getByRole('button', {name: `@${userB.fullName}`});
+ await expect(mentionOnUserB).toBeVisible();
+ });
+
+ test(
+ 'I want to mention multiple people in the same message',
+ {tag: ['@TC-3488', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages, userCPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userC))).then(pm => pm.webapp.pages),
+ ]);
+
+ await createGroup(userAPages, 'Multi-Mention Group', [userB, userC]);
+ await userBPages.conversationList().openConversation('Multi-Mention Group');
+ await userCPages.conversationList().openConversation('Multi-Mention Group');
+
+ // User A sends a message with multiple mentions
+ await userAPages.conversationList().openConversation('Multi-Mention Group');
+ const conversationPageA = userAPages.conversation();
+
+ await conversationPageA.messageInput.fill('Hello ');
+ await conversationPageA.mentionUser(userB.fullName);
+ await conversationPageA.messageInput.pressSequentially(' and ');
+ await conversationPageA.mentionUser(userC.fullName);
+ await conversationPageA.messageInput.press('Enter');
+
+ for (const page of [userAPages, userBPages, userCPages]) {
+ const message = page.conversation().getMessage({content: 'Hello', sender: userA});
+ await expect(message).toBeVisible();
+ await expect(message.getByRole('button', {name: `@${userB.fullName}`})).toBeVisible();
+ await expect(message.getByRole('button', {name: `@${userC.fullName}`})).toBeVisible();
+ }
+ },
+ );
+
+ test(
+ 'I want to be able to edit already sent message with a mention and mention someone else',
+ {tag: ['@TC-3489', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages, userCPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userC))).then(pm => pm.webapp.pages),
+ ]);
+
+ await test.step('Create group', async () => {
+ await createGroup(userAPages, 'Edit-Mention Group', [userB, userC]);
+ await userAPages.conversationList().openConversation('Edit-Mention Group');
+ await userBPages.conversationList().openConversation('Edit-Mention Group');
+ await userCPages.conversationList().openConversation('Edit-Mention Group');
+ });
+
+ await test.step('User A sends an initial message mentioning userB', async () => {
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello');
+
+ for (const page of [userAPages, userBPages, userCPages]) {
+ const message = page.conversation().getMessage({content: 'Hello', sender: userA});
+ await expect(message).toBeVisible();
+ await expect(message.getByRole('button', {name: `@${userB.fullName}`})).toBeVisible();
+ }
+ });
+
+ await test.step('User A edits the message, changing the mention to userC', async () => {
+ const initialMessageOnUserA = userAPages.conversation().getMessage({content: 'Hello', sender: userA});
+ await userAPages.conversation().editMessage(initialMessageOnUserA);
+ await expect(userAPages.conversation().messageInput).toContainText(`@${userB.fullName} Hello`);
+
+ await userAPages.conversation().messageInput.fill(''); // Clear previous content
+ await userAPages.conversation().mentionUser(userC.fullName);
+ await userAPages.conversation().messageInput.pressSequentially(' new greeting');
+ await userAPages.conversation().messageInput.press('Enter');
+ });
+
+ await test.step('Verify the edited message on all users', async () => {
+ for (const page of [userAPages, userBPages, userCPages]) {
+ const message = page.conversation().getMessage({content: 'new greeting', sender: userA});
+ await expect(message).toBeVisible();
+ await expect(message.getByRole('button', {name: `@${userC.fullName}`})).toBeVisible();
+ await expect(message.getByRole('button', {name: `@${userB.fullName}`})).not.toBeVisible();
+ }
+ });
+ },
+ );
+
+ test('I want to be able to write a mention in a 1:1', {tag: ['@TC-3490', '@regression']}, async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
+
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello');
+
+ for (const page of [userAPages, userBPages]) {
+ const message = page.conversation().getMessage({content: 'Hello', sender: userA});
+ await expect(message).toBeVisible();
+ await expect(message.getByRole('button', {name: `@${userB.fullName}`})).toBeVisible();
+ }
+ });
+
+ test(
+ 'I want to send an ephemeral message with a mention',
+ {tag: ['@TC-3491', '@regression']},
+ async ({createPage}) => {
+ const [userAPage, userBPage] = await Promise.all([
+ createPage(withLogin(userA), withConnectedUser(userB)),
+ createPage(withLogin(userB)),
+ ]);
+ const userAPages = PageManager.from(userAPage).webapp.pages;
+ const userBPages = PageManager.from(userBPage).webapp.pages;
+
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
+
+ // User A sends a self deleting message with a mention
+ await userAPages.conversation().enableSelfDeletingMessages();
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Ephemeral mention');
+
+ // Verify message and mention on user B's side
+ const messageOnUserB = userBPages.conversation().getMessage({content: 'Ephemeral mention', sender: userA});
+ await expect(messageOnUserB).toBeVisible();
+ await expect(messageOnUserB.getByRole('button', {name: `@${userB.fullName}`})).toBeVisible();
+
+ // Wait for the message to be deleted (10 seconds as per default setting)
+ await userBPage.waitForTimeout(10_000);
+
+ // Verify message is no longer visible on user B's side
+ await expect(messageOnUserB).not.toBeVisible();
+ },
+ );
+
+ test(
+ 'I want to mention the same person twice in the same message',
+ {tag: ['@TC-3492', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
+
+ // User A sends a message with the same person mentioned twice
+ const conversationPageA = userAPages.conversation();
+ await conversationPageA.messageInput.fill('Hi ');
+ await conversationPageA.mentionUser(userB.fullName);
+ await conversationPageA.messageInput.pressSequentially(' check this out ');
+ await conversationPageA.mentionUser(userB.fullName);
+ await conversationPageA.messageInput.press('Enter');
+
+ for (const page of [userAPages, userBPages]) {
+ const message = page.conversation().getMessage({content: 'check this out', sender: userA});
+ await expect(message).toBeVisible();
+ const mentions = message.getByRole('button', {name: `@${userB.fullName}`});
+ await expect(mentions).toHaveCount(2);
+ }
+ },
+ );
+
+ test(
+ 'I want mention to be shown as a full name even if I searched by username',
+ {tag: ['@TC-3493', '@regression']},
+ async ({createPage}) => {
+ const userAPages = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp.pages;
+ await userAPages.conversationList().openConversation(userB.fullName);
+
+ const conversationPageA = userAPages.conversation();
+ await conversationPageA.messageInput.fill(''); // Clear input
+ await conversationPageA.mentionUser(userB.fullName, userB.username); // Search by username, but expect full name
+ await conversationPageA.messageInput.pressSequentially(' tested it');
+ await conversationPageA.messageInput.press('Enter');
+
+ // Verify on user A's side that the mention is the full name
+ const messageOnUserA = conversationPageA.getMessage({content: 'tested it'});
+ await expect(messageOnUserA).toBeVisible();
+ await expect(messageOnUserA.getByRole('button', {name: `@${userB.fullName}`})).toBeVisible();
+ await expect(messageOnUserA.getByRole('button', {name: `@${userB.username}`})).not.toBeVisible(); // Assert it's not the username
+ },
+ );
+
+ test(
+ 'I want to see the mentions in my profile color in the input field',
+ {tag: ['@TC-3494', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ createPage(withLogin(userA), withConnectedUser(userB)).then(page => PageManager.from(page).webapp.pages),
+ createPage(withLogin(userB)).then(page => PageManager.from(page).webapp.pages),
+ ]);
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
+
+ const conversationPageA = userAPages.conversation();
+
+ await conversationPageA.messageInput.fill(''); // Clear input
+ await conversationPageA.mentionUser(userB.fullName);
+
+ // Assert the color of the mention in the input field for user A
+ const mentionInInputField = conversationPageA.messageInput.getByTestId('item-input-mention');
+ await expect(mentionInInputField).toContainText(`@${userB.fullName}`);
+ await expect(mentionInInputField).toHaveCSS('color', 'rgb(6, 103, 200)');
+
+ await conversationPageA.messageInput.press('Enter');
+
+ // Assert the background color of the mention in the received message for user B
+ const messageOnUserB = userBPages.conversation().getMessage({sender: userA});
+ await expect(messageOnUserB).toBeVisible();
+ await expect(messageOnUserB.getByRole('button', {name: `@${userB.fullName}`})).toHaveCSS(
+ 'background-color',
+ 'rgb(155, 194, 233)',
+ );
+ },
+ );
+
+ test(
+ 'I should not loose drafted text or mentions in input field',
+ {tag: ['@TC-3498', '@regression']},
+ async ({createPage}) => {
+ const userAPages = (await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB)))).webapp.pages;
+ await createGroup(userAPages, 'Draft Group', [userB]);
+
+ const conversationPageA = userAPages.conversation();
+ await test.step('Draft a message with a mention in the group chat', async () => {
+ await userAPages.conversationList().openConversation('Draft Group');
+ await conversationPageA.messageInput.fill('Draft message with ');
+ await conversationPageA.mentionUser(userB.fullName);
+
+ const mentionInInputField = conversationPageA.messageInput.getByTestId('item-input-mention');
+ await expect(mentionInInputField).toBeVisible();
+ });
+
+ await test.step('Switch to the 1:1 chat', async () => {
+ await userAPages.conversationList().openConversation(userB.fullName);
+ await expect(conversationPageA.messageInput).toBeEmpty();
+ });
+
+ await test.step('Switch back to the group chat and verify the draft with the mention is preserved', async () => {
+ await userAPages.conversationList().openConversation('Draft Group');
+ await expect(conversationPageA.messageInput).toHaveText(`Draft message with @${userB.fullName}`);
+
+ const preservedMention = conversationPageA.messageInput.getByTestId('item-input-mention');
+ await expect(preservedMention).toBeVisible();
+ await expect(preservedMention).toContainText(`@${userB.fullName}`);
+ });
+ },
+ );
+
+ test('I want to receive a message containing a mention', {tag: ['@TC-3521', '@regression']}, async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
+
+ // User A sends a message with a mention to User B
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hello');
+
+ // User B verifies the received message and mention
+ const receivedMessage = userBPages.conversation().getMessage({content: 'Hello', sender: userA});
+ await expect(receivedMessage).toBeVisible();
+
+ const mentionInMessage = receivedMessage.getByRole('button', {name: `@${userB.fullName}`});
+ await expect(mentionInMessage).toBeVisible();
+ });
+
+ test(
+ 'I want to see a subtitle in the conversation list when there is one or more unread mentions in the conversation',
+ {tag: ['@TC-3528', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+
+ // Create and open a group conversation for userB to ensure the message from A won't be read immediately
+ await createGroup(userBPages, 'Distraction Group', [userC]);
+ await userBPages.conversationList().openConversation('Distraction Group');
+
+ // User A opens conversation with user B and sends a message with mention
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey, you have an unread mention!');
+
+ // User B is in the 'Distraction Group', so the conversation with user A is unread.
+ // Now check for the mention indicator in the conversation list.
+ const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName);
+ await expect(mentionIndicator).toBeVisible();
+ },
+ );
+
+ test(
+ 'I want to see mention icon when I have unread mention, messages, pings and calls in a conversation',
+ {tag: ['@TC-3529', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+
+ await test.step('Create and open a distraction group conversation for User B', async () => {
+ // userA creates the group, userB opens it. This is the distraction.
+ await createGroup(userAPages, 'Distraction Group', [userB]);
+ await userBPages.conversationList().openConversation('Distraction Group');
+ });
+
+ await test.step('User A tries to call User B in 1:1 conversation but B declines', async () => {
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userAPages.conversation().startCall();
+ await userBPages.calling().leaveCallButton.click();
+ await userAPages.calling().leaveCallButton.click();
+ });
+
+ await test.step('User A sends a plain message to User B in 1:1 conversation', async () => {
+ await userAPages.conversation().sendMessage('Hello, this is a plain message.');
+ });
+
+ await test.step('User A sends a message with a mention to User B in 1:1 conversation', async () => {
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName, 'Hey, you have an unread mention!');
+ });
+
+ await test.step('User A sends a ping to User B in 1:1 conversation', async () => {
+ await userAPages.conversation().sendPing();
+ });
+
+ await test.step('Verify User B sees both unread mention and unread message indicators for 1:1 conversation', async () => {
+ const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName);
+ await expect(mentionIndicator).toBeVisible();
+ });
+ },
+ );
+
+ test(
+ 'I should not see mention indicator in the conversation list if the sender recalls the mention message to me',
+ {tag: ['@TC-3530', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ ]);
+
+ await createGroup(userAPages, 'Test Group', [userB]);
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation('Test Group');
+
+ await userAPages.conversation().sendMessageWithUserMention(userB.fullName);
+ const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName);
+ await expect(mentionIndicator).toBeVisible();
+
+ await userAPages.conversation().deleteMessage(userAPages.conversation().getMessage({sender: userA}), 'Everyone');
+ await expect(mentionIndicator).not.toBeAttached();
+ },
+ );
+
+ test(
+ 'I want to see normal text message (without mention link) when I did not select someone from mention suggestion',
+ {tag: ['@TC-3531', '@regression']},
+ async ({createPage}) => {
+ const [userAPages, userBPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))).then(pm => pm.webapp.pages),
+ ]);
+
+ await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'});
+ await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'});
+ await userAPages.conversation().sendMessage(`@${userB.fullName} Hello`);
+
+ for (const pages of [userAPages, userBPages]) {
+ const message = pages.conversation().getMessage({sender: userA});
+ await expect(message).toContainText(`@${userB.fullName}`);
+ await expect(message.getByRole('button', {name: `@${userB.fullName}`})).not.toBeAttached();
+ }
+ },
+ );
+
+ test(
+ 'I should not see mentions suggestion if I type @ with characters in front',
+ {tag: ['@TC-3532', '@regression']},
+ async ({createPage}) => {
+ const {pages} = PageManager.from(await createPage(withLogin(userA), withConnectedUser(userB))).webapp;
+ await pages.conversationList().openConversation(userB.fullName);
+
+ await pages.conversation().messageInput.pressSequentially(`test@${userB.firstName}`);
+ await expect(pages.conversation().mentionSuggestions).toHaveCount(0);
+ },
+ );
+
+ test(
+ 'I should not see suggestions of people who are not in the group chat',
+ {tag: ['@TC-3533', '@regression']},
+ async ({createPage}) => {
+ const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp;
+
+ await createGroup(pages, 'Test Group', [userB]);
+ await pages.conversationList().openConversation('Test Group');
+
+ // It should be possible to mention userB as he's part of the group
+ await pages.conversation().messageInput.pressSequentially(`@${userB.firstName}`);
+ await expect(pages.conversation().mentionSuggestions).toHaveCount(1);
+
+ // There should not be a suggestion for userC who's not part of the group
+ await pages.conversation().messageInput.fill('');
+ await pages.conversation().messageInput.pressSequentially(`@${userC.firstName}`);
+ await expect(pages.conversation().mentionSuggestions).toHaveCount(0);
+ },
+ );
+
+ test(
+ 'I want to see all group participant list when I tap or type @',
+ {tag: ['@TC-3535', '@regression']},
+ async ({createPage}) => {
+ const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp;
+
+ await createGroup(pages, 'Test Group', [userB, userC]);
+ await pages.conversationList().openConversation('Test Group');
+
+ // It should be possible to mention userB as he's part of the group
+ await pages.conversation().messageInput.pressSequentially(`@`);
+ await expect(pages.conversation().mentionSuggestions).toHaveCount(2);
+ await expect(pages.conversation().mentionSuggestions.filter({hasText: userB.fullName})).toBeVisible();
+ await expect(pages.conversation().mentionSuggestions.filter({hasText: userC.fullName})).toBeVisible();
+ },
+ );
+
+ test(
+ 'I want to mention a name with or without umlaut gives the same suggestion (query normalization)',
+ {tag: ['@TC-3537', '@regression']},
+ async ({createUser, createPage}) => {
+ const memberWithStrangeName = await createUser({firstName: 'Günter'});
+ await team.addTeamMember(memberWithStrangeName);
+
+ const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp;
+ await createGroup(pages, 'Test Group', [memberWithStrangeName]);
+ await pages.conversationList().openConversation('Test Group');
+
+ await pages.conversation().messageInput.pressSequentially('@Gunter');
+ await expect(pages.conversation().mentionSuggestions).toHaveCount(1);
+ await expect(pages.conversation().mentionSuggestions).toContainText(memberWithStrangeName.fullName);
+ },
+ );
+
+ test(
+ 'I want to see guest indicators in the suggestions list',
+ {tag: ['@TC-3540', '@regression']},
+ async ({createPage}) => {
+ const {pages} = PageManager.from(await createPage(withLogin(userA))).webapp;
+ await createGroup(pages, 'Test Group', []);
+
+ await test.step('Create guest link for group & join as guest user', async () => {
+ await pages.conversationList().openConversation('Test Group');
+ await pages.conversation().clickConversationTitle();
+ const link = await pages.conversationDetails().createGuestLink();
+ await createPage(withGuestUser(link, 'Guest User'));
+ });
+
+ await test.step('Verify the temporary user is shown as group member', async () => {
+ const guestMember = pages.conversationDetails().groupMembers.filter({hasText: 'Guest User'});
+ // It may take a moment until the login is done and the user joined
+ await expect(guestMember).toBeVisible({timeout: 60_000});
+ });
+
+ await test.step('Verify the mention suggestion for the guest has a "guest" indicator', async () => {
+ await pages.conversation().messageInput.pressSequentially('@Guest');
+ await expect(pages.conversation().mentionSuggestions).toHaveCount(1);
+ await expect(pages.conversation().mentionSuggestions.getByTestId('status-guest')).toBeVisible();
+ });
+ },
+ );
+
+ test(
+ 'I want to be able to mention participants I am not connected to',
+ {tag: ['@TC-3545', '@regression']},
+ async ({createUser, createPage}) => {
+ const otherUser = await createUser();
+ const [userAPages, userBPages, otherUserPages] = await Promise.all([
+ PageManager.from(createPage(withLogin(userA), withConnectionRequest(otherUser))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages),
+ PageManager.from(createPage(withLogin(otherUser))).then(async pm => {
+ await pm.webapp.pages.conversationList().pendingConnectionRequest.click();
+ await pm.webapp.pages.connectRequest().connectButton.click();
+ return pm.webapp.pages;
+ }),
+ ]);
+
+ await test.step('UserA creates a group including userB and otherUser', async () => {
+ await createGroup(userAPages, 'Test Group', [userB, otherUser]);
+ });
+
+ await test.step("UserB mentions otherUser in the group although they're not connected", async () => {
+ await userBPages.conversationList().openConversation('Test Group');
+ await userBPages.conversation().sendMessageWithUserMention(otherUser.fullName);
+ });
+
+ await test.step('OtherUser receives the message from userB including the mention', async () => {
+ await otherUserPages.conversationList().openConversation('Test Group');
+ const mentionInMessage = otherUserPages
+ .conversation()
+ .getMessage({sender: userB})
+ .getByRole('button', {name: `@${otherUser.fullName}`});
+ await expect(mentionInMessage).toBeVisible();
+ });
+ },
+ );
+
+ test(
+ 'I should not be able to mention a person who left/was removed (from) a conversation',
+ {tag: ['@TC-3546', '@regression']},
+ async ({createPage}) => {
+ const userAPages = PageManager.from(await createPage(withLogin(userA))).webapp.pages;
+ await createGroup(userAPages, 'Test Group', [userB, userC]);
+
+ await test.step('UserA removes userB from the group', async () => {
+ await userAPages.conversationList().openConversation('Test Group');
+ await userAPages.conversation().conversationTitle.click();
+ await userAPages.conversationDetails().openParticipantDetails(userB.fullName);
+ await userAPages.participantDetails().removeFromGroup();
+ });
+
+ await test.step("UserA tries to mention userB but he's not in the suggestions", async () => {
+ await userAPages.conversation().messageInput.pressSequentially('@');
+ await expect(userAPages.conversation().mentionSuggestions).toHaveCount(1);
+ await expect(userAPages.conversation().mentionSuggestions).not.toContainText(userB.fullName);
+ });
+ },
+ );
+});
diff --git a/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts b/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts
index 68fd786c123..f9838b375df 100644
--- a/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts
+++ b/apps/webapp/test/e2e_tests/specs/Notifications/notifications.spec.ts
@@ -87,7 +87,7 @@ test.describe('Notifications', () => {
await pages.conversationList().setNotifications('Nothing');
const conversation = pages.conversationList().getConversationLocator(userB.fullName);
- await expect(conversation.getByTitle('Muted conversation')).toBeVisible();
+ await expect(conversation.mutedIndicator).toBeVisible();
},
);
@@ -101,12 +101,12 @@ test.describe('Notifications', () => {
await pages.conversationList().setNotifications('Nothing');
const conversation = pages.conversationList().getConversationLocator(userB.fullName);
- await expect(conversation.getByTitle('Muted conversation')).toBeVisible();
+ await expect(conversation.mutedIndicator).toBeVisible();
await pages.conversationList().clickConversationOptions(userB.fullName);
await pages.conversationList().setNotifications('Everything');
- await expect(conversation.getByTitle('Muted conversation')).not.toBeVisible();
+ await expect(conversation.mutedIndicator).not.toBeVisible();
},
);
diff --git a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts
index 0790c22afab..019bfe27e1f 100644
--- a/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts
+++ b/apps/webapp/test/e2e_tests/specs/ParticipantProfile/participantProfile.spec.ts
@@ -164,7 +164,7 @@ test.describe('Participant Profile', () => {
await expect(pages.participantDetails().getUserEmailLocator(userC.email)).not.toBeVisible();
await expect(pages.participantDetails().unblockButton).toBeVisible();
- await expect(pages.participantDetails().removeFromGroup).toBeVisible();
+ await expect(pages.participantDetails().removeFromGroupButton).toBeVisible();
});
},
);
@@ -251,7 +251,7 @@ test.describe('Participant Profile', () => {
await createGroup(adminPage, groupName, [userB]);
await openParticipantDetailsFromGroup(adminPage, groupName, userB.fullName);
- const removeFromGroup = adminPage.participantDetails().removeFromGroup;
+ const removeFromGroup = adminPage.participantDetails().removeFromGroupButton;
await expect(removeFromGroup).toBeVisible();
await expect(removeFromGroup).toHaveText('Remove from group…');
},
@@ -271,7 +271,7 @@ test.describe('Participant Profile', () => {
await createGroup(adminPage, groupName, [userB, userC]);
await openParticipantDetailsFromGroup(userBPages, groupName, userC.fullName);
- await expect(userBPages.participantDetails().removeFromGroup).not.toBeVisible();
+ await expect(userBPages.participantDetails().removeFromGroupButton).not.toBeVisible();
},
);
});
diff --git a/apps/webapp/test/e2e_tests/test.fixtures.ts b/apps/webapp/test/e2e_tests/test.fixtures.ts
index 231adfdb39d..f49e171027b 100644
--- a/apps/webapp/test/e2e_tests/test.fixtures.ts
+++ b/apps/webapp/test/e2e_tests/test.fixtures.ts
@@ -228,10 +228,22 @@ export const withConnectionRequest =
await sendConnectionRequest(pageManager, await user);
};
-const createUser = async (api: ApiManagerE2E, options?: {disableTelemetry?: boolean}) => {
+/** PagePlugin to open a guest user link and join the group chat as temporary member */
+export const withGuestUser =
+ (link: string, guestName: string): PagePlugin =>
+ async page => {
+ await page.goto(link);
+ await page.getByRole('link', {name: 'Join in Browser'}).click();
+ await PageManager.from(page).webapp.pages.conversationJoin().joinAsGuest(guestName);
+ };
+
+const createUser = async (
+ api: ApiManagerE2E,
+ options?: {firstName?: string; lastName?: string; disableTelemetry?: boolean},
+) => {
const {disableTelemetry = true} = options ?? {};
- const user = getUser();
+ const user = getUser({firstName: options?.firstName, lastName: options?.lastName});
await api.createPersonalUser(user);
// Optionally decline to send telemetry via the api. This avoids the user being prompted for it in the UI upon first login
diff --git a/apps/webapp/test/e2e_tests/utils/userDataGenerator.ts b/apps/webapp/test/e2e_tests/utils/userDataGenerator.ts
index 3f17dc878d7..60b33eafc89 100644
--- a/apps/webapp/test/e2e_tests/utils/userDataGenerator.ts
+++ b/apps/webapp/test/e2e_tests/utils/userDataGenerator.ts
@@ -56,6 +56,6 @@ export const generateWireEmail = (firstName: string, lastName: string): string =
};
export const generateUsername = (firstName: string, lastName: string): string => {
- const username = `${sanitizeName(firstName)}${sanitizeName(lastName)}`.toLowerCase();
+ const username = `${sanitizeName(firstName)}${sanitizeName(lastName)}${faker.string.numeric(4)}`.toLowerCase();
return username.slice(0, 20);
};