E2E test for scheduled Draft feature (#28433)

Этот коммит содержится в:
yasserfaraazkhan
2024-11-15 02:39:35 +05:30
коммит произвёл GitHub
родитель 3af3af0b25
Коммит 38823603a3
15 изменённых файлов: 955 добавлений и 8 удалений

Просмотреть файл

@@ -12,12 +12,26 @@ export default class ChannelsCenterView {
readonly header;
readonly postCreate;
readonly scheduledDraftOptions;
readonly scheduledDraftChannelInfo;
readonly scheduledDraftChannelIcon;
readonly scheduledDraftChannelInfoMessage;
readonly scheduledDraftChannelInfoMessageText;
readonly scheduledDraftSeeAllLink;
constructor(container: Locator) {
this.container = container;
this.header = new components.ChannelsHeader(this.container.locator('.channel-header'));
this.postCreate = new components.ChannelsPostCreate(container.getByTestId('post-create'));
this.scheduledDraftOptions = new components.ChannelsPostCreate(
container.locator('#dropdown_send_post_options'),
);
this.scheduledDraftChannelInfo = container.locator('div.postBoxIndicator');
this.scheduledDraftChannelIcon = container.locator('#create_post i.icon-draft-indicator');
this.scheduledDraftChannelInfoMessage = container.locator('div.ScheduledPostIndicator span');
this.scheduledDraftChannelInfoMessageText = container.locator('span:has-text("Message scheduled for")');
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all scheduled messages")');
}
async toBeVisible() {
@@ -25,6 +39,14 @@ export default class ChannelsCenterView {
await this.postCreate.toBeVisible();
}
/**
* Click on "See all scheduled messages"
*/
async clickOnSeeAllscheduledDrafts() {
await this.scheduledDraftSeeAllLink.isVisible();
await this.scheduledDraftSeeAllLink.click();
}
/**
* Return the first post in the Center
*/
@@ -86,6 +108,13 @@ export default class ChannelsCenterView {
{timeout},
);
}
async verifyscheduledDraftChannelInfo() {
await this.scheduledDraftChannelInfo.isVisible();
await this.scheduledDraftChannelIcon.isVisible();
const messageLocator = this.scheduledDraftChannelInfoMessage.first();
await expect(messageLocator).toContainText('Message scheduled for');
}
}
export {ChannelsCenterView};

Просмотреть файл

@@ -5,12 +5,12 @@ import {expect, Locator} from '@playwright/test';
export default class ChannelsPostCreate {
readonly container: Locator;
readonly input;
readonly attachmentButton;
readonly emojiButton;
readonly sendMessageButton;
readonly scheduleDraftMessageButton;
constructor(container: Locator, isRHS = false) {
this.container = container;
@@ -24,6 +24,7 @@ export default class ChannelsPostCreate {
this.attachmentButton = container.getByLabel('attachment');
this.emojiButton = container.getByLabel('select an emoji');
this.sendMessageButton = container.getByTestId('SendMessageButton');
this.scheduleDraftMessageButton = container.getByLabel('Schedule message');
}
async toBeVisible() {
@@ -66,6 +67,18 @@ export default class ChannelsPostCreate {
await this.sendMessageButton.click();
}
/**
* Click on Scheduled Draft button to open options
*/
async clickOnScheduleDraftDropdownButton() {
await expect(this.input).toBeVisible();
await expect(this.scheduleDraftMessageButton).toBeVisible();
await expect(this.scheduleDraftMessageButton).toBeEnabled();
await this.scheduleDraftMessageButton.click();
}
/**
* Composes and sends a message
*/

Просмотреть файл

@@ -0,0 +1,26 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class ScheduledDraftMenu {
readonly container: Locator;
readonly scheduleDraftMessageCustomTimeOption;
constructor(container: Locator) {
this.container = container;
this.scheduleDraftMessageCustomTimeOption = container.getByText('Choose a custom time');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async selectCustomTime() {
await this.scheduleDraftMessageCustomTimeOption.click();
}
}
export {ScheduledDraftMenu};

Просмотреть файл

@@ -0,0 +1,90 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Locator} from '@playwright/test';
export default class ScheduledDraftModal {
readonly container: Locator;
readonly confirmButton;
readonly dateInput;
readonly timeLocator;
readonly timeDropdownOptions;
constructor(container: Locator) {
this.container = container;
this.confirmButton = container.locator('button.confirm');
this.dateInput = container.locator('div.Input_wrapper');
this.timeLocator = container.locator('div.dateTime__input');
this.timeDropdownOptions = container.locator('ul.dropdown-menu .MenuItem');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
getDaySuffix(day: number): string {
if (day > 3 && day < 21) return 'th';
switch (day % 10) {
case 1:
return 'st';
case 2:
return 'nd';
case 3:
return 'rd';
default:
return 'th';
}
}
dateLocator(day: number, month: string, dayOfWeek: string) {
const daySuffix = this.getDaySuffix(day);
return this.container.locator(`button[aria-label*='${day}${daySuffix} ${month} (${dayOfWeek})']`);
}
async selectDay(dayFromToday: number = 0) {
await this.dateInput.click();
const pacificDate = this.getPacificDate();
// If dayFromToday is provided, add days to the current date
if (dayFromToday) {
pacificDate.setDate(pacificDate.getDate() + dayFromToday);
}
const day = pacificDate.getDate();
const month = pacificDate.toLocaleString('default', {month: 'long'});
const dayOfWeek = pacificDate.toLocaleDateString('en-US', {weekday: 'long'});
await this.dateLocator(day, month, dayOfWeek).click();
}
async confirm() {
await this.confirmButton.isVisible();
await this.confirmButton.click();
}
/**
* Selecting the First time option from the dropdown for
* scheduled_post_job to send the drafts out
*/
async selectTime() {
await this.timeLocator.click();
const timeButton = this.timeDropdownOptions.first();
await expect(timeButton).toBeVisible();
await timeButton.click();
}
getPacificDate(): Date {
const currentDate = new Date();
// Convert the current date to Pacific Time
const utcTime = currentDate.getTime() + currentDate.getTimezoneOffset() * 60000;
const pacificOffset = -7 * 60; // Pacific Daylight Time (UTC-07:00)
const pacificTime = new Date(utcTime + pacificOffset * 60000);
return pacificTime;
}
}
export {ScheduledDraftModal};

Просмотреть файл

@@ -6,17 +6,24 @@ import {expect, Locator} from '@playwright/test';
export default class ChannelsSidebarLeft {
readonly container: Locator;
readonly findChannelButton;
readonly scheduledDraftCountonLHS;
constructor(container: Locator) {
this.container = container;
this.findChannelButton = container.getByRole('button', {name: 'Find Channels'});
this.scheduledDraftCountonLHS = container.locator('span.scheduledPostBadge');
}
async toBeVisible() {
await expect(this.container).toBeVisible();
}
async assertscheduledDraftCountLHS(count: string) {
await expect(this.scheduledDraftCountonLHS).toBeVisible();
await expect(this.scheduledDraftCountonLHS).toHaveText(count);
}
/**
* Clicks on the sidebar channel link with the given name.
* It can be any sidebar item name including channels, direct messages, or group messages, threads, etc.

Просмотреть файл

@@ -8,12 +8,22 @@ import {components} from '@e2e-support/ui/components';
export default class ChannelsSidebarRight {
readonly container: Locator;
readonly postCreate;
readonly closeButton;
readonly postCreate;
readonly rhsPostBody;
readonly scheduledDraftChannelInfo;
readonly scheduledDraftChannelInfoMessage;
readonly scheduledDraftSeeAllLink;
readonly scheduledDraftChannelInfoMessageText;
constructor(container: Locator) {
this.container = container;
this.scheduledDraftChannelInfo = container.locator('div.postBoxIndicator');
this.scheduledDraftChannelInfoMessage = container.locator('div.ScheduledPostIndicator span');
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all scheduled messages")');
this.scheduledDraftChannelInfoMessageText = container.locator('span:has-text("Message scheduled for")');
this.rhsPostBody = container.locator('.post-message__text');
this.postCreate = new components.ChannelsPostCreate(container.getByTestId('comment-create'), true);
this.closeButton = container.locator('#rhsCloseButton');
}
@@ -50,6 +60,11 @@ export default class ChannelsSidebarRight {
await expect(this.container).not.toBeVisible();
}
async clickOnSeeAllscheduledDrafts() {
await this.scheduledDraftSeeAllLink.isVisible();
await this.scheduledDraftSeeAllLink.click();
}
}
export {ChannelsSidebarRight};

Просмотреть файл

@@ -21,6 +21,8 @@ import {ThreadFooter} from './channels/thread_footer';
import {EmojiGifPicker} from './channels/emoji_gif_picker';
import {GenericConfirmModal} from './channels/generic_confirm_modal';
import {ScheduledDraftMenu} from './channels/scheduled_draft_menu';
import {ScheduledDraftModal} from './channels/scheduled_draft_modal';
import {SystemConsoleSidebar} from './system_console/sidebar';
import {SystemConsoleNavbar} from './system_console/navbar';
@@ -49,6 +51,8 @@ const components = {
PostReminderMenu,
EmojiGifPicker,
GenericConfirmModal,
ScheduledDraftMenu,
ScheduledDraftModal,
SystemConsoleSidebar,
SystemConsoleNavbar,
SystemUsers,

Просмотреть файл

@@ -12,6 +12,8 @@ export default class ChannelsPage {
readonly globalHeader;
readonly centerView;
readonly scheduledDraftDropdown;
readonly scheduledDraftModal;
readonly sidebarLeft;
readonly sidebarRight;
readonly appBar;
@@ -20,6 +22,7 @@ export default class ChannelsPage {
readonly deletePostModal;
readonly settingsModal;
readonly postContainer;
readonly postDotMenu;
readonly postReminderMenu;
@@ -46,21 +49,30 @@ export default class ChannelsPage {
// Popovers
this.emojiGifPickerPopup = new components.EmojiGifPicker(page.locator('#emojiGifPicker'));
this.scheduledDraftDropdown = new components.ScheduledDraftMenu(page.locator('#dropdown_send_post_options'));
this.scheduledDraftModal = new components.ScheduledDraftModal(page.locator('div.modal-content'));
// Posts
this.postContainer = page.locator('div.post-message__text');
}
async toBeVisible() {
await this.centerView.toBeVisible();
}
async getLastPost() {
return this.postContainer.last();
}
async goto(teamName = '', channelName = '') {
let channelsUrl = '/';
if (teamName) {
channelsUrl += `${teamName}`;
if (channelName) {
channelsUrl += `/${channelName}`;
const prefix = channelName.startsWith('@') ? '/messages' : '';
channelsUrl += `${prefix}/${channelName}`;
}
}
await this.page.goto(channelsUrl);
}
}

Просмотреть файл

@@ -0,0 +1,128 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Page} from '@playwright/test';
export default class DraftPage {
readonly page: Page;
readonly badgeCountOnScheduledTab;
readonly confirmbutton;
readonly datePattern;
readonly deleteIcon;
readonly deleteIconToolTip;
readonly noscheduledDraftIcon;
readonly scheduleIcon;
readonly rescheduleIconToolTip;
readonly draftBody;
readonly scheduledDraftPageInfo;
readonly scheduledDraftPanel;
readonly scheduledDraftSendNowButton;
readonly scheduledDraftSendNowButtonToolTip;
constructor(page: Page) {
this.page = page;
this.draftBody = page.locator('div.post__body');
this.scheduleIcon = page.locator('#draft_icon-clock-send-outline_reschedule');
this.datePattern =
/(Today|Tomorrow|(?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}) at \d{1,2}:\d{2} [AP]M/;
this.badgeCountOnScheduledTab = page.locator('a#draft_tabs-tab-0 div.drafts_tab_title span.MuiBadge-badge');
// this.scheduledDraftPageInfo = page.locator('span:has-text("Send on")');
this.scheduledDraftPageInfo = page.locator('.PanelHeader__info');
this.scheduledDraftPanel = (messageContent: string) =>
page.locator(`article.Panel:has(div.post__body:has-text("${messageContent}"))`);
this.deleteIcon = page.locator('#draft_icon-trash-can-outline_delete');
this.deleteIconToolTip = page.locator('text=Delete scheduled post');
this.rescheduleIconToolTip = page.locator('text=Schedule draft');
this.noscheduledDraftIcon = page.locator('.no-results__wrapper');
this.scheduledDraftSendNowButton = page.locator('#draft_icon-send-outline_sendNow');
this.scheduledDraftSendNowButtonToolTip = page.locator('text=Send now');
this.confirmbutton = this.page.locator('button.btn-primary');
}
async goTo(teamName: string) {
await this.page.goto(`/${teamName}/drafts`);
}
async toBeVisible() {
await this.page.waitForLoadState('networkidle');
await expect(this.page).toHaveURL(/.*drafts/);
}
async assertBadgeCountOnTab(badgeCount: string) {
await this.badgeCountOnScheduledTab.isVisible();
await expect(this.badgeCountOnScheduledTab).toHaveText(badgeCount);
}
async assertDraftBody(draftMessage: string) {
await expect(this.draftBody).toBeVisible();
await expect(this.draftBody).toHaveText(draftMessage);
}
async verifyOnHoverActionItems(messageContent: string) {
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.verifyDeleteIcon();
await this.verifyScheduleIcon(messageContent);
await this.verifySendNowIcon();
}
async verifyDeleteIcon() {
await this.deleteIcon.isVisible();
await this.deleteIcon.hover();
await expect(this.deleteIconToolTip).toBeVisible();
await expect(this.deleteIconToolTip).toHaveText('Delete scheduled post');
}
async verifyScheduleIcon(messageContent: string) {
await this.scheduledDraftPanel(messageContent).hover();
await expect(this.scheduleIcon).toBeVisible();
await this.scheduleIcon.hover();
await expect(this.rescheduleIconToolTip).toBeVisible();
await expect(this.rescheduleIconToolTip).toHaveText('Schedule draft');
}
async verifySendNowIcon() {
await this.scheduledDraftSendNowButton.isVisible();
await this.scheduledDraftSendNowButton.hover();
await expect(this.scheduledDraftSendNowButtonToolTip).toBeVisible();
await expect(this.scheduledDraftSendNowButtonToolTip).toHaveText('Send now');
}
async getTimeStampOfMessage(messageContent: string) {
await this.scheduledDraftPanel(messageContent).scrollIntoViewIfNeeded();
await this.scheduledDraftPanel(messageContent).isVisible();
return this.scheduledDraftPanel(messageContent).locator(this.scheduledDraftPageInfo).innerHTML();
}
async openScheduleModal(messageContent: string) {
await this.scheduledDraftPanel(messageContent).scrollIntoViewIfNeeded();
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.scheduleIcon.hover();
await expect(this.rescheduleIconToolTip).toBeVisible();
await expect(this.rescheduleIconToolTip).toHaveText('Schedule draft');
await this.scheduleIcon.click();
}
async deleteScheduledMessage(messageContent: string) {
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.verifyDeleteIcon();
await this.deleteIcon.click();
expect(await this.confirmbutton.textContent()).toEqual('Yes, delete');
await this.confirmbutton.click();
}
async sendScheduledMessage(messageContent: string) {
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.verifySendNowIcon();
await this.scheduledDraftSendNowButton.click();
expect(await this.confirmbutton.textContent()).toEqual('Yes, send now');
await this.confirmbutton.click();
}
}
export {DraftPage};

Просмотреть файл

@@ -7,6 +7,8 @@ import {LoginPage} from './login';
import {ResetPasswordPage} from './reset_password';
import {SignupPage} from './signup';
import {SystemConsolePage} from './system_console';
import {ScheduledDraftPage} from './scheduled_draft';
import {DraftPage} from './drafts';
const pages = {
ChannelsPage,
@@ -14,7 +16,9 @@ const pages = {
LoginPage,
ResetPasswordPage,
SignupPage,
ScheduledDraftPage,
SystemConsolePage,
DraftPage,
};
export {pages, ChannelsPage, LandingLoginPage, LoginPage, SignupPage};
export {pages, ChannelsPage, LandingLoginPage, LoginPage, SignupPage, ScheduledDraftPage, DraftPage};

Просмотреть файл

@@ -0,0 +1,161 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, Page} from '@playwright/test';
export default class ScheduledDraftPage {
readonly page: Page;
readonly badgeCountOnScheduledTab;
readonly confirmbutton;
readonly copyIcon;
readonly copyIconToolTip;
readonly datePattern;
readonly deleteIcon;
readonly deleteIconToolTip;
readonly noscheduledDraftIcon;
readonly rescheduleIcon;
readonly rescheduleIconToolTip;
readonly scheduledDraftBody;
readonly scheduledDraftPageInfo;
readonly scheduledDraftPanel;
readonly scheduledDraftSendNowButton;
readonly scheduledDraftSendNowButtonToolTip;
readonly editIcon;
readonly editBox;
readonly editorSaveButton;
constructor(page: Page) {
this.page = page;
this.datePattern =
/(Today|Tomorrow|(?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}) at \d{1,2}:\d{2} [AP]M/;
this.scheduledDraftBody = page.locator('div.post__body');
this.badgeCountOnScheduledTab = page.locator('a#draft_tabs-tab-1 div.drafts_tab_title span.MuiBadge-badge');
this.scheduledDraftPageInfo = page.locator('.PanelHeader__info');
this.scheduledDraftPanel = (messageContent: string) =>
page.locator(`article.Panel:has(div.post__body:has-text("${messageContent}"))`);
this.deleteIcon = page.locator('#draft_icon-trash-can-outline_delete');
this.deleteIconToolTip = page.locator('text=Delete scheduled post');
this.copyIcon = page.locator('#draft_icon-content-copy_copy_text');
this.copyIconToolTip = page.locator('text=Copy text');
this.rescheduleIcon = page.locator('#draft_icon-clock-send-outline_reschedule');
this.rescheduleIconToolTip = page.locator('text=Reschedule post');
this.noscheduledDraftIcon = page.locator('.no-results__wrapper');
this.scheduledDraftSendNowButton = page.locator('#draft_icon-send-outline_sendNow');
this.scheduledDraftSendNowButtonToolTip = page.locator('text=Send now');
this.confirmbutton = this.page.locator('button.btn-primary');
this.editIcon = page.locator('#draft_icon-pencil-outline_edit');
this.editBox = page.locator('textarea#edit_textbox');
this.editorSaveButton = page.locator('button.save');
}
async toBeVisible() {
await this.page.waitForLoadState('networkidle');
await expect(this.page).toHaveURL(/.*scheduled_posts/);
}
async assertBadgeCountOnTab(badgeCount: string) {
await this.badgeCountOnScheduledTab.isVisible();
await expect(this.badgeCountOnScheduledTab).toHaveText(badgeCount);
}
async assertscheduledDraftBody(draftMessage: string) {
await expect(this.scheduledDraftBody).toBeVisible();
await expect(this.scheduledDraftBody).toHaveText(draftMessage);
}
async verifyOnHoverActionItems(messageContent: string) {
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.verifyDeleteIcon();
await this.verifyCopyIcon();
await this.verifyRescheduleIcon();
await this.verifySendNowIcon();
}
async verifyDeleteIcon() {
await this.deleteIcon.isVisible();
await this.deleteIcon.hover();
await expect(this.deleteIconToolTip).toBeVisible();
await expect(this.deleteIconToolTip).toHaveText('Delete scheduled post');
}
async verifyCopyIcon() {
await this.copyIcon.isVisible();
await this.copyIcon.hover();
await expect(this.copyIconToolTip).toBeVisible();
await expect(this.copyIconToolTip).toHaveText('Copy text');
}
async verifyRescheduleIcon() {
await expect(this.rescheduleIcon).toBeVisible();
await this.rescheduleIcon.hover();
await expect(this.rescheduleIconToolTip).toBeVisible();
await expect(this.rescheduleIconToolTip).toHaveText('Reschedule post');
}
async verifySendNowIcon() {
await this.scheduledDraftSendNowButton.isVisible();
await this.scheduledDraftSendNowButton.hover();
await expect(this.scheduledDraftSendNowButtonToolTip).toBeVisible();
await expect(this.scheduledDraftSendNowButtonToolTip).toHaveText('Send now');
}
async getTimeStampOfMessage(messageContent: string) {
await this.scheduledDraftPanel(messageContent).scrollIntoViewIfNeeded();
await this.scheduledDraftPanel(messageContent).isVisible();
return this.scheduledDraftPanel(messageContent).locator(this.scheduledDraftPageInfo).innerHTML();
}
async openRescheduleModal(messageContent: string) {
await this.scheduledDraftPanel(messageContent).scrollIntoViewIfNeeded();
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.rescheduleIcon.hover();
await expect(this.rescheduleIconToolTip).toBeVisible();
await expect(this.rescheduleIconToolTip).toHaveText('Reschedule post');
await this.rescheduleIcon.click();
}
async deleteScheduledMessage(messageContent: string) {
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.verifyDeleteIcon();
await this.deleteIcon.click();
expect(await this.confirmbutton.textContent()).toEqual('Yes, delete');
await this.confirmbutton.click();
}
async sendScheduledMessage(messageContent: string) {
await this.scheduledDraftPanel(messageContent).isVisible();
await this.scheduledDraftPanel(messageContent).hover();
await this.verifySendNowIcon();
await this.scheduledDraftSendNowButton.click();
expect(await this.confirmbutton.textContent()).toEqual('Yes, send now');
await this.confirmbutton.click();
}
async goTo(teamName: string) {
await this.page.goto(`/${teamName}/scheduled_posts`);
}
async editText(newText: string) {
await this.editIcon.click();
await this.editBox.isVisible();
await this.editBox.fill(newText);
await this.editorSaveButton.isVisible();
await this.editorSaveButton.click();
await this.editBox.isHidden();
await this.scheduledDraftPanel(newText).isVisible();
}
async copyScheduledMessage(draftMessage: string) {
await this.scheduledDraftPanel(draftMessage).isVisible();
await this.scheduledDraftPanel(draftMessage).hover();
await this.verifyCopyIcon();
await this.copyIcon.click();
}
}
export {ScheduledDraftPage};

Просмотреть файл

@@ -0,0 +1,456 @@
import {expect, Page} from '@playwright/test';
import {test} from '@e2e-support/test_fixture';
import {ChannelsPage, ScheduledDraftPage} from '@e2e-support/ui/pages';
import {duration, wait} from '@e2e-support/util';
test('MM-T5643_1 should create a scheduled message from a channel', async ({pw, pages}) => {
test.setTimeout(120000);
const draftMessage = 'Scheduled Draft';
// # Skip test if no license
await pw.skipIfNoLicense();
const {user, team} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage);
await scheduleMessage(channelPage);
await channelPage.centerView.verifyscheduledDraftChannelInfo();
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
// # Hover and verify options
await scheduledDraftPage.verifyOnHoverActionItems(draftMessage);
// # Go back and wait for message to arrive
await goBackToChannelAndWaitForMessageToArrive(page);
// * Verify the message has been sent and there's no more scheduled messages
await expect(channelPage.centerView.scheduledDraftChannelInfoMessage).not.toBeVisible();
await expect(channelPage.sidebarLeft.scheduledDraftCountonLHS).not.toBeVisible();
await expect(await channelPage.getLastPost()).toHaveText(draftMessage);
await verifyNoscheduledDraftsPending(channelPage, team, scheduledDraftPage, draftMessage);
});
test('MM-T5643_6 should create a scheduled message under a thread post ', async ({pw, pages}) => {
test.setTimeout(120000);
const draftMessage = 'Scheduled Threaded Message';
// # Skip test if no license
await pw.skipIfNoLicense();
const {user, team} = await pw.initSetup();
// # Log in as a user in new browser context
const {page} = await pw.testBrowser.login(user);
// # Visit default channel page
const channelPage = new pages.ChannelsPage(page);
await channelPage.goto();
await channelPage.toBeVisible();
await channelPage.centerView.postCreate.postMessage('Root Message');
// # Start a thread by clicking on reply menuitem from post options menu
const post = await channelPage.centerView.getLastPost();
await replyToLastPost(post);
const sidebarRight = channelPage.sidebarRight;
await sidebarRight.toBeVisible();
// # Post a message in the thread
await sidebarRight.postCreate.postMessage('Replying to a thread');
// # Write a message in the reply thread but don't send it
await sidebarRight.postCreate.writeMessage(draftMessage);
await expect(sidebarRight.postCreate.input).toHaveText(draftMessage);
await sidebarRight.postCreate.scheduleDraftMessageButton.isVisible();
await sidebarRight.postCreate.scheduleDraftMessageButton.click();
await scheduleMessage(channelPage);
await sidebarRight.scheduledDraftChannelInfo.isVisible();
const messageLocator = sidebarRight.scheduledDraftChannelInfoMessage.first();
await expect(messageLocator).toContainText('Message scheduled for');
// Save the time displayed in the thread
const scheduledDraftThreadedPanelInfo = await sidebarRight.scheduledDraftChannelInfo.innerText();
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await channelPage.sidebarRight.clickOnSeeAllscheduledDrafts();
const scheduledDraftPageInfo = await scheduledDraftPage.scheduledDraftPageInfo.innerHTML();
await channelPage.sidebarLeft.assertscheduledDraftCountLHS('1');
await scheduledDraftPage.toBeVisible();
await scheduledDraftPage.assertBadgeCountOnTab('1');
await scheduledDraftPage.assertscheduledDraftBody(draftMessage);
await compareMessageTimestamps(scheduledDraftThreadedPanelInfo, scheduledDraftPageInfo, scheduledDraftPage);
// # Hover and verify options
await scheduledDraftPage.verifyOnHoverActionItems(draftMessage);
await goBackToChannelAndWaitForMessageToArrive(page);
await replyToLastPost(post);
// * Verify the message has been sent and there's no more scheduled messages
await expect(channelPage.sidebarRight.scheduledDraftChannelInfoMessage).not.toBeVisible();
await expect(channelPage.sidebarLeft.scheduledDraftCountonLHS).not.toBeVisible();
const lastPost = channelPage.sidebarRight.rhsPostBody.last();
await expect(lastPost).toHaveText(draftMessage);
await expect(scheduledDraftPage.scheduledDraftPanel(draftMessage)).not.toBeVisible();
await verifyNoscheduledDraftsPending(channelPage, team, scheduledDraftPage, draftMessage);
});
test('MM-T5644 should rechedule a scheduled message', async ({pw, pages}) => {
const draftMessage = 'Scheduled Draft';
await pw.skipIfNoLicense();
const {user} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage);
await scheduleMessage(channelPage);
// * Verify the Initial Date and time of scheduled Draft
await channelPage.centerView.verifyscheduledDraftChannelInfo();
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
await scheduledDraftPage.openRescheduleModal(draftMessage);
// # Reschedule it to 2 days from today
await channelPage.scheduledDraftModal.selectDay(2);
await channelPage.scheduledDraftModal.confirm();
// # Note the new Scheduled time
const scheduledDraftPageInfo = await scheduledDraftPage.getTimeStampOfMessage(draftMessage);
// # Go to Channel
await channelPage.goto();
// * Verify the New Time reflecting in the channel
const rescheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await compareMessageTimestamps(rescheduledDraftChannelInfo, scheduledDraftPageInfo, scheduledDraftPage);
});
test('MM-T5645 should delete a scheduled message', async ({pw, pages}) => {
const draftMessage = 'Scheduled Draft';
await pw.skipIfNoLicense();
const {user} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage);
await scheduleMessage(channelPage);
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
await scheduledDraftPage.deleteScheduledMessage(draftMessage);
await expect(scheduledDraftPage.scheduledDraftPanel(draftMessage)).not.toBeVisible();
await expect(scheduledDraftPage.noscheduledDraftIcon).toBeVisible();
});
test('MM-T5643_9 should send a scheduled message immediately', async ({pw, pages}) => {
const draftMessage = 'Scheduled Draft';
await pw.skipIfNoLicense();
const {user} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage);
await scheduleMessage(channelPage);
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
await scheduledDraftPage.sendScheduledMessage(draftMessage);
await expect(scheduledDraftPage.scheduledDraftPanel(draftMessage)).not.toBeVisible();
// Verify message has arrived
await expect(channelPage.centerView.scheduledDraftChannelInfoMessage).not.toBeVisible();
await expect(channelPage.sidebarLeft.scheduledDraftCountonLHS).not.toBeVisible();
await expect(await channelPage.getLastPost()).toHaveText(draftMessage);
});
test('MM-T5643_3 should create a scheduled message from a DM', async ({pw, pages}) => {
test.setTimeout(120000);
const draftMessage = 'Scheduled Draft';
// # Skip test if no license
await pw.skipIfNoLicense();
const {user, team} = await pw.initSetup();
const {user: user2} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage, team.name, `@${user2.username}`);
await scheduleMessage(channelPage);
await channelPage.centerView.verifyscheduledDraftChannelInfo();
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
// # Hover and verify options
await scheduledDraftPage.verifyOnHoverActionItems(draftMessage);
// # Go back and wait for message to arrive
await goBackToChannelAndWaitForMessageToArrive(page);
// * Verify the message has been sent and there's no more scheduled messages
await expect(channelPage.centerView.scheduledDraftChannelInfoMessage).not.toBeVisible();
await expect(channelPage.sidebarLeft.scheduledDraftCountonLHS).not.toBeVisible();
await expect(await channelPage.getLastPost()).toHaveText(draftMessage);
await verifyNoscheduledDraftsPending(channelPage, team, scheduledDraftPage, draftMessage);
});
test('MM-T5648 should create a draft and then schedule it', async ({pw, pages}) => {
test.setTimeout(120000);
const draftMessage = 'Draft to be Scheduled';
await pw.skipIfNoLicense();
const {user, team} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
// await setupChannelPage(channelPage, draftMessage);
await channelPage.goto();
await channelPage.toBeVisible();
await channelPage.centerView.postCreate.writeMessage(draftMessage);
// go to drafts page
const draftsPage = new pages.DraftPage(page);
await draftsPage.goTo(team.name);
await draftsPage.toBeVisible();
await draftsPage.assertBadgeCountOnTab('1');
await draftsPage.assertDraftBody(draftMessage);
await draftsPage.verifyScheduleIcon(draftMessage);
await draftsPage.openScheduleModal(draftMessage);
// # Reschedule it to 2 days from today
await channelPage.scheduledDraftModal.selectDay(2);
await channelPage.scheduledDraftModal.confirm();
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await scheduledDraftPage.goTo(team.name);
await scheduledDraftPage.toBeVisible();
await scheduledDraftPage.assertBadgeCountOnTab('1');
await scheduledDraftPage.assertscheduledDraftBody(draftMessage);
});
test('MM-T5644 should edit scheduled message', async ({pw, pages}) => {
test.setTimeout(120000);
const draftMessage = 'Scheduled Draft';
// # Skip test if no license
await pw.skipIfNoLicense();
const {user, team} = await pw.initSetup();
const {page} = await pw.testBrowser.login(user);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage);
await scheduleMessage(channelPage);
await channelPage.centerView.verifyscheduledDraftChannelInfo();
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
// # Hover and verify options
await scheduledDraftPage.verifyOnHoverActionItems(draftMessage);
const updatedText = 'updated text';
await scheduledDraftPage.editText(updatedText);
// # Go back and wait for message to arrive
await goBackToChannelAndWaitForMessageToArrive(page);
// * Verify the message has been sent and there's no more scheduled messages
await expect(channelPage.centerView.scheduledDraftChannelInfoMessage).not.toBeVisible();
await expect(channelPage.sidebarLeft.scheduledDraftCountonLHS).not.toBeVisible();
await expect(await channelPage.getLastPost()).toHaveText(draftMessage);
await verifyNoscheduledDraftsPending(channelPage, team, scheduledDraftPage, draftMessage);
});
test('MM-T5650 should copy scheduled message', async ({pw, pages, browserName}) => {
test.setTimeout(120000);
// Skip this test in Firefox clipboard permissions are not supported
test.skip(browserName === 'firefox', 'Test not supported in Firefox');
const draftMessage = 'Scheduled Draft';
// # Skip test if no license
await pw.skipIfNoLicense();
const {user} = await pw.initSetup();
const {page, context} = await pw.testBrowser.login(user);
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
const channelPage = new pages.ChannelsPage(page);
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
await setupChannelPage(channelPage, draftMessage);
await scheduleMessage(channelPage);
await channelPage.centerView.verifyscheduledDraftChannelInfo();
const scheduledDraftChannelInfo = await channelPage.centerView.scheduledDraftChannelInfoMessageText.innerText();
await verifyscheduledDrafts(channelPage, pages, draftMessage, scheduledDraftChannelInfo);
await scheduledDraftPage.copyScheduledMessage(draftMessage);
await page.goBack();
await channelPage.centerView.postCreate.input.focus();
await page.keyboard.down('Meta');
await page.keyboard.press('V');
await page.keyboard.up('Meta');
// * Assert the message typed is same as the copied message
await expect(channelPage.centerView.postCreate.input).toHaveText(draftMessage);
});
async function verifyNoscheduledDraftsPending(
channelPage: ChannelsPage,
team: any,
scheduledDraftPage: ScheduledDraftPage,
draftMessage: string,
): Promise<void> {
await channelPage.goto(team.name, 'scheduled_posts');
await expect(scheduledDraftPage.scheduledDraftPanel(draftMessage)).not.toBeVisible();
await expect(scheduledDraftPage.noscheduledDraftIcon).toBeVisible();
}
async function goBackToChannelAndWaitForMessageToArrive(page: Page): Promise<void> {
await page.goBack();
await wait(duration.half_min);
await page.reload();
}
async function replyToLastPost(post: any): Promise<void> {
await post.hover();
await post.postMenu.toBeVisible();
await post.postMenu.reply();
}
async function setupChannelPage(
channelPage: ChannelsPage,
draftMessage: string,
teamName?: string,
channelName?: string,
): Promise<void> {
await channelPage.goto(teamName, channelName);
await channelPage.toBeVisible();
await channelPage.centerView.postCreate.writeMessage(draftMessage);
await channelPage.centerView.postCreate.clickOnScheduleDraftDropdownButton();
}
/**
* Schedules a draft message by selecting a custom time and confirming.
*/
async function scheduleMessage(pageObject: ChannelsPage): Promise<void> {
await pageObject.scheduledDraftDropdown.toBeVisible();
await pageObject.scheduledDraftDropdown.selectCustomTime();
await pageObject.scheduledDraftModal.toBeVisible();
await pageObject.scheduledDraftModal.selectDay();
await pageObject.scheduledDraftModal.selectTime();
await pageObject.scheduledDraftModal.confirm();
}
/**
* Extracts and verifies the scheduled message on the scheduled page and in the channel.
*/
async function verifyscheduledDrafts(
channelPage: ChannelsPage,
pages: any,
draftMessage: string,
scheduledDraftChannelInfo: string,
): Promise<void> {
const scheduledDraftPage = new pages.ScheduledDraftPage(channelPage.page);
await verifyscheduledDraftCount(channelPage, '1');
await scheduledDraftPage.toBeVisible();
await scheduledDraftPage.assertBadgeCountOnTab('1');
await scheduledDraftPage.assertscheduledDraftBody(draftMessage);
const scheduledDraftPageInfo = await scheduledDraftPage.getTimeStampOfMessage(draftMessage);
await compareMessageTimestamps(scheduledDraftChannelInfo, scheduledDraftPageInfo, scheduledDraftPage);
}
/**
* Verifies the scheduled message count on the sidebar.
*/
async function verifyscheduledDraftCount(page: ChannelsPage, expectedCount: string): Promise<void> {
await page.centerView.clickOnSeeAllscheduledDrafts();
await page.sidebarLeft.assertscheduledDraftCountLHS(expectedCount);
}
/**
* Compares the time in the channel and the scheduled page to ensure consistency.
*/
async function compareMessageTimestamps(
timeInChannel: string,
scheduledDraftPageInfo: string,
scheduledDraftPage: ScheduledDraftPage,
): Promise<void> {
// Extract time from channel using the same date pattern
const matchedTimeInChannel = timeInChannel.match(scheduledDraftPage.datePattern);
const timeInSchedulePage = extractTimeFromHtml(scheduledDraftPageInfo, scheduledDraftPage);
if (!matchedTimeInChannel || !timeInSchedulePage) {
throw new Error('Could not extract date and time from one or both elements.');
}
const firstElementTime = matchedTimeInChannel[0];
const secondElementTime = timeInSchedulePage[0];
// Compare extracted times
expect(firstElementTime).toBe(secondElementTime);
}
/**
* Removes HTML tags from the scheduled message content and extracts the time pattern.
*/
function extractTimeFromHtml(htmlContent: string, scheduledDraftPage: ScheduledDraftPage): RegExpMatchArray | null {
// Remove all HTML tags and match the expected time pattern using the datePattern from scheduledDraftPage
const cleanedText = htmlContent.replace(/<\/?[^>]+(>|$)/g, '');
// Use the datePattern to extract the exact match for time
const matchedTime = cleanedText.match(scheduledDraftPage.datePattern);
return matchedTime;
}

Просмотреть файл

@@ -497,6 +497,7 @@ func TestUpdateScheduledPost(t *testing.T) {
},
ScheduledAt: model.GetMillis() + 100000, // 100 seconds in the future
}
createdScheduledPost, appErr := th.App.SaveScheduledPost(th.Context, scheduledPost, user1ConnID)
require.Nil(t, appErr)
require.NotNil(t, createdScheduledPost)
@@ -556,7 +557,6 @@ func TestUpdateScheduledPost(t *testing.T) {
newScheduledAtTime := model.GetMillis() + 9999999
createdScheduledPost.ScheduledAt = newScheduledAtTime
createdScheduledPost.Message = "Updated Message!!!"
updatedScheduledPost, appErr := th.App.UpdateScheduledPost(th.Context, th.BasicUser2.Id, createdScheduledPost, user1ConnID)
require.NotNil(t, appErr)
require.Equal(t, http.StatusForbidden, appErr.StatusCode)

Просмотреть файл

@@ -23,8 +23,8 @@ import Input from 'components/widgets/inputs/input/input';
import Menu from 'components/widgets/menu/menu';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import Constants, {A11yCustomEventTypes} from 'utils/constants';
import type {A11yFocusEventDetail} from 'utils/constants';
import Constants, {A11yCustomEventTypes} from 'utils/constants';
import {relativeFormatDate} from 'utils/datetime';
import {isKeyPressed} from 'utils/keyboard';
import {getCurrentMomentForTimezone} from 'utils/timezone';

Просмотреть файл

@@ -63,8 +63,10 @@ function mapStateToProps(state: GlobalState, props: Props) {
const channel = getChannel(state, channelId);
const useChannelMentions = haveIChannelPermission(state, teamId, channelId, Permissions.USE_CHANNEL_MENTIONS);
const canEdit = haveIChannelPermission(state, teamId, channelId, editPermission);
return {
canEditPost: haveIChannelPermission(state, teamId, channelId, editPermission),
canEditPost: canEdit,
canDeletePost: haveIChannelPermission(state, teamId, channelId, deletePermission),
codeBlockOnCtrlEnter: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'code_block_ctrl_enter', true),
ctrlSend: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter'),