Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@

## Further comments
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->

<!--
E2E reviewers: if this PR touches apps/meteor/tests/e2e/, check the anti-patterns flagged in
apps/meteor/tests/e2e/README.md#anti-patterns-to-flag-in-review
(UI-driven setup in beforeAll/beforeEach, serial suites re-creating their context per test,
etc.). See docs/proposals/e2e-performance-migration.md for the rollout plan.
-->

4 changes: 4 additions & 0 deletions apps/meteor/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export default {
outputDir: 'tests/e2e/.playwright',
reporter: [
['list'],
// JSON reporter — consumed by the weekly timing guardrail workflow
// (scripts/e2e-timing-report.mts) and by anyone wanting to analyse
// per-test durations locally. Small file, safe to always emit.
['json', { outputFile: 'tests/e2e/.playwright/results.json' }],
process.env.REPORTER_ROCKETCHAT_REPORT === 'true' && [
'./reporters/rocketchat.ts',
{
Expand Down
42 changes: 22 additions & 20 deletions apps/meteor/tests/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,26 +245,28 @@ Do **not** apply when:

## API helpers for state seeding

Prefer these helpers in `beforeAll` / `beforeEach` and in setup `test.step`s. All live under `apps/meteor/tests/e2e/utils/`.

| Intent | Helper | REST endpoint |
| ------------------------------------ | --------------------------------------------------------- | ------------------------- |
| Create public channel | `createTargetChannel(api)` | `/channels.create` |
| Create public channel (full room) | `createTargetChannelAndReturnFullRoom(api)` | `/channels.create` |
| Create private channel | `createTargetPrivateChannel(api)` | `/groups.create` |
| Create private group (full room) | `createTargetGroupAndReturnFullRoom(api)` | `/groups.create` |
| Create team | `createTargetTeam(api)` | `/teams.create` |
| Create discussion (fresh parent) | `createTargetDiscussion(api)` | `/rooms.createDiscussion` |
| Create discussion on existing msg | `createDiscussion(api, parentRoomId, parentMsgId, name)` | `/rooms.createDiscussion` |
| Create DM room (get id back) | `createDirectMessageRoom(api, username)` | `/im.create` |
| Send message to a room | `sendMessage(api, roomId, msg)` | `/chat.sendMessage` |
| Send message inside a thread | `sendMessage(api, roomId, msg, parentMsgId)` | `/chat.sendMessage` |
| Send message as a specific user | `sendMessageFromUser(request, user, rid, msg)` | `/chat.postMessage` |
| Delete channel (by name) | `deleteChannel(api, roomName)` | `/channels.delete` |
| Delete room (by id) | `deleteRoom(api, roomId)` | `/rooms.delete` |
| Delete team | `deleteTeam(api, teamName)` | `/teams.delete` |

If the helper you need is missing, add it under `utils/` and re-export it from `utils/index.ts` rather than inlining the REST call in the spec.
Prefer these helpers in `beforeAll` / `beforeEach` and in setup `test.step`s. All live under `apps/meteor/tests/e2e/utils/`, split by concern (`channels.ts`, `groups.ts`, `teams.ts`, `direct-messages.ts`, `discussions.ts`, `messages.ts`, `rooms.ts`). Import from `./utils` — never reach into a specific file.

| Intent | Helper | REST endpoint |
| ------------------------------------ | --------------------------------------------------------------- | ------------------------- |
| Create public channel | `createTargetChannel(api)` | `/channels.create` |
| Create public channel (full room) | `createTargetChannelAndReturnFullRoom(api)` | `/channels.create` |
| Create private channel | `createTargetPrivateChannel(api)` | `/groups.create` |
| Create private group (full room) | `createTargetGroupAndReturnFullRoom(api)` | `/groups.create` |
| Create team | `createTargetTeam(api)` | `/teams.create` |
| Create discussion (fresh parent) | `createTargetDiscussion(api)` | `/rooms.createDiscussion` |
| Create discussion on existing msg | `createDiscussion(api, parentRoomId, parentMsgId, name)` | `/rooms.createDiscussion` |
| Create DM room (get id back) | `createDirectMessageRoom(api, username)` | `/im.create` |
| Send message to a room | `sendMessage(api, roomId, msg)` | `/chat.sendMessage` |
| Send message inside a thread | `createThreadReply(api, roomId, parentMsgId, msg)` | `/chat.sendMessage` |
| Send message as a specific user | `sendMessage(api, roomId, msg, { asUser: user })` | `/chat.sendMessage` |
| Invite users to a room (by username) | `inviteUsersToRoom(api, roomId, usernames)` | `/channels.invite` or `/groups.invite` |
| Set room topic | `setRoomTopic(api, roomId, topic)` | `/rooms.saveRoomSettings` |
| Delete channel (by name) | `deleteChannel(api, roomName)` | `/channels.delete` |
| Delete room (by id) | `deleteRoom(api, roomId)` | `/rooms.delete` |
| Delete team | `deleteTeam(api, teamName)` | `/teams.delete` |

If the helper you need is missing, add it under the matching file in `utils/` and re-export it from `utils/index.ts` rather than inlining the REST call in the spec.

## Template: optimized `.serial` suite

Expand Down
24 changes: 16 additions & 8 deletions apps/meteor/tests/e2e/account-security.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { faker } from '@faker-js/faker';
import type { BrowserContext, Page } from 'playwright-core';

import { ADMIN_CREDENTIALS } from './config/constants';
import { Users } from './fixtures/userStates';
import { AccountSecurity } from './page-objects';
import { setSettingValueById, updateOwnUserPassword } from './utils';
import { test, expect } from './utils/test';

test.use({ storageState: Users.admin.state });

const RANDOM_PASSWORD = faker.helpers
.shuffle([
faker.string.alpha({ casing: 'upper' }),
Expand All @@ -20,22 +19,31 @@ const RANDOM_PASSWORD = faker.helpers

test.describe.serial('account-security', () => {
let poAccountSecurity: AccountSecurity;
let page: Page;
let context: BrowserContext;

test.beforeEach(async ({ page, api }) => {
test.beforeAll(async ({ browser }) => {
context = await browser.newContext({ storageState: Users.admin.state });
page = await context.newPage();
poAccountSecurity = new AccountSecurity(page);
});

test.beforeEach(async ({ api }) => {
await page.goto('/account/security');
await page.waitForSelector('#main-content');
await setSettingValueById(api, 'Accounts_Password_Policy_Enabled', false);
});

test.afterAll(async ({ api }) =>
Promise.all([
test.afterAll(async ({ api }) => {
await Promise.all([
setSettingValueById(api, 'Accounts_AllowPasswordChange', true),
setSettingValueById(api, 'Accounts_TwoFactorAuthentication_Enabled', true),
setSettingValueById(api, 'E2E_Enable', false),
setSettingValueById(api, 'Accounts_Password_Policy_Enabled', true),
]),
);
]);
await page.close();
await context.close();
});

test('should disable and enable email 2FA', async () => {
await poAccountSecurity.security2FASection.click();
Expand Down Expand Up @@ -70,7 +78,7 @@ test.describe.serial('account-security', () => {
]);
});

test('security tab is invisible when password change, 2FA and E2E are disabled', async ({ page }) => {
test('security tab is invisible when password change, 2FA and E2E are disabled', async () => {
const securityTab = poAccountSecurity.sidebar.linkSecurity;
await expect(securityTab).not.toBeVisible();
const mainContent = page.locator('#main-content').getByText('You are not authorized to view this page.').first();
Expand Down
30 changes: 20 additions & 10 deletions apps/meteor/tests/e2e/admin-room.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { faker } from '@faker-js/faker';
import type { BrowserContext, Page } from 'playwright-core';

import { Users } from './fixtures/userStates';
import { AdminInfo, AdminRooms, AdminSectionsHref } from './page-objects';
Expand All @@ -12,28 +13,37 @@ test.describe.serial('admin-rooms', () => {
let privateRoom: string;
let adminRooms: AdminRooms;
let adminInfo: AdminInfo;
let page: Page;
let context: BrowserContext;

test.beforeEach(async ({ page }) => {
test.beforeAll(async ({ browser, api }) => {
[channel, privateRoom] = await Promise.all([createTargetChannel(api), createTargetPrivateChannel(api)]);
context = await browser.newContext({ storageState: Users.admin.state });
page = await context.newPage();
adminRooms = new AdminRooms(page);
await page.goto('/admin/rooms');
});

test.beforeAll(async ({ api }) => {
[channel, privateRoom] = await Promise.all([createTargetChannel(api), createTargetPrivateChannel(api)]);
test.afterAll(async () => {
await page.close();
await context.close();
});

test.beforeEach(async () => {
await page.goto('/admin/rooms');
});

test('should display the Rooms Table', async ({ page }) => {
test('should display the Rooms Table', async () => {
await expect(page.getByRole('main').getByRole('heading', { level: 1, name: 'Rooms', exact: true })).toBeVisible();
await expect(page.getByRole('main').getByRole('table')).toBeVisible();
});

test('should filter room by name', async ({ page }) => {
test('should filter room by name', async () => {
await adminRooms.inputSearchRooms.fill(channel);

await expect(page.locator(`[qa-room-name="${channel}"]`)).toBeVisible();
});

test('should filter rooms by type', async ({ page }) => {
test('should filter rooms by type', async () => {
const dropdown = await adminRooms.dropdownFilterRoomType();
await dropdown.click();

Expand All @@ -48,7 +58,7 @@ test.describe.serial('admin-rooms', () => {
await expect(page.locator('text=Private Channel').first()).toBeVisible();
});

test('should filter rooms by type and name', async ({ page }) => {
test('should filter rooms by type and name', async () => {
await adminRooms.inputSearchRooms.fill(privateRoom);

const dropdown = await adminRooms.dropdownFilterRoomType();
Expand All @@ -59,7 +69,7 @@ test.describe.serial('admin-rooms', () => {
await expect(page.locator(`[qa-room-name="${privateRoom}"]`)).toBeVisible();
});

test('should be empty in case of the search does not find any room', async ({ page }) => {
test('should be empty in case of the search does not find any room', async () => {
const nonExistingChannel = faker.string.alpha(10);

await adminRooms.inputSearchRooms.fill(nonExistingChannel);
Expand All @@ -72,7 +82,7 @@ test.describe.serial('admin-rooms', () => {
await expect(page.locator('text=No results found')).toBeVisible();
});

test('should filter rooms by type and name and clean the filter after changing section', async ({ page }) => {
test('should filter rooms by type and name and clean the filter after changing section', async () => {
adminInfo = new AdminInfo(page);

await adminRooms.inputSearchRooms.fill(privateRoom);
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/tests/e2e/apps/app-modal-interaction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Users } from '../fixtures/userStates';
import { HomeChannel } from '../page-objects';
import { createTargetChannel } from '../utils/create-target-channel';
import { createTargetChannel } from '../utils';
import { test, expect } from '../utils/test';

test.use({ storageState: Users.admin.state });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import { EncryptedRoomPage } from '../page-objects/encrypted-room';
import { Navbar } from '../page-objects/fragments';
import { FileUploadModal } from '../page-objects/fragments/modals';
import { LoginPage } from '../page-objects/login';
import { createTargetGroupAndReturnFullRoom, deleteChannel, deleteRoom } from '../utils';
import { createTargetGroupAndReturnFullRoom, deleteChannel, deleteRoom, sendMessage } from '../utils';
import { preserveSettings } from '../utils/preserveSettings';
import { sendMessageFromUser } from '../utils/sendMessage';
import { test, expect } from '../utils/test';

const settingsList = ['E2E_Enable', 'E2E_Allow_Unencrypted_Messages'];
Expand Down Expand Up @@ -165,11 +164,7 @@ test.describe('E2EE Encryption and Decryption - Basic Features', () => {
await deleteChannel(api, targetChannelName);
});

test('expect to not crash and not show quote message for a message_link which is not accessible to the user', async ({
page,
request,
api,
}) => {
test('expect to not crash and not show quote message for a message_link which is not accessible to the user', async ({ page, api }) => {
const encryptedRoomPage = new EncryptedRoomPage(page);
targetChannelName = faker.string.uuid();

Expand All @@ -191,9 +186,9 @@ test.describe('E2EE Encryption and Decryption - Basic Features', () => {
targetRoomId = user1Channel._id;

// send a message to the private group, which is not accessible to the main user
const sentMessage = (await sendMessageFromUser(request, Users.user2, targetRoomId, 'This is a test message.')).message;
const sentMessageId = await sendMessage(api, targetRoomId, 'This is a test message.', { asUser: Users.user2 });

const messageLink = `${BASE_URL}/group/${user1Channel.name}?msg=${sentMessage._id}`;
const messageLink = `${BASE_URL}/group/${user1Channel.name}?msg=${sentMessageId}`;

await encryptedRoomPage.sendMessage(`This is a message with message link - ${messageLink}`);

Expand Down
20 changes: 14 additions & 6 deletions apps/meteor/tests/e2e/email-inboxes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { faker } from '@faker-js/faker';
import type { BrowserContext, Page } from 'playwright-core';

import { Users } from './fixtures/userStates';
import { AdminEmailInboxes } from './page-objects';
Expand All @@ -8,12 +9,23 @@ test.use({ storageState: Users.admin.state });

test.describe.serial('email-inboxes', () => {
let poAdminEmailInboxes: AdminEmailInboxes;
let page: Page;
let context: BrowserContext;

const email = faker.internet.email();

test.beforeEach(async ({ page }) => {
test.beforeAll(async ({ browser }) => {
context = await browser.newContext({ storageState: Users.admin.state });
page = await context.newPage();
poAdminEmailInboxes = new AdminEmailInboxes(page);
});

test.afterAll(async () => {
await page.close();
await context.close();
});

test.beforeEach(async () => {
await page.goto('/admin/email-inboxes');
});

Expand All @@ -40,12 +52,8 @@ test.describe.serial('email-inboxes', () => {
await expect(poAdminEmailInboxes.itemRow(name)).toBeVisible();
});

test('expect delete an email inbox', async ({ page }) => {
test('expect delete an email inbox', async () => {
await poAdminEmailInboxes.deleteEmailInboxByName(email);
// await poAdminEmailInboxes.itemRow(email).click();
// await poAdminEmailInboxes.btnDelete.click();
// await poUtils.btnModalConfirmDelete.click();
// await expect(poUtils.toastBarSuccess).toBeVisible();

await expect(page.locator('text=No results found')).toBeVisible();
});
Expand Down
32 changes: 23 additions & 9 deletions apps/meteor/tests/e2e/emojis.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { BrowserContext, Page } from 'playwright-core';

import { Users } from './fixtures/userStates';
import { HomeChannel, AdminEmoji } from './page-objects';
import { createTargetChannel } from './utils';
import { createTargetChannelAndReturnFullRoom, deleteRoom } from './utils';
import { test, expect } from './utils/test';

test.use({ storageState: Users.admin.state });
Expand All @@ -9,18 +11,30 @@ test.describe.serial('emoji', () => {
let poHomeChannel: HomeChannel;
let poAdminEmoji: AdminEmoji;
let targetChannel: string;

test.beforeAll(async ({ api }) => {
targetChannel = await createTargetChannel(api);
let targetChannelId: string;
let page: Page;
let context: BrowserContext;

test.beforeAll(async ({ api, browser }) => {
const { channel } = await createTargetChannelAndReturnFullRoom(api);
targetChannel = channel.name!;
targetChannelId = channel._id;
context = await browser.newContext({ storageState: Users.admin.state });
page = await context.newPage();
poHomeChannel = new HomeChannel(page);
});

test.beforeEach(async ({ page }) => {
poHomeChannel = new HomeChannel(page);
test.afterAll(async ({ api }) => {
await deleteRoom(api, targetChannelId);
await page.close();
await context.close();
});

test.beforeEach(async () => {
await page.goto('/home');
});

test('should display emoji picker properly', async ({ page }) => {
test('should display emoji picker properly', async () => {
await poHomeChannel.navbar.openChat(targetChannel);
await poHomeChannel.composer.btnEmoji.click();

Expand All @@ -45,7 +59,7 @@ test.describe.serial('emoji', () => {
});
});

test('expect send emoji via text', async ({ page }) => {
test('expect send emoji via text', async () => {
await poHomeChannel.navbar.openChat(targetChannel);
await poHomeChannel.content.sendMessage(':innocent:');
await page.keyboard.press('Enter');
Expand All @@ -60,7 +74,7 @@ test.describe.serial('emoji', () => {
await expect(poHomeChannel.content.lastUserMessage).toContainText('® © ™ # *');
});

test('should add a custom emoji, send it, rename it, and check render', async ({ page }) => {
test('should add a custom emoji, send it, rename it, and check render', async () => {
const emojiName = 'customemoji';
const newEmojiName = 'renamedemoji';
const emojiUrl = './tests/e2e/fixtures/files/test-image.jpeg';
Expand Down
Loading
Loading