From a6cbbbe5c186ce86e848e1e18318eff0ed885d62 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 16:56:06 +0100 Subject: [PATCH 01/24] test: implement TC-3487 --- .../e2e_tests/specs/Mention/mention.spec.ts | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts 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..deb6af3462e --- /dev/null +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -0,0 +1,135 @@ +import {test, expect, withLogin} 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 userA: User; + let userB: User; + let userC: User; + + test.beforeEach(async ({createUser, createTeam}) => { + [userB, userC] = await Promise.all([createUser(), createUser()]); + const 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 () => {}); + + test( + 'I want to be able to edit already sent message with a mention and mention someone else', + {tag: ['@TC-3489', '@regression']}, + async () => {}, + ); + + test('I want to be able to write a mention in a 1:1', {tag: ['@TC-3490', '@regression']}, async () => {}); + + test('I want to send an ephemeral message with a mention', {tag: ['@TC-3491', '@regression']}, async () => {}); + + test( + 'I want to mention the same person twice in the same message', + {tag: ['@TC-3492', '@regression']}, + async () => {}, + ); + + test( + 'I want mention to be shown as a full name even if I searched by username', + {tag: ['@TC-3493', '@regression']}, + async () => {}, + ); + + test( + 'I want to see the mentions in my profile color in the input field', + {tag: ['@TC-3494', '@regression']}, + async () => {}, + ); + + test( + 'I should not loose drafted text or mentions in input field', + {tag: ['@TC-3498', '@regression']}, + async () => {}, + ); + + test('I want to receive a message containing a mention', {tag: ['@TC-3521', '@regression']}, async () => {}); + + 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 () => {}, + ); + + test( + 'I want to see mention icon when I have unread mention, messages, pings and calls in a conversation', + {tag: ['@TC-3529', '@regression']}, + async () => {}, + ); + + 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 () => {}, + ); + + 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 () => {}, + ); + + test( + 'I should not see mentions suggestion if I type @ with characters in front', + {tag: ['@TC-3532', '@regression']}, + async () => {}, + ); + + test( + 'I should not see suggestions of people who are not in the group chat', + {tag: ['@TC-3533', '@regression']}, + async () => {}, + ); + + test( + 'I want to see all group participant list when I tap or type @', + {tag: ['@TC-3535', '@regression']}, + async () => {}, + ); + + test( + 'I want to mention a name with or without umlaut gives the same suggestion (query normalization)', + {tag: ['@TC-3537', '@regression']}, + async () => {}, + ); + + test('I want to see guest indicators in the suggestions list', {tag: ['@TC-3540', '@regression']}, async () => {}); + + test( + 'I want to be able to mention participants I am not connected to', + {tag: ['@TC-3545', '@regression']}, + async () => {}, + ); + + test( + 'I should not be able to mention a person who left/was removed (from) a conversation', + {tag: ['@TC-3546', '@regression']}, + async () => {}, + ); +}); From 8bee67e698d41c48f45db798802a28adc13f54af Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 17:05:47 +0100 Subject: [PATCH 02/24] test: add TC-3488 --- .../webapp/pages/conversation.page.ts | 17 ++++++---- .../e2e_tests/specs/Mention/mention.spec.ts | 33 ++++++++++++++++++- 2 files changed, 43 insertions(+), 7 deletions(-) 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..ae73a04a148 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 @@ -158,6 +158,15 @@ export class ConversationPage { await this.messageInput.pressSequentially(message, {delay: 100}); } + async mentionUser(userFullName: string) { + await this.messageInput.pressSequentially(`@${userFullName.slice(0, 3)}`); + await this.page + .getByTestId('item-mention-suggestion') + .getByTestId('status-name') + .filter({hasText: userFullName}) + .click(); + } + async replyToMessage(message: Locator) { await message.hover(); await message.getByRole('group').getByTestId('do-reply-message').click(); @@ -174,12 +183,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/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index deb6af3462e..306f440374a 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -33,7 +33,38 @@ test.describe('Mention', () => { await expect(mentionOnUserB).toBeVisible(); }); - test('I want to mention multiple people in the same message', {tag: ['@TC-3488', '@regression']}, async () => {}); + 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', From b278e342251dc191875ce2dc27c837011ad240d0 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 17:32:14 +0100 Subject: [PATCH 03/24] test: implement TC-3489 --- .../e2e_tests/specs/Mention/mention.spec.ts | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 306f440374a..5adf2eb3080 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -69,7 +69,50 @@ test.describe('Mention', () => { test( 'I want to be able to edit already sent message with a mention and mention someone else', {tag: ['@TC-3489', '@regression']}, - async () => {}, + 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 () => {}); From 70e93ca7c755373cde8df8f30840b0cffd1de4df Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 17:36:38 +0100 Subject: [PATCH 04/24] test: implement TC-3490 --- .../e2e_tests/specs/Mention/mention.spec.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 5adf2eb3080..9f2b013e5e6 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -1,4 +1,4 @@ -import {test, expect, withLogin} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withLogin, withConnectedUser} 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'; @@ -115,7 +115,23 @@ test.describe('Mention', () => { }, ); - test('I want to be able to write a mention in a 1:1', {tag: ['@TC-3490', '@regression']}, async () => {}); + 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 () => {}); From 603d12e7a8df35b205b4cf2514f17f17510cf01b Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 17:42:57 +0100 Subject: [PATCH 05/24] test: implement TC-3491 --- .../e2e_tests/specs/Mention/mention.spec.ts | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 9f2b013e5e6..c32c659cb91 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -133,7 +133,36 @@ test.describe('Mention', () => { } }); - test('I want to send an ephemeral message with a mention', {tag: ['@TC-3491', '@regression']}, async () => {}); + 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', From dc948eb5f89373b955bd900f79f05c0d3c48e128 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 17:48:56 +0100 Subject: [PATCH 06/24] test: implement TC-3492 --- .../e2e_tests/specs/Mention/mention.spec.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index c32c659cb91..5f00a4c534f 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -167,7 +167,30 @@ test.describe('Mention', () => { test( 'I want to mention the same person twice in the same message', {tag: ['@TC-3492', '@regression']}, - async () => {}, + 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( From 7562a2bde72b70e9a3f9fc586a6ce037e8aa3c95 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 17:55:01 +0100 Subject: [PATCH 07/24] test: implement TC-3493 --- .../webapp/pages/conversation.page.ts | 7 ++++--- .../e2e_tests/specs/Mention/mention.spec.ts | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) 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 ae73a04a148..18505597980 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 @@ -158,13 +158,14 @@ export class ConversationPage { await this.messageInput.pressSequentially(message, {delay: 100}); } - async mentionUser(userFullName: string) { - await this.messageInput.pressSequentially(`@${userFullName.slice(0, 3)}`); + async mentionUser(userFullName: string, searchQuery?: string) { + const textToType = searchQuery ? `@${searchQuery}` : `@${userFullName.slice(0, 3)}`; + await this.messageInput.pressSequentially(textToType); await this.page .getByTestId('item-mention-suggestion') .getByTestId('status-name') .filter({hasText: userFullName}) - .click(); + .click({timeout: 5000}); } async replyToMessage(message: Locator) { diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 5f00a4c534f..0ae7c401708 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -196,7 +196,22 @@ test.describe('Mention', () => { test( 'I want mention to be shown as a full name even if I searched by username', {tag: ['@TC-3493', '@regression']}, - async () => {}, + 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( From 85340ff9d7c449a1182b8f04f3a785ee36899aa3 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 18:22:21 +0100 Subject: [PATCH 08/24] test: implement TC-3494 --- .../e2e_tests/specs/Mention/mention.spec.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 0ae7c401708..2cdc6557e8e 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -217,7 +217,34 @@ test.describe('Mention', () => { test( 'I want to see the mentions in my profile color in the input field', {tag: ['@TC-3494', '@regression']}, - async () => {}, + 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( From d4b870490641cdd0cad11fd3636209250109eb94 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 18:31:42 +0100 Subject: [PATCH 09/24] test: implement TC-3498 --- .../e2e_tests/specs/Mention/mention.spec.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 2cdc6557e8e..4bed5c489a3 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -250,7 +250,34 @@ test.describe('Mention', () => { test( 'I should not loose drafted text or mentions in input field', {tag: ['@TC-3498', '@regression']}, - async () => {}, + 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 () => {}); From e546033c41686a47752d92101b501d94740e9458 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 18:33:19 +0100 Subject: [PATCH 10/24] test: implement TC-3521 --- .../e2e_tests/specs/Mention/mention.spec.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 4bed5c489a3..258686304a1 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -280,7 +280,25 @@ test.describe('Mention', () => { }, ); - test('I want to receive a message containing a mention', {tag: ['@TC-3521', '@regression']}, async () => {}); + 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', From 5bcb1e20aa383260100fe28d472affa5bdac2be4 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Mon, 23 Feb 2026 18:40:48 +0100 Subject: [PATCH 11/24] test: implement TC-3528 --- .../e2e_tests/specs/Mention/mention.spec.ts | 23 ++++++++++++++++++- .../test/e2e_tests/utils/userDataGenerator.ts | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 258686304a1..f196fcedf8a 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -303,7 +303,28 @@ test.describe('Mention', () => { 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 () => {}, + 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) + .getByTestId('status-mention'); + await expect(mentionIndicator).toBeVisible(); + }, ); test( 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); }; From 3808146dd71b3cb69fdd6ec64f84e396b8d4958f Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 24 Feb 2026 10:42:47 +0100 Subject: [PATCH 12/24] fix: add correct aria label to decline button --- .../script/components/calling/CallingCell/CallingCell.tsx | 1 - .../calling/CallingCell/CallingControls/CallingControls.tsx | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) 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" > From 125a0bb07a403797e5af0aa6ce10909b9e9ba031 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 24 Feb 2026 10:42:57 +0100 Subject: [PATCH 13/24] test: implement TC-3529 --- .../e2e_tests/specs/Mention/mention.spec.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index f196fcedf8a..5c5d97550cc 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -330,7 +330,42 @@ test.describe('Mention', () => { test( 'I want to see mention icon when I have unread mention, messages, pings and calls in a conversation', {tag: ['@TC-3529', '@regression']}, - async () => {}, + 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 conversationLocator = userBPages.conversationList().getConversationLocator(userA.fullName); + await expect(conversationLocator.getByTitle('Unread mention')).toBeVisible(); + }); + }, ); test( From 0e17322d3981b66a4e48ca2923e7f7bbe797fda2 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 10:49:16 +0100 Subject: [PATCH 14/24] refactor: group conversation list item locators --- .../pageManager/webapp/pages/conversationList.page.ts | 10 +++++++--- apps/webapp/test/e2e_tests/specs/Edit/edit.spec.ts | 6 +++--- .../test/e2e_tests/specs/Mention/mention.spec.ts | 9 +++------ .../specs/Notifications/notifications.spec.ts | 6 +++--- 4 files changed, 16 insertions(+), 15 deletions(-) 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/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 index 5c5d97550cc..a4976b5cf0f 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -319,10 +319,7 @@ test.describe('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) - .getByTestId('status-mention'); + const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName); await expect(mentionIndicator).toBeVisible(); }, ); @@ -362,8 +359,8 @@ test.describe('Mention', () => { }); await test.step('Verify User B sees both unread mention and unread message indicators for 1:1 conversation', async () => { - const conversationLocator = userBPages.conversationList().getConversationLocator(userA.fullName); - await expect(conversationLocator.getByTitle('Unread mention')).toBeVisible(); + const {mentionIndicator} = userBPages.conversationList().getConversationLocator(userA.fullName); + await expect(mentionIndicator).toBeVisible(); }); }, ); 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(); }, ); From 426b4b1849727694bd2a69ca489e568269be38d2 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 10:49:27 +0100 Subject: [PATCH 15/24] test: implement TC-3530 --- .../e2e_tests/specs/Mention/mention.spec.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index a4976b5cf0f..82e19c8eee8 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -368,7 +368,23 @@ test.describe('Mention', () => { 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 () => {}, + 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( From 05ef5fce947255e5bdf44a9f22695eff3b59ff15 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 12:57:17 +0100 Subject: [PATCH 16/24] test: implement TC-3531 --- .../e2e_tests/specs/Mention/mention.spec.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 82e19c8eee8..d7bb86a827a 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -390,7 +390,22 @@ test.describe('Mention', () => { 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 () => {}, + 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( From a13c3c70887b5ca211cf188ed62aa5058a31a429 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 13:14:55 +0100 Subject: [PATCH 17/24] test: implement TC-3532 --- .../pageManager/webapp/pages/conversation.page.ts | 8 +++----- apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts | 8 +++++++- 2 files changed, 10 insertions(+), 6 deletions(-) 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 18505597980..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 { @@ -161,11 +163,7 @@ export class ConversationPage { async mentionUser(userFullName: string, searchQuery?: string) { const textToType = searchQuery ? `@${searchQuery}` : `@${userFullName.slice(0, 3)}`; await this.messageInput.pressSequentially(textToType); - await this.page - .getByTestId('item-mention-suggestion') - .getByTestId('status-name') - .filter({hasText: userFullName}) - .click({timeout: 5000}); + await this.mentionSuggestions.filter({hasText: userFullName}).click({timeout: 5000}); } async replyToMessage(message: Locator) { diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index d7bb86a827a..ac729ed9097 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -411,7 +411,13 @@ test.describe('Mention', () => { test( 'I should not see mentions suggestion if I type @ with characters in front', {tag: ['@TC-3532', '@regression']}, - async () => {}, + 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( From 93a27ddd8afffb9e2891c5eb524cd9f71a5bf10b Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 13:18:37 +0100 Subject: [PATCH 18/24] test: implement TC-3533 --- .../test/e2e_tests/specs/Mention/mention.spec.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index ac729ed9097..4ab1f220e55 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -423,7 +423,21 @@ test.describe('Mention', () => { test( 'I should not see suggestions of people who are not in the group chat', {tag: ['@TC-3533', '@regression']}, - async () => {}, + 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( From 1916c1a7f151db356abf67669e9032b6589bc8a8 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 13:21:41 +0100 Subject: [PATCH 19/24] test: implement TC-3535 --- .../test/e2e_tests/specs/Mention/mention.spec.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 4ab1f220e55..37357a11c9c 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -443,7 +443,18 @@ test.describe('Mention', () => { test( 'I want to see all group participant list when I tap or type @', {tag: ['@TC-3535', '@regression']}, - async () => {}, + 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( From 488fb0756e8ef7f828b89fb94880c0af886a4ee3 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 14:16:38 +0100 Subject: [PATCH 20/24] test: implement TC-3537 --- .../e2e_tests/specs/Mention/mention.spec.ts | 18 +++++++++++++++--- apps/webapp/test/e2e_tests/test.fixtures.ts | 7 +++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 37357a11c9c..d8432d6e444 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -1,16 +1,17 @@ -import {test, expect, withLogin, withConnectedUser} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withLogin, withConnectedUser, Team} 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()]); - const team = await createTeam('Test Team', {users: [userB, userC]}); + team = await createTeam('Test Team', {users: [userB, userC]}); userA = team.owner; }); @@ -460,7 +461,18 @@ test.describe('Mention', () => { test( 'I want to mention a name with or without umlaut gives the same suggestion (query normalization)', {tag: ['@TC-3537', '@regression']}, - async () => {}, + 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 () => {}); diff --git a/apps/webapp/test/e2e_tests/test.fixtures.ts b/apps/webapp/test/e2e_tests/test.fixtures.ts index 231adfdb39d..7cf292003b8 100644 --- a/apps/webapp/test/e2e_tests/test.fixtures.ts +++ b/apps/webapp/test/e2e_tests/test.fixtures.ts @@ -228,10 +228,13 @@ export const withConnectionRequest = await sendConnectionRequest(pageManager, await user); }; -const createUser = async (api: ApiManagerE2E, options?: {disableTelemetry?: boolean}) => { +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 From 846b4d58a949ccab3711bd2c1d269bf457816a2e Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 17:05:57 +0100 Subject: [PATCH 21/24] refactor: fix a11y for group member lists --- .../script/components/UserList/UserList.tsx | 20 ++++++++++++++----- .../webapp/pages/conversationDetails.page.ts | 8 +++++++- 2 files changed, 22 insertions(+), 6 deletions(-) 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) && ( <> -

