From 7a410742ad6c1256bee39db79af3e303e7dd4edc Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 11:32:21 -0300 Subject: [PATCH 01/12] chore: (a11y) add `aria-label` to message composer --- .../client/views/room/composer/messageBox/MessageBox.tsx | 2 +- packages/i18n/src/locales/en.i18n.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index 5c2fb4519a125..60e26d06e57ae 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -421,7 +421,7 @@ const MessageBox = ({ isMobile={isMobile} /> {isRecordingVideo && } - + {isRecordingAudio && } Date: Thu, 8 Jan 2026 11:33:18 -0300 Subject: [PATCH 02/12] test: create `Composer` class --- .../e2e/page-objects/fragments/composer.ts | 43 +++++++++++++++++++ .../tests/e2e/page-objects/fragments/index.ts | 1 + 2 files changed, 44 insertions(+) create mode 100644 apps/meteor/tests/e2e/page-objects/fragments/composer.ts diff --git a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts new file mode 100644 index 0000000000000..a8c9b9b2e75ab --- /dev/null +++ b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts @@ -0,0 +1,43 @@ +import type { Locator, Page } from 'playwright-core'; + +export abstract class Composer { + constructor( + protected root: Locator, + protected page?: Page, + ) { + this.root = root; + this.page = page; + } + + get inputMessage(): Locator { + return this.root.locator('[name="msg"]'); + } + + get toolbarPrimaryActions(): Locator { + return this.root.getByRole('toolbar', { name: 'Composer Primary Actions' }); + } + + get allPrimaryActions(): Locator { + return this.toolbarPrimaryActions.getByRole('button'); + } + + get btnComposerEmoji(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Emoji' }); + } + + get btnJoinRoom(): Locator { + return this.root.getByRole('button', { name: 'Join' }); + } +} + +export class MessageComposer extends Composer { + constructor(page: Page) { + super(page.getByRole('group', { name: 'Message composer' })); + } +} + +export class ThreadMessageComposer extends Composer { + constructor(page: Page) { + super(page.getByRole('dialog', { name: 'thread' }).getByRole('group', { name: 'Message composer' })); + } +} diff --git a/apps/meteor/tests/e2e/page-objects/fragments/index.ts b/apps/meteor/tests/e2e/page-objects/fragments/index.ts index 51ba6595b6d06..f1f349b6a82ab 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/index.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/index.ts @@ -12,3 +12,4 @@ export * from './toast-messages'; export * from './export-messages-tab'; export * from './menu'; export * from './toolbar'; +export * from './composer'; From 8ff4bcb8db55d567c178f085aea04ecba1f2cb81 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 11:34:09 -0300 Subject: [PATCH 03/12] test: update message composer references in tests and page objects --- apps/meteor/tests/e2e/embedded-layout.spec.ts | 26 +++++----- apps/meteor/tests/e2e/emojis.spec.ts | 8 +-- .../meteor/tests/e2e/message-composer.spec.ts | 26 +++++----- .../page-objects/fragments/home-content.ts | 50 ++++--------------- .../tests/e2e/page-objects/home-channel.ts | 48 ++++++++++++++---- 5 files changed, 80 insertions(+), 78 deletions(-) diff --git a/apps/meteor/tests/e2e/embedded-layout.spec.ts b/apps/meteor/tests/e2e/embedded-layout.spec.ts index 648eefa4d4ec9..ad15da51437b5 100644 --- a/apps/meteor/tests/e2e/embedded-layout.spec.ts +++ b/apps/meteor/tests/e2e/embedded-layout.spec.ts @@ -65,8 +65,8 @@ test.describe('embedded-layout', () => { await poHomeChannel.navbar.openChat(targetChannelId); await page.goto(embeddedLayoutURL(page.url())); - await expect(poHomeChannel.composer).toBeVisible(); - await expect(poHomeChannel.composer).toBeEnabled(); + await expect(poHomeChannel.composer.inputMessage).toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); await expect(poHomeChannel.btnJoinRoom).not.toBeVisible(); }); @@ -85,14 +85,14 @@ test.describe('embedded-layout', () => { await poHomeChannel.navbar.openChat(targetChannelId); await page.goto(embeddedLayoutURL(page.url())); - await expect(poHomeChannel.composer).toBeVisible(); - await expect(poHomeChannel.composerToolbar).toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).toBeVisible(); + await expect(poHomeChannel.composer.toolbarPrimaryActions).toBeVisible(); - await poHomeChannel.composer.fill('Test message'); - await expect(poHomeChannel.composer).toHaveValue('Test message'); + await poHomeChannel.composer.inputMessage.fill('Test message'); + await expect(poHomeChannel.composer.inputMessage).toHaveValue('Test message'); - await poHomeChannel.composer.fill(''); - await expect(poHomeChannel.composer).toHaveValue(''); + await poHomeChannel.composer.inputMessage.fill(''); + await expect(poHomeChannel.composer.inputMessage).toHaveValue(''); }); }); @@ -102,7 +102,7 @@ test.describe('embedded-layout', () => { await poHomeChannel.navbar.openChat(notMemberChannelId); await page.goto(embeddedLayoutURL(page.url())); - await expect(poHomeChannel.composer).toBeDisabled(); + await expect(poHomeChannel.composer.inputMessage).toBeDisabled(); await expect(poHomeChannel.btnJoinRoom).toBeVisible(); }); @@ -114,8 +114,8 @@ test.describe('embedded-layout', () => { await poHomeChannel.btnJoinRoom.click(); await expect(poHomeChannel.btnJoinRoom).not.toBeVisible(); - await expect(poHomeChannel.composer).toBeVisible(); - await expect(poHomeChannel.composer).toBeEnabled(); + await expect(poHomeChannel.composer.inputMessage).toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); const joinMessage = `Joined and sent message ${Date.now()}`; await poHomeChannel.content.sendMessage(joinMessage); @@ -130,8 +130,8 @@ test.describe('embedded-layout', () => { await poHomeChannel.navbar.openChat(Users.user2.data.username); await page.goto(embeddedLayoutURL(page.url())); - await expect(poHomeChannel.composer).toBeVisible(); - await expect(poHomeChannel.composer).toBeEnabled(); + await expect(poHomeChannel.composer.inputMessage).toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); await expect(poHomeChannel.btnJoinRoom).not.toBeVisible(); const dmMessage = `Embedded DM test ${Date.now()}`; diff --git a/apps/meteor/tests/e2e/emojis.spec.ts b/apps/meteor/tests/e2e/emojis.spec.ts index 34ad63a1f6120..345cbf0b6b9a9 100644 --- a/apps/meteor/tests/e2e/emojis.spec.ts +++ b/apps/meteor/tests/e2e/emojis.spec.ts @@ -22,14 +22,14 @@ test.describe.serial('emoji', () => { test('should display emoji picker properly', async ({ page }) => { await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.content.btnComposerEmoji.click(); + await poHomeChannel.composer.btnComposerEmoji.click(); await test.step('should display scroller', async () => { - await expect(poHomeChannel.content.scrollerEmojiPicker).toBeVisible(); + await expect(poHomeChannel.scrollerEmojiPicker).toBeVisible(); }); await test.step('should focus the active emoji tab category', async () => { - const activityEmojiTab = poHomeChannel.content.getEmojiPickerTabByName('Activity'); + const activityEmojiTab = poHomeChannel.getEmojiPickerTabByName('Activity'); await activityEmojiTab.click(); await expect(activityEmojiTab).toBeFocused(); @@ -37,7 +37,7 @@ test.describe.serial('emoji', () => { await test.step('should pick and send grinning emoji', async () => { await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.content.pickEmoji('grinning'); + await poHomeChannel.pickEmoji('grinning'); await page.keyboard.press('Enter'); await expect(poHomeChannel.content.lastUserMessage).toContainText('😀'); diff --git a/apps/meteor/tests/e2e/message-composer.spec.ts b/apps/meteor/tests/e2e/message-composer.spec.ts index 9819bc5646088..02f4748f8fe75 100644 --- a/apps/meteor/tests/e2e/message-composer.spec.ts +++ b/apps/meteor/tests/e2e/message-composer.spec.ts @@ -25,14 +25,14 @@ test.describe.serial('message-composer', () => { await poHomeChannel.navbar.openChat(targetChannel); await poHomeChannel.content.sendMessage('hello composer'); - await expect(poHomeChannel.composerToolbarActions).toHaveCount(12); + await expect(poHomeChannel.composer.allPrimaryActions).toHaveCount(12); }); test('should have only the main formatter and the main action', async ({ page }) => { await page.setViewportSize({ width: 768, height: 600 }); await poHomeChannel.navbar.openChat(targetChannel); - await expect(poHomeChannel.composerToolbarActions).toHaveCount(6); + await expect(poHomeChannel.composer.allPrimaryActions).toHaveCount(6); }); test('should navigate on toolbar using arrow keys', async ({ page }) => { @@ -41,10 +41,10 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Tab'); await page.keyboard.press('ArrowRight'); await page.keyboard.press('ArrowRight'); - await expect(poHomeChannel.composerToolbar.getByRole('button', { name: 'Italic' })).toBeFocused(); + await expect(poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Italic' })).toBeFocused(); await page.keyboard.press('ArrowLeft'); - await expect(poHomeChannel.composerToolbar.getByRole('button', { name: 'Bold' })).toBeFocused(); + await expect(poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Bold' })).toBeFocused(); }); test('should move the focus away from toolbar using tab key', async ({ page }) => { @@ -53,7 +53,7 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); - await expect(poHomeChannel.composerToolbar.getByRole('button', { name: 'Emoji' })).not.toBeFocused(); + await expect(poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Emoji' })).not.toBeFocused(); }); test('should add a link to the selected text', async ({ page }) => { @@ -63,11 +63,11 @@ test.describe.serial('message-composer', () => { await page.keyboard.type('hello composer'); await page.keyboard.press('Control+A'); // on Windows and Linux await page.keyboard.press('Meta+A'); // on macOS - await poHomeChannel.composerToolbar.getByRole('button', { name: 'Link' }).click(); + await poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Link' }).click(); await page.keyboard.type(url); await page.keyboard.press('Enter'); - await expect(poHomeChannel.composer).toHaveValue(`[hello composer](${url})`); + await expect(poHomeChannel.composer.inputMessage).toHaveValue(`[hello composer](${url})`); }); test('should select popup item and not send the message when pressing enter', async ({ page }) => { @@ -79,9 +79,9 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Enter'); - await expect(poHomeChannel.composer).toHaveValue('hello composer @all '); + await expect(poHomeChannel.composer.inputMessage).toHaveValue('hello composer @all '); - await poHomeChannel.composer.fill(''); + await poHomeChannel.composer.inputMessage.fill(''); }); await test.step('emoji popup', async () => { @@ -89,9 +89,9 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Enter'); - await expect(poHomeChannel.composer).toHaveValue('hello composer :flag_br: '); + await expect(poHomeChannel.composer.inputMessage).toHaveValue('hello composer :flag_br: '); - await poHomeChannel.composer.fill(''); + await poHomeChannel.composer.inputMessage.fill(''); }); await test.step('slash command', async () => { @@ -99,9 +99,9 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Enter'); - await expect(poHomeChannel.composer).toHaveValue('/gimme '); + await expect(poHomeChannel.composer.inputMessage).toHaveValue('/gimme '); - await poHomeChannel.composer.fill(''); + await poHomeChannel.composer.inputMessage.fill(''); }); }); 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 e5b1741d3047a..ff7b2c60c7b48 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -3,6 +3,7 @@ import { resolve, join, relative } from 'node:path'; import type { Locator, Page } from '@playwright/test'; +import { MessageComposer, ThreadMessageComposer } from './composer'; import { expect } from '../../utils/test'; const FIXTURES_PATH = relative(process.cwd(), resolve(__dirname, '../../fixtures/files')); @@ -10,12 +11,17 @@ const FIXTURES_PATH = relative(process.cwd(), resolve(__dirname, '../../fixtures export function getFilePath(fileName: string): string { return join(FIXTURES_PATH, fileName); } - export class HomeContent { protected readonly page: Page; + protected readonly composer: MessageComposer; + + protected readonly threadComposer: ThreadMessageComposer; + constructor(page: Page) { this.page = page; + this.composer = new MessageComposer(page); + this.threadComposer = new ThreadMessageComposer(page); } get channelHeader(): Locator { @@ -30,14 +36,6 @@ export class HomeContent { return this.page.locator('main').getByRole('alert', { name: 'Retention policy warning banner' }); } - get inputMessage(): Locator { - return this.page.locator('[name="msg"]'); - } - - get inputThreadMessage(): Locator { - return this.page.getByRole('dialog').locator('[name="msg"]').last(); - } - get messagePopupUsers(): Locator { return this.page.locator('role=menu[name="People"]'); } @@ -83,7 +81,7 @@ export class HomeContent { } async joinRoomIfNeeded(): Promise { - if (await this.inputMessage.isEnabled()) { + if (await this.composer.inputMessage.isEnabled()) { return; } if (!(await this.btnJoinRoom.isVisible())) { @@ -359,32 +357,6 @@ export class HomeContent { return this.imageGallery.locator(`button[name="${name}"]`); } - get btnComposerEmoji(): Locator { - return this.page.locator('role=toolbar[name="Composer Primary Actions"] >> role=button[name="Emoji"]'); - } - - get dialogEmojiPicker(): Locator { - return this.page.getByRole('dialog', { name: 'Emoji picker' }); - } - - get scrollerEmojiPicker(): Locator { - return this.dialogEmojiPicker.locator('[data-overlayscrollbars]'); - } - - getEmojiPickerTabByName(name: string) { - return this.dialogEmojiPicker.locator(`role=tablist >> role=tab[name="${name}"]`); - } - - getEmojiByName(name: string) { - return this.dialogEmojiPicker.locator(`role=tabpanel >> role=button[name="${name}"]`); - } - - async pickEmoji(emoji: string, section = 'Smileys & People') { - await this.btnComposerEmoji.click(); - await this.getEmojiPickerTabByName(section).click(); - await this.getEmojiByName(emoji).click(); - } - async dragAndDropTxtFile(): Promise { const contract = await fs.readFile(getFilePath('any_file.txt'), 'utf-8'); const dataTransfer = await this.page.evaluateHandle((contract) => { @@ -396,7 +368,7 @@ export class HomeContent { return data; }, contract); - await this.inputMessage.dispatchEvent('dragenter', { dataTransfer }); + await this.composer.inputMessage.dispatchEvent('dragenter', { dataTransfer }); await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer }); } @@ -412,7 +384,7 @@ export class HomeContent { return data; }, contract); - await this.inputMessage.dispatchEvent('dragenter', { dataTransfer }); + await this.composer.inputMessage.dispatchEvent('dragenter', { dataTransfer }); await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer }); } @@ -428,7 +400,7 @@ export class HomeContent { return data; }, contract); - await this.inputThreadMessage.dispatchEvent('dragenter', { dataTransfer }); + await this.threadComposer.inputMessage.dispatchEvent('dragenter', { dataTransfer }); await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer }); } diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index 478ca3d2f84b6..3458a83055d50 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -1,6 +1,15 @@ import type { Locator, Page } from '@playwright/test'; -import { HomeContent, HomeFlextab, Navbar, Sidepanel, RoomSidebar, ToastMessages } from './fragments'; +import { + HomeContent, + HomeFlextab, + Navbar, + Sidepanel, + RoomSidebar, + ToastMessages, + MessageComposer, + ThreadMessageComposer, +} from './fragments'; import { RoomToolbar } from './fragments/toolbar'; import { VoiceCalls } from './fragments/voice-calls'; @@ -23,6 +32,10 @@ export class HomeChannel { readonly toastMessage: ToastMessages; + readonly composer: MessageComposer; + + readonly threadComposer: ThreadMessageComposer; + constructor(page: Page) { this.page = page; this.content = new HomeContent(page); @@ -33,6 +46,8 @@ export class HomeChannel { this.roomToolbar = new RoomToolbar(page); this.voiceCalls = new VoiceCalls(page); this.toastMessage = new ToastMessages(page); + this.composer = new MessageComposer(page); + this.threadComposer = new ThreadMessageComposer(page); } goto() { @@ -43,10 +58,7 @@ export class HomeChannel { return this.page.locator('[data-qa="ContextualbarActionClose"]'); } - get composer(): Locator { - return this.page.locator('textarea[name="msg"]'); - } - + // TODO: move to Composer fragment get composerBoxPopup(): Locator { return this.page.locator('[role="menu"][name="ComposerBoxPopup"]'); } @@ -59,10 +71,6 @@ export class HomeChannel { return this.page.locator('[role=toolbar][aria-label="Composer Primary Actions"]'); } - get composerToolbarActions(): Locator { - return this.page.locator('[role=toolbar][aria-label="Composer Primary Actions"] button'); - } - get roomHeaderFavoriteBtn(): Locator { return this.page.getByRole('main').getByRole('button', { name: 'Favorite' }); } @@ -123,6 +131,28 @@ export class HomeChannel { return this.page.locator('main').getByRole('heading', { name: 'Home' }); } + get dialogEmojiPicker(): Locator { + return this.page.getByRole('dialog', { name: 'Emoji picker' }); + } + + get scrollerEmojiPicker(): Locator { + return this.dialogEmojiPicker.locator('[data-overlayscrollbars]'); + } + + getEmojiPickerTabByName(name: string) { + return this.dialogEmojiPicker.locator(`role=tablist >> role=tab[name="${name}"]`); + } + + getEmojiByName(name: string) { + return this.dialogEmojiPicker.locator(`role=tabpanel >> role=button[name="${name}"]`); + } + + async pickEmoji(emoji: string, section = 'Smileys & People') { + await this.composer.inputMessage.click(); + await this.getEmojiPickerTabByName(section).click(); + await this.getEmojiByName(emoji).click(); + } + async waitForHome(): Promise { await this.homepageHeader.waitFor({ state: 'visible' }); } From 0c1fc458cd0367958a9049a824c12555a1898270 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 12:51:39 -0300 Subject: [PATCH 04/12] test: update message composer references in E2E tests --- .../e2ee-encrypted-channels.spec.ts | 4 +-- .../e2ee-file-encryption.spec.ts | 4 +-- .../e2ee-passphrase-management.spec.ts | 10 +++--- apps/meteor/tests/e2e/embedded-layout.spec.ts | 10 +++--- .../meteor/tests/e2e/message-mentions.spec.ts | 2 +- apps/meteor/tests/e2e/messaging.spec.ts | 6 ++-- ...nichannel-auto-onhold-chat-closing.spec.ts | 2 +- ...omnichannel-canned-responses-usage.spec.ts | 12 +++---- .../omnichannel-manager-role.spec.ts | 14 ++++---- ...mnichannel-manual-selection-logout.spec.ts | 4 +-- .../omnichannel-manual-selection.spec.ts | 6 ++-- .../omnichannel-monitor-role.spec.ts | 14 ++++---- .../omnichannel/omnichannel-takeChat.spec.ts | 2 +- .../e2e/page-objects/fragments/composer.ts | 12 +++++++ .../page-objects/fragments/home-content.ts | 34 ++++++++----------- .../fragments/home-omnichannel-content.ts | 8 ++--- .../tests/e2e/page-objects/home-channel.ts | 4 --- apps/meteor/tests/e2e/permissions.spec.ts | 4 +-- apps/meteor/tests/e2e/quote-messages.spec.ts | 2 +- apps/meteor/tests/e2e/voice-calls-ee.spec.ts | 8 ++--- 20 files changed, 80 insertions(+), 82 deletions(-) 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 index 1ebcd2e7068a1..4e5708d56aa5d 100644 --- a/apps/meteor/tests/e2e/e2e-encryption/e2ee-encrypted-channels.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption/e2ee-encrypted-channels.spec.ts @@ -345,7 +345,7 @@ test.describe('E2EE Encrypted Channels', () => { await poHomeChannel.content.openLastMessageMenu(); await poHomeChannel.content.btnOptionEditMessage.click(); - await poHomeChannel.content.inputMessage.fill(editedMessage); + await poHomeChannel.composer.inputMessage.fill(editedMessage); await page.keyboard.press('Enter'); @@ -369,7 +369,7 @@ test.describe('E2EE Encrypted Channels', () => { await poHomeChannel.content.openLastMessageMenu(); await poHomeChannel.content.btnOptionEditMessage.click(); - await poHomeChannel.content.inputMessage.fill(editedMessage); + await poHomeChannel.composer.inputMessage.fill(editedMessage); await page.keyboard.press('Enter'); 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 4f6b77b883b39..f32a7bd028e18 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 @@ -64,9 +64,9 @@ test.describe('E2EE File Encryption', () => { await poHomeChannel.content.openLastMessageMenu(); await poHomeChannel.content.btnOptionEditMessage.click(); - expect(await poHomeChannel.composer.inputValue()).toBe('any_description'); + expect(await poHomeChannel.composer.inputMessage.inputValue()).toBe('any_description'); - await poHomeChannel.content.inputMessage.fill('edited any_description'); + await poHomeChannel.composer.inputMessage.fill('edited any_description'); await page.keyboard.press('Enter'); await expect(poHomeChannel.content.getFileDescription).toHaveText('edited any_description'); 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 dd5f1e8f00e71..1e6ae7b9a957c 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 @@ -228,7 +228,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await expect(poHomeChannel.roomToolbar.btnMembers).toBeVisible(); await expect(poHomeChannel.roomToolbar.btnRoomInfo).toBeVisible(); - await expect(poHomeChannel.content.inputMessage).not.toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).not.toBeVisible(); await poHomeChannel.btnRoomSaveE2EEPassword.click(); @@ -239,7 +239,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poHomeChannel.btnSavedMyPassword.click(); - await poHomeChannel.content.inputMessage.waitFor(); + await poHomeChannel.composer.inputMessage.waitFor(); await poHomeChannel.content.sendMessage('hello world'); @@ -273,7 +273,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await expect(poHomeChannel.roomToolbar.btnMembers).toBeVisible(); await expect(poHomeChannel.roomToolbar.btnRoomInfo).toBeVisible(); - await expect(poHomeChannel.content.inputMessage).not.toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).not.toBeVisible(); await poHomeChannel.btnRoomEnterE2EEPassword.click(); @@ -283,7 +283,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await expect(poHomeChannel.bannerEnterE2EEPassword).not.toBeVisible(); - await poHomeChannel.content.inputMessage.waitFor(); + await poHomeChannel.composer.inputMessage.waitFor(); // For E2EE to complete init setup await page.waitForTimeout(300); @@ -334,7 +334,7 @@ test.describe.serial('E2EE Passphrase Management - Room Setup States', () => { await poHomeChannel.btnRoomSaveE2EEPassword.click(); await poHomeChannel.btnSavedMyPassword.click(); - await expect(poHomeChannel.content.inputMessage).not.toBeVisible(); + await expect(poHomeChannel.composer.inputMessage).not.toBeVisible(); await expect(page.locator('.rcx-states__title')).toContainText('Check back later'); await poHomeChannel.roomToolbar.btnDisableE2EEncryption.waitFor(); diff --git a/apps/meteor/tests/e2e/embedded-layout.spec.ts b/apps/meteor/tests/e2e/embedded-layout.spec.ts index ad15da51437b5..c834d96c17da8 100644 --- a/apps/meteor/tests/e2e/embedded-layout.spec.ts +++ b/apps/meteor/tests/e2e/embedded-layout.spec.ts @@ -67,7 +67,7 @@ test.describe('embedded-layout', () => { await expect(poHomeChannel.composer.inputMessage).toBeVisible(); await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); - await expect(poHomeChannel.btnJoinRoom).not.toBeVisible(); + await expect(poHomeChannel.composer.btnJoinRoom).not.toBeVisible(); }); test('should allow sending and receiving messages', async ({ page }) => { @@ -103,7 +103,7 @@ test.describe('embedded-layout', () => { await page.goto(embeddedLayoutURL(page.url())); await expect(poHomeChannel.composer.inputMessage).toBeDisabled(); - await expect(poHomeChannel.btnJoinRoom).toBeVisible(); + await expect(poHomeChannel.composer.btnJoinRoom).toBeVisible(); }); test('should allow joining channel and enable messaging', async ({ page }) => { @@ -111,9 +111,9 @@ test.describe('embedded-layout', () => { await poHomeChannel.navbar.openChat(joinChannelId); await page.goto(embeddedLayoutURL(page.url())); - await poHomeChannel.btnJoinRoom.click(); + await poHomeChannel.composer.btnJoinRoom.click(); - await expect(poHomeChannel.btnJoinRoom).not.toBeVisible(); + await expect(poHomeChannel.composer.btnJoinRoom).not.toBeVisible(); await expect(poHomeChannel.composer.inputMessage).toBeVisible(); await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); @@ -132,7 +132,7 @@ test.describe('embedded-layout', () => { await expect(poHomeChannel.composer.inputMessage).toBeVisible(); await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); - await expect(poHomeChannel.btnJoinRoom).not.toBeVisible(); + await expect(poHomeChannel.composer.btnJoinRoom).not.toBeVisible(); const dmMessage = `Embedded DM test ${Date.now()}`; await poHomeChannel.content.sendMessage(dmMessage); diff --git a/apps/meteor/tests/e2e/message-mentions.spec.ts b/apps/meteor/tests/e2e/message-mentions.spec.ts index 9780572dde7ba..87ca04206bad3 100644 --- a/apps/meteor/tests/e2e/message-mentions.spec.ts +++ b/apps/meteor/tests/e2e/message-mentions.spec.ts @@ -109,7 +109,7 @@ test.describe.serial('message-mentions', () => { test('expect show "all" and "here" options', async () => { await poHomeChannel.navbar.openChat('general'); - await poHomeChannel.content.inputMessage.type('@'); + await poHomeChannel.composer.inputMessage.type('@'); await expect(poHomeChannel.content.messagePopupUsers.locator('role=listitem >> text="all"')).toBeVisible(); await expect(poHomeChannel.content.messagePopupUsers.locator('role=listitem >> text="here"')).toBeVisible(); diff --git a/apps/meteor/tests/e2e/messaging.spec.ts b/apps/meteor/tests/e2e/messaging.spec.ts index 74cffb18fb6bc..c6b781aaa2fca 100644 --- a/apps/meteor/tests/e2e/messaging.spec.ts +++ b/apps/meteor/tests/e2e/messaging.spec.ts @@ -87,7 +87,7 @@ test.describe('Messaging', () => { .waitFor(); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); - await expect(poHomeChannel.composer).toBeFocused(); + await expect(poHomeChannel.composer.inputMessage).toBeFocused(); }); }); @@ -124,7 +124,7 @@ test.describe('Messaging', () => { test('should not restore focus on the last focused if it was triggered by click', async ({ page }) => { await poHomeChannel.navbar.openChat(targetChannel); await page.locator('[data-qa-type="message"]:has-text("msg1")').click(); - await poHomeChannel.composer.click(); + await poHomeChannel.composer.inputMessage.click(); await page.keyboard.press('Shift+Tab'); await expect(page.locator('[data-qa-type="message"]:has-text("msg2")')).toBeFocused(); @@ -165,7 +165,7 @@ test.describe('Messaging', () => { await test.step('focus on the second message', async () => { await page.keyboard.press('ArrowUp'); - expect(await poHomeChannel.composer.inputValue()).toBe('msg2'); + expect(await poHomeChannel.composer.inputMessage.inputValue()).toBe('msg2'); }); await test.step('send edited message', async () => { diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts index e8fb0caa49f6b..56875ec87f0dc 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts @@ -64,7 +64,7 @@ test.describe('omnichannel-auto-onhold-chat-closing', () => { await expect(agent.poHomeChannel.content.lastSystemMessageBody).toHaveText( `Chat On Hold: The chat was manually placed On Hold by user1`, ); - await expect(agent.poHomeChannel.content.inputMessage).not.toBeVisible(); + await expect(agent.poHomeChannel.composer.inputMessage).not.toBeVisible(); await expect(agent.poHomeChannel.content.resumeOnHoldOmnichannelChatButton).toBeVisible(); // current url diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-usage.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-usage.spec.ts index 9ece2ff55f997..15daec85c8070 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-usage.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-usage.spec.ts @@ -107,7 +107,7 @@ test.describe('OC - Canned Responses Usage', () => { }); await test.step('expect canned response text to appear in message composer', async () => { - await expect(agent.poHomeChannel.content.inputMessage).toHaveValue(`${cannedResponseText} `); + await expect(agent.poHomeChannel.composer.inputMessage).toHaveValue(`${cannedResponseText} `); }); await test.step('expect agent to be able to send the canned response message', async () => { @@ -162,11 +162,11 @@ test.describe('OC - Canned Responses Usage', () => { }); await test.step('expect to modify the canned response text before sending', async () => { - await agent.poHomeChannel.content.inputMessage.click(); + await agent.poHomeChannel.composer.inputMessage.click(); await agent.page.keyboard.press('End'); - await agent.poHomeChannel.content.inputMessage.pressSequentially(modifiedText); + await agent.poHomeChannel.composer.inputMessage.pressSequentially(modifiedText); - await expect(agent.poHomeChannel.content.inputMessage).toHaveValue(`${cannedResponseText} ${modifiedText}`); + await expect(agent.poHomeChannel.composer.inputMessage).toHaveValue(`${cannedResponseText} ${modifiedText}`); }); await test.step('expect to send the modified message', async () => { @@ -193,13 +193,13 @@ test.describe('OC - Canned Responses Usage', () => { await test.step('expect to use first canned response', async () => { await agent.poHomeChannel.content.useCannedResponse(cannedResponseName); - await expect(agent.poHomeChannel.content.inputMessage).toHaveValue(`${cannedResponseText} `); + await expect(agent.poHomeChannel.composer.inputMessage).toHaveValue(`${cannedResponseText} `); await agent.page.keyboard.press('Enter'); }); await test.step('expect to use second canned response', async () => { await agent.poHomeChannel.content.useCannedResponse(secondResponseName); - await expect(agent.poHomeChannel.content.inputMessage).toHaveValue(`${secondResponseText} `); + await expect(agent.poHomeChannel.composer.inputMessage).toHaveValue(`${secondResponseText} `); await agent.page.keyboard.press('Enter'); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts index b6972f52745e3..5df8a4218ab18 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-manager-role.spec.ts @@ -121,13 +121,13 @@ test.describe('OC - Manager Role', () => { await test.step('expect to be able to join chats', async () => { await poOmnichannel.chats.openChat(ROOM_A); - await expect(poOmnichannel.content.btnJoinRoom).toBeVisible(); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.btnJoinRoom).toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); - await poOmnichannel.content.btnJoinRoom.click(); + await poOmnichannel.composer.btnJoinRoom.click(); await expect(poOmnichannel.content.lastSystemMessageBody).toHaveText('joined the channel'); - await expect(poOmnichannel.content.btnJoinRoom).not.toBeVisible(); - await expect(poOmnichannel.content.inputMessage).toBeVisible(); + await expect(poOmnichannel.composer.btnJoinRoom).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).toBeVisible(); }); await test.step('expect to be able to put a conversation from another agent on hold', async () => { @@ -135,14 +135,14 @@ test.describe('OC - Manager Role', () => { await expect(poOmnichannel.content.lastSystemMessageBody).toHaveText( `Chat On Hold: The chat was manually placed On Hold by ${MANAGER}`, ); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); await expect(poOmnichannel.content.btnResume).toBeVisible(); }); await test.step('expect to be able resume a conversation from another agent on hold', async () => { await poOmnichannel.content.btnResume.click(); await expect(poOmnichannel.content.btnResume).not.toBeVisible(); - await expect(poOmnichannel.content.inputMessage).toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).toBeVisible(); await expect(poOmnichannel.quickActionsRoomToolbar.btnOnHold).toBeVisible(); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection-logout.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection-logout.spec.ts index f62ed89b20042..28d0c4be2ffa1 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection-logout.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection-logout.spec.ts @@ -56,7 +56,7 @@ test.describe('OC - Manual Selection After Relogin', () => { await test.step('expect login and see the chat in queue after login', async () => { await poOmnichannel.sidebar.getSidebarItemByName(room.fname).click(); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); }); await test.step('expect take chat to be visible and return to queue not visible', async () => { @@ -67,7 +67,7 @@ test.describe('OC - Manual Selection After Relogin', () => { await test.step('expect to be able take chat', async () => { await poOmnichannel.content.btnTakeChat.click(); await expect(poOmnichannel.content.lastSystemMessageBody).toHaveText('joined the channel'); - await expect(poOmnichannel.content.inputMessage).toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).toBeVisible(); await expect(poOmnichannel.content.btnTakeChat).not.toBeVisible(); await expect(poOmnichannel.content.btnReturnToQueue).toBeVisible(); await expect(poOmnichannel.sidebar.getSidebarItemByName(room.fname)).toBeVisible(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection.spec.ts index 6e249d0924ac2..1a96b2f8eaab7 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-manual-selection.spec.ts @@ -68,14 +68,14 @@ test.describe('OC - Manual Selection', () => { await test.step('expect to be able join chat in read mode', async () => { await poOmnichannel.sidebar.getSidebarItemByName(room.fname).click(); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); await expect(poOmnichannel.content.btnTakeChat).toBeVisible(); }); await test.step('expect to be able take chat', async () => { await poOmnichannel.content.btnTakeChat.click(); await expect(poOmnichannel.content.lastSystemMessageBody).toHaveText('joined the channel'); - await expect(poOmnichannel.content.inputMessage).toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).toBeVisible(); await expect(poOmnichannel.content.btnTakeChat).not.toBeVisible(); await expect(poOmnichannel.sidebar.getSidebarItemByName(room.fname)).toBeVisible(); }); @@ -96,7 +96,7 @@ test.describe('OC - Manual Selection', () => { await expect(agentB.poHomeOmnichannel.sidebar.getSidebarItemByName(room.fname)).toBeVisible(); await poOmnichannel.sidebar.getSidebarItemByName(room.fname).click(); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); await expect(poOmnichannel.content.btnTakeChat).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-monitor-role.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-monitor-role.spec.ts index 53832b328d94a..618b63c13b1b2 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-monitor-role.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-monitor-role.spec.ts @@ -173,13 +173,13 @@ test.describe('OC - Monitor Role', () => { await test.step('expect to be able to join chats from same unit', async () => { await poOmnichannel.chats.openChat(ROOM_A); - await expect(poOmnichannel.content.btnJoinRoom).toBeVisible(); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.btnJoinRoom).toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); - await poOmnichannel.content.btnJoinRoom.click(); + await poOmnichannel.composer.btnJoinRoom.click(); await expect(poOmnichannel.content.lastSystemMessageBody).toHaveText('joined the channel'); - await expect(poOmnichannel.content.btnJoinRoom).not.toBeVisible(); - await expect(poOmnichannel.content.inputMessage).toBeVisible(); + await expect(poOmnichannel.composer.btnJoinRoom).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).toBeVisible(); }); await test.step('expect to be able to put a conversation from another agent on hold', async () => { @@ -187,14 +187,14 @@ test.describe('OC - Monitor Role', () => { await expect(poOmnichannel.content.lastSystemMessageBody).toHaveText( `Chat On Hold: The chat was manually placed On Hold by ${MONITOR}`, ); - await expect(poOmnichannel.content.inputMessage).not.toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).not.toBeVisible(); await expect(poOmnichannel.content.btnResume).toBeVisible(); }); await test.step('expect to be able resume a conversation from another agent on hold', async () => { await poOmnichannel.content.btnResume.click(); await expect(poOmnichannel.content.btnResume).not.toBeVisible(); - await expect(poOmnichannel.content.inputMessage).toBeVisible(); + await expect(poOmnichannel.composer.inputMessage).toBeVisible(); await expect(poOmnichannel.quickActionsRoomToolbar.btnOnHold).toBeVisible(); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts index 78626f77d70b2..2b11fdad694a0 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts @@ -63,7 +63,7 @@ test.describe('omnichannel-takeChat', () => { await agent.poHomeChannel.navbar.openChat(newVisitor.name); await expect(agent.poHomeChannel.content.btnTakeChat).not.toBeVisible(); - await expect(agent.poHomeChannel.content.inputMessage).toBeVisible(); + await expect(agent.poHomeChannel.composer.inputMessage).toBeVisible(); }); test('When agent is offline should not take the chat', async () => { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts index a8c9b9b2e75ab..5d32aa7656af4 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts @@ -28,6 +28,18 @@ export abstract class Composer { get btnJoinRoom(): Locator { return this.root.getByRole('button', { name: 'Join' }); } + + get btnSend(): Locator { + return this.root.getByRole('button', { name: 'Send', exact: true }); + } + + get btnOptionFileUpload(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Upload file' }); + } + + get btnVideoMessage(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Video message' }); + } } export class MessageComposer extends Composer { 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 ff7b2c60c7b48..0e15d6f434706 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -11,6 +11,7 @@ const FIXTURES_PATH = relative(process.cwd(), resolve(__dirname, '../../fixtures export function getFilePath(fileName: string): string { return join(FIXTURES_PATH, fileName); } + export class HomeContent { protected readonly page: Page; @@ -72,19 +73,15 @@ export class HomeContent { return this.lastUserMessageBody.locator('role=button[name="This message was ignored"]'); } - get btnJoinRoom(): Locator { - return this.page.locator('role=button[name="Join"]'); - } - async joinRoom(): Promise { - await this.btnJoinRoom.click(); + await this.composer.btnJoinRoom.click(); } async joinRoomIfNeeded(): Promise { if (await this.composer.inputMessage.isEnabled()) { return; } - if (!(await this.btnJoinRoom.isVisible())) { + if (!(await this.composer.btnJoinRoom.isVisible())) { return; } await this.joinRoom(); @@ -92,8 +89,8 @@ export class HomeContent { async sendMessage(text: string, enforce = true): Promise { await this.joinRoomIfNeeded(); - await this.page.waitForSelector('[name="msg"]:not([disabled])'); - await this.page.locator('[name="msg"]').fill(text); + await expect(this.composer.inputMessage).toBeEnabled(); + await this.composer.inputMessage.fill(text); if (enforce) { const responsePromise = this.page.waitForResponse( @@ -111,15 +108,16 @@ export class HomeContent { await expect(messageLocator).toBeVisible(); await expect(messageLocator).not.toHaveClass('rcx-message--pending'); } else { - await this.page.getByRole('button', { name: 'Send', exact: true }).click(); + await this.composer.btnSend.click(); } } async dispatchSlashCommand(text: string): Promise { await this.joinRoomIfNeeded(); - await this.page.waitForSelector('[name="msg"]:not([disabled])'); - await this.page.locator('[name="msg"]').fill(''); - await this.page.locator('[name="msg"]').fill(text); + await expect(this.composer.inputMessage).toBeEnabled(); + + await this.composer.inputMessage.fill(''); + await this.composer.inputMessage.fill(text); await this.page.keyboard.press('Enter'); await this.page.keyboard.press('Enter'); } @@ -135,6 +133,7 @@ export class HomeContent { await this.page.locator('role=button[name="Forward"]').click(); } + // TODO: use modal fragments ----------------------------------------- get btnModalCancel(): Locator { return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--secondary'); } @@ -185,6 +184,8 @@ export class HomeContent { return this.page.locator('//div[@id="modal-root"]//fieldset//div[1]//span//input'); } + // ----------------------------------------- + get lastMessageFileName(): Locator { return this.page.locator('[data-qa-type="message"]:last-child [data-qa-type="attachment-title-link"]'); } @@ -265,6 +266,7 @@ export class HomeContent { return this.page.locator('div.thread-list ul.thread [data-qa-type="message"]').last().locator('[data-qa-type="attachment-title-link"]'); } + // TODO: improve locator specificity get menuMore(): Locator { return this.page.getByRole('menu', { name: 'More', exact: true }); } @@ -289,14 +291,6 @@ export class HomeContent { return this.menuMore.getByRole('menuitem', { name: 'Star', exact: true }); } - get btnOptionFileUpload(): Locator { - return this.page.locator('[data-qa-id="file-upload"]'); - } - - get btnVideoMessage(): Locator { - return this.page.locator('[data-qa-id="video-message"]'); - } - get btnVoiceCall(): Locator { return this.primaryRoomActionsToolbar.getByRole('button', { name: 'Voice call' }); } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts index b15aebfaa611b..47630b06220ad 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-omnichannel-content.ts @@ -31,10 +31,6 @@ export class HomeOmnichannelContent extends HomeContent { return this.page.locator('role=button[name="Take it!"]'); } - override get inputMessage(): Locator { - return this.page.locator('[name="msg"]'); - } - get contactContextualBar() { return this.page.getByRole('dialog', { name: 'Contact' }); } @@ -60,9 +56,9 @@ export class HomeOmnichannelContent extends HomeContent { } async useCannedResponse(cannedResponseName: string): Promise { - await this.inputMessage.pressSequentially('!'); + await this.composer.inputMessage.pressSequentially('!'); await this.page.locator('[role="menu"][name="ComposerBoxPopup"]').waitFor({ state: 'visible' }); - await this.inputMessage.pressSequentially(cannedResponseName); + await this.composer.inputMessage.pressSequentially(cannedResponseName); await this.page.keyboard.press('Enter'); } } diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index 3458a83055d50..d07545c818ac7 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -119,10 +119,6 @@ export class HomeChannel { return this.page.getByRole('group', { name: 'Audio recorder', exact: true }); } - get btnJoinRoom(): Locator { - return this.page.getByRole('button', { name: 'Join' }); - } - get statusUploadIndicator(): Locator { return this.page.getByRole('main').getByRole('status'); } diff --git a/apps/meteor/tests/e2e/permissions.spec.ts b/apps/meteor/tests/e2e/permissions.spec.ts index 5ac528ee175cd..4c7c87579f8e2 100644 --- a/apps/meteor/tests/e2e/permissions.spec.ts +++ b/apps/meteor/tests/e2e/permissions.spec.ts @@ -138,7 +138,7 @@ test.describe.serial('permissions', () => { test('expect option (upload file) not be visible', async () => { await poHomeChannel.navbar.openChat(targetChannel); - await expect(poHomeChannel.content.btnOptionFileUpload).toBeDisabled(); + await expect(poHomeChannel.composer.btnOptionFileUpload).toBeDisabled(); }); test.afterAll(async ({ api }) => { @@ -176,7 +176,7 @@ test.describe.serial('permissions', () => { test('expect option (upload video) not be visible', async () => { await poHomeChannel.navbar.openChat(targetChannel); - await expect(poHomeChannel.content.btnVideoMessage).toBeDisabled(); + await expect(poHomeChannel.composer.btnVideoMessage).toBeDisabled(); }); test.afterAll(async ({ api }) => { diff --git a/apps/meteor/tests/e2e/quote-messages.spec.ts b/apps/meteor/tests/e2e/quote-messages.spec.ts index 1723237b91ba9..72d944be30091 100644 --- a/apps/meteor/tests/e2e/quote-messages.spec.ts +++ b/apps/meteor/tests/e2e/quote-messages.spec.ts @@ -53,7 +53,7 @@ test.describe.serial('Quote Messages', () => { await poHomeChannel.content.lastUserMessage.hover(); await poHomeChannel.content.openLastMessageMenu(); await poHomeChannel.content.btnOptionEditMessage.click(); - await poHomeChannel.content.inputMessage.fill(editedQuoteText); + await poHomeChannel.composer.inputMessage.fill(editedQuoteText); await page.keyboard.press('Enter'); }); diff --git a/apps/meteor/tests/e2e/voice-calls-ee.spec.ts b/apps/meteor/tests/e2e/voice-calls-ee.spec.ts index c6bb05cb4c003..d1931f81eaa26 100644 --- a/apps/meteor/tests/e2e/voice-calls-ee.spec.ts +++ b/apps/meteor/tests/e2e/voice-calls-ee.spec.ts @@ -29,7 +29,7 @@ test.describe('Internal Voice Calls - Enterprise Edition', () => { await test.step('should open direct message with user2', async () => { await user1.poHomeChannel.navbar.openChat('user2'); - await expect(user1.poHomeChannel.content.inputMessage).toBeVisible(); + await expect(user1.poHomeChannel.composer.inputMessage).toBeVisible(); }); await test.step('initiate a voice call from room toolbar', async () => { @@ -53,7 +53,7 @@ test.describe('Internal Voice Calls - Enterprise Edition', () => { const [user1, user2] = sessions; await test.step('establish call connection', async () => { await user1.poHomeChannel.navbar.openChat('user2'); - await expect(user1.poHomeChannel.content.inputMessage).toBeVisible(); + await expect(user1.poHomeChannel.composer.inputMessage).toBeVisible(); await user1.poHomeChannel.content.btnVoiceCall.click(); await user1.poHomeChannel.voiceCalls.initiateCall(); await user2.poHomeChannel.voiceCalls.acceptCall(); @@ -107,7 +107,7 @@ test.describe('Internal Voice Calls - Enterprise Edition', () => { await test.step('establish call between user1 and user2', async () => { await user1.poHomeChannel.navbar.openChat('user2'); - await expect(user1.poHomeChannel.content.inputMessage).toBeVisible(); + await expect(user1.poHomeChannel.composer.inputMessage).toBeVisible(); await user1.poHomeChannel.content.btnVoiceCall.click(); await user1.poHomeChannel.voiceCalls.initiateCall(); await user2.poHomeChannel.voiceCalls.acceptCall(); @@ -138,7 +138,7 @@ test.describe('Internal Voice Calls - Enterprise Edition', () => { await test.step('user1 initiates call to user2', async () => { await user1.poHomeChannel.navbar.openChat('user2'); - await expect(user1.poHomeChannel.content.inputMessage).toBeVisible(); + await expect(user1.poHomeChannel.composer.inputMessage).toBeVisible(); await user1.poHomeChannel.content.btnVoiceCall.click(); await user1.poHomeChannel.voiceCalls.initiateCall(); }); From 6a2a2fbee8a3b01ff066cb180b4c01925607c818 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 16:00:15 -0300 Subject: [PATCH 05/12] test: improve composer root specificity --- .../meteor/client/views/room/body/RoomBody.tsx | 2 +- .../Threads/components/ThreadChat.tsx | 2 +- .../e2e/page-objects/fragments/composer.ts | 18 +++++++++++------- .../e2e/page-objects/fragments/home-content.ts | 16 ++++++---------- .../tests/e2e/page-objects/home-channel.ts | 12 ++++++------ packages/i18n/src/locales/en.i18n.json | 4 +++- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/apps/meteor/client/views/room/body/RoomBody.tsx b/apps/meteor/client/views/room/body/RoomBody.tsx index 8e5bd8da996ef..6a9a325f1711f 100644 --- a/apps/meteor/client/views/room/body/RoomBody.tsx +++ b/apps/meteor/client/views/room/body/RoomBody.tsx @@ -274,7 +274,7 @@ const RoomBody = (): ReactElement => { - + { - + { - await this.composer.btnJoinRoom.click(); - } - async joinRoomIfNeeded(): Promise { if (await this.composer.inputMessage.isEnabled()) { return; @@ -84,7 +80,7 @@ export class HomeContent { if (!(await this.composer.btnJoinRoom.isVisible())) { return; } - await this.joinRoom(); + await this.composer.btnJoinRoom.click(); } async sendMessage(text: string, enforce = true): Promise { diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index d07545c818ac7..e95f7132a3933 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -7,8 +7,8 @@ import { Sidepanel, RoomSidebar, ToastMessages, - MessageComposer, - ThreadMessageComposer, + RoomComposer, + ThreadComposer, } from './fragments'; import { RoomToolbar } from './fragments/toolbar'; import { VoiceCalls } from './fragments/voice-calls'; @@ -32,9 +32,9 @@ export class HomeChannel { readonly toastMessage: ToastMessages; - readonly composer: MessageComposer; + readonly composer: RoomComposer; - readonly threadComposer: ThreadMessageComposer; + readonly threadComposer: ThreadComposer; constructor(page: Page) { this.page = page; @@ -46,8 +46,8 @@ export class HomeChannel { this.roomToolbar = new RoomToolbar(page); this.voiceCalls = new VoiceCalls(page); this.toastMessage = new ToastMessages(page); - this.composer = new MessageComposer(page); - this.threadComposer = new ThreadMessageComposer(page); + this.composer = new RoomComposer(page); + this.threadComposer = new ThreadComposer(page); } goto() { diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 075c51428e027..5a8803893ecdf 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -7087,5 +7087,7 @@ "__username__profile_picture": "{{username}}'s profile picture", "User_card": "User card", "timestamps.relativeTimeDescription": "1 year ago", - "Message_composer": "Message composer" + "Message_composer": "Message composer", + "Thread_composer": "Thread composer", + "Room_composer": "Room composer" } \ No newline at end of file From c9304f574efdeea93059a550df3fe4f0227196b8 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 16:00:21 -0300 Subject: [PATCH 06/12] test: update composer references in `channel-management` `image-gallery` `omnichannel` `preview-public-channel` --- apps/meteor/tests/e2e/channel-management.spec.ts | 2 +- apps/meteor/tests/e2e/image-gallery.spec.ts | 4 ++-- .../omnichannel-contact-center-chats.spec.ts | 13 ++++++++----- .../meteor/tests/e2e/preview-public-channel.spec.ts | 4 ++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts index be61f83e2e649..a9c62347b53a3 100644 --- a/apps/meteor/tests/e2e/channel-management.spec.ts +++ b/apps/meteor/tests/e2e/channel-management.spec.ts @@ -210,7 +210,7 @@ test.describe.serial('channel-management', () => { const user1Channel = new HomeChannel(user1Page); await user1Page.goto(`/channel/${targetChannel}`); await user1Channel.content.waitForChannel(); - await expect(user1Channel.composer).toBeVisible(); + await expect(user1Channel.composer.inputMessage).toBeVisible(); }); test('should set user1 as moderator', async () => { diff --git a/apps/meteor/tests/e2e/image-gallery.spec.ts b/apps/meteor/tests/e2e/image-gallery.spec.ts index e7e0171880e59..34a84cab458e1 100644 --- a/apps/meteor/tests/e2e/image-gallery.spec.ts +++ b/apps/meteor/tests/e2e/image-gallery.spec.ts @@ -24,10 +24,10 @@ test.describe.serial('Image Gallery', async () => { poHomeChannel = new HomeChannel(page); await poHomeChannel.navbar.openChat(targetChannelLargeImage); - await poHomeChannel.content.btnJoinRoom.click(); + await poHomeChannel.composer.btnJoinRoom.click(); await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.content.btnJoinRoom.click(); + await poHomeChannel.composer.btnJoinRoom.click(); }); test.afterAll(async ({ api }) => { diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center-chats.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center-chats.spec.ts index 96bb00336be0c..3e46bfc04960b 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center-chats.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center-chats.spec.ts @@ -3,7 +3,7 @@ import type { Page } from '@playwright/test'; import { IS_EE } from '../config/constants'; import { Users } from '../fixtures/userStates'; -import { OmnichannelChats } from '../page-objects'; +import { HomeOmnichannel, OmnichannelChats } from '../page-objects'; import { createAgent, makeAgentAvailable } from '../utils/omnichannel/agents'; import { addAgentToDepartment, createDepartment } from '../utils/omnichannel/departments'; import { createConversation, updateRoom } from '../utils/omnichannel/rooms'; @@ -20,6 +20,7 @@ test.use({ storageState: Users.admin.state }); test.describe('OC - Contact Center Chats [Auto Selection]', async () => { let poOmnichats: OmnichannelChats; + let poHomeOmnichannel: HomeOmnichannel; let departments: Awaited>[]; let conversations: Awaited>[]; let agents: Awaited>[]; @@ -151,13 +152,15 @@ test.describe('OC - Contact Center Chats [Auto Selection]', async () => { }); }); - test('OC - Contact Center Chats - Access in progress conversation from another agent', async () => { + test('OC - Contact Center Chats - Access in progress conversation from another agent', async ({ page }) => { + poHomeOmnichannel = new HomeOmnichannel(page); + await test.step('expect to be able to join', async () => { const { visitor: visitorB } = conversations[1].data; await poOmnichats.openChat(visitorB.name); - await expect(poOmnichats.content.btnJoinRoom).toBeVisible(); - await poOmnichats.content.btnJoinRoom.click(); - await expect(poOmnichats.content.btnJoinRoom).not.toBeVisible(); + await expect(poHomeOmnichannel.composer.btnJoinRoom).toBeVisible(); + await poHomeOmnichannel.composer.btnJoinRoom.click(); + await expect(poHomeOmnichannel.composer.btnJoinRoom).not.toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/preview-public-channel.spec.ts b/apps/meteor/tests/e2e/preview-public-channel.spec.ts index f28b5e7ce1640..b2056fd2c2175 100644 --- a/apps/meteor/tests/e2e/preview-public-channel.spec.ts +++ b/apps/meteor/tests/e2e/preview-public-channel.spec.ts @@ -49,7 +49,7 @@ test.describe('Preview public channel', () => { await poHomeChannel.navbar.openChat(Users.user2.data.username); await expect(poHomeChannel.content.btnJoinChannel).not.toBeVisible(); - await expect(poHomeChannel.composer).toBeEnabled(); + await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); }); test('should not let user role preview public rooms', async ({ api }) => { @@ -74,7 +74,7 @@ test.describe('Preview public channel', () => { await expect(poHomeChannel.content.lastUserMessageBody).toContainText(targetChannelMessage); - await poHomeChannel.btnJoinRoom.click(); + await poHomeChannel.composer.btnJoinRoom.click(); await expect( page.locator('[role="alert"]', { From 9081cf6232106a55e4fd10c5fdf54179a8195ce1 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 16:43:27 -0300 Subject: [PATCH 07/12] test: move more locators to composer class --- .../tests/e2e/channel-management.spec.ts | 4 +-- apps/meteor/tests/e2e/feature-preview.spec.ts | 4 +-- .../meteor/tests/e2e/message-composer.spec.ts | 8 +++--- .../e2e/page-objects/fragments/composer.ts | 28 +++++++++++++++---- .../page-objects/fragments/home-content.ts | 19 ++++--------- .../tests/e2e/page-objects/home-channel.ts | 24 +--------------- apps/meteor/tests/e2e/permissions.spec.ts | 2 +- .../tests/e2e/preview-public-channel.spec.ts | 6 ++-- 8 files changed, 41 insertions(+), 54 deletions(-) diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts index a9c62347b53a3..bd76bafb4ec16 100644 --- a/apps/meteor/tests/e2e/channel-management.spec.ts +++ b/apps/meteor/tests/e2e/channel-management.spec.ts @@ -145,7 +145,7 @@ test.describe.serial('channel-management', () => { test('should create a discussion using the message composer', async ({ page }) => { discussionName = faker.string.uuid(); await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.content.btnMenuMoreActions.click(); + await poHomeChannel.composer.btnMenuMoreActions.click(); await page.getByRole('menuitem', { name: 'Discussion' }).click(); await poHomeChannel.content.inputDiscussionName.fill(discussionName); await poHomeChannel.content.btnCreateDiscussionModal.click(); @@ -196,7 +196,7 @@ test.describe.serial('channel-management', () => { const user1Channel = new HomeChannel(user1Page); await user1Page.goto(`/channel/${targetChannel}`); await user1Channel.content.waitForChannel(); - await expect(user1Channel.readOnlyFooter).toBeVisible(); + await expect(user1Channel.composer.readOnlyFooter).toBeVisible(); }); test('should unmuteUser user1', async () => { diff --git a/apps/meteor/tests/e2e/feature-preview.spec.ts b/apps/meteor/tests/e2e/feature-preview.spec.ts index 840575c5df67b..b0481e8cfdd18 100644 --- a/apps/meteor/tests/e2e/feature-preview.spec.ts +++ b/apps/meteor/tests/e2e/feature-preview.spec.ts @@ -259,7 +259,7 @@ test.describe.serial('feature preview', () => { const discussionName = faker.string.uuid(); - await poHomeChannel.content.btnMenuMoreActions.click(); + await poHomeChannel.composer.btnMenuMoreActions.click(); await page.getByRole('menuitem', { name: 'Discussion' }).click(); await poHomeChannel.content.inputDiscussionName.fill(discussionName); await poHomeChannel.content.btnCreateDiscussionModal.click(); @@ -585,7 +585,7 @@ test.describe.serial('feature preview', () => { }); await test.step('create discussion in DM', async () => { - await poHomeChannel.content.btnMenuMoreActions.click(); + await poHomeChannel.composer.btnMenuMoreActions.click(); await page.getByRole('menuitem', { name: 'Discussion' }).click(); await poHomeChannel.content.inputDiscussionName.fill(discussionName); await poHomeChannel.content.btnCreateDiscussionModal.click(); diff --git a/apps/meteor/tests/e2e/message-composer.spec.ts b/apps/meteor/tests/e2e/message-composer.spec.ts index 02f4748f8fe75..690515cb5a2bc 100644 --- a/apps/meteor/tests/e2e/message-composer.spec.ts +++ b/apps/meteor/tests/e2e/message-composer.spec.ts @@ -112,21 +112,21 @@ test.describe.serial('message-composer', () => { await test.step('mention popup', async () => { await page.keyboard.type('hello composer @rocket.cat'); - await expect(poHomeChannel.composerBoxPopup.getByText('rocket.cat')).toBeVisible(); + await expect(poHomeChannel.composer.boxPopup.getByText('rocket.cat')).toBeVisible(); }); }); test.describe('audio recorder', () => { test('should open audio recorder', async () => { await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.composerToolbar.getByRole('button', { name: 'Audio message', exact: true }).click(); + await poHomeChannel.composer.btnAudioMessage.click(); await expect(poHomeChannel.audioRecorder).toBeVisible(); }); test('should stop recording when clicking on cancel', async () => { await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.composerToolbar.getByRole('button', { name: 'Audio message', exact: true }).click(); + await poHomeChannel.composer.btnAudioMessage.click(); await expect(poHomeChannel.audioRecorder).toBeVisible(); await poHomeChannel.audioRecorder.getByRole('button', { name: 'Cancel recording', exact: true }).click(); @@ -135,7 +135,7 @@ test.describe.serial('message-composer', () => { test('should open file modal when clicking on "Finish recording"', async ({ page }) => { await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.composerToolbar.getByRole('button', { name: 'Audio message', exact: true }).click(); + await poHomeChannel.composer.btnAudioMessage.click(); await expect(poHomeChannel.audioRecorder).toBeVisible(); await page.waitForTimeout(1000); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts index 55d8286b63f09..ead31d82908c8 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts @@ -17,6 +17,10 @@ export abstract class Composer { return this.msgComposer.locator('[name="msg"]'); } + get btnJoinRoom(): Locator { + return this.root.getByRole('button', { name: 'Join' }); + } + get toolbarPrimaryActions(): Locator { return this.msgComposer.getByRole('toolbar', { name: 'Composer Primary Actions' }); } @@ -26,11 +30,11 @@ export abstract class Composer { } get btnComposerEmoji(): Locator { - return this.toolbarPrimaryActions.getByRole('button', { name: 'Emoji' }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'Emoji', exact: true }); } - get btnJoinRoom(): Locator { - return this.root.getByRole('button', { name: 'Join' }); + get btnAudioMessage(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Audio message', exact: true }); } get btnSend(): Locator { @@ -38,11 +42,23 @@ export abstract class Composer { } get btnOptionFileUpload(): Locator { - return this.toolbarPrimaryActions.getByRole('button', { name: 'Upload file' }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'Upload file', exact: true }); } get btnVideoMessage(): Locator { - return this.toolbarPrimaryActions.getByRole('button', { name: 'Video message' }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'Video message', exact: true }); + } + + get btnMenuMoreActions() { + return this.toolbarPrimaryActions.getByRole('button', { name: 'More actions', exact: true }); + } + + get boxPopup(): Locator { + return this.root.locator('[role="menu"][name="ComposerBoxPopup"]'); + } + + get readOnlyFooter(): Locator { + return this.root.getByText('This room is read only'); } } @@ -54,6 +70,6 @@ export class RoomComposer extends Composer { export class ThreadComposer extends Composer { constructor(page: Page) { - super(page.getByRole('dialog', { name: 'thread' }).locator('footer[aria-label="Thread composer"]')); + super(page.locator('footer[aria-label="Thread composer"]')); } } 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 78823f2726428..3f36cc6d13ca7 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts @@ -94,7 +94,7 @@ export class HomeContent { /api\/v1\/method.call\/sendMessage/.test(response.url()) && response.status() === 200 && response.request().method() === 'POST', ); - await this.page.getByRole('button', { name: 'Send', exact: true }).click(); + await this.composer.btnSend.click(); const response = await (await responsePromise).json(); @@ -291,14 +291,6 @@ export class HomeContent { return this.primaryRoomActionsToolbar.getByRole('button', { name: 'Voice call' }); } - get btnRecordAudio(): Locator { - return this.page.locator('[data-qa-id="audio-message"]'); - } - - get btnMenuMoreActions() { - return this.page.getByRole('button', { name: 'More actions', exact: true }); - } - get userCard(): Locator { return this.page.getByRole('dialog', { name: 'User card', exact: true }); } @@ -523,6 +515,11 @@ export class HomeContent { } async sendMessageInThread(text: string): Promise { + await this.threadComposer.inputMessage.fill(text); + await this.threadComposer.btnSend.click(); + } + + async sendMessageInVideoConfPopup(text: string): Promise { await this.page.getByRole('dialog').getByRole('textbox', { name: 'Message' }).fill(text); await this.page.getByRole('dialog').getByRole('button', { name: 'Send', exact: true }).click(); } @@ -539,10 +536,6 @@ export class HomeContent { return this.page.getByRole('button', { name: 'Clear selection' }); } - get btnJoinChannel() { - return this.page.getByRole('button', { name: 'Join channel' }); - } - get contactUnknownCallout() { return this.page.getByRole('status', { name: 'Unknown contact. This contact is not on the contact list.' }); } diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index e95f7132a3933..ef9377167540b 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -1,15 +1,6 @@ import type { Locator, Page } from '@playwright/test'; -import { - HomeContent, - HomeFlextab, - Navbar, - Sidepanel, - RoomSidebar, - ToastMessages, - RoomComposer, - ThreadComposer, -} from './fragments'; +import { HomeContent, HomeFlextab, Navbar, Sidepanel, RoomSidebar, ToastMessages, RoomComposer, ThreadComposer } from './fragments'; import { RoomToolbar } from './fragments/toolbar'; import { VoiceCalls } from './fragments/voice-calls'; @@ -58,27 +49,14 @@ export class HomeChannel { return this.page.locator('[data-qa="ContextualbarActionClose"]'); } - // TODO: move to Composer fragment - get composerBoxPopup(): Locator { - return this.page.locator('[role="menu"][name="ComposerBoxPopup"]'); - } - get userCardToolbar(): Locator { return this.page.locator('[role=toolbar][aria-label="User card actions"]'); } - get composerToolbar(): Locator { - return this.page.locator('[role=toolbar][aria-label="Composer Primary Actions"]'); - } - get roomHeaderFavoriteBtn(): Locator { return this.page.getByRole('main').getByRole('button', { name: 'Favorite' }); } - get readOnlyFooter(): Locator { - return this.page.locator('footer', { hasText: 'This room is read only' }); - } - get roomHeaderToolbar(): Locator { return this.page.locator('[role=toolbar][aria-label="Primary Room actions"]'); } diff --git a/apps/meteor/tests/e2e/permissions.spec.ts b/apps/meteor/tests/e2e/permissions.spec.ts index 4c7c87579f8e2..a2f230c56373c 100644 --- a/apps/meteor/tests/e2e/permissions.spec.ts +++ b/apps/meteor/tests/e2e/permissions.spec.ts @@ -157,7 +157,7 @@ test.describe.serial('permissions', () => { test('expect option (upload audio) not be visible', async () => { await poHomeChannel.navbar.openChat(targetChannel); - await expect(poHomeChannel.content.btnRecordAudio).toBeDisabled(); + await expect(poHomeChannel.composer.btnAudioMessage).toBeDisabled(); }); test.afterAll(async ({ api }) => { diff --git a/apps/meteor/tests/e2e/preview-public-channel.spec.ts b/apps/meteor/tests/e2e/preview-public-channel.spec.ts index b2056fd2c2175..c6c52bb26004d 100644 --- a/apps/meteor/tests/e2e/preview-public-channel.spec.ts +++ b/apps/meteor/tests/e2e/preview-public-channel.spec.ts @@ -48,7 +48,7 @@ test.describe('Preview public channel', () => { await poHomeChannel.navbar.openChat(Users.user2.data.username); - await expect(poHomeChannel.content.btnJoinChannel).not.toBeVisible(); + await expect(poHomeChannel.composer.btnJoinRoom).not.toBeVisible(); await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); }); @@ -58,7 +58,7 @@ test.describe('Preview public channel', () => { await poHomeChannel.navbar.btnDirectory.click(); await poDirectory.openChannel(targetChannel); - await expect(poHomeChannel.content.btnJoinChannel).toBeVisible(); + await expect(poHomeChannel.composer.btnJoinRoom).toBeVisible(); await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible(); }); }); @@ -90,7 +90,7 @@ test.describe('Preview public channel', () => { await poDirectory.openChannel(targetChannel); await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible(); - await poHomeChannel.content.btnJoinChannel.click(); + await poHomeChannel.composer.btnJoinRoom.click(); await expect( page.locator('[role="alert"]', { From 6f9781480be7942ee421e381df997d068e490f27 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 18:01:34 -0300 Subject: [PATCH 08/12] test: update emoji picker interactions and refine composer button locators --- apps/meteor/tests/e2e/emojis.spec.ts | 1 + .../tests/e2e/page-objects/fragments/composer.ts | 10 +++++----- apps/meteor/tests/e2e/page-objects/home-channel.ts | 6 +++++- apps/meteor/tests/e2e/preview-public-channel.spec.ts | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/meteor/tests/e2e/emojis.spec.ts b/apps/meteor/tests/e2e/emojis.spec.ts index 345cbf0b6b9a9..cb99b8869b0eb 100644 --- a/apps/meteor/tests/e2e/emojis.spec.ts +++ b/apps/meteor/tests/e2e/emojis.spec.ts @@ -33,6 +33,7 @@ test.describe.serial('emoji', () => { await activityEmojiTab.click(); await expect(activityEmojiTab).toBeFocused(); + await poHomeChannel.composer.inputMessage.click(); // To close the emoji picker }); await test.step('should pick and send grinning emoji', async () => { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts index ead31d82908c8..6a5bf42ebe25a 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts @@ -34,23 +34,23 @@ export abstract class Composer { } get btnAudioMessage(): Locator { - return this.toolbarPrimaryActions.getByRole('button', { name: 'Audio message', exact: true }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'Audio message' }); } get btnSend(): Locator { - return this.msgComposer.getByRole('button', { name: 'Send', exact: true }); + return this.msgComposer.getByRole('button', { name: 'Send' }); } get btnOptionFileUpload(): Locator { - return this.toolbarPrimaryActions.getByRole('button', { name: 'Upload file', exact: true }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'Upload file' }); } get btnVideoMessage(): Locator { - return this.toolbarPrimaryActions.getByRole('button', { name: 'Video message', exact: true }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'Video message' }); } get btnMenuMoreActions() { - return this.toolbarPrimaryActions.getByRole('button', { name: 'More actions', exact: true }); + return this.toolbarPrimaryActions.getByRole('button', { name: 'More actions' }); } get boxPopup(): Locator { diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index ef9377167540b..b583a4bfabe5c 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -113,6 +113,10 @@ export class HomeChannel { return this.dialogEmojiPicker.locator('[data-overlayscrollbars]'); } + get btnJoinChannel() { + return this.page.getByRole('button', { name: 'Join channel' }); + } + getEmojiPickerTabByName(name: string) { return this.dialogEmojiPicker.locator(`role=tablist >> role=tab[name="${name}"]`); } @@ -122,7 +126,7 @@ export class HomeChannel { } async pickEmoji(emoji: string, section = 'Smileys & People') { - await this.composer.inputMessage.click(); + await this.composer.btnComposerEmoji.click(); await this.getEmojiPickerTabByName(section).click(); await this.getEmojiByName(emoji).click(); } diff --git a/apps/meteor/tests/e2e/preview-public-channel.spec.ts b/apps/meteor/tests/e2e/preview-public-channel.spec.ts index c6c52bb26004d..86e8de34d81e7 100644 --- a/apps/meteor/tests/e2e/preview-public-channel.spec.ts +++ b/apps/meteor/tests/e2e/preview-public-channel.spec.ts @@ -58,7 +58,7 @@ test.describe('Preview public channel', () => { await poHomeChannel.navbar.btnDirectory.click(); await poDirectory.openChannel(targetChannel); - await expect(poHomeChannel.composer.btnJoinRoom).toBeVisible(); + await expect(poHomeChannel.btnJoinChannel).toBeVisible(); await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible(); }); }); From 571cb842c288beb314c691585d4a2a8f9869fa9a Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Thu, 8 Jan 2026 21:28:00 -0300 Subject: [PATCH 09/12] test: update button locator for joining channel in `preview-public-channel` --- apps/meteor/tests/e2e/preview-public-channel.spec.ts | 2 +- packages/i18n/src/locales/en.i18n.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/tests/e2e/preview-public-channel.spec.ts b/apps/meteor/tests/e2e/preview-public-channel.spec.ts index 86e8de34d81e7..12331bc14221c 100644 --- a/apps/meteor/tests/e2e/preview-public-channel.spec.ts +++ b/apps/meteor/tests/e2e/preview-public-channel.spec.ts @@ -90,7 +90,7 @@ test.describe('Preview public channel', () => { await poDirectory.openChannel(targetChannel); await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible(); - await poHomeChannel.composer.btnJoinRoom.click(); + await poHomeChannel.btnJoinChannel.click(); await expect( page.locator('[role="alert"]', { diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 5a8803893ecdf..35e07ad893b3e 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -1014,7 +1014,7 @@ }, "Cam_off": "Cam Off", "Cam_on": "Cam On", - "Camera_access_not_allowed": "Camera access was not allowed, please check your browser settings.", + "Camera_access_not_allowed": "Video message - camera access was not allowed, please check your browser settings.", "Cancel": "Cancel", "Cancel__planName__subscription": "Cancel {{planName}} subscription", "Cancel_message_input": "Cancel", @@ -3402,7 +3402,7 @@ "Message_Audio": "Audio Message", "Message_AudioRecorderEnabled": "Audio Recorder Enabled", "Message_AudioRecorderEnabled_Description": "Requires 'audio/mp3' files to be an accepted media type within 'File Upload' settings.", - "Message_Audio_Recording_Disabled": "Message audio recording disabled", + "Message_Audio_Recording_Disabled": "Audio message - message audio recording disabled", "Message_Audio_bitRate": "Audio Message Bit Rate", "Message_BadWordsFilterList": "Add Bad Words to the Blacklist", "Message_BadWordsFilterListDescription": "Add List of Comma-separated list of bad words to filter", From 86ab056c3e9dab426ec4313a540d4cbb681bda50 Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 9 Jan 2026 18:24:39 -0300 Subject: [PATCH 10/12] test: remove page from composer class --- apps/meteor/tests/e2e/page-objects/fragments/composer.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts index 6a5bf42ebe25a..378ad827af260 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts @@ -1,12 +1,8 @@ import type { Locator, Page } from 'playwright-core'; export abstract class Composer { - constructor( - protected root: Locator, - protected page?: Page, - ) { + constructor(protected root: Locator) { this.root = root; - this.page = page; } private get msgComposer(): Locator { From 3b65ef736f8161c1da678467e5857e2f9f916e6b Mon Sep 17 00:00:00 2001 From: juliajforesti Date: Fri, 9 Jan 2026 18:26:48 -0300 Subject: [PATCH 11/12] chore: remove unnecessary `aria-label` from MessageBox --- .../views/room/composer/messageBox/MessageBox.tsx | 2 +- .../tests/e2e/page-objects/fragments/composer.ts | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx index 60e26d06e57ae..5c2fb4519a125 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBox.tsx @@ -421,7 +421,7 @@ const MessageBox = ({ isMobile={isMobile} /> {isRecordingVideo && } - + {isRecordingAudio && } Date: Fri, 9 Jan 2026 19:50:58 -0300 Subject: [PATCH 12/12] test: add more composer actions to PO --- apps/meteor/tests/e2e/emojis.spec.ts | 2 +- apps/meteor/tests/e2e/message-composer.spec.ts | 8 ++++---- .../tests/e2e/page-objects/fragments/composer.ts | 14 +++++++++++++- apps/meteor/tests/e2e/page-objects/home-channel.ts | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/meteor/tests/e2e/emojis.spec.ts b/apps/meteor/tests/e2e/emojis.spec.ts index cb99b8869b0eb..6f0c99284c525 100644 --- a/apps/meteor/tests/e2e/emojis.spec.ts +++ b/apps/meteor/tests/e2e/emojis.spec.ts @@ -22,7 +22,7 @@ test.describe.serial('emoji', () => { test('should display emoji picker properly', async ({ page }) => { await poHomeChannel.navbar.openChat(targetChannel); - await poHomeChannel.composer.btnComposerEmoji.click(); + await poHomeChannel.composer.btnEmoji.click(); await test.step('should display scroller', async () => { await expect(poHomeChannel.scrollerEmojiPicker).toBeVisible(); diff --git a/apps/meteor/tests/e2e/message-composer.spec.ts b/apps/meteor/tests/e2e/message-composer.spec.ts index 690515cb5a2bc..14d700e5f21d0 100644 --- a/apps/meteor/tests/e2e/message-composer.spec.ts +++ b/apps/meteor/tests/e2e/message-composer.spec.ts @@ -41,10 +41,10 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Tab'); await page.keyboard.press('ArrowRight'); await page.keyboard.press('ArrowRight'); - await expect(poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Italic' })).toBeFocused(); + await expect(poHomeChannel.composer.btnItalicFormatter).toBeFocused(); await page.keyboard.press('ArrowLeft'); - await expect(poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Bold' })).toBeFocused(); + await expect(poHomeChannel.composer.btnBoldFormatter).toBeFocused(); }); test('should move the focus away from toolbar using tab key', async ({ page }) => { @@ -53,7 +53,7 @@ test.describe.serial('message-composer', () => { await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); - await expect(poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Emoji' })).not.toBeFocused(); + await expect(poHomeChannel.composer.btnEmoji).not.toBeFocused(); }); test('should add a link to the selected text', async ({ page }) => { @@ -63,7 +63,7 @@ test.describe.serial('message-composer', () => { await page.keyboard.type('hello composer'); await page.keyboard.press('Control+A'); // on Windows and Linux await page.keyboard.press('Meta+A'); // on macOS - await poHomeChannel.composer.toolbarPrimaryActions.getByRole('button', { name: 'Link' }).click(); + await poHomeChannel.composer.btnLinkFormatter.click(); await page.keyboard.type(url); await page.keyboard.press('Enter'); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts index 22d88d21b2141..457ffd72fabdf 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/composer.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/composer.ts @@ -21,7 +21,7 @@ export abstract class Composer { return this.toolbarPrimaryActions.getByRole('button'); } - get btnComposerEmoji(): Locator { + get btnEmoji(): Locator { return this.toolbarPrimaryActions.getByRole('button', { name: 'Emoji', exact: true }); } @@ -45,6 +45,18 @@ export abstract class Composer { return this.toolbarPrimaryActions.getByRole('button', { name: 'More actions' }); } + get btnItalicFormatter(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Italic', exact: true }); + } + + get btnBoldFormatter(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Bold', exact: true }); + } + + get btnLinkFormatter(): Locator { + return this.toolbarPrimaryActions.getByRole('button', { name: 'Link', exact: true }); + } + get boxPopup(): Locator { return this.root.locator('[role="menu"][name="ComposerBoxPopup"]'); } diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index b583a4bfabe5c..4efd3bcdc4303 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -126,7 +126,7 @@ export class HomeChannel { } async pickEmoji(emoji: string, section = 'Smileys & People') { - await this.composer.btnComposerEmoji.click(); + await this.composer.btnEmoji.click(); await this.getEmojiPickerTabByName(section).click(); await this.getEmojiByName(emoji).click(); }