diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-channel.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-channel.spec.ts new file mode 100644 index 0000000000000..8c460e064ed8b --- /dev/null +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-channel.spec.ts @@ -0,0 +1,120 @@ +import { faker } from '@faker-js/faker'; + +import { Users } from '../fixtures/userStates'; +import { HomeChannel } from '../page-objects'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; +import { DisableRoomEncryptionModal, EnableRoomEncryptionModal, CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; +import { preserveSettings } from '../utils/preserveSettings'; +import { resolvePrivateRoomId } from '../utils/resolve-room-id'; +import { test, expect } from '../utils/test'; + +const settingsList = ['E2E_Enable', 'E2E_Allow_Unencrypted_Messages']; + +preserveSettings(settingsList); + +test.describe('E2EE Channel Basic Functionality', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let poHomeChannel: HomeChannel; + let encryptedRoomPage: EncryptedRoomPage; + let enableEncryptionModal: EnableRoomEncryptionModal; + let disableEncryptionModal: DisableRoomEncryptionModal; + let createE2EEChannel: CreateE2EEChannel; + + test.use({ storageState: Users.userE2EE.state }); + + test.beforeAll(async ({ api }) => { + await api.post('/settings/E2E_Enable', { value: true }); + await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + enableEncryptionModal = new EnableRoomEncryptionModal(page); + disableEncryptionModal = new DisableRoomEncryptionModal(page); + encryptedRoomPage = new EncryptedRoomPage(page); + createE2EEChannel = new CreateE2EEChannel(page); + await poHomeChannel.goto(); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + + test('expect create a private channel encrypted and send an encrypted message', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await poHomeChannel.content.waitForChannel(); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('send encrypted message and verify encryption', async () => { + await poHomeChannel.content.sendMessage('hello world'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + + await test.step('disable encryption', async () => { + await poHomeChannel.tabs.kebab.click({ force: true }); + await expect(poHomeChannel.tabs.btnDisableE2E).toBeVisible(); + await poHomeChannel.tabs.btnDisableE2E.click({ force: true }); + await disableEncryptionModal.disable(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeHidden(); + }); + + await test.step('send unencrypted message and verify no encryption', async () => { + await poHomeChannel.content.sendMessage('hello world not encrypted'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world not encrypted'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).not.toBeVisible(); + }); + + await test.step('re-enable encryption', async () => { + await poHomeChannel.tabs.kebab.click({ force: true }); + await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); + await poHomeChannel.tabs.btnEnableE2E.click({ force: true }); + await enableEncryptionModal.enable(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('send encrypted message again and verify encryption restored', async () => { + await poHomeChannel.content.sendMessage('hello world encrypted again'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world encrypted again'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + }); + + test('expect create a private channel, encrypt it and send an encrypted message', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create private channel', async () => { + await poHomeChannel.sidenav.openNewByLabel('Channel'); + await poHomeChannel.sidenav.inputChannelName.fill(channelName); + await poHomeChannel.sidenav.btnCreate.click(); + + await poHomeChannel.content.waitForChannel(); + const roomId = await resolvePrivateRoomId(page, channelName); + + createdChannels.push({ name: channelName, id: roomId ?? undefined }); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.toastSuccess).toBeVisible(); + await poHomeChannel.dismissToast(); + }); + + await test.step('enable encryption', async () => { + await poHomeChannel.tabs.kebab.click(); + await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); + await poHomeChannel.tabs.btnEnableE2E.click(); + await enableEncryptionModal.enable(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('send encrypted message and verify', async () => { + await poHomeChannel.content.sendMessage('hello world'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encrypted-channels.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-encrypted-channels.spec.ts deleted file mode 100644 index 56ab640f9ff3a..0000000000000 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encrypted-channels.spec.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { faker } from '@faker-js/faker'; - -import { Users } from '../fixtures/userStates'; -import { HomeChannel } from '../page-objects'; -import { preserveSettings } from '../utils/preserveSettings'; -import { test, expect } from '../utils/test'; - -const settingsList = [ - 'E2E_Enable', - 'E2E_Allow_Unencrypted_Messages', - 'E2E_Enabled_Default_DirectRooms', - 'E2E_Enabled_Default_PrivateRooms', -]; - -preserveSettings(settingsList); - -test.describe('E2EE Encrypted Channels', () => { - let poHomeChannel: HomeChannel; - - test.use({ storageState: Users.userE2EE.state }); - - test.beforeAll(async ({ api }) => { - await api.post('/settings/E2E_Enable', { value: true }); - await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); - await api.post('/settings/E2E_Enabled_Default_DirectRooms', { value: false }); - await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: false }); - await api.post('/im.delete', { username: 'user2' }); - }); - - test.beforeEach(async ({ page }) => { - poHomeChannel = new HomeChannel(page); - await page.goto('/home'); - }); - - test('expect create a private channel encrypted and send an encrypted message', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('hello world'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - - await poHomeChannel.tabs.kebab.click({ force: true }); - - await expect(poHomeChannel.tabs.btnDisableE2E).toBeVisible(); - await poHomeChannel.tabs.btnDisableE2E.click({ force: true }); - await expect(page.getByRole('dialog', { name: 'Disable encryption' })).toBeVisible(); - await page.getByRole('button', { name: 'Disable encryption' }).click(); - await poHomeChannel.dismissToast(); - await page.waitForTimeout(1000); - - await poHomeChannel.content.sendMessage('hello world not encrypted'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world not encrypted'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).not.toBeVisible(); - - await poHomeChannel.tabs.kebab.click({ force: true }); - await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); - await poHomeChannel.tabs.btnEnableE2E.click({ force: true }); - await expect(page.getByRole('dialog', { name: 'Enable encryption' })).toBeVisible(); - await page.getByRole('button', { name: 'Enable encryption' }).click(); - await poHomeChannel.dismissToast(); - await page.waitForTimeout(1000); - - await poHomeChannel.content.sendMessage('hello world encrypted again'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world encrypted again'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - }); - - test('expect create a private encrypted channel and send a encrypted thread message', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('This is the thread main message.'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is the thread main message.'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - - await page.locator('[data-qa-type="message"]').last().hover(); - await page.locator('role=button[name="Reply in thread"]').click(); - - await expect(page).toHaveURL(/.*thread/); - - await expect(poHomeChannel.content.mainThreadMessageText).toContainText('This is the thread main message.'); - await expect(poHomeChannel.content.mainThreadMessageText.locator('.rcx-icon--name-key')).toBeVisible(); - - await poHomeChannel.content.toggleAlsoSendThreadToChannel(true); - await page.getByRole('dialog').locator('[name="msg"]').last().fill('This is an encrypted thread message also sent in channel'); - await page.keyboard.press('Enter'); - await expect(poHomeChannel.content.lastThreadMessageText).toContainText('This is an encrypted thread message also sent in channel'); - await expect(poHomeChannel.content.lastThreadMessageText.locator('.rcx-icon--name-key')).toBeVisible(); - await expect(poHomeChannel.content.lastUserMessage).toContainText('This is an encrypted thread message also sent in channel'); - await expect(poHomeChannel.content.mainThreadMessageText).toContainText('This is the thread main message.'); - await expect(poHomeChannel.content.mainThreadMessageText.locator('.rcx-icon--name-key')).toBeVisible(); - }); - - test('expect create a private encrypted channel and check disabled message menu actions on an encrypted message', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('This is an encrypted message.'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - - await page.locator('[data-qa-type="message"]').last().hover(); - await expect(page.locator('role=button[name="Forward message not available on encrypted content"]')).toBeDisabled(); - - await poHomeChannel.content.openLastMessageMenu(); - - await expect(page.locator('role=menuitem[name="Reply in direct message"]')).toHaveClass(/disabled/); - await expect(page.locator('role=menuitem[name="Copy link"]')).toHaveClass(/disabled/); - }); - - test('expect create a private channel, encrypt it and send an encrypted message', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.openNewByLabel('Channel'); - await poHomeChannel.sidenav.inputChannelName.fill(channelName); - await poHomeChannel.sidenav.btnCreate.click(); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.toastSuccess).toBeVisible(); - - await poHomeChannel.dismissToast(); - - await poHomeChannel.tabs.kebab.click(); - await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); - await poHomeChannel.tabs.btnEnableE2E.click(); - await expect(page.getByRole('dialog', { name: 'Enable encryption' })).toBeVisible(); - await page.getByRole('button', { name: 'Enable encryption' }).click(); - await page.waitForTimeout(1000); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('hello world'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - }); - - test('expect create a encrypted private channel and mention user', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('hello @user1'); - - const userMention = page.getByRole('button', { - name: 'user1', - }); - - await expect(userMention).toBeVisible(); - }); - - test('expect create a encrypted private channel, mention a channel and navigate to it', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('Are you in the #general channel?'); - - const channelMention = page.getByRole('button', { - name: 'general', - }); - - await expect(channelMention).toBeVisible(); - - await channelMention.click(); - - await expect(page).toHaveURL(`/channel/general`); - }); - - test('expect create a encrypted private channel, mention a channel and user', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('Are you in the #general channel, @user1 ?'); - - const channelMention = page.getByRole('button', { - name: 'general', - }); - - const userMention = page.getByRole('button', { - name: 'user1', - }); - - await expect(userMention).toBeVisible(); - await expect(channelMention).toBeVisible(); - }); - - test('expect create a private channel, send unecrypted messages, encrypt the channel and delete the last message and check the last message in the sidebar', async ({ - page, - }) => { - const channelName = faker.string.uuid(); - - // Enable Sidebar Extended display mode - await poHomeChannel.sidenav.setDisplayMode('Extended'); - - // Create private channel - await poHomeChannel.sidenav.openNewByLabel('Channel'); - await poHomeChannel.sidenav.inputChannelName.fill(channelName); - await poHomeChannel.sidenav.btnCreate.click(); - await expect(page).toHaveURL(`/group/${channelName}`); - await expect(poHomeChannel.toastSuccess).toBeVisible(); - await poHomeChannel.dismissToast(); - - // Send Unencrypted Messages - await poHomeChannel.content.sendMessage('first unencrypted message'); - await poHomeChannel.content.sendMessage('second unencrypted message'); - - // Encrypt channel - await poHomeChannel.tabs.kebab.click({ force: true }); - await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); - await poHomeChannel.tabs.btnEnableE2E.click({ force: true }); - await expect(page.getByRole('dialog', { name: 'Enable encryption' })).toBeVisible(); - await page.getByRole('button', { name: 'Enable encryption' }).click(); - await page.waitForTimeout(1000); - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - // Send Encrypted Messages - const encriptedMessage1 = 'first ENCRYPTED message'; - const encriptedMessage2 = 'second ENCRYPTED message'; - await poHomeChannel.content.sendMessage(encriptedMessage1); - await poHomeChannel.content.sendMessage(encriptedMessage2); - - // Delete last message - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText(encriptedMessage2); - await poHomeChannel.content.openLastMessageMenu(); - await page.locator('role=menuitem[name="Delete"]').click(); - await page.locator('#modal-root .rcx-button-group--align-end .rcx-button--danger').click(); - - // Check last message in the sidebar - const sidebarChannel = poHomeChannel.sidenav.getSidebarItemByName(channelName); - await expect(sidebarChannel).toBeVisible(); - await expect(sidebarChannel.locator('span')).toContainText(encriptedMessage1); - }); - - test('expect create a private encrypted channel and pin/star an encrypted message', async ({ page }) => { - const channelName = faker.string.uuid(); - - await poHomeChannel.sidenav.createEncryptedChannel(channelName); - - await expect(page).toHaveURL(`/group/${channelName}`); - - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - - await poHomeChannel.content.sendMessage('This message should be pinned and stared.'); - - await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This message should be pinned and stared.'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); - - await poHomeChannel.content.openLastMessageMenu(); - await page.locator('role=menuitem[name="Star"]').click(); - - await expect(poHomeChannel.toastSuccess).toBeVisible(); - await poHomeChannel.dismissToast(); - - await poHomeChannel.content.openLastMessageMenu(); - await page.locator('role=menuitem[name="Pin"]').click(); - await page.locator('#modal-root >> button:has-text("Yes, pin message")').click(); - - await expect(poHomeChannel.toastSuccess).toBeVisible(); - await poHomeChannel.dismissToast(); - - await poHomeChannel.tabs.kebab.click(); - await poHomeChannel.tabs.btnPinnedMessagesList.click(); - - await expect(page.getByRole('dialog', { name: 'Pinned Messages' })).toBeVisible(); - - const lastPinnedMessage = page.getByRole('dialog', { name: 'Pinned Messages' }).locator('[data-qa-type="message"]').last(); - await expect(lastPinnedMessage).toContainText('This message should be pinned and stared.'); - await lastPinnedMessage.hover(); - await lastPinnedMessage.locator('role=button[name="More"]').waitFor(); - await lastPinnedMessage.locator('role=button[name="More"]').click(); - await expect(page.locator('role=menuitem[name="Copy link"]')).toHaveClass(/disabled/); - - await poHomeChannel.btnContextualbarClose.click(); - - await poHomeChannel.tabs.kebab.click(); - await poHomeChannel.tabs.btnStarredMessageList.click(); - - const lastStarredMessage = page.getByRole('dialog', { name: 'Starred Messages' }).locator('[data-qa-type="message"]').last(); - await expect(page.getByRole('dialog', { name: 'Starred Messages' })).toBeVisible(); - await expect(lastStarredMessage).toContainText('This message should be pinned and stared.'); - await lastStarredMessage.hover(); - await lastStarredMessage.locator('role=button[name="More"]').waitFor(); - await lastStarredMessage.locator('role=button[name="More"]').click(); - await expect(page.locator('role=menuitem[name="Copy link"]')).toHaveClass(/disabled/); - }); -}); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts index 7cd6bf8d4fe12..abc5b3867ac03 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-encryption-decryption.spec.ts @@ -4,8 +4,10 @@ import { setupE2EEPassword } from './setupE2EEPassword'; import { Users } from '../fixtures/userStates'; import { EncryptedRoomPage } from '../page-objects/encrypted-room'; import { HomeSidenav } from '../page-objects/fragments'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; import { FileUploadModal } from '../page-objects/fragments/file-upload-modal'; import { LoginPage } from '../page-objects/login'; +import { deleteRoom } from '../utils/create-target-channel'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -14,6 +16,9 @@ const settingsList = ['E2E_Enable', 'E2E_Allow_Unencrypted_Messages']; preserveSettings(settingsList); test.describe('E2EE Encryption and Decryption - Basic Features', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let createE2EEChannel: CreateE2EEChannel; + test.use({ storageState: Users.admin.state }); test.beforeAll(async ({ api }) => { @@ -21,8 +26,13 @@ test.describe('E2EE Encryption and Decryption - Basic Features', () => { await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); }); + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + test.beforeEach(async ({ api, page }) => { const loginPage = new LoginPage(page); + createE2EEChannel = new CreateE2EEChannel(page); await api.post('/method.call/e2e.resetOwnE2EKey', { message: JSON.stringify({ msg: 'method', id: '1', method: 'e2e.resetOwnE2EKey', params: [] }), @@ -43,7 +53,7 @@ test.describe('E2EE Encryption and Decryption - Basic Features', () => { await setupE2EEPassword(page); - await sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); await expect(encryptedRoomPage.encryptedIcon).toBeVisible(); @@ -84,7 +94,7 @@ test.describe('E2EE Encryption and Decryption - Basic Features', () => { await setupE2EEPassword(page); // Create an encrypted channel - await sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); await expect(encryptedRoomPage.encryptedIcon).toBeVisible(); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts index 7b009c7723db8..d1cc626a32f7a 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-file-encryption.spec.ts @@ -2,6 +2,9 @@ import { faker } from '@faker-js/faker'; import { Users } from '../fixtures/userStates'; import { HomeChannel } from '../page-objects'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -16,7 +19,10 @@ const settingsList = [ const originalSettings = preserveSettings(settingsList); test.describe('E2EE File Encryption', () => { + const createdChannels: { name: string; id?: string | null }[] = []; let poHomeChannel: HomeChannel; + let encryptedRoomPage: EncryptedRoomPage; + let createE2EEChannel: CreateE2EEChannel; test.use({ storageState: Users.userE2EE.state }); @@ -25,7 +31,10 @@ test.describe('E2EE File Encryption', () => { await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); await api.post('/settings/E2E_Enabled_Default_DirectRooms', { value: false }); await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: false }); - await api.post('/im.delete', { username: 'user2' }); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); }); test.afterAll(async ({ api }) => { @@ -35,6 +44,8 @@ test.describe('E2EE File Encryption', () => { test.beforeEach(async ({ page }) => { poHomeChannel = new HomeChannel(page); + encryptedRoomPage = new EncryptedRoomPage(page); + createE2EEChannel = new CreateE2EEChannel(page); await page.goto('/home'); }); @@ -42,7 +53,7 @@ test.describe('E2EE File Encryption', () => { await test.step('create an encrypted channel', async () => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -55,7 +66,7 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.fileNameInput.fill('any_file1.txt'); await poHomeChannel.content.btnModalConfirm.click(); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); await expect(poHomeChannel.content.getFileDescription).toHaveText('any_description'); await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file1.txt'); }); @@ -65,7 +76,7 @@ test.describe('E2EE File Encryption', () => { await test.step('create an encrypted room', async () => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -78,7 +89,7 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.fileNameInput.fill('any_file1.txt'); await poHomeChannel.content.btnModalConfirm.click(); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); await expect(poHomeChannel.content.getFileDescription).toHaveText('message 1'); await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file1.txt'); }); @@ -93,7 +104,7 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.fileNameInput.fill('any_file2.txt'); await poHomeChannel.content.btnModalConfirm.click(); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); await expect(poHomeChannel.content.getFileDescription).toHaveText('message 2'); await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file2.txt'); }); @@ -108,7 +119,7 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.fileNameInput.fill('any_file3.txt'); await poHomeChannel.content.btnModalConfirm.click(); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); await expect(poHomeChannel.content.getFileDescription).toHaveText('message 2'); await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file2.txt'); }); @@ -129,7 +140,7 @@ test.describe('E2EE File Encryption', () => { await test.step('create an encrypted channel', async () => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -140,7 +151,7 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.sendMessage('This is an encrypted message.'); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); }); await test.step('send a text file in channel, file should not be encrypted', async () => { @@ -149,7 +160,7 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.fileNameInput.fill('any_file1.txt'); await poHomeChannel.content.btnModalConfirm.click(); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).not.toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).not.toBeVisible(); await expect(poHomeChannel.content.getFileDescription).toHaveText('any_description'); await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file1.txt'); }); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-key-reset.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-key-reset.spec.ts index fab641ba6021c..f978456f83b15 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-key-reset.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-key-reset.spec.ts @@ -3,6 +3,7 @@ import type { Page } from '@playwright/test'; import { createAuxContext } from '../fixtures/createAuxContext'; import { Users } from '../fixtures/userStates'; import { AccountProfile } from '../page-objects'; +import { LoginPage } from '../page-objects/login'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -17,6 +18,8 @@ preserveSettings(settingsList); test.describe('E2EE Key Reset', () => { let anotherClientPage: Page; + let loginPage: LoginPage; + let anotherClientLoginPage: LoginPage; test.use({ storageState: Users.userE2EE.state }); @@ -25,11 +28,12 @@ test.describe('E2EE Key Reset', () => { await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); await api.post('/settings/E2E_Enabled_Default_DirectRooms', { value: false }); await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: false }); - await api.post('/im.delete', { username: 'user2' }); }); test.beforeEach(async ({ browser, page }) => { anotherClientPage = (await createAuxContext(browser, Users.userE2EE)).page; + loginPage = new LoginPage(page); + anotherClientLoginPage = new LoginPage(anotherClientPage); await page.goto('/home'); }); @@ -45,7 +49,7 @@ test.describe('E2EE Key Reset', () => { await poAccountProfile.securityE2EEncryptionSection.click(); await poAccountProfile.securityE2EEncryptionResetKeyButton.click(); - await expect(page.locator('role=button[name="Login"]')).toBeVisible(); - await expect(anotherClientPage.locator('role=button[name="Login"]')).toBeVisible(); + await expect(loginPage.loginButton).toBeVisible(); + await expect(anotherClientLoginPage.loginButton).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-legacy-format.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-legacy-format.spec.ts index f82f7ba8a836a..3e29aee2c6ba7 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-legacy-format.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-legacy-format.spec.ts @@ -5,6 +5,9 @@ import { BASE_API_URL } from '../config/constants'; import injectInitialData from '../fixtures/inject-initial-data'; import { Users, restoreState } from '../fixtures/userStates'; import { HomeChannel } from '../page-objects'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -46,7 +49,10 @@ const sendEncryptedMessage = async (request: APIRequestContext, rid: string, enc }; test.describe('E2EE Legacy Format', () => { + const createdChannels: { name: string; id?: string | null }[] = []; let poHomeChannel: HomeChannel; + let encryptedRoomPage: EncryptedRoomPage; + let createE2EEChannel: CreateE2EEChannel; test.use({ storageState: Users.userE2EE.state }); @@ -55,11 +61,16 @@ test.describe('E2EE Legacy Format', () => { await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); await api.post('/settings/E2E_Enabled_Default_DirectRooms', { value: false }); await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: false }); - await api.post('/im.delete', { username: 'user2' }); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); }); test.beforeEach(async ({ page }) => { poHomeChannel = new HomeChannel(page); + encryptedRoomPage = new EncryptedRoomPage(page); + createE2EEChannel = new CreateE2EEChannel(page); await page.goto('/home'); }); @@ -69,7 +80,9 @@ test.describe('E2EE Legacy Format', () => { await injectInitialData(); await restoreState(page, Users.userE2EE, { except: ['private_key', 'public_key', 'e2e.randomPassword'] }); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + await poHomeChannel.sidenav.waitForHome(); + + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -85,6 +98,6 @@ test.describe('E2EE Legacy Format', () => { await sendEncryptedMessage(request, rid, encryptedMessage); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('Old format message'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-mentions.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-mentions.spec.ts new file mode 100644 index 0000000000000..67a9cf1e3222d --- /dev/null +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-mentions.spec.ts @@ -0,0 +1,85 @@ +import { faker } from '@faker-js/faker'; + +import { Users } from '../fixtures/userStates'; +import { HomeChannel } from '../page-objects'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; +import { preserveSettings } from '../utils/preserveSettings'; +import { test, expect } from '../utils/test'; + +const settingsList = ['E2E_Enable']; + +preserveSettings(settingsList); + +test.describe('E2EE Mentions', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let poHomeChannel: HomeChannel; + let createE2EEChannel: CreateE2EEChannel; + + test.use({ storageState: Users.userE2EE.state }); + + test.beforeAll(async ({ api }) => { + await api.post('/settings/E2E_Enable', { value: true }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + createE2EEChannel = new CreateE2EEChannel(page); + await poHomeChannel.goto(); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + + test('expect create an encrypted private channel and mention user', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('mention user', async () => { + await poHomeChannel.content.sendMessage('hello @user1'); + await expect(poHomeChannel.content.getUserMention('user1')).toBeVisible(); + }); + }); + + test('expect create an encrypted private channel, mention a channel and navigate to it', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('mention channel', async () => { + await poHomeChannel.content.sendMessage('Are you in the #general channel?'); + await expect(poHomeChannel.content.getChannelMention('general')).toBeVisible(); + }); + + await test.step('navigate to channel', async () => { + await poHomeChannel.content.getChannelMention('general').click(); + await expect(page).toHaveURL(`/channel/general`); + }); + }); + + test('expect create an encrypted private channel, mention a channel and user', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('mention channel and user', async () => { + await poHomeChannel.content.sendMessage('Are you in the #general channel, @user1 ?'); + await expect(poHomeChannel.content.getUserMention('user1')).toBeVisible(); + await expect(poHomeChannel.content.getChannelMention('general')).toBeVisible(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-message-actions.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-message-actions.spec.ts new file mode 100644 index 0000000000000..9f138700f7208 --- /dev/null +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-message-actions.spec.ts @@ -0,0 +1,112 @@ +import { faker } from '@faker-js/faker'; + +import { Users } from '../fixtures/userStates'; +import { HomeChannel } from '../page-objects'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; +import { PinnedMessagesTab, StarredMessagesTab } from '../page-objects/fragments'; +import { E2EEMessageActions, CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; +import { preserveSettings } from '../utils/preserveSettings'; +import { test, expect } from '../utils/test'; + +const settingsList = ['E2E_Enable']; + +preserveSettings(settingsList); + +test.describe('E2EE Message Actions', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let poHomeChannel: HomeChannel; + let pinnedMessagesTab: PinnedMessagesTab; + let starredMessagesTab: StarredMessagesTab; + let e2eeMessageActions: E2EEMessageActions; + let encryptedRoomPage: EncryptedRoomPage; + let createE2EEChannel: CreateE2EEChannel; + test.use({ storageState: Users.userE2EE.state }); + + test.beforeAll(async ({ api }) => { + await api.post('/settings/E2E_Enable', { value: true }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + pinnedMessagesTab = new PinnedMessagesTab(page); + starredMessagesTab = new StarredMessagesTab(page); + e2eeMessageActions = new E2EEMessageActions(page); + encryptedRoomPage = new EncryptedRoomPage(page); + createE2EEChannel = new CreateE2EEChannel(page); + await poHomeChannel.goto(); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + + test('expect create a private encrypted channel and check disabled message menu actions on an encrypted message', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('send encrypted message', async () => { + await poHomeChannel.content.sendMessage('This is an encrypted message.'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + + await test.step('verify disabled message menu actions', async () => { + await poHomeChannel.content.lastUserMessage.hover(); + await e2eeMessageActions.expectForwardMessageToBeDisabled(); + + await poHomeChannel.content.openLastMessageMenu(); + + await e2eeMessageActions.expectReplyInDirectMessageToBeDisabled(); + await e2eeMessageActions.expectCopyLinkToBeDisabled(); + }); + }); + + test('expect create a private encrypted channel and pin/star an encrypted message', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel and send message', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + await poHomeChannel.content.sendMessage('This message should be pinned and starred.'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This message should be pinned and starred.'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + + await test.step('star the message', async () => { + await poHomeChannel.content.openLastMessageMenu(); + await poHomeChannel.content.btnOptionStarMessage.click(); + await expect(poHomeChannel.toastSuccess).toBeVisible(); + await poHomeChannel.dismissToast(); + }); + + await test.step('pin the message', async () => { + await poHomeChannel.content.openLastMessageMenu(); + await poHomeChannel.content.btnOptionPinMessage.click(); + await poHomeChannel.content.btnModalConfirm.click(); + await expect(poHomeChannel.toastSuccess).toBeVisible(); + await poHomeChannel.dismissToast(); + }); + + await test.step('verify pinned message and disabled copy link', async () => { + await pinnedMessagesTab.openTab(); + await pinnedMessagesTab.expectLastMessageToContainText('This message should be pinned and starred.'); + await pinnedMessagesTab.openLastMessageMenu(); + await e2eeMessageActions.expectCopyLinkToBeDisabled(); + await poHomeChannel.btnContextualbarClose.click(); + }); + + await test.step('verify starred message and disabled copy link', async () => { + await starredMessagesTab.openTab(); + await starredMessagesTab.expectLastMessageToContainText('This message should be pinned and starred.'); + await starredMessagesTab.openLastMessageMenu(); + await e2eeMessageActions.expectCopyLinkToBeDisabled(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts index b2c1e427dc974..25dae2c49f0db 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-otr.spec.ts @@ -1,5 +1,6 @@ import { Users } from '../fixtures/userStates'; import { HomeChannel } from '../page-objects'; +import { DisableRoomEncryptionModal, EnableRoomEncryptionModal } from '../page-objects/fragments/e2ee'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -14,6 +15,8 @@ preserveSettings(settingsList); test.describe('E2EE OTR (Off-The-Record)', () => { let poHomeChannel: HomeChannel; + let disableRoomEncryptionModal: DisableRoomEncryptionModal; + let enableRoomEncryptionModal: EnableRoomEncryptionModal; test.use({ storageState: Users.userE2EE.state }); @@ -27,6 +30,8 @@ test.describe('E2EE OTR (Off-The-Record)', () => { test.beforeEach(async ({ page }) => { poHomeChannel = new HomeChannel(page); + disableRoomEncryptionModal = new DisableRoomEncryptionModal(page); + enableRoomEncryptionModal = new EnableRoomEncryptionModal(page); await page.goto('/home'); }); @@ -43,21 +48,16 @@ test.describe('E2EE OTR (Off-The-Record)', () => { await poHomeChannel.tabs.kebab.click({ force: true }); if (await poHomeChannel.tabs.btnDisableE2E.isVisible()) { await poHomeChannel.tabs.btnDisableE2E.click({ force: true }); - await expect(page.getByRole('dialog', { name: 'Disable encryption' })).toBeVisible(); - await page.getByRole('button', { name: 'Disable encryption' }).click(); - await poHomeChannel.dismissToast(); + await disableRoomEncryptionModal.disable(); await poHomeChannel.tabs.kebab.click({ force: true }); } await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); await poHomeChannel.tabs.btnEnableE2E.click({ force: true }); - await expect(page.getByRole('dialog', { name: 'Enable encryption' })).toBeVisible(); - await page.getByRole('button', { name: 'Enable encryption' }).click(); + await enableRoomEncryptionModal.enable(); await page.waitForTimeout(1000); await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); - await poHomeChannel.dismissToast(); - await poHomeChannel.tabs.kebab.click({ force: true }); await expect(poHomeChannel.tabs.btnEnableOTR).toBeVisible(); await poHomeChannel.tabs.btnEnableOTR.click({ force: true }); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-passphrase-management.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-passphrase-management.spec.ts index 4ce8b687e223f..0e1217351299f 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-passphrase-management.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-passphrase-management.spec.ts @@ -5,14 +5,17 @@ import { Users, storeState, restoreState } from '../fixtures/userStates'; import { AccountProfile, HomeChannel } from '../page-objects'; import { setupE2EEPassword } from './setupE2EEPassword'; import { AccountSecurityPage } from '../page-objects/account-security'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; import { HomeSidenav } from '../page-objects/fragments'; import { E2EEKeyDecodeFailureBanner, EnterE2EEPasswordBanner, EnterE2EEPasswordModal, ResetE2EEPasswordModal, + CreateE2EEChannel, } from '../page-objects/fragments/e2ee'; import { LoginPage } from '../page-objects/login'; +import { deleteRoom } from '../utils/create-target-channel'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -23,11 +26,19 @@ const settingsList = [ 'E2E_Enabled_Default_PrivateRooms', ]; -const originalSettings = preserveSettings(settingsList); - test.describe('E2EE Passphrase Management - Initial Setup', () => { + let loginPage: LoginPage; + let enterE2EEPasswordBanner: EnterE2EEPasswordBanner; + let enterE2EEPasswordModal: EnterE2EEPasswordModal; + let e2EEKeyDecodeFailureBanner: E2EEKeyDecodeFailureBanner; + let sidenav: HomeSidenav; + let accountSecurityPage: AccountSecurityPage; + let resetE2EEPasswordModal: ResetE2EEPasswordModal; + test.use({ storageState: Users.admin.state }); + preserveSettings(settingsList); + test.beforeAll(async ({ api }) => { await api.post('/settings/E2E_Enable', { value: true }); await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); @@ -35,15 +46,14 @@ test.describe('E2EE Passphrase Management - Initial Setup', () => { await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: false }); }); - test.afterAll(async ({ api }) => { - await api.post('/settings/E2E_Enable', { value: originalSettings.E2E_Enable }); - await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: originalSettings.E2E_Allow_Unencrypted_Messages }); - await api.post('/settings/E2E_Enabled_Default_DirectRooms', { value: originalSettings.E2E_Enabled_Default_DirectRooms }); - await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: originalSettings.E2E_Enabled_Default_PrivateRooms }); - }); - test.beforeEach(async ({ api, page }) => { - const loginPage = new LoginPage(page); + loginPage = new LoginPage(page); + enterE2EEPasswordBanner = new EnterE2EEPasswordBanner(page); + enterE2EEPasswordModal = new EnterE2EEPasswordModal(page); + e2EEKeyDecodeFailureBanner = new E2EEKeyDecodeFailureBanner(page); + sidenav = new HomeSidenav(page); + accountSecurityPage = new AccountSecurityPage(page); + resetE2EEPasswordModal = new ResetE2EEPasswordModal(page); await api.post('/method.call/e2e.resetOwnE2EKey', { message: JSON.stringify({ msg: 'method', id: '1', method: 'e2e.resetOwnE2EKey', params: [] }), @@ -55,12 +65,6 @@ test.describe('E2EE Passphrase Management - Initial Setup', () => { }); test('expect the randomly generated password to work', async ({ page }) => { - const loginPage = new LoginPage(page); - const enterE2EEPasswordBanner = new EnterE2EEPasswordBanner(page); - const enterE2EEPasswordModal = new EnterE2EEPasswordModal(page); - const e2EEKeyDecodeFailureBanner = new E2EEKeyDecodeFailureBanner(page); - const sidenav = new HomeSidenav(page); - const password = await setupE2EEPassword(page); // Log out @@ -77,10 +81,7 @@ test.describe('E2EE Passphrase Management - Initial Setup', () => { await e2EEKeyDecodeFailureBanner.expectToNotBeVisible(); }); - test('expect to manually reset the password', async ({ page }) => { - const accountSecurityPage = new AccountSecurityPage(page); - const loginPage = new LoginPage(page); - + test('expect to manually reset the password', async () => { // Reset the E2EE key to start the flow from the beginning await accountSecurityPage.goto(); await accountSecurityPage.resetE2EEPassword(); @@ -89,12 +90,6 @@ test.describe('E2EE Passphrase Management - Initial Setup', () => { }); test('should reset e2e password from the modal', async ({ page }) => { - const sidenav = new HomeSidenav(page); - const loginPage = new LoginPage(page); - const enterE2EEPasswordBanner = new EnterE2EEPasswordBanner(page); - const enterE2EEPasswordModal = new EnterE2EEPasswordModal(page); - const resetE2EEPasswordModal = new ResetE2EEPasswordModal(page); - await setupE2EEPassword(page); // Logout @@ -113,13 +108,6 @@ test.describe('E2EE Passphrase Management - Initial Setup', () => { }); test('expect to manually set a new password', async ({ page }) => { - const accountSecurityPage = new AccountSecurityPage(page); - const loginPage = new LoginPage(page); - const enterE2EEPasswordBanner = new EnterE2EEPasswordBanner(page); - const enterE2EEPasswordModal = new EnterE2EEPasswordModal(page); - const e2EEKeyDecodeFailureBanner = new E2EEKeyDecodeFailureBanner(page); - const sidenav = new HomeSidenav(page); - const newPassword = faker.string.uuid(); await setupE2EEPassword(page); @@ -149,15 +137,26 @@ test.use({ storageState: Users.admin.state }); const roomSetupSettingsList = ['E2E_Enable', 'E2E_Allow_Unencrypted_Messages']; test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { + const createdChannels: { name: string; id?: string | null }[] = []; let poAccountProfile: AccountProfile; let poHomeChannel: HomeChannel; + let encryptedRoomPage: EncryptedRoomPage; + let loginPage: LoginPage; + let enterE2EEPasswordModal: EnterE2EEPasswordModal; let e2eePassword: string; preserveSettings(roomSetupSettingsList); + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + test.beforeEach(async ({ page }) => { poAccountProfile = new AccountProfile(page); poHomeChannel = new HomeChannel(page); + encryptedRoomPage = new EncryptedRoomPage(page); + loginPage = new LoginPage(page); + enterE2EEPasswordModal = new EnterE2EEPasswordModal(page); }); test.beforeAll(async ({ api }) => { @@ -174,7 +173,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poAccountProfile.securityE2EEncryptionSection.click(); await poAccountProfile.securityE2EEncryptionResetKeyButton.click(); - await page.locator('role=button[name="Login"]').waitFor(); + await loginPage.waitForIt(); await injectInitialData(); await restoreState(page, Users.admin); @@ -186,7 +185,8 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + const createE2EEChannel = new CreateE2EEChannel(page); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -214,7 +214,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poHomeChannel.content.sendMessage('hello world'); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); }); test('expect enter password state on encrypted room', async ({ page }) => { @@ -228,7 +228,8 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + const createE2EEChannel = new CreateE2EEChannel(page); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -247,9 +248,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poHomeChannel.btnRoomEnterE2EEPassword.click(); - await page.locator('#modal-root input').fill(e2eePassword); - - await page.locator('#modal-root .rcx-button--primary').click(); + await enterE2EEPasswordModal.enterPassword(e2eePassword); await expect(poHomeChannel.bannerEnterE2EEPassword).not.toBeVisible(); @@ -260,7 +259,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poHomeChannel.content.sendMessage('hello world'); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); await storeState(page, Users.admin); }); @@ -270,7 +269,8 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + const createE2EEChannel = new CreateE2EEChannel(page); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -279,28 +279,28 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poHomeChannel.content.sendMessage('hello world'); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('hello world'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); await poHomeChannel.sidenav.btnUserProfileMenu.click(); await poHomeChannel.sidenav.accountProfileOption.click(); - await page.locator('role=navigation >> a:has-text("Security")').click(); + await page.getByRole('navigation').getByRole('link', { name: 'Security' }).click(); await poAccountProfile.securityE2EEncryptionSection.click(); await poAccountProfile.securityE2EEncryptionResetKeyButton.click(); - await page.locator('role=button[name="Login"]').waitFor(); + await loginPage.waitForIt(); await page.reload(); - await page.locator('role=button[name="Login"]').waitFor(); + await loginPage.waitForIt(); await injectInitialData(); await restoreState(page, Users.admin); - await page.locator('role=navigation >> role=button[name=Search]').click(); - await page.locator('role=search >> role=searchbox').fill(channelName); - await page.locator(`role=search >> role=listbox >> role=link >> text="${channelName}"`).click(); + await poHomeChannel.sidenav.openSearch(); + await poHomeChannel.sidenav.inputSearch.fill(channelName); + await poHomeChannel.sidenav.getSearchItemByName(channelName).click(); await poHomeChannel.btnRoomSaveE2EEPassword.click(); await poHomeChannel.btnSavedMyPassword.click(); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-pdf-export.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-pdf-export.spec.ts index 610ce150d86d2..e22d3658cf6a2 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-pdf-export.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-pdf-export.spec.ts @@ -2,9 +2,10 @@ import { faker } from '@faker-js/faker'; import { Users } from '../fixtures/userStates'; import { EncryptedRoomPage } from '../page-objects/encrypted-room'; -import { HomeSidenav } from '../page-objects/fragments'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; import { ExportMessagesTab } from '../page-objects/fragments/export-messages-tab'; import { LoginPage } from '../page-objects/login'; +import { deleteRoom } from '../utils/create-target-channel'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -18,6 +19,8 @@ const settingsList = [ preserveSettings(settingsList); test.describe('E2EE PDF Export', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let createE2EEChannel: CreateE2EEChannel; test.use({ storageState: Users.admin.state }); test.beforeAll(async ({ api }) => { @@ -28,8 +31,13 @@ test.describe('E2EE PDF Export', () => { // Note: Using admin user, so no need for userE2EE cleanup }); + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + test.beforeEach(async ({ api, page }) => { const loginPage = new LoginPage(page); + createE2EEChannel = new CreateE2EEChannel(page); await api.post('/method.call/e2e.resetOwnE2EKey', { message: JSON.stringify({ msg: 'method', id: '1', method: 'e2e.resetOwnE2EKey', params: [] }), @@ -41,13 +49,12 @@ test.describe('E2EE PDF Export', () => { }); test('should display only the download file method when exporting messages in an e2ee room', async ({ page }) => { - const sidenav = new HomeSidenav(page); const encryptedRoomPage = new EncryptedRoomPage(page); const exportMessagesTab = new ExportMessagesTab(page); const channelName = faker.string.uuid(); - await sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); await expect(encryptedRoomPage.encryptedRoomHeaderIcon).toBeVisible(); @@ -57,13 +64,12 @@ test.describe('E2EE PDF Export', () => { }); test('should allow exporting messages as PDF in an encrypted room', async ({ page }) => { - const sidenav = new HomeSidenav(page); const encryptedRoomPage = new EncryptedRoomPage(page); const exportMessagesTab = new ExportMessagesTab(page); const channelName = faker.string.uuid(); - await sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); await expect(encryptedRoomPage.encryptedRoomHeaderIcon).toBeVisible(); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-server-settings.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-server-settings.spec.ts index 9a6fd5de8576d..3b0bcda335e82 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-server-settings.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-server-settings.spec.ts @@ -3,6 +3,9 @@ import { faker } from '@faker-js/faker'; import { IS_EE } from '../config/constants'; import { Users } from '../fixtures/userStates'; import { HomeChannel } from '../page-objects'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; import { preserveSettings } from '../utils/preserveSettings'; import { test, expect } from '../utils/test'; @@ -16,7 +19,10 @@ const settingsList = [ preserveSettings(settingsList); test.describe('E2EE Server Settings', () => { + const createdChannels: { name: string; id?: string | null }[] = []; let poHomeChannel: HomeChannel; + let encryptedRoomPage: EncryptedRoomPage; + let createE2EEChannel: CreateE2EEChannel; test.use({ storageState: Users.userE2EE.state }); @@ -25,11 +31,16 @@ test.describe('E2EE Server Settings', () => { await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); await api.post('/settings/E2E_Enabled_Default_DirectRooms', { value: false }); await api.post('/settings/E2E_Enabled_Default_PrivateRooms', { value: false }); - await api.post('/im.delete', { username: 'user2' }); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); }); test.beforeEach(async ({ page }) => { poHomeChannel = new HomeChannel(page); + encryptedRoomPage = new EncryptedRoomPage(page); + createE2EEChannel = new CreateE2EEChannel(page); await page.goto('/home'); }); @@ -37,7 +48,7 @@ test.describe('E2EE Server Settings', () => { test.skip(!IS_EE, 'Premium Only'); const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -46,11 +57,11 @@ test.describe('E2EE Server Settings', () => { await poHomeChannel.content.sendMessage('This is an encrypted message.'); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); - await page.locator('[name="msg"]').type('/'); + await poHomeChannel.content.inputMessage.type('/'); await expect(page.locator('#popup-item-contextualbar')).not.toHaveClass(/disabled/); - await page.locator('[name="msg"]').clear(); + await poHomeChannel.content.inputMessage.clear(); await poHomeChannel.content.dispatchSlashCommand('/contextualbar'); await expect(poHomeChannel.btnContextualbarClose).toBeVisible(); @@ -79,7 +90,7 @@ test.describe('E2EE Server Settings', () => { test('expect slash commands to be disabled in an e2ee room', async ({ page }) => { const channelName = faker.string.uuid(); - await poHomeChannel.sidenav.createEncryptedChannel(channelName); + await createE2EEChannel.createAndStore(channelName, createdChannels); await expect(page).toHaveURL(`/group/${channelName}`); @@ -88,9 +99,9 @@ test.describe('E2EE Server Settings', () => { await poHomeChannel.content.sendMessage('This is an encrypted message.'); await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.'); - await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible(); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); - await page.locator('[name="msg"]').pressSequentially('/'); + await poHomeChannel.content.inputMessage.pressSequentially('/'); await expect(page.locator('#popup-item-contextualbar')).toHaveClass(/disabled/); }); }); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-sidebar.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-sidebar.spec.ts new file mode 100644 index 0000000000000..d3839ed2b45ee --- /dev/null +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-sidebar.spec.ts @@ -0,0 +1,89 @@ +import { faker } from '@faker-js/faker'; + +import { Users } from '../fixtures/userStates'; +import { HomeChannel } from '../page-objects'; +import { EnableRoomEncryptionModal } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; +import { preserveSettings } from '../utils/preserveSettings'; +import { resolvePrivateRoomId } from '../utils/resolve-room-id'; +import { test, expect } from '../utils/test'; + +const settingsList = ['E2E_Enable', 'E2E_Allow_Unencrypted_Messages']; + +preserveSettings(settingsList); + +test.describe('E2EE Channel Sidebar Integration', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let poHomeChannel: HomeChannel; + let enableEncryptionModal: EnableRoomEncryptionModal; + + test.use({ storageState: Users.userE2EE.state }); + + test.beforeAll(async ({ api }) => { + await api.post('/settings/E2E_Enable', { value: true }); + await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + enableEncryptionModal = new EnableRoomEncryptionModal(page); + await page.goto('/home'); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + + test('expect create a private channel, send unencrypted messages, encrypt the channel and delete the last message and check the last message in the sidebar', async ({ + page, + }) => { + const channelName = faker.string.uuid(); + const encryptedMessage1 = 'first ENCRYPTED message'; + const encryptedMessage2 = 'second ENCRYPTED message'; + + await test.step('enable sidebar Extended display mode', async () => { + await poHomeChannel.sidenav.setDisplayMode('Extended'); + }); + + await test.step('create private channel', async () => { + await poHomeChannel.sidenav.openNewByLabel('Channel'); + await poHomeChannel.sidenav.inputChannelName.fill(channelName); + await poHomeChannel.sidenav.btnCreate.click(); + await poHomeChannel.content.waitForChannel(); + const roomId = await resolvePrivateRoomId(page, channelName); + createdChannels.push({ name: channelName, id: roomId }); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.toastSuccess).toBeVisible(); + await poHomeChannel.dismissToast(); + }); + + await test.step('send unencrypted messages', async () => { + await poHomeChannel.content.sendMessage('first unencrypted message'); + await poHomeChannel.content.sendMessage('second unencrypted message'); + }); + + await test.step('enable encryption', async () => { + await poHomeChannel.tabs.kebab.click({ force: true }); + await expect(poHomeChannel.tabs.btnEnableE2E).toBeVisible(); + await poHomeChannel.tabs.btnEnableE2E.click({ force: true }); + await enableEncryptionModal.enable(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('send encrypted messages', async () => { + await poHomeChannel.content.sendMessage(encryptedMessage1); + await poHomeChannel.content.sendMessage(encryptedMessage2); + }); + + await test.step('delete last message', async () => { + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText(encryptedMessage2); + await poHomeChannel.content.deleteLastMessage(); + }); + + await test.step('check last message in the sidebar', async () => { + const sidebarChannel = poHomeChannel.sidenav.getSidebarItemByName(channelName); + await expect(sidebarChannel).toBeVisible(); + await expect(sidebarChannel.locator('span')).toContainText(encryptedMessage1); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/e2e-encryption/e2ee-threads.spec.ts b/apps/meteor/tests/e2e/e2e-encryption/e2ee-threads.spec.ts new file mode 100644 index 0000000000000..e27ae8c7d5a4c --- /dev/null +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-threads.spec.ts @@ -0,0 +1,75 @@ +import { faker } from '@faker-js/faker'; + +import { Users } from '../fixtures/userStates'; +import { HomeChannel } from '../page-objects'; +import { EncryptedRoomPage } from '../page-objects/encrypted-room'; +import { CreateE2EEChannel } from '../page-objects/fragments/e2ee'; +import { deleteRoom } from '../utils/create-target-channel'; +import { preserveSettings } from '../utils/preserveSettings'; +import { test, expect } from '../utils/test'; + +const settingsList = ['E2E_Enable']; + +preserveSettings(settingsList); + +test.describe('E2EE Thread Messages', () => { + const createdChannels: { name: string; id?: string | null }[] = []; + let poHomeChannel: HomeChannel; + let encryptedRoomPage: EncryptedRoomPage; + let createE2EEChannel: CreateE2EEChannel; + test.use({ storageState: Users.userE2EE.state }); + + test.beforeAll(async ({ api }) => { + await api.post('/settings/E2E_Enable', { value: true }); + }); + + test.beforeEach(async ({ page }) => { + poHomeChannel = new HomeChannel(page); + encryptedRoomPage = new EncryptedRoomPage(page); + createE2EEChannel = new CreateE2EEChannel(page); + await poHomeChannel.goto(); + }); + + test.afterAll(async ({ api }) => { + await Promise.all(createdChannels.map(({ id }) => (id ? deleteRoom(api, id) : Promise.resolve()))); + }); + + test('expect create a private encrypted channel and send an encrypted thread message', async ({ page }) => { + const channelName = faker.string.uuid(); + + await test.step('create encrypted channel', async () => { + await createE2EEChannel.createAndStore(channelName, createdChannels); + await expect(page).toHaveURL(`/group/${channelName}`); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + }); + + await test.step('send main thread message', async () => { + await poHomeChannel.content.sendMessage('This is the thread main message.'); + await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is the thread main message.'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + + await test.step('open reply in thread', async () => { + await poHomeChannel.content.openReplyInThread(); + await expect(page).toHaveURL(/.*thread/); + }); + + await test.step('verify main thread message is encrypted', async () => { + await expect(poHomeChannel.content.mainThreadMessageText).toContainText('This is the thread main message.'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + + await test.step('send encrypted reply on thread message', async () => { + await poHomeChannel.content.toggleAlsoSendThreadToChannel(true); + await poHomeChannel.content.sendMessageInThread('This is an encrypted thread message also sent in channel'); + }); + + await test.step('verify all thread messages are encrypted', async () => { + await expect(poHomeChannel.content.lastThreadMessageText).toContainText('This is an encrypted thread message also sent in channel'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + await expect(poHomeChannel.content.lastUserMessage).toContainText('This is an encrypted thread message also sent in channel'); + await expect(poHomeChannel.content.mainThreadMessageText).toContainText('This is the thread main message.'); + await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible(); + }); + }); +}); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/e2ee.ts b/apps/meteor/tests/e2e/page-objects/fragments/e2ee.ts index 151915016bbc2..766276635a5fa 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/e2ee.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/e2ee.ts @@ -1,7 +1,9 @@ import type { Locator, Page } from '@playwright/test'; +import { HomeSidenav } from './home-sidenav'; import { Modal } from './modal'; import { ToastMessages } from './toast-messages'; +import { resolvePrivateRoomId } from '../../utils/resolve-room-id'; import { expect } from '../../utils/test'; import { LoginPage } from '../login'; @@ -152,3 +154,63 @@ export class DisableRoomEncryptionModal extends Modal { await this.toastMessages.dismissToast('success'); } } + +export class E2EEMessageActions { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + private get forwardMessageButton(): Locator { + return this.page.getByRole('button', { name: 'Forward message not available on encrypted content' }); + } + + private get replyInDirectMessageOption(): Locator { + return this.page.getByRole('menuitem', { name: 'Reply in direct message' }); + } + + private get copyLinkOption(): Locator { + return this.page.getByRole('menuitem', { name: 'Copy link' }); + } + + async expectForwardMessageToBeDisabled(): Promise { + await expect(this.forwardMessageButton).toBeDisabled(); + } + + async expectReplyInDirectMessageToBeDisabled(): Promise { + await expect(this.replyInDirectMessageOption).toHaveClass(/disabled/); + } + + async expectCopyLinkToBeDisabled(): Promise { + await expect(this.copyLinkOption).toHaveClass(/disabled/); + } +} + +export class CreateE2EEChannel { + private readonly page: Page; + + private readonly sidenav: HomeSidenav; + + constructor(page: Page) { + this.page = page; + this.sidenav = new HomeSidenav(page); + } + + async create(name: string): Promise { + await this.sidenav.createEncryptedChannel(name); + const id = await resolvePrivateRoomId(this.page, name); + await expect(id, `Failed to resolve roomId for ${name}`).toBeTruthy(); + return id || ''; + } + + async storeChannel(name: string, id: string, createdChannels: { name: string; id?: string | null }[]): Promise { + createdChannels.push({ name, id }); + } + + async createAndStore(name: string, createdChannels: { name: string; id?: string | null }[]): Promise { + const id = await this.create(name); + await this.storeChannel(name, id, createdChannels); + return id; + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts index f767c9321245a..9add09d376d97 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -63,6 +63,14 @@ export class HomeContent { return this.page.locator('.rcx-room-header i.rcx-icon--name-key'); } + getUserMention(username: string): Locator { + return this.page.getByRole('button', { name: username }); + } + + getChannelMention(channelName: string): Locator { + return this.page.getByRole('button', { name: channelName }); + } + get lastIgnoredUserMessage(): Locator { return this.lastUserMessageBody.locator('role=button[name="This message was ignored"]'); } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts index 2c5fc0b13ede9..3c915200c94e7 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts @@ -56,6 +56,7 @@ export class HomeFlextab { return this.page.locator('role=menuitem[name="Teams Members"]'); } + // TODO: replace this with the room-toolbar get kebab(): Locator { return this.toolbarPrimaryActions.locator('role=button[name="Options"]'); } @@ -95,12 +96,4 @@ export class HomeFlextab { get userInfoUsername(): Locator { return this.page.locator('[data-qa="UserInfoUserName"]'); } - - get btnPinnedMessagesList(): Locator { - return this.page.locator('[data-key="pinned-messages"]'); - } - - get btnStarredMessageList(): Locator { - return this.page.locator('[data-key="starred-messages"]'); - } } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/index.ts b/apps/meteor/tests/e2e/page-objects/fragments/index.ts index 5541c078b7e31..e39747e4dc246 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/index.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/index.ts @@ -8,3 +8,5 @@ export * from './navbar'; export * from './sidebar'; export * from './sidepanel'; export * from './report-message-modal'; +export * from './pinned-messages-tab'; +export * from './starred-messages-tab'; diff --git a/apps/meteor/tests/e2e/page-objects/fragments/message.ts b/apps/meteor/tests/e2e/page-objects/fragments/message.ts index 77dee24c85ca0..02e1f29ca836e 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/message.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/message.ts @@ -14,4 +14,14 @@ export class Message { get encryptedIcon() { return this.root.locator('.rcx-icon--name-key'); } + + get moreButton() { + return this.root.getByRole('button', { name: 'More' }); + } + + async openMenu(): Promise { + await this.root.hover(); + await this.moreButton.waitFor(); + await this.moreButton.click(); + } } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/pinned-messages-tab.ts b/apps/meteor/tests/e2e/page-objects/fragments/pinned-messages-tab.ts new file mode 100644 index 0000000000000..a1fb186c3d6b1 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/pinned-messages-tab.ts @@ -0,0 +1,33 @@ +import type { Locator, Page } from '@playwright/test'; + +import { Message } from './message'; +import { RoomToolbar } from './room-toolbar'; +import { expect } from '../../utils/test'; + +export class PinnedMessagesTab { + private readonly root: Locator; + + private readonly roomToolbar: RoomToolbar; + + constructor(page: Page) { + this.root = page.getByRole('dialog', { name: 'Pinned Messages' }); + this.roomToolbar = new RoomToolbar(page); + } + + private get lastMessage(): Message { + return new Message(this.root.locator('[data-qa-type="message"]').last()); + } + + async openTab(): Promise { + await this.roomToolbar.openPinnedMessagesList(); + await expect(this.root).toBeVisible(); + } + + async expectLastMessageToContainText(text: string): Promise { + await expect(this.lastMessage.root).toContainText(text); + } + + async openLastMessageMenu(): Promise { + await this.lastMessage.openMenu(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/room-toolbar.ts b/apps/meteor/tests/e2e/page-objects/fragments/room-toolbar.ts new file mode 100644 index 0000000000000..28835f578d866 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/room-toolbar.ts @@ -0,0 +1,39 @@ +import type { Locator, Page } from '@playwright/test'; + +export class RoomToolbar { + private readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + private get toolbarPrimaryActions(): Locator { + return this.page.getByRole('toolbar', { name: 'Primary Room actions' }); + } + + private get btnToolbarPrimaryActionsMore(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Options' }); + } + + private get menuToolbarPrimaryActionsMore(): Locator { + return this.page.getByRole('menu', { name: 'Options' }); + } + + private get optionPinnedMessages(): Locator { + return this.menuToolbarPrimaryActionsMore.getByRole('menuitem', { name: 'Pinned Messages' }); + } + + private get optionStarredMessages(): Locator { + return this.menuToolbarPrimaryActionsMore.getByRole('menuitem', { name: 'Starred Messages' }); + } + + async openPinnedMessagesList(): Promise { + await this.btnToolbarPrimaryActionsMore.click(); + await this.optionPinnedMessages.click(); + } + + async openStarredMessagesList(): Promise { + await this.btnToolbarPrimaryActionsMore.click(); + await this.optionStarredMessages.click(); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/starred-messages-tab.ts b/apps/meteor/tests/e2e/page-objects/fragments/starred-messages-tab.ts new file mode 100644 index 0000000000000..ef162323b7de1 --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/starred-messages-tab.ts @@ -0,0 +1,33 @@ +import type { Locator, Page } from '@playwright/test'; + +import { Message } from './message'; +import { RoomToolbar } from './room-toolbar'; +import { expect } from '../../utils/test'; + +export class StarredMessagesTab { + private readonly root: Locator; + + private readonly roomToolbar: RoomToolbar; + + constructor(page: Page) { + this.root = page.getByRole('dialog', { name: 'Starred Messages' }); + this.roomToolbar = new RoomToolbar(page); + } + + private get lastMessage(): Message { + return new Message(this.root.locator('[data-qa-type="message"]').last()); + } + + async openTab(): Promise { + await this.roomToolbar.openStarredMessagesList(); + await expect(this.root).toBeVisible(); + } + + async expectLastMessageToContainText(text: string): Promise { + await expect(this.lastMessage.root).toContainText(text); + } + + async openLastMessageMenu(): Promise { + await this.lastMessage.openMenu(); + } +} diff --git a/apps/meteor/tests/e2e/utils/index.ts b/apps/meteor/tests/e2e/utils/index.ts index 7548171e8758d..178bbfa6d8bb5 100644 --- a/apps/meteor/tests/e2e/utils/index.ts +++ b/apps/meteor/tests/e2e/utils/index.ts @@ -2,3 +2,4 @@ export * from './create-target-channel'; export * from './setSettingValueById'; export * from './getSettingValueById'; export * from './setUserPreferences'; +export * from './resolve-room-id'; diff --git a/apps/meteor/tests/e2e/utils/resolve-room-id.ts b/apps/meteor/tests/e2e/utils/resolve-room-id.ts new file mode 100644 index 0000000000000..0359072df32e0 --- /dev/null +++ b/apps/meteor/tests/e2e/utils/resolve-room-id.ts @@ -0,0 +1,50 @@ +import type { Page } from '@playwright/test'; + +export type RoomType = 'c' | 'p' | 'd'; + +/** + * Resolves room ID from room name using the Meteor method getRoomByTypeAndName. + * This is necessary for E2E encrypted rooms as it uses the room coordinator system + * which has specialized logic that the REST API endpoints don't have. + */ +export async function resolveRoomId(page: Page, type: RoomType, name: string): Promise { + return page.evaluate( + async ({ roomType, roomName }: { roomType: RoomType; roomName: string }) => { + // Type-safe access to Meteor + const windowWithMeteor = window as unknown as { Meteor?: { call?: (method: string, ...args: unknown[]) => void } }; + const meteor = windowWithMeteor.Meteor; + + if (!meteor?.call || typeof meteor.call !== 'function') { + return null; + } + + const meteorCall = meteor.call; + + // Promisify the Meteor method call + return new Promise((resolve) => { + meteorCall('getRoomByTypeAndName', roomType, roomName, (error: unknown, room: { _id?: string } | null) => { + if (error || !room?._id) { + resolve(null); + return; + } + resolve(room._id); + }); + }); + }, + { roomType: type, roomName: name }, + ); +} + +/** + * Resolves private room (group) ID by name - for E2E encrypted channels + */ +export function resolvePrivateRoomId(page: Page, name: string): Promise { + return resolveRoomId(page, 'p', name); +} + +/** + * Resolves channel ID by name + */ +export function resolveChannelRoomId(page: Page, name: string): Promise { + return resolveRoomId(page, 'c', name); +}