From 0f8537ec3b6b757d9816d35f1a733fd9cf39f534 Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Fri, 27 Feb 2026 09:31:37 +0100 Subject: [PATCH 1/4] test: add conversations regression tests --- .../webapp/pages/conversationDetails.page.ts | 9 +- .../webapp/pages/groupCreation.page.ts | 4 +- .../specs/Conversations/conversations.spec.ts | 330 ++++++++++++++++++ 3 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts 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..e41b0b810ac 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 @@ -30,6 +30,7 @@ export class ConversationDetailsPage { readonly blockConversationButton: Locator; readonly clearConversationContentButton: Locator; readonly selectedSearchList: Locator; + readonly searchPeopleInput: Locator; readonly searchList: Locator; readonly deleteGroupButton: Locator; readonly notificationsButton: Locator; @@ -47,7 +48,8 @@ export class ConversationDetailsPage { this.blockConversationButton = this.conversationDetails.getByTestId('do-block'); this.clearConversationContentButton = this.conversationDetails.getByRole('button', {name: 'Clear Content'}); this.selectedSearchList = this.page.getByTestId('selected-search-list'); - this.searchList = this.page.getByTestId('search-list'); + this.searchPeopleInput = page.getByRole('textbox', {name: 'Search by name'}); + this.searchList = this.page.locator('#add-participants').getByRole('list'); this.deleteGroupButton = this.page.getByRole('button', {name: 'Delete group'}); this.notificationsButton = this.page.getByRole('button', {name: 'Notifications'}); this.editConversationNameButton = this.page.getByRole('button', {name: 'Change conversation name'}); @@ -110,6 +112,11 @@ export class ConversationDetailsPage { await this.getLocatorByUser(fullName).click(); } + async participantStatus(fullName: string) { + const userLocator = await this.getLocatorByUser(fullName); + return userLocator.getByTestId(/^status-(external|guest|admin)$/); + } + getParticipantStatus(fullName: string) { return this.getLocatorByUser(fullName).getByTestId('status-availability-icon'); } diff --git a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/groupCreation.page.ts b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/groupCreation.page.ts index 8db7f8c349e..7611e2a80cf 100644 --- a/apps/webapp/test/e2e_tests/pageManager/webapp/pages/groupCreation.page.ts +++ b/apps/webapp/test/e2e_tests/pageManager/webapp/pages/groupCreation.page.ts @@ -23,6 +23,7 @@ export class GroupCreationPage { readonly page: Page; readonly groupCreationModal: Locator; + readonly searchPeopleList: Locator; readonly groupNameInput: Locator; readonly nextButton: Locator; readonly createGroupButton: Locator; @@ -43,7 +44,8 @@ export class GroupCreationPage { this.filesCheckbox = page.locator('[data-uie-name="do-toggle-cells"]'); this.searchPeopleInput = page.getByRole('dialog').getByLabel('Search by name'); - this.searchPeopleResults = page.getByRole('dialog').getByRole('list').getByRole('listitem'); + this.searchPeopleList = page.getByRole('dialog').getByRole('list'); + this.searchPeopleResults = this.searchPeopleList.getByRole('listitem'); } async setGroupName(name: string) { diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts new file mode 100644 index 00000000000..d42c520a674 --- /dev/null +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -0,0 +1,330 @@ +/* + * 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 {User} from 'test/e2e_tests/data/user'; +import {PageManager} from 'test/e2e_tests/pageManager'; +import {test, expect, withConnectedUser, withLogin, Team, withConnectionRequest} from 'test/e2e_tests/test.fixtures'; +import {createGroup} from 'test/e2e_tests/utils/userActions'; + +test.describe('Conversations', () => { + let team: Team; + let userA: User; + let userB: User; + let userC: User; + + test.beforeEach(async ({createTeam, createUser}) => { + userB = await createUser(); + userC = await createUser(); + team = await createTeam('Test Team', {users: [userB, userC]}); + userA = team.owner; + }); + + test( + 'I want to see a system message with all group members mentioned on creating a group', + {tag: ['@TC-2965', '@regression']}, + async ({createPage}) => { + const userAPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); + const groupName = 'Test Group'; + await createGroup(userAPages, groupName, [userB, userC]); + userAPages.conversationList().openConversation(groupName); + const pattern = new RegExp( + `You started the conversation\\s*${groupName}\\s*with\\s*${userB.fullName}\\s*and\\s*${userC.fullName}`, + 'i', + ); + + await expect(userAPages.conversation().systemMessages.first()).toContainText(pattern); + }, + ); + + test( + 'I want to see a system message with all group members mentioned when someone else created a group', + {tag: ['@TC-2966', '@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), + ]); + + const groupName = 'Test Group'; + await createGroup(userAPages, groupName, [userB, userC]); + + userBPages.conversationList().openConversation(groupName); + const pattern = new RegExp( + `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, + 'i', + ); + await expect(userBPages.conversation().systemMessages.first()).toContainText(pattern); + }, + ); + + test( + 'I want to see "No matching results. Try entering a different name." when I search for non existent users', + {tag: ['@TC-2987', '@regression']}, + async ({createPage}) => { + const userAPages = await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then( + pm => pm.webapp.pages, + ); + + const groupName = 'Test Group'; + const noResultsMessage = 'No matching results. Try entering a different name.'; + + await userAPages.conversationList().clickCreateGroup(); + await userAPages.groupCreation().setGroupName(groupName); + await userAPages.groupCreation().searchPeopleInput.fill('non_existent_user'); + + await expect(userAPages.groupCreation().searchPeopleList).toContainText(noResultsMessage); + + await userAPages.groupCreation().searchPeopleInput.clear(); + await userAPages.groupCreation().selectGroupMembers(userB.username); + await userAPages.groupCreation().clickCreateGroupButton(); + + await userAPages.conversation().toggleGroupInformation(); + await userAPages.conversationDetails().clickAddPeopleButton(); + await userAPages.conversationDetails().searchPeopleInput.fill('non_existent_user'); + await expect(userAPages.conversationDetails().searchList).toContainText(noResultsMessage); + }, + ); + + test( + 'Verify the name of the group conversation can be edited', + {tag: ['@TC-418', '@regression']}, + async ({createPage}) => { + const userAPages = await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then( + pm => pm.webapp.pages, + ); + + const groupName = 'Test Group'; + await createGroup(userAPages, groupName, [userB, userC]); + + await userAPages.conversationList().openConversation(groupName); + await userAPages.conversation().clickConversationInfoButton(); + await userAPages.conversationDetails().changeConversationName('New Group Name'); + + await expect(userAPages.conversation().conversationTitle).not.toContainText(groupName); + await expect(userAPages.conversation().conversationTitle).toContainText('New Group Name'); + }, + ); + + test( + 'I want to see Guest icon in participants list', + {tag: ['@TC-421', '@regression']}, + async ({createPage, createUser}) => { + const guestUser = await createUser(); + + const [adminPage, guestPages] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), + ]); + + await guestPages.conversationList().openPendingConnectionRequest(); + await guestPages.connectRequest().clickConnectButton(); + + const groupName = 'Test Group'; + await createGroup(adminPage, groupName, [userB, guestUser]); + + await adminPage.conversationList().openConversation(groupName); + await adminPage.conversation().clickConversationInfoButton(); + await expect(await adminPage.conversationDetails().participantStatus(guestUser.fullName)).toHaveAttribute( + 'data-uie-name', + 'status-guest', + ); + await expect(await adminPage.conversationDetails().participantStatus(userB.fullName)).not.toBeVisible(); + }, + ); + + test( + 'I want to see usernames for Guest users in participants list', + {tag: ['@TC-423', '@regression']}, + async ({createPage, createUser}) => { + const guestUser = await createUser(); + + const [adminPage, guestPages] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), + ]); + + await guestPages.conversationList().openPendingConnectionRequest(); + await guestPages.connectRequest().clickConnectButton(); + + const groupName = 'Test Group'; + await createGroup(adminPage, groupName, [userB, guestUser]); + + await adminPage.conversationList().openConversation(groupName); + await adminPage.conversation().clickConversationInfoButton(); + await adminPage.conversationDetails().openParticipantDetails(guestUser.fullName); + await expect(adminPage.participantDetails().userStatus).toContainText('Guest'); + + await adminPage.conversation().clickConversationInfoButton(); + await adminPage.conversationDetails().openParticipantDetails(userB.fullName); + await expect(adminPage.participantDetails().userStatus).not.toBeVisible(); + }, + ); + + test( + 'I want to see the empty admins section when there are no Admins', + {tag: ['@TC-431', '@regression']}, + async ({createPage}) => { + const [adminPagesManager, userBPages] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))), + PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), + ]); + + const {pages: adminPages, modals} = adminPagesManager.webapp; + + const groupName = 'Test Group'; + await createGroup(adminPages, groupName, [userB, userC]); + + // Confirm that the userA is an admin in the group + await userBPages.conversationList().openConversation(groupName); + await userBPages.conversation().clickConversationInfoButton(); + expect(userBPages.conversation().adminsList).toBeVisible(); + expect(userBPages.conversation().adminsList.getByRole('listitem')).toHaveCount(1); + + // User A leaves the group + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + await adminPages.conversation().leaveConversation(); + await modals.leaveConversation().clickConfirm(); + + // User B sees the empty admins section + await userBPages.conversationList().openConversation(groupName); + await userBPages.conversation().clickConversationInfoButton(); + expect(adminPages.conversation().adminsList).not.toBeVisible(); + }, + ); + + test( + 'I should not see Members section when there are only admins', + {tag: ['@TC-432', '@regression']}, + async ({createPage}) => { + const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); + const userBPages = await PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages); + + const groupName = 'Test Group'; + await createGroup(adminPages, groupName, [userB]); + + // User B can see members section + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + expect(adminPages.conversation().membersList).toBeVisible(); + expect(adminPages.conversation().membersList.getByRole('listitem')).toHaveCount(1); + + // User A makes userB an admin + await adminPages.conversation().makeUserAdmin(userB.fullName); + + // User A and B cannot see members section + await userBPages.conversationList().openConversation(groupName); + + for (const userPages of [adminPages, userBPages]) { + await userPages.conversation().clickConversationInfoButton(); + await expect(userPages.conversation().adminsList.getByRole('listitem')).toHaveCount(2); + expect(userPages.conversation().membersList).not.toBeVisible(); + } + }, + ); + + test( + 'I want to see my own profile in the Admin section when I create a conversation', + {tag: ['@TC-434', '@regression']}, + async ({createPage}) => { + const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); + + const groupName = 'Test Group'; + await createGroup(adminPages, groupName, [userB]); + + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + + await expect(adminPages.conversation().adminsList.getByRole('listitem')).toHaveCount(1); + expect(adminPages.conversation().adminsList.getByRole('listitem')).toContainText(userA.fullName); + }, + ); + + test( + 'I want to see External icon for External member in Members list', + {tag: ['@TC-435', '@regression']}, + async ({createPage, createUser}) => { + const externalUser = await createUser(); + + await team.addTeamMember(externalUser, {role: 'EXTERNAL'}); + const adminPage = await PageManager.from(createPage(withLogin(userA), withConnectedUser(externalUser))).then( + pm => pm.webapp.pages, + ); + + const groupName = 'Test Group'; + await createGroup(adminPage, groupName, [userB, externalUser]); + + await adminPage.conversationList().openConversation(groupName); + await adminPage.conversation().clickConversationInfoButton(); + await expect(await adminPage.conversationDetails().participantStatus(externalUser.fullName)).toHaveAttribute( + 'data-uie-name', + 'status-guest', + ); + + await adminPage.conversation().clickConversationInfoButton(); + await adminPage.conversationDetails().openParticipantDetails(userB.fullName); + await expect(adminPage.participantDetails().userStatus).not.toBeVisible(); + }, + ); + + test( + 'I want to see delete group button i options when I am conversation admin but not creator', + {tag: ['@TC-436', '@regression']}, + async ({createPage}) => { + const [adminPages, userBPages] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), + ]); + const groupName = 'Test Group'; + await createGroup(adminPages, groupName, [userB, userC]); + + // User A makes userB an admin + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + await adminPages.conversation().makeUserAdmin(userB.fullName); + + // User B can see delete group and options in conversation details + await userBPages.conversationList().openConversation(groupName); + await userBPages.conversation().clickConversationInfoButton(); + await expect(userBPages.conversationDetails().deleteGroupButton).toBeVisible(); + await expect(userBPages.conversationDetails().guestOptionsButton).toBeVisible(); + await expect(userBPages.conversationDetails().selfDeletingMessageButton).toBeVisible(); + }, + ); + + test( + 'I can see the system message "You renamed the conversation" after renaming conversation', + {tag: ['@TC-496', '@regression']}, + async ({createPage}) => { + const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); + const groupName = 'Test Group'; + await createGroup(adminPages, groupName, [userB, userC]); + + // User A renames the conversation + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + await adminPages.conversationDetails().changeConversationName('New Group Name'); + + // Verify that the system message is displayed correctly + await expect(adminPages.conversation().systemMessages.last()).toContainText('You renamed the conversation', { + ignoreCase: true, + }); + }, + ); +}); From 18a0d2f27714193a43d3f48e1fb3a2e7e2ebbe35 Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Wed, 4 Mar 2026 15:30:33 +0100 Subject: [PATCH 2/4] test: complete conversations regression tests --- .../webapp/pages/conversationList.page.ts | 2 + .../specs/Conversations/conversations.spec.ts | 181 +++++++++++++++++- 2 files changed, 176 insertions(+), 7 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..b0dc6126f80 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 @@ -24,6 +24,7 @@ import {User} from 'test/e2e_tests/data/user'; export class ConversationListPage { readonly page: Page; + readonly list: Locator; readonly blockConversationMenuButton: Locator; readonly createGroupButton: Locator; readonly pendingConnectionRequest: Locator; @@ -43,6 +44,7 @@ export class ConversationListPage { constructor(page: Page) { this.page = page; + this.list = page.getByRole('list', {name: 'Conversation list'}); this.blockConversationMenuButton = page.getByRole('menu').getByRole('button', {name: 'Block'}); this.pendingConnectionRequest = page.locator('[data-uie-name="connection-request"]'); this.createGroupButton = page.getByTestId('conversation-list-header').getByTestId('go-create-group'); diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts index d42c520a674..203701b057e 100644 --- a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -20,7 +20,8 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; import {test, expect, withConnectedUser, withLogin, Team, withConnectionRequest} from 'test/e2e_tests/test.fixtures'; -import {createGroup} from 'test/e2e_tests/utils/userActions'; +import {interceptNotifications} from 'test/e2e_tests/utils/mockNotifications.util'; +import {connectWithUser, createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; test.describe('Conversations', () => { let team: Team; @@ -167,12 +168,12 @@ test.describe('Conversations', () => { await adminPage.conversationList().openConversation(groupName); await adminPage.conversation().clickConversationInfoButton(); - await adminPage.conversationDetails().openParticipantDetails(guestUser.fullName); - await expect(adminPage.participantDetails().userStatus).toContainText('Guest'); - await adminPage.conversation().clickConversationInfoButton(); - await adminPage.conversationDetails().openParticipantDetails(userB.fullName); - await expect(adminPage.participantDetails().userStatus).not.toBeVisible(); + await expect(adminPage.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); + await expect(await adminPage.conversationDetails().participantStatus(guestUser.fullName)).toHaveAttribute( + 'data-uie-name', + 'status-guest', + ); }, ); @@ -274,7 +275,7 @@ test.describe('Conversations', () => { await adminPage.conversation().clickConversationInfoButton(); await expect(await adminPage.conversationDetails().participantStatus(externalUser.fullName)).toHaveAttribute( 'data-uie-name', - 'status-guest', + 'status-external', ); await adminPage.conversation().clickConversationInfoButton(); @@ -308,6 +309,81 @@ test.describe('Conversations', () => { }, ); + test( + 'Verify conversation scrolls to first unread message when opening', + {tag: ['@TC-487', '@regression']}, + async ({createPage}) => { + const [userAPages, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA), withConnectedUser(userC))), + ]); + + const {pages, components} = userBPageManager.webapp; + + // User B opens conversation with User A and stay on the page + await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + + // User A sends a message to User B + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + const totalMessages = 20; + + // User A sends messages to User B + for (let i = 1; i <= totalMessages; i++) { + await userAPages.conversation().sendMessage(`READ message: ${i}`); + } + + await expect(userAPages.conversation().getMessage({content: `READ message: ${totalMessages}`})).toBeVisible(); + + // User B can see the last conversation message in viewport (confirm scroll behavior in opened conversation) + await expect( + pages + .conversation() + .getMessage({sender: userA}) + .filter({hasText: `READ message: ${totalMessages - 1}`}), + ).toBeInViewport(); + + // User B opens conversation with User C and stays on the page + await pages.conversationList().openConversation(userC.fullName); + + // User A sends messages to User B + for (let i = 1; i <= totalMessages; i++) { + await userAPages.conversation().sendMessage(`UNREAD message: ${i}`); + } + + const conversationWithUserA = pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); + + await expect(conversationWithUserA.getByTestId('status-unread')).toContainText(`${totalMessages}`); + await expect(conversationWithUserA).toContainText(`UNREAD message: ${totalMessages}`); + + await components.conversationSidebar().clickAllConversationsButton(); + await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + + await expect(pages.conversation().getMessage({sender: userA}).last()).toContainText( + `UNREAD message: ${totalMessages}`, + ); + + // User B should not see read messages in viewport + await expect(pages.conversation().getMessage({content: /^READ message: 1$/})).not.toBeInViewport(); + await expect( + pages + .conversation() + .getMessage({sender: userA}) + .filter({hasText: `/^READ/ message: ${totalMessages}$`}), + ).not.toBeInViewport(); + + // User B should see first unread message in viewport + await expect(pages.conversation().getMessage({content: /UNREAD message: 1$/})).toBeInViewport(); + + // User B should not see last unread message in viewport when there is a lot of unread messages + await expect( + pages.conversation().getMessage({content: new RegExp(`^UNREAD message: ${totalMessages}$`)}), + ).not.toBeInViewport(); + + await pages.conversation().getMessage({sender: userA}).last().scrollIntoViewIfNeeded(); + await expect(conversationWithUserA.getByTestId('status-unread')).not.toBeVisible(); + }, + ); + test( 'I can see the system message "You renamed the conversation" after renaming conversation', {tag: ['@TC-496', '@regression']}, @@ -327,4 +403,95 @@ test.describe('Conversations', () => { }); }, ); + + test( + 'I should not see a (push) notification when the role of another person has changed', + {tag: ['@TC-503', '@regression']}, + async ({createPage}) => { + const [adminPage, userBPage] = await Promise.all([ + createPage(withLogin(userA), withConnectedUser(userB)), + createPage(withLogin(userB)), + ]); + + const adminPages = PageManager.from(adminPage).webapp.pages; + const userBPages = PageManager.from(userBPage).webapp.pages; + + const groupName = 'Test Group'; + await createGroup(adminPages, groupName, [userB, userC]); + await userBPages.conversationList().openConversation(groupName); + + const {getNotifications: getUserBNotifications} = await interceptNotifications(userBPage); + // User A makes User C an admin + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().toggleGroupInformation(); + await adminPages.conversation().makeUserAdmin(userC.fullName); + + // Verify User C is admin in the conversation + await userBPages.conversation().clickConversationInfoButton(); + await expect(userBPages.conversation().adminsList).toContainText(userC.fullName); + // Verify that User B doesn't receive a push notification + await expect.poll(() => getUserBNotifications()).toHaveLength(0); + }, + ); + + test( + 'Verify help text is gone when I have at least one conversation after I was offline', + {tag: ['@TC-778', '@regression']}, + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))), + PageManager.from(createPage(withLogin(userB))), + ]); + + const {pages, components} = userAPageManager.webapp; + + await expect(pages.conversationList().list).toContainText('Welcome to Wire'); + await expect(pages.conversationList().list.getByRole('listitem')).toHaveCount(0); + + // User A logs out + await components.conversationSidebar().clickPreferencesButton(); + await pages.settings().accountButton.click(); + await pages.account().clickLogoutButton(); + + // User B connects to User A + await connectWithUser(userBPageManager, userA); + + // User A logs in + await userAPageManager.openLoginPage(); + await pages.login().login(userA); + await components.conversationSidebar().clickAllConversationsButton(); + + await expect(pages.conversationList().list).not.toContainText('Welcome to Wire'); + await expect(pages.conversationList().list.getByRole('listitem')).toHaveCount(1); + }, + ); + + test( + 'Verify help text is gone when I have at least one conversation while I am online', + {tag: ['@TC-780', '@regression']}, + async ({createPage, createUser}) => { + const userD = await createUser(); + const [userAPageManager, userDPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA))), + PageManager.from(createPage(withLogin(userD))), + ]); + + const pages = userAPageManager.webapp.pages; + // User A can see help text + await expect(pages.conversationList().list).toContainText('Welcome to Wire'); + await expect(pages.conversationList().list.getByRole('listitem')).toHaveCount(0); + + // User D sends a connection request to User A + await sendConnectionRequest(userDPageManager, userA); + + // User A accepts connection request from User A + await pages.conversationList().openPendingConnectionRequest(); + await pages.connectRequest().clickConnectButton(); + + // User A sees conversation with userD instead of help text + await expect(pages.conversationList().list).not.toContainText('Welcome to Wire'); + await expect(pages.conversationList().list.getByRole('listitem').first()).toContainText(userD.fullName); + await expect(pages.conversationList().list.getByRole('listitem')).toHaveCount(1); + }, + ); }); From ce85317789f665a6a6558db9ce8a6753be20b96d Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Thu, 5 Mar 2026 16:44:03 +0100 Subject: [PATCH 3/4] refactor: implement test.step for complex conversation flows --- .../webapp/pages/conversation.page.ts | 2 + .../webapp/pages/conversationDetails.page.ts | 3 +- .../specs/Conversations/conversations.spec.ts | 397 ++++++++++++------ 3 files changed, 265 insertions(+), 137 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..33aa17349e5 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 @@ -53,6 +53,7 @@ export class ConversationPage { /** Messages in conversation, only contains message items which have been sent successfully */ readonly messages: Locator; readonly messageDetails: Locator; + readonly emojiSuggestionOptions: Locator; readonly messageItems: Locator; readonly filesTab: Locator; readonly typingIndicator: Locator; @@ -99,6 +100,7 @@ export class ConversationPage { `[data-uie-name="item-message"]:not([data-uie-send-status="1"]):not([data-uie-send-status="-1"]):not(.system-message)`, ); this.messageDetails = page.locator('#message-details'); + this.emojiSuggestionOptions = page.getByRole('listbox', {name: 'Typeahead menu'}).getByRole('button'); this.filesTab = page.locator('#conversation-tab-files'); this.typingIndicator = page.getByTestId('typing-indicator-title'); this.itemPendingRequest = page.getByTestId('item-pending-requests'); 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 e41b0b810ac..f5571d5cbae 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 @@ -113,8 +113,7 @@ export class ConversationDetailsPage { } async participantStatus(fullName: string) { - const userLocator = await this.getLocatorByUser(fullName); - return userLocator.getByTestId(/^status-(external|guest|admin)$/); + return this.getLocatorByUser(fullName).getByTestId(/^status-(external|guest|admin)$/); } getParticipantStatus(fullName: string) { diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts index 203701b057e..80517ad2702 100644 --- a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -28,6 +28,7 @@ test.describe('Conversations', () => { let userA: User; let userB: User; let userC: User; + const groupName = 'Test Group'; test.beforeEach(async ({createTeam, createUser}) => { userB = await createUser(); @@ -36,43 +37,58 @@ test.describe('Conversations', () => { userA = team.owner; }); - test( - 'I want to see a system message with all group members mentioned on creating a group', - {tag: ['@TC-2965', '@regression']}, - async ({createPage}) => { - const userAPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); - const groupName = 'Test Group'; - await createGroup(userAPages, groupName, [userB, userC]); - userAPages.conversationList().openConversation(groupName); - const pattern = new RegExp( - `You started the conversation\\s*${groupName}\\s*with\\s*${userB.fullName}\\s*and\\s*${userC.fullName}`, - 'i', - ); - - await expect(userAPages.conversation().systemMessages.first()).toContainText(pattern); + const systemMessageTestCases = [ + { + title: 'I create a group', + tag: '@TC-2965', + actor: 'userA', + targetUser: 'userA', + getPattern: (groupName: string, userA: User, userB: User, userC: User) => + new RegExp( + `You started the conversation\\s*${groupName}\\s*with\\s*${userB.fullName}\\s*and\\s*${userC.fullName}`, + 'i', + ), }, - ); - - test( - 'I want to see a system message with all group members mentioned when someone else created a group', - {tag: ['@TC-2966', '@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), - ]); - - const groupName = 'Test Group'; - await createGroup(userAPages, groupName, [userB, userC]); - - userBPages.conversationList().openConversation(groupName); - const pattern = new RegExp( - `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, - 'i', - ); - await expect(userBPages.conversation().systemMessages.first()).toContainText(pattern); + { + title: 'someone else created a group ', + tag: '@TC-2966', + actor: 'userA', + targetUser: 'userB', + getPattern: (groupName: string, userA: User, userB: User, userC: User) => + new RegExp( + `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, + 'i', + ), }, - ); + ]; + + for (const data of systemMessageTestCases) { + test( + `I want to see a system message with all group members mentioned when ${data.title}`, + {tag: [data.tag, '@regression']}, + async ({createPage}) => { + const [adminPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB))), + ]); + + const adminPages = adminPageManager.webapp.pages; + const targetPages = data.targetUser === 'userA' ? adminPageManager.webapp.pages : userBPageManager.webapp.pages; + + // 2. Action: Create the group (Always done by User A in this set) + await test.step('Create group', async () => { + await createGroup(adminPages, groupName, [userB, userC]); + }); + + // 3. Verification: Check the specific pattern for this test case + await test.step(`Verify message for ${data.targetUser}`, async () => { + await targetPages.conversationList().openConversation(groupName); + const pattern = data.getPattern(groupName, userA, userB, userC); + await expect(targetPages.conversation().systemMessages.first()).toContainText(pattern); + }); + }, + ); + } test( 'I want to see "No matching results. Try entering a different name." when I search for non existent users', @@ -82,19 +98,21 @@ test.describe('Conversations', () => { pm => pm.webapp.pages, ); - const groupName = 'Test Group'; const noResultsMessage = 'No matching results. Try entering a different name.'; + // User A tries to create a group with non-existent users await userAPages.conversationList().clickCreateGroup(); await userAPages.groupCreation().setGroupName(groupName); await userAPages.groupCreation().searchPeopleInput.fill('non_existent_user'); await expect(userAPages.groupCreation().searchPeopleList).toContainText(noResultsMessage); + // User A creates a group with existing user await userAPages.groupCreation().searchPeopleInput.clear(); await userAPages.groupCreation().selectGroupMembers(userB.username); await userAPages.groupCreation().clickCreateGroupButton(); + // User A tries to add non-existent users to the group await userAPages.conversation().toggleGroupInformation(); await userAPages.conversationDetails().clickAddPeopleButton(); await userAPages.conversationDetails().searchPeopleInput.fill('non_existent_user'); @@ -110,7 +128,6 @@ test.describe('Conversations', () => { pm => pm.webapp.pages, ); - const groupName = 'Test Group'; await createGroup(userAPages, groupName, [userB, userC]); await userAPages.conversationList().openConversation(groupName); @@ -122,40 +139,28 @@ test.describe('Conversations', () => { }, ); - test( - 'I want to see Guest icon in participants list', - {tag: ['@TC-421', '@regression']}, - async ({createPage, createUser}) => { - const guestUser = await createUser(); - - const [adminPage, guestPages] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), - PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), - ]); - - await guestPages.conversationList().openPendingConnectionRequest(); - await guestPages.connectRequest().clickConnectButton(); - - const groupName = 'Test Group'; - await createGroup(adminPage, groupName, [userB, guestUser]); - - await adminPage.conversationList().openConversation(groupName); - await adminPage.conversation().clickConversationInfoButton(); - await expect(await adminPage.conversationDetails().participantStatus(guestUser.fullName)).toHaveAttribute( - 'data-uie-name', - 'status-guest', - ); - await expect(await adminPage.conversationDetails().participantStatus(userB.fullName)).not.toBeVisible(); + const guestTestCases = [ + { + title: 'I want to see usernames for Guest users in participants list', + tag: '@TC-421', + action: async (pages: PageManager['webapp']['pages'], guestUserName?: string) => { + await expect(pages.conversation().membersList).toContainText(guestUserName); + }, }, - ); + { + title: 'I want to see Guest icon in participants list', + tag: '@TC-423', + action: async (pages: PageManager['webapp']['pages']) => { + await expect(await pages.conversationDetails().participantStatus(userB.fullName)).not.toBeVisible(); + }, + }, + ]; - test( - 'I want to see usernames for Guest users in participants list', - {tag: ['@TC-423', '@regression']}, - async ({createPage, createUser}) => { + for (const data of guestTestCases) { + test(`${data.title}`, {tag: [data.tag, '@regression']}, async ({createPage, createUser}) => { const guestUser = await createUser(); - const [adminPage, guestPages] = await Promise.all([ + const [adminPages, guestPages] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), ]); @@ -163,19 +168,17 @@ test.describe('Conversations', () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - const groupName = 'Test Group'; - await createGroup(adminPage, groupName, [userB, guestUser]); - - await adminPage.conversationList().openConversation(groupName); - await adminPage.conversation().clickConversationInfoButton(); + await createGroup(adminPages, groupName, [userB, guestUser]); - await expect(adminPage.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); - await expect(await adminPage.conversationDetails().participantStatus(guestUser.fullName)).toHaveAttribute( + await adminPages.conversationList().openConversation(groupName); + await adminPages.conversation().clickConversationInfoButton(); + await expect(await adminPages.conversationDetails().participantStatus(guestUser.fullName)).toHaveAttribute( 'data-uie-name', 'status-guest', ); - }, - ); + await data.action(adminPages, guestUser.username); + }); + } test( 'I want to see the empty admins section when there are no Admins', @@ -188,7 +191,6 @@ test.describe('Conversations', () => { const {pages: adminPages, modals} = adminPagesManager.webapp; - const groupName = 'Test Group'; await createGroup(adminPages, groupName, [userB, userC]); // Confirm that the userA is an admin in the group @@ -202,6 +204,7 @@ test.describe('Conversations', () => { await adminPages.conversation().clickConversationInfoButton(); await adminPages.conversation().leaveConversation(); await modals.leaveConversation().clickConfirm(); + await expect(adminPages.conversation().systemMessages.filter({hasText: 'You left'})).toBeVisible(); // User B sees the empty admins section await userBPages.conversationList().openConversation(groupName); @@ -217,7 +220,6 @@ test.describe('Conversations', () => { const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); const userBPages = await PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages); - const groupName = 'Test Group'; await createGroup(adminPages, groupName, [userB]); // User B can see members section @@ -245,8 +247,6 @@ test.describe('Conversations', () => { {tag: ['@TC-434', '@regression']}, async ({createPage}) => { const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); - - const groupName = 'Test Group'; await createGroup(adminPages, groupName, [userB]); await adminPages.conversationList().openConversation(groupName); @@ -262,37 +262,35 @@ test.describe('Conversations', () => { {tag: ['@TC-435', '@regression']}, async ({createPage, createUser}) => { const externalUser = await createUser(); - await team.addTeamMember(externalUser, {role: 'EXTERNAL'}); + const adminPage = await PageManager.from(createPage(withLogin(userA), withConnectedUser(externalUser))).then( pm => pm.webapp.pages, ); - const groupName = 'Test Group'; await createGroup(adminPage, groupName, [userB, externalUser]); await adminPage.conversationList().openConversation(groupName); await adminPage.conversation().clickConversationInfoButton(); + await expect(await adminPage.conversation().membersList).toBeVisible(); await expect(await adminPage.conversationDetails().participantStatus(externalUser.fullName)).toHaveAttribute( 'data-uie-name', 'status-external', ); - await adminPage.conversation().clickConversationInfoButton(); - await adminPage.conversationDetails().openParticipantDetails(userB.fullName); - await expect(adminPage.participantDetails().userStatus).not.toBeVisible(); + // Verify no status icon is displayed for standard members + await expect(await adminPage.conversationDetails().participantStatus(userB.fullName)).not.toBeVisible(); }, ); test( - 'I want to see delete group button i options when I am conversation admin but not creator', + 'I want to see delete group button and options when I am conversation admin but not creator', {tag: ['@TC-436', '@regression']}, async ({createPage}) => { const [adminPages, userBPages] = await Promise.all([ PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages), PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages), ]); - const groupName = 'Test Group'; await createGroup(adminPages, groupName, [userB, userC]); // User A makes userB an admin @@ -319,77 +317,207 @@ test.describe('Conversations', () => { ]); const {pages, components} = userBPageManager.webapp; - - // User B opens conversation with User A and stay on the page - await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - - // User A sends a message to User B - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); const totalMessages = 20; - // User A sends messages to User B - for (let i = 1; i <= totalMessages; i++) { - await userAPages.conversation().sendMessage(`READ message: ${i}`); - } + await test.step('User B opens conversation with User A to mark previous history as read', async () => { + await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - await expect(userAPages.conversation().getMessage({content: `READ message: ${totalMessages}`})).toBeVisible(); + // User A sends "Read" messages while User B is looking at the chat + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + for (let i = 1; i <= totalMessages; i++) { + await userAPages.conversation().sendMessage(`READ message: ${i}`); + } - // User B can see the last conversation message in viewport (confirm scroll behavior in opened conversation) - await expect( - pages + // User B can see the last conversation message in viewport (confirm scroll behavior in opened conversation) + const lastReadMessage = pages .conversation() .getMessage({sender: userA}) - .filter({hasText: `READ message: ${totalMessages - 1}`}), - ).toBeInViewport(); + .filter({hasText: `READ message: ${totalMessages}`}); + await expect(lastReadMessage).toBeInViewport(); + }); - // User B opens conversation with User C and stays on the page - await pages.conversationList().openConversation(userC.fullName); + await test.step('User B switches context to another conversation', async () => { + await pages.conversationList().openConversation(userC.fullName); + }); - // User A sends messages to User B - for (let i = 1; i <= totalMessages; i++) { - await userAPages.conversation().sendMessage(`UNREAD message: ${i}`); - } + await test.step('User A sends new unread messages to User B', async () => { + for (let i = 1; i <= totalMessages; i++) { + await userAPages.conversation().sendMessage(`UNREAD message: ${i}`); + } - const conversationWithUserA = pages.conversationList().getConversationLocator(userA.fullName, {protocol: 'mls'}); + const conversationWithUserA = pages + .conversationList() + .getConversationLocator(userA.fullName, {protocol: 'mls'}); + await expect(conversationWithUserA.getByTestId('status-unread')).toContainText(`${totalMessages}`); + }); - await expect(conversationWithUserA.getByTestId('status-unread')).toContainText(`${totalMessages}`); - await expect(conversationWithUserA).toContainText(`UNREAD message: ${totalMessages}`); + await test.step('User B opens the conversation and verifies scroll position', async () => { + await components.conversationSidebar().clickAllConversationsButton(); + await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - await components.conversationSidebar().clickAllConversationsButton(); - await pages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + // 1. Verify conversation scroll is NOT at the very bottom (last unread shouldn't be visible yet) + const lastUnreadMessage = pages + .conversation() + .getMessage({content: new RegExp(`UNREAD message: ${totalMessages}$`)}); + await expect(lastUnreadMessage).not.toBeInViewport(); - await expect(pages.conversation().getMessage({sender: userA}).last()).toContainText( - `UNREAD message: ${totalMessages}`, - ); + // 2. Verify conversation scroll is NOT at the old read messages + const firstReadMessage = pages.conversation().getMessage({content: /^READ message: 1$/}); + await expect(firstReadMessage).not.toBeInViewport(); - // User B should not see read messages in viewport - await expect(pages.conversation().getMessage({content: /^READ message: 1$/})).not.toBeInViewport(); - await expect( - pages - .conversation() - .getMessage({sender: userA}) - .filter({hasText: `/^READ/ message: ${totalMessages}$`}), - ).not.toBeInViewport(); + // 3. Verify conversation scroll is at the first unread message + const firstUnreadMessage = pages.conversation().getMessage({content: /UNREAD message: 1$/}); + await expect(firstUnreadMessage).toBeInViewport(); + }); + + await test.step('User B scrolls to bottom and verifies unread status is cleared', async () => { + const lastMessage = pages.conversation().getMessage({sender: userA}).last(); + await lastMessage.scrollIntoViewIfNeeded(); + + const conversationWithUserA = pages + .conversationList() + .getConversationLocator(userA.fullName, {protocol: 'mls'}); + await expect(conversationWithUserA.getByTestId('status-unread')).not.toBeVisible(); + }); + }, + ); + + test( + 'Verify after user was removed from group they cannot do some actions', + {tag: ['@TC-492', '@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 test.step('Setup: Create group and exchange initial messages', async () => { + await createGroup(userBPages, groupName, [userA, userC]); + + // User A and B both send messages to start conversation + await userAPages.conversationList().openConversation(groupName); + await userAPages.conversation().sendMessage('Message 1'); + + await userBPages.conversationList().openConversation(groupName); + await userBPages.conversation().sendMessage('Message 2'); + + // User A reacts to Message 2 while still a member + const message2 = userAPages.conversation().getMessage({sender: userB}); + await userAPages.conversation().reactOnMessage(message2, 'plus-one'); + await expect(userAPages.conversation().getReactionOnMessage(message2, 'plus-one')).toBeVisible(); + }); + + await test.step('User B removes User A from the group', async () => { + await userBPages.conversation().toggleGroupInformation(); + await userBPages.conversation().removeMemberFromGroup(userA.fullName); + + const removedSystemMessage = userAPages.conversation().systemMessages.filter({ + hasText: `${userB.fullName} removed you`, + }); + await expect(removedSystemMessage).toBeVisible(); + }); + + await test.step('Verify User A restricted message interactions', async () => { + const message1 = userAPages.conversation().getMessage({content: 'Message 1'}); + const message2 = userAPages.conversation().getMessage({sender: userB}); + const messageActions = message1.getByRole('group', {name: 'Message actions'}).getByRole('button'); - // User B should see first unread message in viewport - await expect(pages.conversation().getMessage({content: /UNREAD message: 1$/})).toBeInViewport(); + // 1. Cannot like messages (Hover should not reveal full action set) + await message1.hover(); + await expect(messageActions).toHaveCount(1); - // User B should not see last unread message in viewport when there is a lot of unread messages - await expect( - pages.conversation().getMessage({content: new RegExp(`^UNREAD message: ${totalMessages}$`)}), - ).not.toBeInViewport(); + // 2. Cannot remove existing reaction + const reactionPill = userAPages.conversation().getReactionOnMessage(message2, 'plus-one'); + await reactionPill.click(); + await expect(reactionPill).toBeVisible(); - await pages.conversation().getMessage({sender: userA}).last().scrollIntoViewIfNeeded(); - await expect(conversationWithUserA.getByTestId('status-unread')).not.toBeVisible(); + // 3. Cannot see message input area + await expect(userAPages.conversation().messageInput).not.toBeVisible(); + }); + + await test.step('Verify User A restricted group management', async () => { + await userAPages.conversation().toggleGroupInformation(); + + // 1. Cannot add new members + await expect(userAPages.conversation().addMemberButton).not.toBeVisible(); + + // 2. Cannot edit group name + await expect(userAPages.conversationDetails().editConversationNameButton).not.toBeVisible(); + + await userAPages.conversation().toggleGroupInformation(); + }); + + await test.step('Verify User A restricted message editing and deletion', async () => { + const message1 = userAPages.conversation().getMessage({content: 'Message 1'}); + const messageOptions = await userAPages.conversation().openMessageOptions(message1); + + // 1. Cannot Delete for everyone + await expect(messageOptions).not.toContainText('Delete for everyone'); + + // 2. Cannot Edit message + await expect(messageOptions).not.toContainText('Edit'); + }); }, ); + test('Verify emoji autocomplete', {tag: ['@TC-495', '@regression']}, async ({createPage}) => { + const userAPages = await PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))).then( + pm => pm.webapp.pages, + ); + + const getEmojiAt = async (index: number) => { + await userAPages.conversation().messageInput.pressSequentially(':smi', {delay: 150}); + await expect(userAPages.conversation().emojiSuggestionOptions).toHaveCount(5); + // Inner text contains emoji and label + const emoji = await userAPages.conversation().emojiSuggestionOptions.nth(index).locator('span').first().innerText(); + return emoji; + }; + + await test.step('Open conversation', async () => { + await userAPages.conversationList().openConversation(userB.fullName); + }); + + await test.step('Select first emoji with Enter', async () => { + const firstEmoji = await getEmojiAt(0); + await userAPages.conversation().messageInput.press('Enter'); + await userAPages.conversation().sendMessageButton.click(); + await expect(userAPages.conversation().getMessage({content: firstEmoji})).toBeVisible(); + }); + + await test.step('Navigate with arrows and select with Enter', async () => { + const thirdEmoji = await getEmojiAt(2); + await userAPages.conversation().messageInput.press('ArrowDown'); + await userAPages.conversation().messageInput.press('ArrowDown'); + await userAPages.conversation().messageInput.press('Enter'); + await userAPages.conversation().sendMessageButton.click(); + await expect(userAPages.conversation().getMessage({content: thirdEmoji})).toBeVisible(); + }); + + await test.step('Select emoji with mouse click', async () => { + const clickedEmoji = await getEmojiAt(3); + await userAPages.conversation().emojiSuggestionOptions.nth(3).click(); + await userAPages.conversation().sendMessageButton.click(); + await expect(userAPages.conversation().getMessage({content: clickedEmoji})).toBeVisible(); + }); + + await test.step('Dismiss emoji menu with Escape', async () => { + await getEmojiAt(0); + await userAPages.conversation().messageInput.press('Escape'); + await expect(userAPages.conversation().emojiSuggestionOptions).not.toBeVisible(); + await expect(userAPages.conversation().getMessage({sender: userA})).toHaveCount(3); + }); + + await test.step('Verify menu does not appear without leading space', async () => { + await userAPages.conversation().messageInput.fill('hello:smi'); + await expect(userAPages.conversation().emojiSuggestionOptions).not.toBeVisible(); + }); + }); + test( 'I can see the system message "You renamed the conversation" after renaming conversation', {tag: ['@TC-496', '@regression']}, async ({createPage}) => { const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); - const groupName = 'Test Group'; await createGroup(adminPages, groupName, [userB, userC]); // User A renames the conversation @@ -416,7 +544,6 @@ test.describe('Conversations', () => { const adminPages = PageManager.from(adminPage).webapp.pages; const userBPages = PageManager.from(userBPage).webapp.pages; - const groupName = 'Test Group'; await createGroup(adminPages, groupName, [userB, userC]); await userBPages.conversationList().openConversation(groupName); From 70e094352764f201f5525209da17b212bee925ec Mon Sep 17 00:00:00 2001 From: zhanna_chaikovska Date: Fri, 6 Mar 2026 15:42:03 +0100 Subject: [PATCH 4/4] fix: implement correct emoji autocomplete for TC-495 --- .../webapp/pages/conversation.page.ts | 2 - .../webapp/pages/conversationDetails.page.ts | 4 +- .../specs/Conversations/conversations.spec.ts | 235 +++++++----------- .../e2e_tests/specs/Status/status.spec.ts | 4 +- 4 files changed, 93 insertions(+), 152 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 33aa17349e5..ff4e2b5e609 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 @@ -53,7 +53,6 @@ export class ConversationPage { /** Messages in conversation, only contains message items which have been sent successfully */ readonly messages: Locator; readonly messageDetails: Locator; - readonly emojiSuggestionOptions: Locator; readonly messageItems: Locator; readonly filesTab: Locator; readonly typingIndicator: Locator; @@ -100,7 +99,6 @@ export class ConversationPage { `[data-uie-name="item-message"]:not([data-uie-send-status="1"]):not([data-uie-send-status="-1"]):not(.system-message)`, ); this.messageDetails = page.locator('#message-details'); - this.emojiSuggestionOptions = page.getByRole('listbox', {name: 'Typeahead menu'}).getByRole('button'); this.filesTab = page.locator('#conversation-tab-files'); this.typingIndicator = page.getByTestId('typing-indicator-title'); this.itemPendingRequest = page.getByTestId('item-pending-requests'); 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 f5571d5cbae..d7b69d43de4 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 @@ -112,11 +112,11 @@ export class ConversationDetailsPage { await this.getLocatorByUser(fullName).click(); } - async participantStatus(fullName: string) { + getUserRoleIcon(fullName: string) { return this.getLocatorByUser(fullName).getByTestId(/^status-(external|guest|admin)$/); } - getParticipantStatus(fullName: string) { + getUserAvailabilityIcon(fullName: string) { return this.getLocatorByUser(fullName).getByTestId('status-availability-icon'); } diff --git a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts index 80517ad2702..9b6a5a816d3 100644 --- a/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Conversations/conversations.spec.ts @@ -21,7 +21,7 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; import {test, expect, withConnectedUser, withLogin, Team, withConnectionRequest} from 'test/e2e_tests/test.fixtures'; import {interceptNotifications} from 'test/e2e_tests/utils/mockNotifications.util'; -import {connectWithUser, createGroup, sendConnectionRequest} from 'test/e2e_tests/utils/userActions'; +import {connectWithUser, createGroup} from 'test/e2e_tests/utils/userActions'; test.describe('Conversations', () => { let team: Team; @@ -37,58 +37,41 @@ test.describe('Conversations', () => { userA = team.owner; }); - const systemMessageTestCases = [ - { - title: 'I create a group', - tag: '@TC-2965', - actor: 'userA', - targetUser: 'userA', - getPattern: (groupName: string, userA: User, userB: User, userC: User) => - new RegExp( - `You started the conversation\\s*${groupName}\\s*with\\s*${userB.fullName}\\s*and\\s*${userC.fullName}`, - 'i', - ), - }, - { - title: 'someone else created a group ', - tag: '@TC-2966', - actor: 'userA', - targetUser: 'userB', - getPattern: (groupName: string, userA: User, userB: User, userC: User) => - new RegExp( - `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, - 'i', - ), + test( + 'I want to see a system message with all group members mentioned on creating a group', + {tag: ['@TC-2965', '@regression']}, + async ({createPage}) => { + const userAPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); + + await createGroup(userAPages, groupName, [userB, userC]); + userAPages.conversationList().openConversation(groupName); + + const firstMessage = userAPages.conversation().systemMessages.first(); + await expect(firstMessage).toContainText(new RegExp(`You started the conversation\\s*${groupName}\\s*with`, 'i')); + await expect(firstMessage).toContainText(userB.fullName); + await expect(firstMessage).toContainText(userC.fullName); }, - ]; - - for (const data of systemMessageTestCases) { - test( - `I want to see a system message with all group members mentioned when ${data.title}`, - {tag: [data.tag, '@regression']}, - async ({createPage}) => { - const [adminPageManager, userBPageManager] = await Promise.all([ - PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), - PageManager.from(createPage(withLogin(userB))), - ]); - - const adminPages = adminPageManager.webapp.pages; - const targetPages = data.targetUser === 'userA' ? adminPageManager.webapp.pages : userBPageManager.webapp.pages; - - // 2. Action: Create the group (Always done by User A in this set) - await test.step('Create group', async () => { - await createGroup(adminPages, groupName, [userB, userC]); - }); + ); - // 3. Verification: Check the specific pattern for this test case - await test.step(`Verify message for ${data.targetUser}`, async () => { - await targetPages.conversationList().openConversation(groupName); - const pattern = data.getPattern(groupName, userA, userB, userC); - await expect(targetPages.conversation().systemMessages.first()).toContainText(pattern); - }); - }, - ); - } + test( + 'I want to see a system message with all group members mentioned when someone else created a group', + {tag: ['@TC-2966', '@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, groupName, [userB, userC]); + userBPages.conversationList().openConversation(groupName); + + const pattern = new RegExp( + `${userA.fullName} started the conversation\\s*${groupName}\\s*with\\s*${userC.fullName}\\s*and\\s*you`, + 'i', + ); + await expect(userBPages.conversation().systemMessages.first()).toContainText(pattern); + }, + ); test( 'I want to see "No matching results. Try entering a different name." when I search for non existent users', @@ -139,28 +122,12 @@ test.describe('Conversations', () => { }, ); - const guestTestCases = [ - { - title: 'I want to see usernames for Guest users in participants list', - tag: '@TC-421', - action: async (pages: PageManager['webapp']['pages'], guestUserName?: string) => { - await expect(pages.conversation().membersList).toContainText(guestUserName); - }, - }, - { - title: 'I want to see Guest icon in participants list', - tag: '@TC-423', - action: async (pages: PageManager['webapp']['pages']) => { - await expect(await pages.conversationDetails().participantStatus(userB.fullName)).not.toBeVisible(); - }, - }, - ]; - - for (const data of guestTestCases) { - test(`${data.title}`, {tag: [data.tag, '@regression']}, async ({createPage, createUser}) => { + test( + 'I want to see Guest icon in participants list', + {tag: ['@TC-421', '@regression']}, + async ({createPage, createUser}) => { const guestUser = await createUser(); - - const [adminPages, guestPages] = await Promise.all([ + const [adminPage, guestPages] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), ]); @@ -168,17 +135,39 @@ test.describe('Conversations', () => { await guestPages.conversationList().openPendingConnectionRequest(); await guestPages.connectRequest().clickConnectButton(); - await createGroup(adminPages, groupName, [userB, guestUser]); + await createGroup(adminPage, groupName, [userB, guestUser]); - await adminPages.conversationList().openConversation(groupName); - await adminPages.conversation().clickConversationInfoButton(); - await expect(await adminPages.conversationDetails().participantStatus(guestUser.fullName)).toHaveAttribute( + await adminPage.conversationList().openConversation(groupName); + await adminPage.conversation().clickConversationInfoButton(); + await expect(adminPage.conversationDetails().getUserRoleIcon(guestUser.fullName)).toHaveAttribute( 'data-uie-name', 'status-guest', ); - await data.action(adminPages, guestUser.username); - }); - } + }, + ); + + test( + 'I want to see usernames for Guest users in participants list', + {tag: ['@TC-423', '@regression']}, + async ({createPage, createUser}) => { + const guestUser = await createUser(); + + const [adminPage, guestPages] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectionRequest(guestUser))).then(pm => pm.webapp.pages), + PageManager.from(createPage(withLogin(guestUser))).then(pm => pm.webapp.pages), + ]); + + await guestPages.conversationList().openPendingConnectionRequest(); + await guestPages.connectRequest().clickConnectButton(); + + await createGroup(adminPage, groupName, [userB, guestUser]); + + await adminPage.conversationList().openConversation(groupName); + await adminPage.conversation().clickConversationInfoButton(); + + await expect(adminPage.conversation().membersList.filter({hasText: guestUser.fullName})).toBeVisible(); + }, + ); test( 'I want to see the empty admins section when there are no Admins', @@ -217,8 +206,10 @@ test.describe('Conversations', () => { 'I should not see Members section when there are only admins', {tag: ['@TC-432', '@regression']}, async ({createPage}) => { - const adminPages = await PageManager.from(createPage(withLogin(userA))).then(pm => pm.webapp.pages); - const userBPages = await PageManager.from(createPage(withLogin(userB))).then(pm => pm.webapp.pages); + const [adminPages, 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(adminPages, groupName, [userB]); @@ -272,14 +263,11 @@ test.describe('Conversations', () => { await adminPage.conversationList().openConversation(groupName); await adminPage.conversation().clickConversationInfoButton(); - await expect(await adminPage.conversation().membersList).toBeVisible(); - await expect(await adminPage.conversationDetails().participantStatus(externalUser.fullName)).toHaveAttribute( + await expect(adminPage.conversation().membersList).toBeVisible(); + await expect(adminPage.conversationDetails().getUserRoleIcon(externalUser.fullName)).toHaveAttribute( 'data-uie-name', 'status-external', ); - - // Verify no status icon is displayed for standard members - await expect(await adminPage.conversationDetails().participantStatus(userB.fullName)).not.toBeVisible(); }, ); @@ -465,52 +453,13 @@ test.describe('Conversations', () => { pm => pm.webapp.pages, ); - const getEmojiAt = async (index: number) => { - await userAPages.conversation().messageInput.pressSequentially(':smi', {delay: 150}); - await expect(userAPages.conversation().emojiSuggestionOptions).toHaveCount(5); - // Inner text contains emoji and label - const emoji = await userAPages.conversation().emojiSuggestionOptions.nth(index).locator('span').first().innerText(); - return emoji; - }; - - await test.step('Open conversation', async () => { - await userAPages.conversationList().openConversation(userB.fullName); - }); - - await test.step('Select first emoji with Enter', async () => { - const firstEmoji = await getEmojiAt(0); - await userAPages.conversation().messageInput.press('Enter'); - await userAPages.conversation().sendMessageButton.click(); - await expect(userAPages.conversation().getMessage({content: firstEmoji})).toBeVisible(); - }); - - await test.step('Navigate with arrows and select with Enter', async () => { - const thirdEmoji = await getEmojiAt(2); - await userAPages.conversation().messageInput.press('ArrowDown'); - await userAPages.conversation().messageInput.press('ArrowDown'); - await userAPages.conversation().messageInput.press('Enter'); - await userAPages.conversation().sendMessageButton.click(); - await expect(userAPages.conversation().getMessage({content: thirdEmoji})).toBeVisible(); - }); - - await test.step('Select emoji with mouse click', async () => { - const clickedEmoji = await getEmojiAt(3); - await userAPages.conversation().emojiSuggestionOptions.nth(3).click(); - await userAPages.conversation().sendMessageButton.click(); - await expect(userAPages.conversation().getMessage({content: clickedEmoji})).toBeVisible(); - }); - - await test.step('Dismiss emoji menu with Escape', async () => { - await getEmojiAt(0); - await userAPages.conversation().messageInput.press('Escape'); - await expect(userAPages.conversation().emojiSuggestionOptions).not.toBeVisible(); - await expect(userAPages.conversation().getMessage({sender: userA})).toHaveCount(3); - }); - - await test.step('Verify menu does not appear without leading space', async () => { - await userAPages.conversation().messageInput.fill('hello:smi'); - await expect(userAPages.conversation().emojiSuggestionOptions).not.toBeVisible(); - }); + await userAPages.conversationList().openConversation(userB.fullName); + + await userAPages.conversation().messageInput.pressSequentially(':) ', {delay: 100}); + expect(userAPages.conversation().messageInput).toContainText('🙂'); + + await userAPages.conversation().sendMessageButton.click(); + await expect(userAPages.conversation().getMessage({content: '🙂'})).toBeVisible(); }); test( @@ -526,9 +475,9 @@ test.describe('Conversations', () => { await adminPages.conversationDetails().changeConversationName('New Group Name'); // Verify that the system message is displayed correctly - await expect(adminPages.conversation().systemMessages.last()).toContainText('You renamed the conversation', { - ignoreCase: true, - }); + await expect( + adminPages.conversation().systemMessages.filter({hasText: 'You renamed the conversation'}), + ).toBeVisible(); }, ); @@ -596,11 +545,10 @@ test.describe('Conversations', () => { test( 'Verify help text is gone when I have at least one conversation while I am online', {tag: ['@TC-780', '@regression']}, - async ({createPage, createUser}) => { - const userD = await createUser(); - const [userAPageManager, userDPageManager] = await Promise.all([ + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA))), - PageManager.from(createPage(withLogin(userD))), + PageManager.from(createPage(withLogin(userB))), ]); const pages = userAPageManager.webapp.pages; @@ -608,16 +556,11 @@ test.describe('Conversations', () => { await expect(pages.conversationList().list).toContainText('Welcome to Wire'); await expect(pages.conversationList().list.getByRole('listitem')).toHaveCount(0); - // User D sends a connection request to User A - await sendConnectionRequest(userDPageManager, userA); - - // User A accepts connection request from User A - await pages.conversationList().openPendingConnectionRequest(); - await pages.connectRequest().clickConnectButton(); + await connectWithUser(userBPageManager, userA); // User A sees conversation with userD instead of help text await expect(pages.conversationList().list).not.toContainText('Welcome to Wire'); - await expect(pages.conversationList().list.getByRole('listitem').first()).toContainText(userD.fullName); + await expect(pages.conversationList().list.getByRole('listitem').first()).toContainText(userB.fullName); await expect(pages.conversationList().list.getByRole('listitem')).toHaveCount(1); }, ); diff --git a/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts b/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts index b8f67ed5203..22f688257b5 100644 --- a/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/Status/status.spec.ts @@ -481,8 +481,8 @@ test.describe('Status', () => { // User B should see the BUSY status in the searchable group participant list await userBPages.conversationList().openConversation(groupName); await userBPages.conversation().clickConversationInfoButton(); - await expect(userBPages.conversationDetails().getParticipantStatus(userA.fullName)).toBeVisible(); - await expect(userBPages.conversationDetails().getParticipantStatus(userA.fullName)).toHaveAttribute( + await expect(userBPages.conversationDetails().getUserAvailabilityIcon(userA.fullName)).toBeVisible(); + await expect(userBPages.conversationDetails().getUserAvailabilityIcon(userA.fullName)).toHaveAttribute( 'data-uie-value', 'busy', );