test: Decouple E2E encryption tests (#36000)

pull/35991/head^2
Tasso Evangelista 8 months ago committed by GitHub
parent 635b8cd0f1
commit a3dcfb65ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx
  2. 2
      apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx
  3. 13
      apps/meteor/client/views/e2e/SaveE2EPasswordModal.tsx
  4. 377
      apps/meteor/tests/e2e/e2e-encryption.spec.ts
  5. 7
      apps/meteor/tests/e2e/fixtures/inject-initial-data.ts
  6. 51
      apps/meteor/tests/e2e/page-objects/account-security.ts
  7. 47
      apps/meteor/tests/e2e/page-objects/encrypted-room.ts
  8. 125
      apps/meteor/tests/e2e/page-objects/fragments/e2ee.ts
  9. 34
      apps/meteor/tests/e2e/page-objects/fragments/file-upload-modal.ts
  10. 4
      apps/meteor/tests/e2e/page-objects/fragments/home-content.ts
  11. 5
      apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts
  12. 17
      apps/meteor/tests/e2e/page-objects/fragments/message.ts
  13. 11
      apps/meteor/tests/e2e/page-objects/fragments/modal.ts
  14. 14
      apps/meteor/tests/e2e/page-objects/fragments/toast-messages.ts
  15. 48
      apps/meteor/tests/e2e/page-objects/login.ts

@ -51,7 +51,7 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM
return (
<>
{isMessageEncrypted && <MessageBody>{t('E2E_message_encrypted_placeholder')}</MessageBody>}
{isMessageEncrypted && <MessageBody data-qa-type='message-body'>{t('E2E_message_encrypted_placeholder')}</MessageBody>}
{!!quotes?.length && <Attachments attachments={quotes} />}

@ -46,7 +46,7 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem
return (
<>
{isMessageEncrypted && <MessageBody>{t('E2E_message_encrypted_placeholder')}</MessageBody>}
{isMessageEncrypted && <MessageBody data-qa-type='message-body'>{t('E2E_message_encrypted_placeholder')}</MessageBody>}
{!!quotes?.length && <Attachments attachments={quotes} />}

@ -2,7 +2,7 @@ import { Box, CodeSnippet } from '@rocket.chat/fuselage';
import { useClipboard } from '@rocket.chat/fuselage-hooks';
import { ExternalLink } from '@rocket.chat/ui-client';
import DOMPurify from 'dompurify';
import type { ReactElement } from 'react';
import { useId, type ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import GenericModal from '../../components/GenericModal';
@ -19,6 +19,7 @@ const DOCS_URL = 'https://go.rocket.chat/i/e2ee-guide';
const SaveE2EPasswordModal = ({ randomPassword, onClose, onCancel, onConfirm }: SaveE2EPasswordModalProps): ReactElement => {
const { t } = useTranslation();
const { copy, hasCopied } = useClipboard(randomPassword);
const passwordId = useId();
return (
<GenericModal
@ -40,8 +41,14 @@ const SaveE2EPasswordModal = ({ randomPassword, onClose, onCancel, onConfirm }:
<Box is='p' fontWeight='bold' mb={20}>
{t('E2E_password_save_text')}
</Box>
<p>{t('Your_E2EE_password_is')}</p>
<CodeSnippet buttonText={hasCopied ? t('Copied') : t('Copy')} buttonDisabled={hasCopied} onClick={() => copy()} mbs={8}>
<p id={passwordId}>{t('Your_E2EE_password_is')}</p>
<CodeSnippet
aria-labelledby={passwordId}
buttonText={hasCopied ? t('Copied') : t('Copy')}
buttonDisabled={hasCopied}
onClick={() => copy()}
mbs={8}
>
{randomPassword}
</CodeSnippet>
</GenericModal>

@ -6,20 +6,27 @@ import { createAuxContext } from './fixtures/createAuxContext';
import injectInitialData from './fixtures/inject-initial-data';
import { Users, storeState, restoreState } from './fixtures/userStates';
import { AccountProfile, HomeChannel } from './page-objects';
import { AccountSecurityPage } from './page-objects/account-security';
import { EncryptedRoomPage } from './page-objects/encrypted-room';
import { HomeSidenav } from './page-objects/fragments';
import {
E2EEKeyDecodeFailureBanner,
EnterE2EEPasswordBanner,
EnterE2EEPasswordModal,
SaveE2EEPasswordBanner,
SaveE2EEPasswordModal,
} from './page-objects/fragments/e2ee';
import { FileUploadModal } from './page-objects/fragments/file-upload-modal';
import { HomeFlextabExportMessages } from './page-objects/fragments/home-flextab-exportMessages';
import { LoginPage } from './page-objects/login';
import { test, expect } from './utils/test';
test.use({ storageState: Users.admin.state });
test.describe.serial('e2e-encryption initial setup', () => {
let poAccountProfile: AccountProfile;
let poHomeChannel: HomeChannel;
let password: string;
const newPassword = 'new password';
test.beforeAll(async () => {
await injectInitialData();
});
test.beforeEach(async ({ page }) => {
poAccountProfile = new AccountProfile(page);
poHomeChannel = new HomeChannel(page);
});
test.describe('initial setup', () => {
test.use({ storageState: Users.admin.state });
test.beforeAll(async ({ api }) => {
await api.post('/settings/E2E_Enable', { value: true });
@ -31,223 +38,259 @@ test.describe.serial('e2e-encryption initial setup', () => {
await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: false });
});
test.afterEach(async ({ api }) => {
await api.recreateContext();
});
test("expect reset user's e2e encryption key", async ({ page }) => {
await page.goto('/account/security');
// Reset key to start the flow from the beginning
// It will execute a logout
await poAccountProfile.securityE2EEncryptionSection.click();
await poAccountProfile.securityE2EEncryptionResetKeyButton.click();
await page.locator('role=button[name="Login"]').waitFor();
await injectInitialData();
// Login again, check the banner to save the generated password and test it
await restoreState(page, Users.admin);
await poHomeChannel.bannerSaveEncryptionPassword.click();
password = (await page.evaluate(() => localStorage.getItem('e2e.randomPassword'))) || 'undefined';
await expect(poHomeChannel.dialogSaveE2EEPassword).toContainText(password);
test.beforeEach(async ({ api, page }) => {
const loginPage = new LoginPage(page);
await poHomeChannel.btnSavedMyPassword.click();
await api.post('/method.call/e2e.resetOwnE2EKey', {
message: JSON.stringify({ msg: 'method', id: '1', method: 'e2e.resetOwnE2EKey', params: [] }),
});
await expect(poHomeChannel.bannerSaveEncryptionPassword).not.toBeVisible();
await page.goto('/home');
await loginPage.waitForIt();
await loginPage.loginByUserState(Users.admin);
});
await poHomeChannel.sidenav.logout();
test('expect the randomly generated password to work', async ({ page }) => {
const loginPage = new LoginPage(page);
const saveE2EEPasswordBanner = new SaveE2EEPasswordBanner(page);
const saveE2EEPasswordModal = new SaveE2EEPasswordModal(page);
const enterE2EEPasswordBanner = new EnterE2EEPasswordBanner(page);
const enterE2EEPasswordModal = new EnterE2EEPasswordModal(page);
const e2EEKeyDecodeFailureBanner = new E2EEKeyDecodeFailureBanner(page);
const sidenav = new HomeSidenav(page);
await page.locator('role=button[name="Login"]').waitFor();
// Click the banner to open the dialog to save the generated password
await saveE2EEPasswordBanner.click();
const password = await saveE2EEPasswordModal.getPassword();
await saveE2EEPasswordModal.confirm();
await saveE2EEPasswordBanner.waitForDisappearance();
await injectInitialData();
// Log out
await sidenav.logout();
await restoreState(page, Users.admin);
// Login again
await loginPage.loginByUserState(Users.admin);
await poHomeChannel.bannerEnterE2EEPassword.click();
// Enter the saved password
await enterE2EEPasswordBanner.click();
await enterE2EEPasswordModal.enterPassword(password);
await page.locator('#modal-root input').fill(password);
// No error banner
await e2EEKeyDecodeFailureBanner.expectToNotBeVisible();
});
await page.locator('#modal-root .rcx-button--primary').click();
test('expect to manually reset the password', async ({ page }) => {
const accountSecurityPage = new AccountSecurityPage(page);
const loginPage = new LoginPage(page);
await expect(poHomeChannel.bannerEnterE2EEPassword).not.toBeVisible();
// Reset the E2EE key to start the flow from the beginning
await accountSecurityPage.goto();
await accountSecurityPage.resetE2EEPassword();
await storeState(page, Users.admin);
await loginPage.loginByUserState(Users.admin);
});
test('expect change the e2ee password', async ({ page }) => {
await page.goto('/account/security');
test('expect to manually set a new password', async ({ page }) => {
const accountSecurityPage = new AccountSecurityPage(page);
const loginPage = new LoginPage(page);
const saveE2EEPasswordBanner = new SaveE2EEPasswordBanner(page);
const saveE2EEPasswordModal = new SaveE2EEPasswordModal(page);
const enterE2EEPasswordBanner = new EnterE2EEPasswordBanner(page);
const enterE2EEPasswordModal = new EnterE2EEPasswordModal(page);
const e2EEKeyDecodeFailureBanner = new E2EEKeyDecodeFailureBanner(page);
const sidenav = new HomeSidenav(page);
await restoreState(page, Users.admin);
const newPassword = faker.string.uuid();
await poAccountProfile.securityE2EEncryptionSection.click();
await poAccountProfile.securityE2EEncryptionPassword.click();
await poAccountProfile.securityE2EEncryptionPassword.fill(newPassword);
await poAccountProfile.securityE2EEncryptionPasswordConfirmation.fill(newPassword);
await poAccountProfile.securityE2EEncryptionSavePasswordButton.click();
// Click the banner to open the dialog to save the generated password
await saveE2EEPasswordBanner.click();
await saveE2EEPasswordModal.confirm();
await saveE2EEPasswordBanner.waitForDisappearance();
await poAccountProfile.btnClose.click();
// Set a new password
await accountSecurityPage.goto();
await accountSecurityPage.setE2EEPassword(newPassword);
await accountSecurityPage.close();
await poHomeChannel.sidenav.logout();
// Log out
await sidenav.logout();
await page.locator('role=button[name="Login"]').waitFor();
await injectInitialData();
// Login again
await loginPage.loginByUserState(Users.admin);
await restoreState(page, Users.admin, { except: ['public_key', 'private_key'] });
// Enter the saved password
await enterE2EEPasswordBanner.click();
await enterE2EEPasswordModal.enterPassword(newPassword);
await poHomeChannel.bannerEnterE2EEPassword.click();
// No error banner
await e2EEKeyDecodeFailureBanner.expectToNotBeVisible();
});
});
await page.locator('#modal-root input').fill(password);
test.describe('basic features', () => {
test.use({ storageState: Users.admin.state });
await page.locator('#modal-root .rcx-button--primary').click();
test.beforeAll(async ({ api }) => {
await api.post('/settings/E2E_Enable', { value: true });
await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true });
});
await poHomeChannel.btnNotPossibleDecodeKey.click();
test.afterAll(async ({ api }) => {
await api.post('/settings/E2E_Enable', { value: false });
await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: false });
});
await page.locator('#modal-root input').fill(newPassword);
test.beforeEach(async ({ api, page }) => {
const loginPage = new LoginPage(page);
await page.locator('#modal-root .rcx-button--primary').click();
await api.post('/method.call/e2e.resetOwnE2EKey', {
message: JSON.stringify({ msg: 'method', id: '1', method: 'e2e.resetOwnE2EKey', params: [] }),
});
await expect(poHomeChannel.btnNotPossibleDecodeKey).not.toBeVisible();
await expect(poHomeChannel.bannerEnterE2EEPassword).not.toBeVisible();
await page.goto('/home');
await loginPage.waitForIt();
await loginPage.loginByUserState(Users.admin);
});
test('expect placeholder text in place of encrypted message', async ({ page }) => {
await page.goto('/home');
const loginPage = new LoginPage(page);
const saveE2EEPasswordBanner = new SaveE2EEPasswordBanner(page);
const saveE2EEPasswordModal = new SaveE2EEPasswordModal(page);
const encryptedRoomPage = new EncryptedRoomPage(page);
const sidenav = new HomeSidenav(page);
const channelName = faker.string.uuid();
const messageText = 'This is an encrypted message.';
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await saveE2EEPasswordBanner.click();
await saveE2EEPasswordModal.confirm();
await saveE2EEPasswordBanner.waitForDisappearance();
await poHomeChannel.dismissToast();
await sidenav.createEncryptedChannel(channelName);
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await expect(page).toHaveURL(`/group/${channelName}`);
await expect(encryptedRoomPage.encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.encryptionNotReadyIndicator).not.toBeVisible();
await poHomeChannel.content.sendMessage('This is an encrypted message.');
await encryptedRoomPage.sendMessage(messageText);
await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.lastMessage.body).toHaveText(messageText);
await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.');
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();
await sidenav.logout();
// Logout and login
await poHomeChannel.sidenav.logout();
await page.locator('role=button[name="Login"]').waitFor();
await injectInitialData();
await restoreState(page, Users.admin, { except: ['private_key', 'public_key'] });
await loginPage.loginByUserState(Users.admin);
await poHomeChannel.sidenav.openChat(channelName);
// Navigate to the encrypted channel WITHOUT entering the password
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await sidenav.openChat(channelName);
await expect(encryptedRoomPage.encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.encryptionNotReadyIndicator).toBeVisible();
await expect(poHomeChannel.content.lastUserMessage).toContainText(
await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.lastMessage.body).toHaveText(
'This message is end-to-end encrypted. To view it, you must enter your encryption key in your account settings.',
);
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();
await poHomeChannel.content.lastUserMessage.hover();
await expect(page.locator('[role=toolbar][aria-label="Message actions"]')).not.toBeVisible();
});
test('expect placeholder text in place of encrypted file description, when non-encrypted files upload in disabled e2ee room', async ({
page,
}) => {
await page.goto('/home');
test('expect placeholder text in place of encrypted file upload description', async ({ page }) => {
const encryptedRoomPage = new EncryptedRoomPage(page);
const loginPage = new LoginPage(page);
const saveE2EEPasswordBanner = new SaveE2EEPasswordBanner(page);
const saveE2EEPasswordModal = new SaveE2EEPasswordModal(page);
const fileUploadModal = new FileUploadModal(page);
const sidenav = new HomeSidenav(page);
const channelName = faker.string.uuid();
const fileName = faker.system.commonFileName('txt');
const fileDescription = faker.lorem.sentence();
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await poHomeChannel.sidenav.openChat(channelName);
// Click the banner to open the dialog to save the generated password
await saveE2EEPasswordBanner.click();
await saveE2EEPasswordModal.confirm();
await saveE2EEPasswordBanner.waitForDisappearance();
await poHomeChannel.content.dragAndDropTxtFile();
await poHomeChannel.content.descriptionInput.fill('any_description');
await poHomeChannel.content.fileNameInput.fill('any_file1.txt');
await poHomeChannel.content.btnModalConfirm.click();
// Create an encrypted channel
await sidenav.createEncryptedChannel(channelName);
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();
await expect(poHomeChannel.content.getFileDescription).toHaveText('any_description');
await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file1.txt');
await test.step('disable E2EE in the room', async () => {
await poHomeChannel.tabs.kebab.click();
await expect(poHomeChannel.tabs.btnDisableE2E).toBeVisible();
await poHomeChannel.tabs.btnDisableE2E.click();
await expect(page.getByRole('dialog', { name: 'Disable encryption' })).toBeVisible();
await page.getByRole('button', { name: 'Disable encryption' }).click();
await poHomeChannel.dismissToast();
// will wait till the key icon in header goes away
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toHaveCount(0);
await expect(page).toHaveURL(`/group/${channelName}`);
await expect(encryptedRoomPage.encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.encryptionNotReadyIndicator).not.toBeVisible();
await test.step('upload the file with encryption', async () => {
// Upload a file
await encryptedRoomPage.dragAndDropTxtFile();
await fileUploadModal.setName(fileName);
await fileUploadModal.setDescription(fileDescription);
await fileUploadModal.send();
// Check the file upload
await expect(encryptedRoomPage.lastMessage.encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.lastMessage.fileUploadName).toContainText(fileName);
await expect(encryptedRoomPage.lastMessage.body).toHaveText(fileDescription);
});
await page.reload();
await test.step('upload the file in disabled E2EE room', async () => {
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).not.toBeVisible();
await poHomeChannel.content.dragAndDropTxtFile();
await poHomeChannel.content.descriptionInput.fill('any_description');
await poHomeChannel.content.fileNameInput.fill('any_file1.txt');
await poHomeChannel.content.btnModalConfirm.click();
await test.step('disable encryption in the room', async () => {
await encryptedRoomPage.disableEncryption();
await expect(encryptedRoomPage.encryptedIcon).not.toBeVisible();
});
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).not.toBeVisible();
await test.step('upload the file without encryption', async () => {
await encryptedRoomPage.dragAndDropTxtFile();
await fileUploadModal.setName(fileName);
await fileUploadModal.setDescription(fileDescription);
await fileUploadModal.send();
await expect(poHomeChannel.content.getFileDescription).toHaveText('any_description');
await expect(poHomeChannel.content.lastMessageFileName).toContainText('any_file1.txt');
await expect(encryptedRoomPage.lastMessage.encryptedIcon).not.toBeVisible();
await expect(encryptedRoomPage.lastMessage.fileUploadName).toContainText(fileName);
await expect(encryptedRoomPage.lastMessage.body).toHaveText(fileDescription);
});
await test.step('Enable E2EE in the room', async () => {
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 poHomeChannel.dismissToast();
// will wait till the key icon in header appears
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toHaveCount(1);
await test.step('enable encryption in the room', async () => {
await encryptedRoomPage.enableEncryption();
await expect(encryptedRoomPage.encryptedIcon).toBeVisible();
});
// Logout to remove e2ee keys
await poHomeChannel.sidenav.logout();
// Log out
await sidenav.logout();
// Login again
await page.locator('role=button[name="Login"]').waitFor();
await injectInitialData();
await restoreState(page, Users.admin, { except: ['private_key', 'public_key'] });
await loginPage.loginByUserState(Users.admin);
await poHomeChannel.sidenav.openChat(channelName);
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await sidenav.openChat(channelName);
await expect(encryptedRoomPage.encryptedIcon).toBeVisible();
await expect(poHomeChannel.content.nthMessage(0)).toContainText(
await expect(encryptedRoomPage.lastNthMessage(1).body).toHaveText(
'This message is end-to-end encrypted. To view it, you must enter your encryption key in your account settings.',
);
await expect(poHomeChannel.content.nthMessage(0).locator('.rcx-icon--name-key')).toBeVisible();
await expect(encryptedRoomPage.lastNthMessage(1).encryptedIcon).toBeVisible();
await expect(encryptedRoomPage.lastMessage.encryptedIcon).not.toBeVisible();
await expect(encryptedRoomPage.lastMessage.fileUploadName).toContainText(fileName);
await expect(encryptedRoomPage.lastMessage.body).toHaveText(fileDescription);
});
test('should display only the download file method when exporting messages in an e2ee room', async ({ page }) => {
await page.goto('/home');
const sidenav = new HomeSidenav(page);
const encryptedRoomPage = new EncryptedRoomPage(page);
const exportMessagesTab = new HomeFlextabExportMessages(page);
const channelName = faker.string.uuid();
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await expect(encryptedRoomPage.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.tabs.kebab.click({ force: true });
await poHomeChannel.tabs.btnExportMessages.click();
await expect(poHomeChannel.tabs.exportMessages.downloadFileMethod).toBeVisible();
await encryptedRoomPage.showExportMessagesTab();
await expect(exportMessagesTab.downloadFileMethod).toBeVisible();
await expect(exportMessagesTab.sendEmailMethod).not.toBeVisible();
});
});
test.use({ storageState: Users.admin.state });
test.describe.serial('e2e-encryption', () => {
// test.skip();
let poHomeChannel: HomeChannel;
test.use({ storageState: Users.userE2EE.state });
@ -275,8 +318,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('hello world');
@ -319,8 +360,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('This is the thread main message.');
@ -353,8 +392,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('This is an encrypted message.');
@ -406,8 +443,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('hello @user1');
@ -426,8 +461,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('Are you in the #general channel?');
@ -450,8 +483,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('Are you in the #general channel, @user1 ?');
@ -667,8 +698,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('This is an encrypted message.');
@ -710,8 +739,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('This is an encrypted message.');
@ -778,8 +805,6 @@ test.describe.serial('e2e-encryption', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('This message should be pinned and stared.');
@ -849,6 +874,8 @@ test.describe.serial('e2e-encryption', () => {
});
test.describe.serial('e2ee room setup', () => {
// test.skip();
let poAccountProfile: AccountProfile;
let poHomeChannel: HomeChannel;
let e2eePassword: string;
@ -1040,6 +1067,8 @@ test.describe.serial('e2ee room setup', () => {
});
test.describe('e2ee support legacy formats', () => {
// test.skip();
test.use({ storageState: Users.userE2EE.state });
let poHomeChannel: HomeChannel;
@ -1068,8 +1097,6 @@ test.describe('e2ee support legacy formats', () => {
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
const rid = await page.locator('[data-qa-rc-room]').getAttribute('data-qa-rc-room');

@ -1,3 +1,4 @@
import type { ISetting, IUser } from '@rocket.chat/core-typings';
import { MongoClient } from 'mongodb';
import * as constants from '../config/constants';
@ -23,7 +24,7 @@ export default async function injectInitialData() {
await connection
.db()
.collection('users')
.collection<IUser>('users')
.updateOne(
{ username: Users.admin.data.username },
{ $addToSet: { 'services.resume.loginTokens': { when: Users.admin.data.loginExpire, hashedToken: Users.admin.data.hashedToken } } },
@ -74,8 +75,8 @@ export default async function injectInitialData() {
].map((setting) =>
connection
.db()
.collection('rocketchat_settings')
.updateOne({ _id: setting._id as any }, { $set: { value: setting.value } }),
.collection<ISetting>('rocketchat_settings')
.updateOne({ _id: setting._id }, { $set: { value: setting.value } }),
),
);

@ -0,0 +1,51 @@
import type { Page } from '@playwright/test';
export class AccountSecurityPage {
constructor(protected readonly page: Page) {}
goto() {
return this.page.goto('/account/security');
}
private get expandE2EESectionButton() {
return this.page.getByRole('button', { name: 'End-to-end encryption' });
}
private get resetE2EEPasswordButton() {
return this.page.getByRole('button', { name: 'Reset E2EE key' });
}
private get newE2EEPasswordInput() {
return this.page.getByRole('textbox', { name: 'New encryption password' });
}
private get confirmNewE2EEPasswordInput() {
return this.page.getByRole('textbox', { name: 'Confirm new encryption password' });
}
private get saveChangesButton() {
return this.page.getByRole('button', { name: 'Save changes' });
}
private get closeButton() {
return this.page.getByRole('navigation').getByRole('button', { name: 'Close' });
}
async resetE2EEPassword() {
await this.expandE2EESectionButton.click();
await this.resetE2EEPasswordButton.click();
// Logged out
}
async setE2EEPassword(newPassword: string) {
await this.expandE2EESectionButton.click();
await this.newE2EEPasswordInput.fill(newPassword);
await this.confirmNewE2EEPasswordInput.fill(newPassword);
await this.saveChangesButton.click();
}
async close() {
await this.closeButton.click();
}
}

@ -0,0 +1,47 @@
import { HomeContent, HomeFlextab } from './fragments';
import { DisableRoomEncryptionModal, EnableRoomEncryptionModal } from './fragments/e2ee';
import { Message } from './fragments/message';
export class EncryptedRoomPage extends HomeContent {
get encryptedIcon() {
return this.page.locator('.rcx-room-header i.rcx-icon--name-key');
}
get encryptionNotReadyIndicator() {
return this.page.getByText("You're sending an unencrypted message");
}
get lastMessage() {
return new Message(this.page.locator('[data-qa-type="message"]').last());
}
lastNthMessage(index: number) {
return new Message(this.page.locator(`[data-qa-type="message"]`).nth(-index - 1));
}
async enableEncryption() {
const tabs = new HomeFlextab(this.page);
const enableRoomEncryptionModal = new EnableRoomEncryptionModal(this.page);
await tabs.kebab.click();
await tabs.btnEnableE2E.click();
await enableRoomEncryptionModal.enable();
}
async disableEncryption() {
const tabs = new HomeFlextab(this.page);
const disableRoomEncryptionModal = new DisableRoomEncryptionModal(this.page);
await tabs.kebab.click();
await tabs.btnDisableE2E.click();
await disableRoomEncryptionModal.disable();
}
async showExportMessagesTab() {
const tabs = new HomeFlextab(this.page);
await tabs.kebab.click();
await tabs.btnExportMessages.click();
}
}

@ -0,0 +1,125 @@
import type { Locator, Page } from '@playwright/test';
import { Modal } from './modal';
import { ToastMessages } from './toast-messages';
import { expect } from '../../utils/test';
abstract class E2EEBanner {
constructor(protected root: Locator) {}
click() {
return this.root.click();
}
async waitForDisappearance() {
await expect(this.root).not.toBeVisible();
}
}
export class SaveE2EEPasswordBanner extends E2EEBanner {
constructor(page: Page) {
super(page.getByRole('button', { name: 'Save your encryption password' }));
}
}
export class EnterE2EEPasswordBanner extends E2EEBanner {
constructor(page: Page) {
// TODO: there is a typo in the default translation
super(page.getByRole('button', { name: 'Enter your E2E password' }));
}
}
export class E2EEKeyDecodeFailureBanner extends E2EEBanner {
constructor(page: Page) {
super(page.getByRole('button', { name: "Wasn't possible to decode your encryption key to be imported." }));
}
async expectToNotBeVisible() {
await expect(this.root).not.toBeVisible();
}
}
export class SaveE2EEPasswordModal extends Modal {
private readonly toastMessages: ToastMessages;
constructor(page: Page) {
super(page.getByRole('dialog', { name: 'Save your encryption password' }));
this.toastMessages = new ToastMessages(page);
}
private get password() {
return this.root.getByLabel('Your E2EE password is:').getByRole('code');
}
private get savedPasswordButton() {
return this.root.getByRole('button', { name: 'I saved my password' });
}
async getPassword() {
return (await this.password.textContent()) ?? '';
}
async confirm() {
await this.savedPasswordButton.click();
await this.waitForDismissal();
await this.toastMessages.dismissToast('success');
}
}
export class EnterE2EEPasswordModal extends Modal {
constructor(page: Page) {
super(page.getByRole('dialog', { name: 'Enter E2EE password' }));
}
private get passwordInput() {
return this.root.getByPlaceholder('Please enter your E2EE password');
}
private get enterE2EEPasswordButton() {
return this.root.getByRole('button', { name: 'Enable encryption' });
}
async enterPassword(password: string) {
await this.passwordInput.fill(password);
await this.enterE2EEPasswordButton.click();
await this.waitForDismissal();
}
}
export class EnableRoomEncryptionModal extends Modal {
private readonly toastMessages: ToastMessages;
constructor(page: Page) {
super(page.getByRole('dialog', { name: 'Enable encryption' }));
this.toastMessages = new ToastMessages(page);
}
private get enableButton() {
return this.root.getByRole('button', { name: 'Enable encryption' });
}
async enable() {
await this.enableButton.click();
await this.waitForDismissal();
await this.toastMessages.dismissToast('success');
}
}
export class DisableRoomEncryptionModal extends Modal {
private readonly toastMessages: ToastMessages;
constructor(page: Page) {
super(page.getByRole('dialog', { name: 'Disable encryption' }));
this.toastMessages = new ToastMessages(page);
}
private get disableButton() {
return this.root.getByRole('button', { name: 'Disable encryption' });
}
async disable() {
await this.disableButton.click();
await this.waitForDismissal();
await this.toastMessages.dismissToast('success');
}
}

@ -0,0 +1,34 @@
import type { Page } from '@playwright/test';
import { Modal } from './modal';
export class FileUploadModal extends Modal {
constructor(page: Page) {
super(page.getByRole('dialog', { name: 'File Upload' }));
}
private get fileNameInput() {
return this.root.getByRole('textbox', { name: 'File name' });
}
private get fileDescriptionInput() {
return this.root.getByRole('textbox', { name: 'File description' });
}
private get sendButton() {
return this.root.getByRole('button', { name: 'Send' });
}
setName(fileName: string) {
return this.fileNameInput.fill(fileName);
}
setDescription(description: string) {
return this.fileDescriptionInput.fill(description);
}
async send() {
await this.sendButton.click();
await this.waitForDismissal();
}
}

@ -489,4 +489,8 @@ export class HomeContent {
get btnDismissContactUnknownCallout() {
return this.contactUnknownCallout.getByRole('button', { name: 'Dismiss' });
}
async expectLastMessageToHaveText(text: string): Promise<void> {
await expect(this.lastUserMessageBody).toHaveText(text);
}
}

@ -1,5 +1,6 @@
import type { Locator, Page } from '@playwright/test';
import { ToastMessages } from './toast-messages';
import { expect } from '../../utils/test';
export class HomeSidenav {
@ -211,10 +212,14 @@ export class HomeSidenav {
}
async createEncryptedChannel(name: string) {
const toastMessages = new ToastMessages(this.page);
await this.openNewByLabel('Channel');
await this.inputChannelName.fill(name);
await this.advancedSettingsAccordion.click();
await this.checkboxEncryption.click();
await this.btnCreate.click();
await toastMessages.dismissToast('success');
}
}

@ -0,0 +1,17 @@
import type { Locator } from '@playwright/test';
export class Message {
constructor(public readonly root: Locator) {}
get body() {
return this.root.locator('[data-qa-type="message-body"]');
}
get fileUploadName() {
return this.root.locator('[data-qa-type="attachment-title-link"]');
}
get encryptedIcon() {
return this.root.locator('.rcx-icon--name-key');
}
}

@ -0,0 +1,11 @@
import type { Locator } from '@playwright/test';
import { expect } from '../../utils/test';
export abstract class Modal {
constructor(protected root: Locator) {}
waitForDismissal() {
return expect(this.root).not.toBeVisible();
}
}

@ -0,0 +1,14 @@
import type { Page } from '@playwright/test';
export class ToastMessages {
constructor(private readonly page: Page) {}
private readonly toastByType = {
success: this.page.locator('.rcx-toastbar.rcx-toastbar--success'),
};
async dismissToast(type: 'success') {
await this.toastByType[type].locator('button >> i.rcx-icon--name-cross.rcx-icon').click();
await this.page.mouse.move(0, 0);
}
}

@ -0,0 +1,48 @@
import type { Page } from '@playwright/test';
import type { IUser } from '@rocket.chat/core-typings';
import { MongoClient } from 'mongodb';
import * as constants from '../config/constants';
import type { IUserState } from '../fixtures/userStates';
import { expect } from '../utils/test';
export class LoginPage {
constructor(protected readonly page: Page) {}
get loginButton() {
return this.page.getByRole('button', { name: 'Login' });
}
/** @deprecated ideally the previous action should ensure the user is logged out and we should just assume to be at the login page */
async waitForIt() {
await this.loginButton.waitFor();
}
protected async waitForLogin() {
await expect(this.loginButton).not.toBeVisible();
}
async loginByUserState(userState: IUserState) {
// Creates a login token for the user
const connection = await MongoClient.connect(constants.URL_MONGODB);
await connection
.db()
.collection<IUser>('users')
.updateOne(
{ username: userState.data.username },
{ $addToSet: { 'services.resume.loginTokens': { when: userState.data.loginExpire, hashedToken: userState.data.hashedToken } } },
);
await connection.close();
// Injects the login token to the local storage
await this.page.evaluate((items) => {
items.forEach(({ name, value }) => {
window.localStorage.setItem(name, value);
});
}, userState.state.origins[0].localStorage);
await this.waitForLogin();
}
}
Loading…
Cancel
Save