From fb8255b49866b98f0a1ecd28ee2caf7db88b88da Mon Sep 17 00:00:00 2001 From: lehnerja Date: Fri, 6 Feb 2026 15:50:33 +0100 Subject: [PATCH 01/11] test(historyBackup): add regression tests for history backup and restore This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). Refs: WPB-19954 --- .../webapp/pages/conversationDetails.page.ts | 6 + .../webapp/pages/conversationList.page.ts | 2 + .../specs/HistoryBackup/historyBackup.spec.ts | 360 ++++++++++++++++++ 3 files changed, 368 insertions(+) create mode 100644 apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.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 ed6fbe64976..e4242ce2058 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 @@ -31,6 +31,9 @@ export class ConversationDetailsPage { readonly clearConversationContentButton: Locator; readonly selectedSearchList: Locator; readonly searchList: Locator; + readonly deleteGroupButton: Locator; + readonly notificationsButton: Locator; + readonly editConversationNameButton: Locator; constructor(page: Page) { this.page = page; @@ -44,6 +47,9 @@ export class ConversationDetailsPage { 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.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'}); } async waitForSidebar() { 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 9faeb13440a..0baa8b87cbc 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 @@ -39,6 +39,7 @@ export class ConversationListPage { readonly conversationListHeaderTitle: Locator; readonly joinCallButton: Locator; readonly clearContentButton: Locator; + readonly mutedConversationBadge: Locator; constructor(page: Page) { this.page = page; @@ -60,6 +61,7 @@ export class ConversationListPage { this.conversationListHeaderTitle = page.locator('[data-uie-name="conversation-list-header-title"]'); this.joinCallButton = page.getByRole('button', {name: 'Join'}); this.clearContentButton = page.getByRole('button', {name: 'Clear content'}); + this.mutedConversationBadge = page.locator('[data-uie-name="status-silence"]'); } async isConversationItemVisible(conversationName: string) { diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts new file mode 100644 index 00000000000..74b8c744f7e --- /dev/null +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -0,0 +1,360 @@ +/* + * 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, withLogin, withConnectedUser} from 'test/e2e_tests/test.fixtures'; +import {createGroup, loginUser, logOutUser} from 'test/e2e_tests/utils/userActions'; +import {generateSecurePassword, generateWireEmail} from '../../utils/userDataGenerator'; + +test.describe('History Backup', () => { + let userA: User; + let userB: User; + + test.beforeEach(async ({createTeam}) => { + const team = await createTeam('Test Team', {withMembers: 1}); + userA = team.owner; + userB = team.members[0]; + }); + + const createAndSaveBackup = async ( + pageManager: PageManager, + password?: string, + filenamePrefix?: string, + ): Promise => { + const {pages, modals} = pageManager.webapp; + + await pages.account().clickBackUpButton(); + await expect(modals.passwordAdvancedSecurity().modal).toBeVisible(); + if (password) { + await modals.passwordAdvancedSecurity().enterPassword(password); + } + await modals.passwordAdvancedSecurity().clickBackUpNow(); + await expect(modals.passwordAdvancedSecurity().modal).toBeHidden(); + expect(pages.historyExport().isVisible()).toBeTruthy(); + const [download] = await Promise.all([ + pages.historyExport().page.waitForEvent('download'), + pages.historyExport().clickSaveFileButton(), + ]); + const backupName = `./test-results/downloads/${filenamePrefix}${download.suggestedFilename()}`; + await download.saveAs(backupName); + return backupName; + }; + + test( + 'I want to import a backup that I exported when I was using a different email/password', + {tag: ['@TC-118', '@regression']}, + async ({createPage, api}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), + ]); + const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages} = userBPageManager.webapp; + + const conversationName = 'Test group'; + await createGroup(userAPages, conversationName, [userB]); + + const messageUserA = 'Message from User A'; + const messageUserB = 'Message from User B'; + + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + + // User A creates Backup + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + + await test.step('Member changes their email address to a new email address', async () => { + const newEmail = generateWireEmail(userA.lastName); + await userAPages.account().changeEmailAddress(newEmail); + await userAModals.acknowledge().clickAction(); // Acknowledge verify email address modal + + const activationUrl = await api.inbucket.getAccountActivationURL(newEmail); + await userAPageManager.openNewTab(activationUrl); + await userAPages.account().isDisplayedEmailEquals(newEmail); + userA.email = newEmail; + }); + + await test.step('Member resets their password ', async () => { + const [newPage] = await Promise.all([ + userAPageManager.getContext().waitForEvent('page'), // Wait for the new tab + userAPages.account().clickResetPasswordButton(), + ]); + + const resetPasswordPageManager = PageManager.from(newPage); + const resetPasswordPage = resetPasswordPageManager.webapp.pages.requestResetPassword(); + await resetPasswordPage.requestPasswordResetForEmail(userA.email); + const resetPasswordUrl = await api.inbucket.getResetPasswordURL(userA.email); + await newPage.close(); // Close the new tab + + const newPassword = generateSecurePassword(); + userA.password = newPassword; // Update member's password for password reset + + await userAPageManager.openUrl(resetPasswordUrl); + await userAPages.resetPassword().setNewPassword(newPassword); + await userAPages.resetPassword().isPasswordChangeMessageVisible(); + + // Logging in with the new password + await userAPageManager.openMainPage(); + await loginUser(userA, userAPageManager); + }); + + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + + await userAComponents.conversationSidebar().allConverationsButton.click(); + await userAPages.conversationList().openConversation(conversationName); + await expect(userAPages.conversation().getMessage({sender: userB})).toContainText(messageUserB); + await expect(userAPages.conversation().getMessage({sender: userA})).toContainText(messageUserA); + }, + ); + + test( + "I should not be able to restore from the history of another person's account", + {tag: ['@TC-125', '@regression']}, + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), + ]); + + const {pages: userAPages, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages, components: userBComponents} = userBPageManager.webapp; + + const messageUserA = 'Message from User A'; + const messageUserB = 'Message from User B'; + + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversation().sendMessage(messageUserB); + + // User A creates History Backup + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + + // User B tries to import History Backup from User A + await logOutUser(userBPageManager, true); + await loginUser(userB, userBPageManager); + await userBPages.historyInfo().clickConfirmButton(); + await userBComponents.conversationSidebar().clickPreferencesButton(); + await userBPages.account().backupFileInput.setInputFiles(backupName); + + const errorHeadline = userBPages.historyImport().title; + const errorInfo = userBPages.historyImport().description; + await expect(errorHeadline).toBeVisible(); + await expect(errorHeadline).toHaveText('Wrong backup'); + await expect(errorInfo).toHaveText('You cannot restore history from a different account.'); + }, + ); + + test( + 'I want to see new name and system message of the renamed conversation when it was renamed after export', + {tag: ['@TC-131', '@regression']}, + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), + ]); + const {pages: userAPages, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages} = userBPageManager.webapp; + + const conversationName = 'Test group'; + await createGroup(userBPages, conversationName, [userA]); + + const messageUserA = 'Message from User A'; + const messageUserB = 'Message from User B'; + + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + + // User A creates Backup + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + await userAComponents.conversationSidebar().allConverationsButton.click(); + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + + // User B renames group conversation + await userBPages.conversation().conversationInfoButton.click(); + await userBPages.conversationDetails().editConversationNameButton.click(); + const textFieldConversationName = userBPages + .conversationDetails() + .page.locator('textarea[data-uie-name="enter-name"]'); + await textFieldConversationName.fill(''); + const renamedConversationName = 'renamedConversationName'; + await textFieldConversationName.fill(renamedConversationName); + await textFieldConversationName.press('Enter'); + + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + + // User A sees renamed conversation + await userAComponents.conversationSidebar().allConverationsButton.click(); + await expect(userAPages.conversationList().getConversationLocator(renamedConversationName)).toBeVisible(); + + // User A sees system message that User B had renamed the conversation + await userAPages.conversationList().openConversation(renamedConversationName); + const renamedSystemMessage = userAPages.conversation().systemMessages.last(); + await expect(renamedSystemMessage).toContainText(`${userB.fullName} renamed the conversation`); + }, + ); + + test( + 'I want to have the same mute or archive state of a conversation after import', + {tag: ['@TC-133', '@regression']}, + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), + ]); + const {pages: userAPages, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages} = userBPageManager.webapp; + + const conversationName = 'Test group'; + await createGroup(userAPages, conversationName, [userB]); + + const messageUserA = 'Message from User A'; + const messageUserB = 'Message from User B'; + + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + + // User A mutes group conversation with User B + await userAPages.conversation().conversationInfoButton.click(); + await userAPages.conversationDetails().notificationsButton.click(); + await userAPages + .conversationDetails() + .page.getByRole('radiogroup') + .locator('label', {hasText: 'Nothing'}) + .click(); + + // User A archives 1:1 conversation with User B + await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversation().conversationInfoButton.click(); + await userAPages.conversationDetails().archiveButton.click(); + + // User A creates Backup + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + + await userAComponents.conversationSidebar().allConverationsButton.click(); + await userAPages.conversationList().openConversation(conversationName); + await expect(userAPages.conversationList().mutedConversationBadge).toBeVisible(); + + await userAComponents.conversationSidebar().archiveButton.click(); + const archivedConversation = userAPages.conversationList().getConversationLocator(userB.fullName); + await expect(archivedConversation).toBeVisible(); + }, + ); + + test( + 'I should not be able to import a backup with wrong password', + {tag: ['@TC-135', '@regression']}, + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), + ]); + + const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages} = userBPageManager.webapp; + + const messageUserA = 'Message from User A'; + const messageUserB = 'Message from User B'; + + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversation().sendMessage(messageUserB); + + // User A creates History Backup + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager, userA.password); + + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); + + // User A tries to restore backup with wrong password + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + await userAModals.passwordAdvancedSecurity().enterPassword('wrongPassword1.'); + await userAModals.passwordAdvancedSecurity().clickAction(); + + const errorHeadline = userAPages.historyImport().title; + const errorInfo = userAPages.historyImport().description; + await expect(errorHeadline).toBeVisible(); + await expect(errorHeadline).toHaveText('Wrong Password'); + await expect(errorInfo).toHaveText('Please verify your input and try again'); + }, + ); + + test( + 'I should not see the deleted group after restore from the backup', + {tag: ['@TC-1097', '@regression']}, + async ({createPage}) => { + const [userAPageManager, userBPageManager] = await Promise.all([ + PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), + PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), + ]); + const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; + const {pages: userBPages} = userBPageManager.webapp; + + const conversationName = 'Test group'; + await createGroup(userAPages, conversationName, [userB]); + + const messageUserA = 'Message from User A'; + const messageUserB = 'Message from User B'; + + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + + await userAPages.conversation().conversationInfoButton.click(); + await userAPages.conversationDetails().deleteGroupButton.click(); + const deleteGroupModal = userAModals.confirm(); + expect(await deleteGroupModal.getModalTitle()).toContain('Delete group conversation?'); + await deleteGroupModal.clickAction(); + + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + await expect(userAPages.conversationList().getConversationLocator(conversationName)).not.toBeVisible(); + }, + ); +}); From 21a5f9bc2ff3da943c86d639d9856bd9cf7d26a2 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Fri, 6 Feb 2026 16:46:57 +0100 Subject: [PATCH 02/11] test(historyBackup): add regression tests for history backup and restore This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../specs/HistoryBackup/historyBackup.spec.ts | 295 ++++++++++-------- 1 file changed, 163 insertions(+), 132 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 74b8c744f7e..79e3b370c23 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -74,16 +74,18 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await userAPages.conversationList().openConversation(conversationName); - await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); - await userBPages.conversation().sendMessage(messageUserB); + await test.step('User A are writing messages to each other', async () => { + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + }); // User A creates Backup await userAComponents.conversationSidebar().clickPreferencesButton(); const backupName = await createAndSaveBackup(userAPageManager); - await test.step('Member changes their email address to a new email address', async () => { + await test.step('User A changes their Email address', async () => { const newEmail = generateWireEmail(userA.lastName); await userAPages.account().changeEmailAddress(newEmail); await userAModals.acknowledge().clickAction(); // Acknowledge verify email address modal @@ -94,9 +96,9 @@ test.describe('History Backup', () => { userA.email = newEmail; }); - await test.step('Member resets their password ', async () => { + await test.step('User A changes their Password', async () => { const [newPage] = await Promise.all([ - userAPageManager.getContext().waitForEvent('page'), // Wait for the new tab + userAPageManager.getContext().waitForEvent('page'), userAPages.account().clickResetPasswordButton(), ]); @@ -104,16 +106,15 @@ test.describe('History Backup', () => { const resetPasswordPage = resetPasswordPageManager.webapp.pages.requestResetPassword(); await resetPasswordPage.requestPasswordResetForEmail(userA.email); const resetPasswordUrl = await api.inbucket.getResetPasswordURL(userA.email); - await newPage.close(); // Close the new tab + await newPage.close(); const newPassword = generateSecurePassword(); - userA.password = newPassword; // Update member's password for password reset + userA.password = newPassword; await userAPageManager.openUrl(resetPasswordUrl); await userAPages.resetPassword().setNewPassword(newPassword); await userAPages.resetPassword().isPasswordChangeMessageVisible(); - // Logging in with the new password await userAPageManager.openMainPage(); await loginUser(userA, userAPageManager); }); @@ -121,10 +122,12 @@ test.describe('History Backup', () => { await userAComponents.conversationSidebar().clickPreferencesButton(); await userAPages.account().backupFileInput.setInputFiles(backupName); - await userAComponents.conversationSidebar().allConverationsButton.click(); - await userAPages.conversationList().openConversation(conversationName); - await expect(userAPages.conversation().getMessage({sender: userB})).toContainText(messageUserB); - await expect(userAPages.conversation().getMessage({sender: userA})).toContainText(messageUserA); + await test.step('Validate conversation is still visible with all messages after restoring backup', async () => { + await userAComponents.conversationSidebar().allConverationsButton.click(); + await userAPages.conversationList().openConversation(conversationName); + await expect(userAPages.conversation().getMessage({sender: userB})).toContainText(messageUserB); + await expect(userAPages.conversation().getMessage({sender: userA})).toContainText(messageUserA); + }); }, ); @@ -143,27 +146,30 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - await userBPages.conversation().sendMessage(messageUserB); + await test.step('User A and B are writing messages to each other', async () => { + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversation().sendMessage(messageUserB); + }); - // User A creates History Backup - await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + await test.step('User A creates History Backup and User B tries to restore it', async () => { + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + await logOutUser(userBPageManager, true); + await loginUser(userB, userBPageManager); + await userBPages.historyInfo().clickConfirmButton(); + await userBComponents.conversationSidebar().clickPreferencesButton(); + await userBPages.account().backupFileInput.setInputFiles(backupName); + }); - // User B tries to import History Backup from User A - await logOutUser(userBPageManager, true); - await loginUser(userB, userBPageManager); - await userBPages.historyInfo().clickConfirmButton(); - await userBComponents.conversationSidebar().clickPreferencesButton(); - await userBPages.account().backupFileInput.setInputFiles(backupName); - - const errorHeadline = userBPages.historyImport().title; - const errorInfo = userBPages.historyImport().description; - await expect(errorHeadline).toBeVisible(); - await expect(errorHeadline).toHaveText('Wrong backup'); - await expect(errorInfo).toHaveText('You cannot restore history from a different account.'); + await test.step('Validate User B cannot import History Backup from User A', async () => { + const errorHeadline = userBPages.historyImport().title; + const errorInfo = userBPages.historyImport().description; + await expect(errorHeadline).toBeVisible(); + await expect(errorHeadline).toHaveText('Wrong backup'); + await expect(errorInfo).toHaveText('You cannot restore history from a different account.'); + }); }, ); @@ -184,39 +190,44 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await userAPages.conversationList().openConversation(conversationName); - await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); - await userBPages.conversation().sendMessage(messageUserB); - - // User A creates Backup - await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); - await userAComponents.conversationSidebar().allConverationsButton.click(); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - - // User B renames group conversation - await userBPages.conversation().conversationInfoButton.click(); - await userBPages.conversationDetails().editConversationNameButton.click(); - const textFieldConversationName = userBPages - .conversationDetails() - .page.locator('textarea[data-uie-name="enter-name"]'); - await textFieldConversationName.fill(''); const renamedConversationName = 'renamedConversationName'; - await textFieldConversationName.fill(renamedConversationName); - await textFieldConversationName.press('Enter'); - await userAComponents.conversationSidebar().clickPreferencesButton(); - await userAPages.account().backupFileInput.setInputFiles(backupName); + await test.step('User A and B writing in their group conversation', async () => { + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + }); + + await test.step('User A creates History Backup, User B renames group conversation and User A restores the Backup', async () => { + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + await userAComponents.conversationSidebar().allConverationsButton.click(); + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + // User B renames group conversation + await userBPages.conversation().conversationInfoButton.click(); + await userBPages.conversationDetails().editConversationNameButton.click(); + const textFieldConversationName = userBPages + .conversationDetails() + .page.locator('textarea[data-uie-name="enter-name"]'); + await textFieldConversationName.fill(''); + await textFieldConversationName.fill(renamedConversationName); + await textFieldConversationName.press('Enter'); + + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + }); - // User A sees renamed conversation - await userAComponents.conversationSidebar().allConverationsButton.click(); - await expect(userAPages.conversationList().getConversationLocator(renamedConversationName)).toBeVisible(); + await test.step('Validate User A sees renamed conversation and system message', async () => { + // User A sees renamed conversation + await userAComponents.conversationSidebar().allConverationsButton.click(); + await expect(userAPages.conversationList().getConversationLocator(renamedConversationName)).toBeVisible(); - // User A sees system message that User B had renamed the conversation - await userAPages.conversationList().openConversation(renamedConversationName); - const renamedSystemMessage = userAPages.conversation().systemMessages.last(); - await expect(renamedSystemMessage).toContainText(`${userB.fullName} renamed the conversation`); + // User A sees system message that User B had renamed the conversation + await userAPages.conversationList().openConversation(renamedConversationName); + const renamedSystemMessage = userAPages.conversation().systemMessages.last(); + await expect(renamedSystemMessage).toContainText(`${userB.fullName} renamed the conversation`); + }); }, ); @@ -237,42 +248,49 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await userAPages.conversationList().openConversation(conversationName); - await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); - await userBPages.conversation().sendMessage(messageUserB); - - // User A mutes group conversation with User B - await userAPages.conversation().conversationInfoButton.click(); - await userAPages.conversationDetails().notificationsButton.click(); - await userAPages - .conversationDetails() - .page.getByRole('radiogroup') - .locator('label', {hasText: 'Nothing'}) - .click(); - - // User A archives 1:1 conversation with User B - await userAPages.conversationList().openConversation(userB.fullName); - await userAPages.conversation().conversationInfoButton.click(); - await userAPages.conversationDetails().archiveButton.click(); + await test.step('User A and B writing messages to each other', async () => { + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + }); - // User A creates Backup - await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + await test.step('User A mutes group conversation with User B', async () => { + await userAPages.conversation().conversationInfoButton.click(); + await userAPages.conversationDetails().notificationsButton.click(); + await userAPages + .conversationDetails() + .page.getByRole('radiogroup') + .locator('label', {hasText: 'Nothing'}) + .click(); + }); - await logOutUser(userAPageManager, true); - await loginUser(userA, userAPageManager); - await userAPages.historyInfo().clickConfirmButton(); - await userAComponents.conversationSidebar().clickPreferencesButton(); - await userAPages.account().backupFileInput.setInputFiles(backupName); + await test.step('User A archives 1:1 conversation with User B', async () => { + await userAPages.conversationList().openConversation(userB.fullName); + await userAPages.conversation().conversationInfoButton.click(); + await userAPages.conversationDetails().archiveButton.click(); + }); + + await test.step('User A creates History Backup and restores it', async () => { + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); + + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + }); - await userAComponents.conversationSidebar().allConverationsButton.click(); - await userAPages.conversationList().openConversation(conversationName); - await expect(userAPages.conversationList().mutedConversationBadge).toBeVisible(); + await test.step('Validate muted and archived state are the same', async () => { + await userAComponents.conversationSidebar().allConverationsButton.click(); + await userAPages.conversationList().openConversation(conversationName); + await expect(userAPages.conversationList().mutedConversationBadge).toBeVisible(); - await userAComponents.conversationSidebar().archiveButton.click(); - const archivedConversation = userAPages.conversationList().getConversationLocator(userB.fullName); - await expect(archivedConversation).toBeVisible(); + await userAComponents.conversationSidebar().archiveButton.click(); + const archivedConversation = userAPages.conversationList().getConversationLocator(userB.fullName); + await expect(archivedConversation).toBeVisible(); + }); }, ); @@ -291,30 +309,34 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); - await userBPages.conversation().sendMessage(messageUserB); + await test.step('User A and B writing messages to each other', async () => { + await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); + await userBPages.conversation().sendMessage(messageUserB); + }); - // User A creates History Backup - await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager, userA.password); + await test.step('User A creates History Backup and tries to restore it with wrong password', async () => { + // User A creates History Backup + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager, userA.password); - await logOutUser(userAPageManager, true); - await loginUser(userA, userAPageManager); + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); - // User A tries to restore backup with wrong password - await userAPages.historyInfo().clickConfirmButton(); - await userAComponents.conversationSidebar().clickPreferencesButton(); - await userAPages.account().backupFileInput.setInputFiles(backupName); - await userAModals.passwordAdvancedSecurity().enterPassword('wrongPassword1.'); - await userAModals.passwordAdvancedSecurity().clickAction(); - - const errorHeadline = userAPages.historyImport().title; - const errorInfo = userAPages.historyImport().description; - await expect(errorHeadline).toBeVisible(); - await expect(errorHeadline).toHaveText('Wrong Password'); - await expect(errorInfo).toHaveText('Please verify your input and try again'); + // User A tries to restore backup with wrong password + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + await userAModals.passwordAdvancedSecurity().enterPassword('wrongPassword1.'); + await userAModals.passwordAdvancedSecurity().clickAction(); + + const errorHeadline = userAPages.historyImport().title; + const errorInfo = userAPages.historyImport().description; + await expect(errorHeadline).toBeVisible(); + await expect(errorHeadline).toHaveText('Wrong Password'); + await expect(errorInfo).toHaveText('Please verify your input and try again'); + }); }, ); @@ -335,26 +357,35 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await userAPages.conversationList().openConversation(conversationName); - await userAPages.conversation().sendMessage(messageUserA); - await userBPages.conversationList().openConversation(conversationName); - await userBPages.conversation().sendMessage(messageUserB); + await test.step('User A and User B are writing messages to each other', async () => { + await userAPages.conversationList().openConversation(conversationName); + await userAPages.conversation().sendMessage(messageUserA); + await userBPages.conversationList().openConversation(conversationName); + await userBPages.conversation().sendMessage(messageUserB); + }); - await userAPages.conversation().conversationInfoButton.click(); - await userAPages.conversationDetails().deleteGroupButton.click(); - const deleteGroupModal = userAModals.confirm(); - expect(await deleteGroupModal.getModalTitle()).toContain('Delete group conversation?'); - await deleteGroupModal.clickAction(); + await test.step('User A deletes group conversation with User B', async () => { + await userAPages.conversation().conversationInfoButton.click(); + await userAPages.conversationDetails().deleteGroupButton.click(); + const deleteGroupModal = userAModals.confirm(); + expect(await deleteGroupModal.getModalTitle()).toContain('Delete group conversation?'); + await deleteGroupModal.clickAction(); + }); - await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + await test.step('User A creates History Backup', async () => { + await userAComponents.conversationSidebar().clickPreferencesButton(); + const backupName = await createAndSaveBackup(userAPageManager); - await logOutUser(userAPageManager, true); - await loginUser(userA, userAPageManager); - await userAPages.historyInfo().clickConfirmButton(); - await userAComponents.conversationSidebar().clickPreferencesButton(); - await userAPages.account().backupFileInput.setInputFiles(backupName); - await expect(userAPages.conversationList().getConversationLocator(conversationName)).not.toBeVisible(); + await logOutUser(userAPageManager, true); + await loginUser(userA, userAPageManager); + await userAPages.historyInfo().clickConfirmButton(); + await userAComponents.conversationSidebar().clickPreferencesButton(); + await userAPages.account().backupFileInput.setInputFiles(backupName); + }); + + await test.step('Validate deleted group conversation is no longer visible', async () => { + await expect(userAPages.conversationList().getConversationLocator(conversationName)).not.toBeVisible(); + }); }, ); }); From eb0e82022e267ec59ee344a5798be525d0c9ca89 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Tue, 10 Feb 2026 09:45:38 +0100 Subject: [PATCH 03/11] test(historyBackup): add regression tests for history backup and restore This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../backupRestoration-TC-8634.spec.ts | 24 ++------------- .../specs/HistoryBackup/historyBackup.spec.ts | 30 ++----------------- .../test/e2e_tests/utils/userActions.ts | 20 +++++++++++++ 3 files changed, 26 insertions(+), 48 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts index 4a5b8f99337..fbd6bfdc935 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts @@ -18,7 +18,7 @@ */ import {removeCreatedUser} from 'test/e2e_tests/utils/tearDown.util'; -import {createGroup, loginUser, logOutUser} from 'test/e2e_tests/utils/userActions'; +import {createAndSaveBackup, createGroup, loginUser, logOutUser} from 'test/e2e_tests/utils/userActions'; import {getUser} from '../../data/user'; import {test, expect} from '../../test.fixtures'; @@ -35,24 +35,6 @@ let passwordProtectedBackupName: string; test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web']}, async ({pageManager, api}) => { const {pages, modals, components} = pageManager.webapp; - const createAndSaveBackup = async (password?: string, filenamePrefix?: string): Promise => { - await pages.account().clickBackUpButton(); - expect(modals.passwordAdvancedSecurity().isTitleVisible()).toBeTruthy(); - if (password) { - await modals.passwordAdvancedSecurity().enterPassword(password); - } - await modals.passwordAdvancedSecurity().clickBackUpNow(); - expect(modals.passwordAdvancedSecurity().isTitleHidden()).toBeTruthy(); - expect(pages.historyExport().isVisible()).toBeTruthy(); - const [download] = await Promise.all([ - pages.historyExport().page.waitForEvent('download'), - pages.historyExport().clickSaveFileButton(), - ]); - const backupName = `./test-results/downloads/${filenamePrefix}${download.suggestedFilename()}`; - await download.saveAs(backupName); - return backupName; - }; - // Creating preconditions for the test via API await test.step('Preconditions: Creating preconditions for the test via API', async () => { await api.createPersonalUser(userA); @@ -81,11 +63,11 @@ test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web'] await test.step('User creates and saves a backup', async () => { await components.conversationSidebar().clickPreferencesButton(); - backupName = await createAndSaveBackup(); + backupName = await createAndSaveBackup(pageManager); }); await test.step('User creates and saves a password backup', async () => { - passwordProtectedBackupName = await createAndSaveBackup(userA.password, 'password-'); + passwordProtectedBackupName = await createAndSaveBackup(pageManager, userA.password, 'password-'); }); await test.step('User logs out and clears all data', async () => { diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 79e3b370c23..b54e1e9404c 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -20,8 +20,9 @@ import {User} from 'test/e2e_tests/data/user'; import {PageManager} from 'test/e2e_tests/pageManager'; import {test, expect, withLogin, withConnectedUser} from 'test/e2e_tests/test.fixtures'; -import {createGroup, loginUser, logOutUser} from 'test/e2e_tests/utils/userActions'; +import {createAndSaveBackup, createGroup, loginUser, logOutUser} from 'test/e2e_tests/utils/userActions'; import {generateSecurePassword, generateWireEmail} from '../../utils/userDataGenerator'; +import {RequestResetPasswordPage} from '../../pageManager/webapp/pages/requestResetPassword.page'; test.describe('History Backup', () => { let userA: User; @@ -33,30 +34,6 @@ test.describe('History Backup', () => { userB = team.members[0]; }); - const createAndSaveBackup = async ( - pageManager: PageManager, - password?: string, - filenamePrefix?: string, - ): Promise => { - const {pages, modals} = pageManager.webapp; - - await pages.account().clickBackUpButton(); - await expect(modals.passwordAdvancedSecurity().modal).toBeVisible(); - if (password) { - await modals.passwordAdvancedSecurity().enterPassword(password); - } - await modals.passwordAdvancedSecurity().clickBackUpNow(); - await expect(modals.passwordAdvancedSecurity().modal).toBeHidden(); - expect(pages.historyExport().isVisible()).toBeTruthy(); - const [download] = await Promise.all([ - pages.historyExport().page.waitForEvent('download'), - pages.historyExport().clickSaveFileButton(), - ]); - const backupName = `./test-results/downloads/${filenamePrefix}${download.suggestedFilename()}`; - await download.saveAs(backupName); - return backupName; - }; - test( 'I want to import a backup that I exported when I was using a different email/password', {tag: ['@TC-118', '@regression']}, @@ -102,8 +79,7 @@ test.describe('History Backup', () => { userAPages.account().clickResetPasswordButton(), ]); - const resetPasswordPageManager = PageManager.from(newPage); - const resetPasswordPage = resetPasswordPageManager.webapp.pages.requestResetPassword(); + const resetPasswordPage = new RequestResetPasswordPage(newPage); await resetPasswordPage.requestPasswordResetForEmail(userA.email); const resetPasswordUrl = await api.inbucket.getResetPasswordURL(userA.email); await newPage.close(); diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index 146a6c35e01..953090928e7 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -115,3 +115,23 @@ export async function sendConnectionRequest(senderPageManager: PageManager, rece await pages.startUI().selectUsers(receiver.username); await modals.userProfile().clickConnectButton(); } + +export async function createAndSaveBackup(pageManager: PageManager, password?: string, filenamePrefix?: string) { + const {pages, modals} = pageManager.webapp; + + await pages.account().clickBackUpButton(); + await expect(modals.passwordAdvancedSecurity().modal).toBeVisible(); + if (password) { + await modals.passwordAdvancedSecurity().enterPassword(password); + } + await modals.passwordAdvancedSecurity().clickBackUpNow(); + await expect(modals.passwordAdvancedSecurity().modal).toBeHidden(); + expect(pages.historyExport().isVisible()).toBeTruthy(); + const [download] = await Promise.all([ + pages.historyExport().page.waitForEvent('download'), + pages.historyExport().clickSaveFileButton(), + ]); + const backupName = `./test-results/downloads/${filenamePrefix}${download.suggestedFilename()}`; + await download.saveAs(backupName); + return backupName; +} From f7da64171e37b4796b420a4e3086fc42d1b8f0a2 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Tue, 10 Feb 2026 18:08:05 +0100 Subject: [PATCH 04/11] test(historyBackup): changed according to review comments This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../webapp/pages/conversationDetails.page.ts | 7 ++++ .../specs/HistoryBackup/historyBackup.spec.ts | 41 +++++++++++-------- 2 files changed, 31 insertions(+), 17 deletions(-) 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 e4242ce2058..cb2f03cce33 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 @@ -34,6 +34,7 @@ export class ConversationDetailsPage { readonly deleteGroupButton: Locator; readonly notificationsButton: Locator; readonly editConversationNameButton: Locator; + readonly textFieldForConversationName: Locator; constructor(page: Page) { this.page = page; @@ -50,6 +51,7 @@ export class ConversationDetailsPage { 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'}); + this.textFieldForConversationName = this.page.locator('textarea[data-uie-name="enter-name"]'); } async waitForSidebar() { @@ -187,4 +189,9 @@ export class ConversationDetailsPage { async clickClearConversationContentButton() { await this.clearConversationContentButton.click(); } + + async setNotificationsForConversation(value: 'Everything' | 'Mentions and replies' | 'Nothing') { + await this.notificationsButton.click(); + await this.page.getByRole('radiogroup').locator('label', {hasText: value}).click(); + } } diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index b54e1e9404c..94e5d5f9cd2 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -42,7 +42,9 @@ test.describe('History Backup', () => { PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), ]); - const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; + + // Use 'let' here so we can re-assign these later to the new browser context + let {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; const conversationName = 'Test group'; @@ -89,10 +91,24 @@ test.describe('History Backup', () => { await userAPageManager.openUrl(resetPasswordUrl); await userAPages.resetPassword().setNewPassword(newPassword); - await userAPages.resetPassword().isPasswordChangeMessageVisible(); + await expect(userAPages.resetPassword().passwordChangeMessage).toBeVisible(); - await userAPageManager.openMainPage(); - await loginUser(userA, userAPageManager); + await userAPageManager.page.context().close(); + + // Initialize a new context and page + const newBrowserContext = await userAPageManager.page.context().browser()!.newContext(); + const newPageUserA = await newBrowserContext.newPage(); + const newUserAPageManager = PageManager.from(newPageUserA); + + await newUserAPageManager.openMainPage(); + await loginUser(userA, newUserAPageManager); + + // Reassign pages, modals and components for new context + userAPages = newUserAPageManager.webapp.pages; + userAModals = newUserAPageManager.webapp.modals; + userAComponents = newUserAPageManager.webapp.components; + + await userAPages.historyInfo().continueButton.click(); }); await userAComponents.conversationSidebar().clickPreferencesButton(); @@ -183,10 +199,7 @@ test.describe('History Backup', () => { // User B renames group conversation await userBPages.conversation().conversationInfoButton.click(); await userBPages.conversationDetails().editConversationNameButton.click(); - const textFieldConversationName = userBPages - .conversationDetails() - .page.locator('textarea[data-uie-name="enter-name"]'); - await textFieldConversationName.fill(''); + const textFieldConversationName = userBPages.conversationDetails().textFieldForConversationName; await textFieldConversationName.fill(renamedConversationName); await textFieldConversationName.press('Enter'); @@ -233,12 +246,7 @@ test.describe('History Backup', () => { await test.step('User A mutes group conversation with User B', async () => { await userAPages.conversation().conversationInfoButton.click(); - await userAPages.conversationDetails().notificationsButton.click(); - await userAPages - .conversationDetails() - .page.getByRole('radiogroup') - .locator('label', {hasText: 'Nothing'}) - .click(); + await userAPages.conversationDetails().setNotificationsForConversation('Nothing'); }); await test.step('User A archives 1:1 conversation with User B', async () => { @@ -343,9 +351,8 @@ test.describe('History Backup', () => { await test.step('User A deletes group conversation with User B', async () => { await userAPages.conversation().conversationInfoButton.click(); await userAPages.conversationDetails().deleteGroupButton.click(); - const deleteGroupModal = userAModals.confirm(); - expect(await deleteGroupModal.getModalTitle()).toContain('Delete group conversation?'); - await deleteGroupModal.clickAction(); + expect(await userAModals.confirm().modalTitle).toContainText('Delete group conversation?'); + await userAModals.confirm().clickAction(); }); await test.step('User A creates History Backup', async () => { From 58684e38a02174eecc75eb5d9afcbfcf649c6002 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Wed, 11 Feb 2026 10:17:33 +0100 Subject: [PATCH 05/11] test(historyBackup): changed according to copilot comments This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../pageManager/webapp/pages/conversationDetails.page.ts | 5 ++++- .../test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts | 3 +-- apps/webapp/test/e2e_tests/utils/userActions.ts | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) 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 cb2f03cce33..fa6a89e9754 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 @@ -192,6 +192,9 @@ export class ConversationDetailsPage { async setNotificationsForConversation(value: 'Everything' | 'Mentions and replies' | 'Nothing') { await this.notificationsButton.click(); - await this.page.getByRole('radiogroup').locator('label', {hasText: value}).click(); + const notificationsPanel = this.page.locator('aside#right-column'); + const radioGroup = notificationsPanel.getByRole('radiogroup'); + await radioGroup.waitFor({state: 'visible'}); + await radioGroup.getByText(value).click(); } } diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 94e5d5f9cd2..02aa70328c0 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -43,7 +43,6 @@ test.describe('History Backup', () => { PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), ]); - // Use 'let' here so we can re-assign these later to the new browser context let {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; @@ -351,7 +350,7 @@ test.describe('History Backup', () => { await test.step('User A deletes group conversation with User B', async () => { await userAPages.conversation().conversationInfoButton.click(); await userAPages.conversationDetails().deleteGroupButton.click(); - expect(await userAModals.confirm().modalTitle).toContainText('Delete group conversation?'); + await expect(userAModals.confirm().modalTitle).toContainText('Delete group conversation?'); await userAModals.confirm().clickAction(); }); diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index 953090928e7..9e8c4b4d93a 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -126,12 +126,13 @@ export async function createAndSaveBackup(pageManager: PageManager, password?: s } await modals.passwordAdvancedSecurity().clickBackUpNow(); await expect(modals.passwordAdvancedSecurity().modal).toBeHidden(); - expect(pages.historyExport().isVisible()).toBeTruthy(); + expect(await pages.historyExport().isVisible()).toBeTruthy(); const [download] = await Promise.all([ pages.historyExport().page.waitForEvent('download'), pages.historyExport().clickSaveFileButton(), ]); - const backupName = `./test-results/downloads/${filenamePrefix}${download.suggestedFilename()}`; + const safePrefix = filenamePrefix ?? ''; + const backupName = `./test-results/downloads/${safePrefix}${download.suggestedFilename()}`; await download.saveAs(backupName); return backupName; } From b7a45a3584b1232d5a7cc8577e8e3888fd18f729 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Wed, 11 Feb 2026 14:18:01 +0100 Subject: [PATCH 06/11] test(historyBackup): changed according to review comments This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../specs/HistoryBackup/historyBackup.spec.ts | 31 ++++++------------- .../test/e2e_tests/utils/userActions.ts | 2 +- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 02aa70328c0..43d09b4e5f5 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -43,7 +43,7 @@ test.describe('History Backup', () => { PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), ]); - let {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; + const {pages: userAPages, modals: userAModals, components: userAComponents} = userAPageManager.webapp; const {pages: userBPages} = userBPageManager.webapp; const conversationName = 'Test group'; @@ -93,31 +93,20 @@ test.describe('History Backup', () => { await expect(userAPages.resetPassword().passwordChangeMessage).toBeVisible(); await userAPageManager.page.context().close(); + }); - // Initialize a new context and page - const newBrowserContext = await userAPageManager.page.context().browser()!.newContext(); - const newPageUserA = await newBrowserContext.newPage(); - const newUserAPageManager = PageManager.from(newPageUserA); - - await newUserAPageManager.openMainPage(); - await loginUser(userA, newUserAPageManager); - - // Reassign pages, modals and components for new context - userAPages = newUserAPageManager.webapp.pages; - userAModals = newUserAPageManager.webapp.modals; - userAComponents = newUserAPageManager.webapp.components; + const newUserAPageManager = PageManager.from(await createPage(withLogin(userA, {confirmNewHistory: true}))); - await userAPages.historyInfo().continueButton.click(); - }); + const {pages: userAPages2, components: userAComponents2} = newUserAPageManager.webapp; - await userAComponents.conversationSidebar().clickPreferencesButton(); - await userAPages.account().backupFileInput.setInputFiles(backupName); + await userAComponents2.conversationSidebar().clickPreferencesButton(); + await userAPages2.account().backupFileInput.setInputFiles(backupName); await test.step('Validate conversation is still visible with all messages after restoring backup', async () => { - await userAComponents.conversationSidebar().allConverationsButton.click(); - await userAPages.conversationList().openConversation(conversationName); - await expect(userAPages.conversation().getMessage({sender: userB})).toContainText(messageUserB); - await expect(userAPages.conversation().getMessage({sender: userA})).toContainText(messageUserA); + await userAComponents2.conversationSidebar().allConverationsButton.click(); + await userAPages2.conversationList().openConversation(conversationName); + await expect(userAPages2.conversation().getMessage({sender: userB})).toContainText(messageUserB); + await expect(userAPages2.conversation().getMessage({sender: userA})).toContainText(messageUserA); }); }, ); diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index 9e8c4b4d93a..179fcf4e450 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -126,7 +126,7 @@ export async function createAndSaveBackup(pageManager: PageManager, password?: s } await modals.passwordAdvancedSecurity().clickBackUpNow(); await expect(modals.passwordAdvancedSecurity().modal).toBeHidden(); - expect(await pages.historyExport().isVisible()).toBeTruthy(); + await expect(pages.historyExport().exportSuccessHeadline).toBeVisible(); const [download] = await Promise.all([ pages.historyExport().page.waitForEvent('download'), pages.historyExport().clickSaveFileButton(), From 66ca662284049276e611ab4e99915f67e34fe668 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Wed, 11 Feb 2026 14:53:21 +0100 Subject: [PATCH 07/11] test(historyBackup): changed according to copilot comments This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../backupRestoration-TC-8634.spec.ts | 6 ++--- .../specs/HistoryBackup/historyBackup.spec.ts | 24 +++++++++---------- .../test/e2e_tests/utils/userActions.ts | 14 ++++++++--- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts index fbd6bfdc935..8369436c006 100644 --- a/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/CriticalFlow/backupRestoration-TC-8634.spec.ts @@ -32,7 +32,7 @@ const groupMessage = 'This is a group message!'; let backupName: string; let passwordProtectedBackupName: string; -test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web']}, async ({pageManager, api}) => { +test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web']}, async ({pageManager, api}, testInfo) => { const {pages, modals, components} = pageManager.webapp; // Creating preconditions for the test via API @@ -63,11 +63,11 @@ test('Setting up new device with a backup', {tag: ['@TC-8634', '@crit-flow-web'] await test.step('User creates and saves a backup', async () => { await components.conversationSidebar().clickPreferencesButton(); - backupName = await createAndSaveBackup(pageManager); + backupName = await createAndSaveBackup(testInfo, pageManager); }); await test.step('User creates and saves a password backup', async () => { - passwordProtectedBackupName = await createAndSaveBackup(pageManager, userA.password, 'password-'); + passwordProtectedBackupName = await createAndSaveBackup(testInfo, pageManager, userA.password, 'password-'); }); await test.step('User logs out and clears all data', async () => { diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 43d09b4e5f5..500f7ac1639 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -37,7 +37,7 @@ test.describe('History Backup', () => { test( 'I want to import a backup that I exported when I was using a different email/password', {tag: ['@TC-118', '@regression']}, - async ({createPage, api}) => { + async ({createPage, api}, testInfo) => { const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), @@ -61,7 +61,7 @@ test.describe('History Backup', () => { // User A creates Backup await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + const backupName = await createAndSaveBackup(testInfo, userAPageManager); await test.step('User A changes their Email address', async () => { const newEmail = generateWireEmail(userA.lastName); @@ -114,7 +114,7 @@ test.describe('History Backup', () => { test( "I should not be able to restore from the history of another person's account", {tag: ['@TC-125', '@regression']}, - async ({createPage}) => { + async ({createPage}, testInfo) => { const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), @@ -135,7 +135,7 @@ test.describe('History Backup', () => { await test.step('User A creates History Backup and User B tries to restore it', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + const backupName = await createAndSaveBackup(testInfo, userAPageManager); await logOutUser(userBPageManager, true); await loginUser(userB, userBPageManager); await userBPages.historyInfo().clickConfirmButton(); @@ -156,7 +156,7 @@ test.describe('History Backup', () => { test( 'I want to see new name and system message of the renamed conversation when it was renamed after export', {tag: ['@TC-131', '@regression']}, - async ({createPage}) => { + async ({createPage}, testInfo) => { const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), @@ -181,7 +181,7 @@ test.describe('History Backup', () => { await test.step('User A creates History Backup, User B renames group conversation and User A restores the Backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + const backupName = await createAndSaveBackup(testInfo, userAPageManager); await userAComponents.conversationSidebar().allConverationsButton.click(); await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); // User B renames group conversation @@ -211,7 +211,7 @@ test.describe('History Backup', () => { test( 'I want to have the same mute or archive state of a conversation after import', {tag: ['@TC-133', '@regression']}, - async ({createPage}) => { + async ({createPage}, testInfo) => { const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), @@ -245,7 +245,7 @@ test.describe('History Backup', () => { await test.step('User A creates History Backup and restores it', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + const backupName = await createAndSaveBackup(testInfo, userAPageManager); await logOutUser(userAPageManager, true); await loginUser(userA, userAPageManager); @@ -269,7 +269,7 @@ test.describe('History Backup', () => { test( 'I should not be able to import a backup with wrong password', {tag: ['@TC-135', '@regression']}, - async ({createPage}) => { + async ({createPage}, testInfo) => { const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), @@ -291,7 +291,7 @@ test.describe('History Backup', () => { await test.step('User A creates History Backup and tries to restore it with wrong password', async () => { // User A creates History Backup await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager, userA.password); + const backupName = await createAndSaveBackup(testInfo, userAPageManager, userA.password); await logOutUser(userAPageManager, true); await loginUser(userA, userAPageManager); @@ -315,7 +315,7 @@ test.describe('History Backup', () => { test( 'I should not see the deleted group after restore from the backup', {tag: ['@TC-1097', '@regression']}, - async ({createPage}) => { + async ({createPage}, testInfo) => { const [userAPageManager, userBPageManager] = await Promise.all([ PageManager.from(createPage(withLogin(userA), withConnectedUser(userB))), PageManager.from(createPage(withLogin(userB), withConnectedUser(userA))), @@ -345,7 +345,7 @@ test.describe('History Backup', () => { await test.step('User A creates History Backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(userAPageManager); + const backupName = await createAndSaveBackup(testInfo, userAPageManager); await logOutUser(userAPageManager, true); await loginUser(userA, userAPageManager); diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index 179fcf4e450..32933655d6f 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -17,7 +17,7 @@ * */ -import {expect} from 'playwright/test'; +import {expect, TestInfo} from 'playwright/test'; import {ApiManagerE2E} from '../backend/apiManager.e2e'; import {User} from '../data/user'; @@ -116,7 +116,15 @@ export async function sendConnectionRequest(senderPageManager: PageManager, rece await modals.userProfile().clickConnectButton(); } -export async function createAndSaveBackup(pageManager: PageManager, password?: string, filenamePrefix?: string) { +/** + * @param testInfo is needed to make backup filename unique + */ +export async function createAndSaveBackup( + testInfo: TestInfo, + pageManager: PageManager, + password?: string, + filenamePrefix?: string, +) { const {pages, modals} = pageManager.webapp; await pages.account().clickBackUpButton(); @@ -132,7 +140,7 @@ export async function createAndSaveBackup(pageManager: PageManager, password?: s pages.historyExport().clickSaveFileButton(), ]); const safePrefix = filenamePrefix ?? ''; - const backupName = `./test-results/downloads/${safePrefix}${download.suggestedFilename()}`; + const backupName = testInfo.outputPath(`${safePrefix}${download.suggestedFilename()}`); await download.saveAs(backupName); return backupName; } From e786e72d695b225af992e054fa478c48d7a7a0d6 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Wed, 11 Feb 2026 14:55:38 +0100 Subject: [PATCH 08/11] test(historyBackup): changed wording This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- apps/webapp/test/e2e_tests/utils/userActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/test/e2e_tests/utils/userActions.ts b/apps/webapp/test/e2e_tests/utils/userActions.ts index 32933655d6f..494143f32ea 100644 --- a/apps/webapp/test/e2e_tests/utils/userActions.ts +++ b/apps/webapp/test/e2e_tests/utils/userActions.ts @@ -117,7 +117,7 @@ export async function sendConnectionRequest(senderPageManager: PageManager, rece } /** - * @param testInfo is needed to make backup filename unique + * @param testInfo is needed to create unique backup filename */ export async function createAndSaveBackup( testInfo: TestInfo, From 7d6416b484428e528b18c60edb9553cd82293dcc Mon Sep 17 00:00:00 2001 From: lehnerja Date: Wed, 11 Feb 2026 15:36:49 +0100 Subject: [PATCH 09/11] test(historyBackup): changed wording according to copilot This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../specs/HistoryBackup/historyBackup.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 500f7ac1639..b53c61bc132 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -52,7 +52,7 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await test.step('User A are writing messages to each other', async () => { + await test.step('User A and B write messages to each other', async () => { await userAPages.conversationList().openConversation(conversationName); await userAPages.conversation().sendMessage(messageUserA); await userBPages.conversationList().openConversation(conversationName); @@ -126,7 +126,7 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await test.step('User A and B are writing messages to each other', async () => { + await test.step('User A and B write messages to each other', async () => { await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); await userAPages.conversation().sendMessage(messageUserA); await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); @@ -172,7 +172,7 @@ test.describe('History Backup', () => { const renamedConversationName = 'renamedConversationName'; - await test.step('User A and B writing in their group conversation', async () => { + await test.step('User A and B write in their group conversation', async () => { await userAPages.conversationList().openConversation(conversationName); await userAPages.conversation().sendMessage(messageUserA); await userBPages.conversationList().openConversation(conversationName); @@ -225,7 +225,7 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await test.step('User A and B writing messages to each other', async () => { + await test.step('User A and B write messages to each other', async () => { await userAPages.conversationList().openConversation(conversationName); await userAPages.conversation().sendMessage(messageUserA); await userBPages.conversationList().openConversation(conversationName); @@ -281,7 +281,7 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await test.step('User A and B writing messages to each other', async () => { + await test.step('User A and B write messages to each other', async () => { await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); await userAPages.conversation().sendMessage(messageUserA); await userBPages.conversationList().openConversation(userA.fullName, {protocol: 'mls'}); @@ -329,7 +329,7 @@ test.describe('History Backup', () => { const messageUserA = 'Message from User A'; const messageUserB = 'Message from User B'; - await test.step('User A and User B are writing messages to each other', async () => { + await test.step('User A and User B write messages to each other', async () => { await userAPages.conversationList().openConversation(conversationName); await userAPages.conversation().sendMessage(messageUserA); await userBPages.conversationList().openConversation(conversationName); From 773ce18b049ea81343bdcbee5f0298ad0abd9455 Mon Sep 17 00:00:00 2001 From: lehnerja Date: Wed, 11 Feb 2026 15:52:19 +0100 Subject: [PATCH 10/11] test(historyBackup): changed mutedBadge into helper This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../pageManager/webapp/pages/conversationList.page.ts | 6 ++++-- .../e2e_tests/specs/HistoryBackup/historyBackup.spec.ts | 2 +- 2 files changed, 5 insertions(+), 3 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 0baa8b87cbc..91195f9c545 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 @@ -39,7 +39,6 @@ export class ConversationListPage { readonly conversationListHeaderTitle: Locator; readonly joinCallButton: Locator; readonly clearContentButton: Locator; - readonly mutedConversationBadge: Locator; constructor(page: Page) { this.page = page; @@ -61,7 +60,6 @@ export class ConversationListPage { this.conversationListHeaderTitle = page.locator('[data-uie-name="conversation-list-header-title"]'); this.joinCallButton = page.getByRole('button', {name: 'Join'}); this.clearContentButton = page.getByRole('button', {name: 'Clear content'}); - this.mutedConversationBadge = page.locator('[data-uie-name="status-silence"]'); } async isConversationItemVisible(conversationName: string) { @@ -152,4 +150,8 @@ export class ConversationListPage { getMoveToFolderButton(folderName: string) { return this.moveToMenu.getByRole('button', {name: folderName, exact: true}); } + + async getMutedConversationBadge(conversationName: string) { + return this.getConversationLocator(conversationName).locator('[data-uie-name="status-silence"]'); + } } diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index b53c61bc132..8bcd1819efa 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -257,7 +257,7 @@ test.describe('History Backup', () => { await test.step('Validate muted and archived state are the same', async () => { await userAComponents.conversationSidebar().allConverationsButton.click(); await userAPages.conversationList().openConversation(conversationName); - await expect(userAPages.conversationList().mutedConversationBadge).toBeVisible(); + await expect(await userAPages.conversationList().getMutedConversationBadge(conversationName)).toBeVisible(); await userAComponents.conversationSidebar().archiveButton.click(); const archivedConversation = userAPages.conversationList().getConversationLocator(userB.fullName); From 66cbe0a4c0292aaf9de8ee392c51197453977d5d Mon Sep 17 00:00:00 2001 From: lehnerja Date: Thu, 12 Feb 2026 10:55:45 +0100 Subject: [PATCH 11/11] test(historyBackup): refactored test steps, locators and helper function This commit introduces a new test suite `historyBackup.spec.ts` to verify the history export and import functionality. Implemented scenarios include: - Importing backups after changing account credentials (email/password) (@TC-118). - Verifying cross-account restoration is prevented (@TC-125). - Verifying restoration with an incorrect password is prevented (@TC-135). - Ensuring conversation states (renames, system messages, mute, archive) are preserved after restore (@TC-131, @TC-133). - Verifying deleted groups do not reappear after restoration (@TC-1097). - added Test steps Refs: WPB-19954 --- .../webapp/pages/conversationDetails.page.ts | 10 +++-- .../webapp/pages/conversationList.page.ts | 2 +- .../specs/HistoryBackup/historyBackup.spec.ts | 42 +++++++++++++------ 3 files changed, 37 insertions(+), 17 deletions(-) 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 fa6a89e9754..27d5806aebd 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 @@ -193,8 +193,12 @@ export class ConversationDetailsPage { async setNotificationsForConversation(value: 'Everything' | 'Mentions and replies' | 'Nothing') { await this.notificationsButton.click(); const notificationsPanel = this.page.locator('aside#right-column'); - const radioGroup = notificationsPanel.getByRole('radiogroup'); - await radioGroup.waitFor({state: 'visible'}); - await radioGroup.getByText(value).click(); + await notificationsPanel.getByRole('radiogroup').getByText(value).click(); + } + + async changeConversationName(newConversationName: string) { + await this.editConversationNameButton.click(); + await this.textFieldForConversationName.fill(newConversationName); + await this.textFieldForConversationName.press('Enter'); } } 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 91195f9c545..d219dc57992 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 @@ -152,6 +152,6 @@ export class ConversationListPage { } async getMutedConversationBadge(conversationName: string) { - return this.getConversationLocator(conversationName).locator('[data-uie-name="status-silence"]'); + return this.getConversationLocator(conversationName).getByTitle('Muted conversation'); } } diff --git a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts index 8bcd1819efa..3242ab34811 100644 --- a/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts +++ b/apps/webapp/test/e2e_tests/specs/HistoryBackup/historyBackup.spec.ts @@ -133,9 +133,14 @@ test.describe('History Backup', () => { await userBPages.conversation().sendMessage(messageUserB); }); - await test.step('User A creates History Backup and User B tries to restore it', async () => { + let backupName: string; + + await test.step('User A creates History Backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(testInfo, userAPageManager); + backupName = await createAndSaveBackup(testInfo, userAPageManager); + }); + + await test.step('User B tries to restore User A\'s backup', async () => { await logOutUser(userBPageManager, true); await loginUser(userB, userBPageManager); await userBPages.historyInfo().clickConfirmButton(); @@ -179,18 +184,21 @@ test.describe('History Backup', () => { await userBPages.conversation().sendMessage(messageUserB); }); - await test.step('User A creates History Backup, User B renames group conversation and User A restores the Backup', async () => { + let backupName: string; + + await test.step('User A creates History Backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(testInfo, userAPageManager); + backupName = await createAndSaveBackup(testInfo, userAPageManager); await userAComponents.conversationSidebar().allConverationsButton.click(); - await userAPages.conversationList().openConversation(userB.fullName, {protocol: 'mls'}); - // User B renames group conversation + await userAPages.conversationList().openConversation(userB.fullName, { protocol: 'mls' }); + }); + + await test.step('User B renames group conversation', async () => { await userBPages.conversation().conversationInfoButton.click(); - await userBPages.conversationDetails().editConversationNameButton.click(); - const textFieldConversationName = userBPages.conversationDetails().textFieldForConversationName; - await textFieldConversationName.fill(renamedConversationName); - await textFieldConversationName.press('Enter'); + await userBPages.conversationDetails().changeConversationName(renamedConversationName); + }); + await test.step('User A restores the Backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); await userAPages.account().backupFileInput.setInputFiles(backupName); }); @@ -202,7 +210,7 @@ test.describe('History Backup', () => { // User A sees system message that User B had renamed the conversation await userAPages.conversationList().openConversation(renamedConversationName); - const renamedSystemMessage = userAPages.conversation().systemMessages.last(); + const renamedSystemMessage = userAPages.conversation().systemMessages.filter({hasText: `${userB.fullName} renamed the conversation`}); await expect(renamedSystemMessage).toContainText(`${userB.fullName} renamed the conversation`); }); }, @@ -243,13 +251,20 @@ test.describe('History Backup', () => { await userAPages.conversationDetails().archiveButton.click(); }); - await test.step('User A creates History Backup and restores it', async () => { + let backupName: string; + + await test.step('User A creates History Backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); - const backupName = await createAndSaveBackup(testInfo, userAPageManager); + backupName = await createAndSaveBackup(testInfo, userAPageManager); + }); + await test.step('User A logs out and logs back in', async () => { await logOutUser(userAPageManager, true); await loginUser(userA, userAPageManager); await userAPages.historyInfo().clickConfirmButton(); + }); + + await test.step('User A restores the backup', async () => { await userAComponents.conversationSidebar().clickPreferencesButton(); await userAPages.account().backupFileInput.setInputFiles(backupName); }); @@ -355,6 +370,7 @@ test.describe('History Backup', () => { }); await test.step('Validate deleted group conversation is no longer visible', async () => { + await userAComponents.conversationSidebar().allConverationsButton.click(); await expect(userAPages.conversationList().getConversationLocator(conversationName)).not.toBeVisible(); }); },