Files
upstream-rocketchat/apps/meteor/tests/e2e/page-objects/fragments/home-content.ts
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

681 lines
23 KiB
TypeScript

import fs from 'fs/promises';
import { resolve, join, relative } from 'node:path';
import type { Locator, Page } from '@playwright/test';
import { RoomComposer, ThreadComposer } from './composer';
import { RoomHeader } from './room-header';
import { createMediaResponsePromise } from '../../fixtures/responses/mediaResponse';
import { expect } from '../../utils/test';
const FIXTURES_PATH = relative(process.cwd(), resolve(__dirname, '../../fixtures/files'));
export function getFilePath(fileName: string): string {
return join(FIXTURES_PATH, fileName);
}
export class HomeContent {
protected readonly page: Page;
readonly header: RoomHeader;
readonly composer: RoomComposer;
protected readonly threadComposer: ThreadComposer;
constructor(page: Page) {
this.page = page;
this.composer = new RoomComposer(page);
this.threadComposer = new ThreadComposer(page);
this.header = new RoomHeader(page.locator('main header'));
}
get channelHeader(): Locator {
return this.header.root;
}
get burgerButton(): Locator {
return this.channelHeader.getByRole('button', { name: 'Open sidebar' });
}
get channelRetentionPolicyWarning(): Locator {
return this.page.locator('main').getByRole('alert', { name: 'Retention policy warning banner' });
}
get messagePopupUsers(): Locator {
return this.page.locator('role=menu[name="People"]');
}
get mainMessageList(): Locator {
return this.page.getByRole('list', { name: 'Message list', exact: true });
}
get threadMessageList(): Locator {
return this.page.getByRole('list', { name: 'Thread message list', exact: true });
}
/** The open room, matched by id. Use it to assert a permalink landed in the room it should. */
getRoomById(roomId: string): Locator {
return this.page.locator(`[data-qa-rc-room="${roomId}"]`);
}
get messageListItems(): Locator {
return this.mainMessageList.locator('[role="listitem"][aria-roledescription="message"]');
}
get mainMessageListScroller(): Locator {
return this.page.locator('[data-overlayscrollbars]', { has: this.mainMessageList });
}
get threadMessageListScroller(): Locator {
return this.page.locator('[data-overlayscrollbars]', { has: this.threadMessageList });
}
get systemMessageListItems(): Locator {
return this.mainMessageList.locator('[role="listitem"][aria-roledescription="system message"]');
}
get threadMessageListItems(): Locator {
return this.threadMessageList.locator('[role="listitem"][aria-roledescription="thread message"]');
}
get lastUserMessage(): Locator {
return this.messageListItems.last();
}
get lastUserThreadMessage(): Locator {
return this.threadMessageListItems.last();
}
get lastThreadMessagePreview(): Locator {
return this.page.getByRole('listitem').locator('[role="link"][aria-roledescription="thread message preview"]').last();
}
get lastUserMessageDownloadLink(): Locator {
return this.lastUserMessage.getByRole('link', { name: 'Download' });
}
nthMessage(index: number): Locator {
return this.messageListItems.nth(index);
}
get lastUserMessageBody(): Locator {
return this.lastUserMessage.locator('[role="document"][aria-roledescription="message body"]');
}
get lastUserMessageAttachment(): Locator {
return this.page.locator('[role="document"][aria-roledescription="message attachment"]').last();
}
get lastUserMessageNotSequential(): Locator {
return this.mainMessageList.locator('[role="listitem"][aria-roledescription="message"][data-sequential="false"]').last();
}
get encryptedRoomHeaderIcon(): Locator {
return this.page.locator('.rcx-room-header i.rcx-icon--name-key');
}
get lastIgnoredUserMessage(): Locator {
return this.lastUserMessageBody.locator('role=button[name="This message was ignored"]');
}
get lastIgnoredThreadMessage(): Locator {
return this.lastUserThreadMessage.getByRole('button', { name: 'This message was ignored' });
}
get ignoredThreadMessages(): Locator {
return this.threadMessageListItems.getByRole('button', { name: 'This message was ignored' });
}
async joinRoomIfNeeded(): Promise<void> {
if (await this.composer.inputMessage.isEnabled()) {
return;
}
if (!(await this.composer.btnJoinRoom.isVisible())) {
return;
}
await this.composer.btnJoinRoom.click();
}
async sendMessage(text: string, enforce = true): Promise<void> {
await this.joinRoomIfNeeded();
await expect(this.composer.inputMessage).toBeEnabled();
await this.composer.inputMessage.fill(text);
if (!enforce) {
await this.composer.btnSend.click();
return;
}
// Wait for the message to settle in the DOM. Transport-agnostic — the
// message may be sent via REST (`/api/v1/method.call/sendMessage`) or
// via DDP/WS depending on whether DDPSDK is the active dispatcher;
// either way Meteor's optimistic insert renders the new list item
// before the server confirms, and the `rcx-message--pending` class
// drops once the server result lands.
if ((await this.messageListItems.count()) > 0) {
const lastMessageBeforeSend = await this.messageListItems.last().getAttribute('id');
await this.composer.btnSend.click();
await expect.poll(() => this.messageListItems.last().getAttribute('id'), { timeout: 10_000 }).not.toEqual(lastMessageBeforeSend);
await expect(this.lastUserMessage).not.toHaveClass(/rcx-message--pending/);
} else {
// No previous messages, nothing to compare
await this.composer.btnSend.click();
await expect(this.lastUserMessage).not.toHaveClass(/rcx-message--pending/);
}
}
async dispatchSlashCommand(text: string): Promise<void> {
await this.joinRoomIfNeeded();
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');
}
async forwardMessage(chatName: string) {
await this.messageListItems.last().hover();
await this.page.locator('role=button[name="Forward message"]').click();
await this.page.getByRole('textbox', { name: 'Person or Channel', exact: true }).click();
await this.page.keyboard.type(chatName);
await this.page.locator('#position-container').getByText(chatName).waitFor();
await this.page.locator('#position-container').getByText(chatName).click();
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');
}
private get fileUploadModal(): Locator {
return this.page.getByRole('dialog', { name: 'File Upload' });
}
get btnModalConfirm(): Locator {
return this.page.locator('#modal-root .rcx-button-group--align-end .rcx-button--primary');
}
get btnModalConfirmDelete(): Locator {
return this.page.getByRole('button', { name: 'Yes, delete', exact: true });
}
get btnCancelQuotePreview(): Locator {
return this.page.getByRole('button', { name: 'Dismiss quoted message' });
}
get getFileDescription(): Locator {
return this.lastUserMessage.locator('[role="document"][aria-roledescription="message body"]');
}
get inputFileUploadName(): Locator {
return this.fileUploadModal.getByRole('textbox', { name: 'File name' });
}
get btnUpdateFileUpload(): Locator {
return this.fileUploadModal.getByRole('button', { name: 'Update' });
}
get btnCancelUpdateFileUpload(): Locator {
return this.fileUploadModal.getByRole('button', { name: 'Cancel' });
}
// -----------------------------------------
getLastMessageByFileName(filename: string): Locator {
return this.messageListItems
.filter({ has: this.page.getByRole('link', { name: filename }) })
.last()
.getByRole('link', { name: filename });
}
get lastMessageTextAttachment(): Locator {
return this.messageListItems.last().locator('[role="document"][aria-roledescription="message attachment"]');
}
get lastMessageTextAttachmentEqualsText(): Locator {
return this.messageListItems.last().locator('.rcx-attachment__details .rcx-message-body');
}
get btnQuoteMessage(): Locator {
return this.page.getByRole('button', { name: 'Quote' });
}
get quotePreview(): Locator {
return this.page.locator('footer blockquote');
}
get quotedMessage(): Locator {
return this.page.getByRole('blockquote');
}
quotedFileDescription(fileDescription: string): Locator {
return this.quotedMessage.getByText(fileDescription);
}
quotedFileName(fileName: string): Locator {
return this.quotedMessage.getByTitle(fileName);
}
threadMessageQuotedFileDescription(fileDescription: string): Locator {
return this.threadQuotedMessage.getByText(fileDescription);
}
threadMessageQuotedFileName(fileName: string): Locator {
return this.threadQuotedMessage.getByTitle(fileName);
}
get linkPreview(): Locator {
return this.lastUserMessage.getByText('Link Preview');
}
quotedLinkText(name: string): Locator {
return this.quotedMessage.getByRole('link', { name });
}
get threadQuotedMessage(): Locator {
return this.page.getByRole('dialog').getByRole('blockquote');
}
get threadQuotePreview(): Locator {
return this.page.getByRole('dialog').locator('footer blockquote');
}
get lastThreadMessageTextAttachmentEqualsText(): Locator {
return this.threadMessageListItems.last().locator('.rcx-attachment__details');
}
get mainThreadMessageText(): Locator {
return this.threadMessageListItems.first();
}
get lastThreadMessagePreviewText(): Locator {
return this.page.locator('div.messages-box .messages-list [role=link]').last();
}
get lastThreadMessageFileDescription(): Locator {
return this.threadMessageListItems.last().locator('[role="document"][aria-roledescription="message body"]');
}
getLastThreadMessageByFileName(filename: string): Locator {
return this.threadMessageListItems.last().getByRole('link', { name: filename });
}
// TODO: improve locator specificity
get menuMore(): Locator {
return this.page.getByRole('menu', { name: 'More', exact: true });
}
get lastThreadMessageTextAttachment(): Locator {
return this.threadMessageListItems.last().locator('[role="document"][aria-roledescription="message attachment"]');
}
get btnOptionEditMessage(): Locator {
return this.menuMore.getByRole('menuitem', { name: 'Edit', exact: true });
}
get btnOptionDeleteMessage(): Locator {
return this.menuMore.getByRole('menuitem', { name: 'Delete', exact: true });
}
get btnOptionPinMessage(): Locator {
return this.menuMore.getByRole('menuitem', { name: 'Pin', exact: true });
}
get btnOptionStarMessage(): Locator {
return this.menuMore.getByRole('menuitem', { name: 'Star', exact: true });
}
get btnOptionReplyInDm(): Locator {
return this.menuMore.getByRole('menuitem', { name: 'Reply in direct message', exact: true });
}
get btnVoiceCall(): Locator {
return this.primaryRoomActionsToolbar.getByRole('button', { name: 'Voice call' });
}
get btnContactInformation(): Locator {
return this.page.getByRole('button', { name: 'User Info' });
}
get btnContactInfoVoiceCall(): Locator {
return this.page.getByRole('group').getByRole('button', { name: 'Voice call' });
}
get btnSendTranscript(): Locator {
return this.page.locator('role=button[name="Send transcript"]');
}
get btnSendTranscriptToEmail(): Locator {
return this.page.locator('li.rcx-option', { hasText: 'Send via email' });
}
get btnSendTranscriptAsPDF(): Locator {
return this.page.locator('li.rcx-option', { hasText: 'Export as PDF' });
}
get btnNewCannedResponse(): Locator {
return this.page.locator('.rcx-vertical-bar button:has-text("Create")');
}
get imageGallery(): Locator {
return this.page.getByRole('dialog', { name: 'Image gallery', exact: true });
}
get imageGalleryImage(): Locator {
return this.imageGallery.locator('.swiper-zoom-container img');
}
async getGalleryButtonByName(name: string) {
return this.imageGallery.locator(`button[name="${name}"]`);
}
async dragAndDropTxtFile({ waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> {
const contract = await fs.readFile(getFilePath('any_file.txt'), 'utf-8');
const dataTransfer = await this.page.evaluateHandle((contract) => {
const data = new DataTransfer();
const file = new File([`${contract}`], 'any_file.txt', {
type: 'text/plain',
});
data.items.add(file);
return data;
}, contract);
const responsePromise = waitForResponse ? createMediaResponsePromise(this.page) : null;
await this.composer.inputMessage.dispatchEvent('dragenter', { dataTransfer });
await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer });
if (responsePromise) {
await responsePromise;
}
}
async dragAndDropLstFile({ waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> {
const contract = await fs.readFile(getFilePath('lst-test.lst'), 'utf-8');
const dataTransfer = await this.page.evaluateHandle((contract) => {
const data = new DataTransfer();
const file = new File([`${contract}`], 'lst-test.lst', {
type: 'text/plain',
});
data.items.add(file);
return data;
}, contract);
const responsePromise = waitForResponse ? createMediaResponsePromise(this.page) : null;
await this.composer.inputMessage.dispatchEvent('dragenter', { dataTransfer });
await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer });
if (responsePromise) {
await responsePromise;
}
}
async dragAndDropTxtFileToThread({ waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> {
const contract = await fs.readFile(getFilePath('any_file.txt'), 'utf-8');
const dataTransfer = await this.page.evaluateHandle((contract) => {
const data = new DataTransfer();
const file = new File([`${contract}`], 'any_file.txt', {
type: 'text/plain',
});
data.items.add(file);
return data;
}, contract);
const responsePromise = waitForResponse ? createMediaResponsePromise(this.page) : null;
await this.threadComposer.inputMessage.dispatchEvent('dragenter', { dataTransfer });
await this.page.locator('[role=dialog][data-qa="DropTargetOverlay"]').dispatchEvent('drop', { dataTransfer });
if (responsePromise) {
await responsePromise;
}
}
async sendMultipleFilesMessage(fileNames: string[], { waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> {
await this.page
.getByLabel('Room composer')
.locator('input[type=file]')
.setInputFiles(fileNames.map((name) => getFilePath(name)));
if (!waitForResponse) {
return;
}
await expect(this.composer.btnSend).toBeEnabled();
}
async sendFileMessage(fileName: string, { waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> {
const responsePromise = waitForResponse ? createMediaResponsePromise(this.page) : null;
await this.page.getByLabel('Room composer').locator('input[type=file]').setInputFiles(getFilePath(fileName));
if (responsePromise) {
await responsePromise;
}
}
async sendFileMessageToThread(fileName: string, { waitForResponse = true }: { waitForResponse?: boolean } = {}): Promise<void> {
await this.threadComposer.inputMessage.click();
const responsePromise = waitForResponse ? createMediaResponsePromise(this.page) : null;
await this.page.getByLabel('Thread composer').locator('input[type=file]').setInputFiles(getFilePath(fileName));
if (responsePromise) {
await responsePromise;
}
}
async openLastMessageMenu(): Promise<void> {
await this.lastUserMessage.hover();
await this.lastUserMessage.getByRole('button', { name: 'More', exact: true }).waitFor();
await this.lastUserMessage.getByRole('button', { name: 'More', exact: true }).click();
}
async openLastThreadMessageMenu(): Promise<void> {
await this.lastUserThreadMessage.hover();
await this.lastUserThreadMessage.getByRole('button', { name: 'More', exact: true }).waitFor();
await this.lastUserThreadMessage.getByRole('button', { name: 'More', exact: true }).click();
}
async toggleAlsoSendThreadToChannel(isChecked: boolean): Promise<void> {
await this.page
.getByRole('dialog')
.locator('label', { has: this.page.getByRole('checkbox', { name: 'Also send to channel' }) })
.setChecked(isChecked);
}
get lastSystemMessageBody(): Locator {
return this.page.locator('[role=document][aria-roledescription="system message body"]').last();
}
get resumeOnHoldOmnichannelChatButton(): Locator {
return this.page.locator('button.rcx-button--primary >> text="Resume"');
}
get primaryRoomActionsToolbar(): Locator {
return this.page.getByRole('toolbar', { name: 'Primary Room actions' });
}
get btnVideoCall(): Locator {
return this.page.locator('[role=toolbar][aria-label="Primary Room actions"]').getByRole('button', { name: 'Video call' });
}
get btnToolbarOptions(): Locator {
return this.primaryRoomActionsToolbar.getByRole('button', { name: 'Options', exact: true });
}
get optionsMenu(): Locator {
return this.page.getByRole('menu', { name: 'Options', exact: true });
}
get starredMessagesMenuOption(): Locator {
return this.optionsMenu.getByRole('menuitem', { name: 'Starred Messages', exact: true });
}
getVideoConfPopup(name?: string): Locator {
return this.page.getByRole('dialog', { name });
}
get btnStartVideoCall(): Locator {
return this.getVideoConfPopup().getByRole('button', { name: 'Start call' });
}
get btnVideoConfMic(): Locator {
return this.getVideoConfPopup().getByRole('button', { name: 'Mic' });
}
get btnDeclineVideoCall(): Locator {
return this.page.locator('.rcx-button--secondary-danger.rcx-button >> text="Decline"');
}
get videoConfMessageBlock(): Locator {
return this.page.locator('.rcx-videoconf-message-block');
}
get btnAnonymousSignIn(): Locator {
return this.page.locator('footer >> role=button[name="Sign in to start talking"]');
}
get btnAnonymousTalk(): Locator {
return this.page.locator('role=button[name="Or talk as anonymous"]');
}
get nextSlideButton(): Locator {
return this.page.getByLabel('Next slide');
}
get previousSlideButton(): Locator {
return this.page.getByLabel('Previous slide');
}
get currentGalleryImage(): Locator {
return this.page.locator('div[class="swiper-slide swiper-slide-active"] img');
}
// TODO: use getSystemMessageByText instead
findSystemMessage(text: string): Locator {
return this.page.locator(`[role="document"][aria-roledescription="system message body"]`, { hasText: text });
}
getSystemMessageByText(text: string): Locator {
return this.page.locator('[role="listitem"][aria-roledescription="system message"]', { hasText: text });
}
getMessageByText(text: string): Locator {
return this.page.locator('[role="listitem"][aria-roledescription="message"]', { hasText: text });
}
getLastMessageActionButton(name: string): Locator {
return this.lastUserMessage.getByRole('button', { name, exact: true });
}
getLastThreadMessageActionButton(name: string): Locator {
return this.lastUserThreadMessage.getByRole('button', { name, exact: true });
}
getMessageById(id: string): Locator {
return this.page.locator(`[role="listitem"][aria-roledescription="message"][id="${id}"]`);
}
/** Thread replies carry `aria-roledescription="thread message"`, so `getMessageById` skips them. */
getThreadMessageById(id: string): Locator {
return this.threadMessageList.locator(`[role="listitem"][id="${id}"]`);
}
async scrollToMessage(messageLocator: Locator, direction: 'up' | 'down' = 'up'): Promise<Locator> {
const scroller = this.mainMessageListScroller;
const delta = direction === 'up' ? -400 : 400;
await expect(async () => {
await scroller.evaluate((el, d) => el.scrollBy(0, d), delta);
await expect(messageLocator.first()).toBeVisible({ timeout: 500 });
}).toPass({ timeout: 30000, intervals: [100] });
return messageLocator;
}
get btnJoinChannel(): Locator {
return this.page.getByRole('main').getByRole('button', { name: 'Join channel', exact: true });
}
async waitForChannel(): Promise<void> {
await this.page.locator('role=main').waitFor();
// a room the user cannot preview renders a join screen instead of the header and message list
await this.mainMessageList.or(this.btnJoinChannel).first().waitFor();
if (await this.btnJoinChannel.isVisible()) {
return;
}
await this.page.locator('role=main >> role=heading[level=1]').waitFor();
await expect(this.mainMessageList).not.toHaveAttribute('aria-busy', 'true');
}
async waitForThread(): Promise<void> {
const messageList = this.page.getByRole('list', { name: 'Thread message list', exact: true });
await messageList.waitFor();
await expect(messageList).not.toHaveAttribute('aria-busy', 'true');
}
async openReplyInThread(): Promise<void> {
await this.lastUserMessage.hover();
await this.lastUserMessage.getByRole('button', { name: 'Reply in thread' }).waitFor();
await this.lastUserMessage.getByRole('button', { name: 'Reply in thread' }).click();
}
async sendMessageInThread(text: string): Promise<void> {
await this.threadComposer.inputMessage.fill(text);
await this.threadComposer.btnSend.click();
}
async sendMessageInVideoConfPopup(text: string): Promise<void> {
await this.page.getByRole('dialog').getByRole('textbox', { name: 'Message' }).fill(text);
await this.page.getByRole('dialog').getByRole('button', { name: 'Send', exact: true }).click();
}
async deleteLastMessage(): Promise<void> {
await this.lastUserMessage.hover();
await this.openLastMessageMenu();
await this.btnOptionDeleteMessage.click();
await this.btnModalConfirmDelete.click();
await expect(this.btnModalConfirmDelete).toBeDisabled();
}
get btnClearSelection() {
return this.page.getByRole('button', { name: 'Clear selection' });
}
get contactUnknownCallout() {
return this.page.getByRole('status', { name: 'Unknown contact. This contact is not on the contact list.' });
}
get btnDismissContactUnknownCallout() {
return this.contactUnknownCallout.getByRole('button', { name: 'Dismiss' });
}
get btnOptionStartDiscussion(): Locator {
return this.page.getByRole('menuitem', { name: 'Start a Discussion' });
}
async quoteMessage(quoteText: string, originalMessageText?: string): Promise<void> {
await this.lastUserMessage.hover();
await this.btnQuoteMessage.click();
if (originalMessageText) {
await expect(this.quotePreview).toBeVisible();
await expect(this.quotePreview).toContainText(originalMessageText);
}
await this.sendMessage(quoteText);
}
get clearAllUnreadsModal(): Locator {
return this.page.getByRole('dialog', { name: 'Clear all unreads?' });
}
async markAllRoomsAsRead(): Promise<void> {
await this.page.keyboard.down('Shift');
await this.page.keyboard.press('Escape');
await this.page.keyboard.up('Shift');
await expect(this.clearAllUnreadsModal).toBeVisible();
await this.clearAllUnreadsModal.getByRole('button', { name: 'Yes, clear all!' }).click();
}
}