+

{t('searchListAdmins', {count: adminCount})}

{admins.length > 0 && ( -
    +
      {admins.slice(0, maxShownUsers).map(user => renderListItem(user))}
    )} @@ -208,11 +214,15 @@ export const UserList = ({ {members.length > 0 && maxShownUsers > admins.length && ( <> -

    +

    {t('searchListMembers', {count: memberCount})}

    -
      +
        {members.slice(0, maxShownUsers - admins.length).map(user => renderListItem(user))}
      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..aec0e7678fe 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 @@ -22,6 +22,9 @@ import {Locator, Page} from '@playwright/test'; export class ConversationDetailsPage { readonly page: Page; + readonly groupAdmins: Locator; + readonly groupMembers: Locator; + readonly addPeopleButton: Locator; readonly conversationDetails: Locator; readonly guestOptionsButton: Locator; @@ -38,9 +41,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'); From 8c237f8c48c6311df4e2b5222a621e8525bc9601 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 17:27:06 +0100 Subject: [PATCH 22/24] test: implement TC-3540 mention guest user --- apps/webapp/playwright.config.ts | 2 +- .../test/e2e_tests/pageManager/index.ts | 7 +- .../webapp/modals/guestLinkPassword.modal.ts | 35 ++++++++++ .../webapp/pages/conversationDetails.page.ts | 12 ++++ .../webapp/pages/conversationJoin.page.ts | 15 ++++ .../webapp/pages/guestOptions.page.ts | 70 +++++++++++-------- .../e2e_tests/specs/Mention/mention.spec.ts | 30 +++++++- apps/webapp/test/e2e_tests/test.fixtures.ts | 9 +++ 8 files changed, 147 insertions(+), 33 deletions(-) create mode 100644 apps/webapp/test/e2e_tests/pageManager/webapp/modals/guestLinkPassword.modal.ts create mode 100644 apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationJoin.page.ts 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/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/conversationDetails.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/conversationDetails.page.ts index aec0e7678fe..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,6 +18,7 @@ */ import {Locator, Page} from '@playwright/test'; +import {GuestOptionsPage} from './guestOptions.page'; export class ConversationDetailsPage { readonly page: Page; @@ -141,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/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/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index d8432d6e444..6aada0c46c3 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -1,4 +1,4 @@ -import {test, expect, withLogin, withConnectedUser, Team} from 'test/e2e_tests/test.fixtures'; +import {test, expect, withLogin, withConnectedUser, Team, withGuestUser} 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'; @@ -475,7 +475,33 @@ test.describe('Mention', () => { }, ); - test('I want to see guest indicators in the suggestions list', {tag: ['@TC-3540', '@regression']}, async () => {}); + 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', diff --git a/apps/webapp/test/e2e_tests/test.fixtures.ts b/apps/webapp/test/e2e_tests/test.fixtures.ts index 7cf292003b8..f49e171027b 100644 --- a/apps/webapp/test/e2e_tests/test.fixtures.ts +++ b/apps/webapp/test/e2e_tests/test.fixtures.ts @@ -228,6 +228,15 @@ export const withConnectionRequest = await sendConnectionRequest(pageManager, await user); }; +/** 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}, From 834d040220b9ee16b268f271a43cf4e3261fde46 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 17:48:11 +0100 Subject: [PATCH 23/24] test: implement TC-3545 --- .../e2e_tests/specs/Mention/mention.spec.ts | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 6aada0c46c3..8a977ef12ff 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -1,4 +1,12 @@ -import {test, expect, withLogin, withConnectedUser, Team, withGuestUser} from 'test/e2e_tests/test.fixtures'; +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'; @@ -506,7 +514,36 @@ test.describe('Mention', () => { test( 'I want to be able to mention participants I am not connected to', {tag: ['@TC-3545', '@regression']}, - async () => {}, + 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( From d3deea0e9a5555ca3035fbde91bdcfbbf6ebd784 Mon Sep 17 00:00:00 2001 From: Mark Brockhoff Date: Tue, 3 Mar 2026 18:01:24 +0100 Subject: [PATCH 24/24] test: implement TC-3546 --- .../webapp/pages/participantDetails.page.ts | 10 ++++++++-- .../e2e_tests/specs/Mention/mention.spec.ts | 18 +++++++++++++++++- .../participantProfile.spec.ts | 6 +++--- 3 files changed, 28 insertions(+), 6 deletions(-) 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/Mention/mention.spec.ts b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts index 8a977ef12ff..a14b3b497df 100644 --- a/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Mention/mention.spec.ts @@ -549,6 +549,22 @@ test.describe('Mention', () => { test( 'I should not be able to mention a person who left/was removed (from) a conversation', {tag: ['@TC-3546', '@regression']}, - async () => {}, + 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/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(); }, ); });