E2E/Playwright: Refactor and fix tests for scheduled posts (#30871)
Этот коммит содержится в:
@@ -32,12 +32,12 @@ export class TestBrowser {
|
||||
|
||||
const channelsPage = new pages.ChannelsPage(page);
|
||||
const systemConsolePage = new pages.SystemConsolePage(page);
|
||||
const scheduledDraftPage = new pages.ScheduledDraftPage(page);
|
||||
const draftPage = new pages.DraftPage(page);
|
||||
const scheduledPostsPage = new pages.ScheduledPostsPage(page);
|
||||
const draftsPage = new pages.DraftsPage(page);
|
||||
|
||||
this.context = context;
|
||||
|
||||
return {context, page, channelsPage, systemConsolePage, scheduledDraftPage, draftPage};
|
||||
return {context, page, channelsPage, systemConsolePage, scheduledPostsPage, draftsPage};
|
||||
}
|
||||
|
||||
async close() {
|
||||
|
||||
@@ -4,3 +4,7 @@
|
||||
export const appsPluginId = 'com.mattermost.apps';
|
||||
export const callsPluginId = 'com.mattermost.calls';
|
||||
export const playbooksPluginId = 'playbooks';
|
||||
|
||||
// Remote users hour limit taken from webapp/channels/src/utils/constants.ts
|
||||
export const REMOTE_USERS_HOUR_LIMIT_END_OF_THE_DAY = 22;
|
||||
export const REMOTE_USERS_HOUR_LIMIT_BEGINNING_OF_THE_DAY = 6;
|
||||
|
||||
@@ -24,6 +24,9 @@ export async function baseGlobalSetup() {
|
||||
({client: adminClient, user: adminUser} = await makeClient(defaultAdmin));
|
||||
}
|
||||
|
||||
// Print playwright configs
|
||||
printPlaywrightTestConfig();
|
||||
|
||||
await sysadminSetup(adminClient, adminUser);
|
||||
}
|
||||
|
||||
@@ -69,6 +72,14 @@ async function sysadminSetup(client: Client4, user: UserProfile | null) {
|
||||
await printPluginDetails(client);
|
||||
}
|
||||
|
||||
function printPlaywrightTestConfig() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Playwright Test Config:
|
||||
- Headless = ${testConfig.headless}
|
||||
- SlowMo = ${testConfig.slowMo}
|
||||
- Workers = ${testConfig.workers}`);
|
||||
}
|
||||
|
||||
async function printLicenseInfo(client: Client4) {
|
||||
const license = await client.getClientLicenseOld();
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -94,7 +105,7 @@ async function printClientInfo(client: Client4) {
|
||||
- TelemetryId = ${config.TelemetryId}
|
||||
- ServiceEnvironment = ${config.ServiceEnvironment}`);
|
||||
|
||||
const {LogSettings, ServiceSettings, FeatureFlags} = await client.getConfig();
|
||||
const {LogSettings, ServiceSettings, PluginSettings, FeatureFlags} = await client.getConfig();
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Notable Server Config:
|
||||
- ServiceSettings.EnableSecurityFixAlert = ${ServiceSettings?.EnableSecurityFixAlert}
|
||||
@@ -108,6 +119,12 @@ async function printClientInfo(client: Client4) {
|
||||
.map(([key, value]) => ` - ${key} = ${value}`)
|
||||
.join('\n'),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Plugin Settings:
|
||||
- Enable = ${PluginSettings?.Enable}
|
||||
- EnableUploads = ${PluginSettings?.EnableUploads}
|
||||
- AutomaticPrepackagedPlugins = ${PluginSettings?.AutomaticPrepackagedPlugins}`);
|
||||
}
|
||||
|
||||
async function printPluginDetails(client: Client4) {
|
||||
|
||||
@@ -14,9 +14,56 @@ export {
|
||||
LoginPage,
|
||||
ResetPasswordPage,
|
||||
SignupPage,
|
||||
ScheduledDraftPage,
|
||||
ScheduledPostsPage,
|
||||
SystemConsolePage,
|
||||
DraftPage,
|
||||
DraftsPage,
|
||||
} from './ui/pages';
|
||||
|
||||
export {
|
||||
components,
|
||||
GlobalHeader,
|
||||
SearchPopover,
|
||||
ChannelsCenterView,
|
||||
ChannelsSidebarLeft,
|
||||
ChannelsSidebarRight,
|
||||
ChannelsAppBar,
|
||||
ChannelsHeader,
|
||||
ChannelsPostCreate,
|
||||
ChannelsPostEdit,
|
||||
ChannelsPost,
|
||||
DraftPost,
|
||||
FindChannelsModal,
|
||||
DeletePostModal,
|
||||
DeleteScheduledPostModal,
|
||||
SettingsModal,
|
||||
PostDotMenu,
|
||||
PostMenu,
|
||||
ThreadFooter,
|
||||
Footer,
|
||||
MainHeader,
|
||||
PostReminderMenu,
|
||||
EmojiGifPicker,
|
||||
GenericConfirmModal,
|
||||
ScheduleMessageMenu,
|
||||
ScheduleMessageModal,
|
||||
ScheduledPostIndicator,
|
||||
ScheduledDraftModal,
|
||||
ScheduledPost,
|
||||
SendMessageNowModal,
|
||||
SystemConsoleSidebar,
|
||||
SystemConsoleNavbar,
|
||||
SystemUsers,
|
||||
SystemUsersFilterPopover,
|
||||
SystemUsersFilterMenu,
|
||||
SystemUsersColumnToggleMenu,
|
||||
SystemConsoleFeatureDiscovery,
|
||||
SystemConsoleMobileSecurity,
|
||||
MessagePriority,
|
||||
UserProfilePopover,
|
||||
UserAccountMenu,
|
||||
DeletePostConfirmationDialog,
|
||||
RestorePostConfirmationDialog,
|
||||
ProfileModal,
|
||||
} from './ui/components';
|
||||
|
||||
export {TestArgs, ScreenshotOptions} from './types';
|
||||
|
||||
@@ -396,7 +396,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
AboutLink: 'https://mattermost.com/pl/about-mattermost',
|
||||
HelpLink: 'https://mattermost.com/pl/help/',
|
||||
ReportAProblemLink: 'https://mattermost.com/pl/report-a-bug',
|
||||
ReportAProblemType: 'link',
|
||||
ReportAProblemType: 'default',
|
||||
ReportAProblemMail: '',
|
||||
AllowDownloadLogs: true,
|
||||
ForgotPasswordLink: '',
|
||||
|
||||
@@ -7,4 +7,4 @@ export {getOnPremServerConfig} from './default_config';
|
||||
export {initSetup, getAdminClient} from './init';
|
||||
export {createRandomPost} from './post';
|
||||
export {createRandomTeam} from './team';
|
||||
export {createNewUserProfile, createRandomUser, getDefaultAdminUser} from './user';
|
||||
export {createNewUserProfile, createRandomUser, getDefaultAdminUser, isOutsideRemoteUserHour} from './user';
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Client4} from '@mattermost/client';
|
||||
import {UserProfile} from '@mattermost/types/users';
|
||||
import {UserProfile, UserTimezone} from '@mattermost/types/users';
|
||||
import {DateTime} from 'luxon';
|
||||
|
||||
import {getRandomId} from '@/util';
|
||||
import {testConfig} from '@/test_config';
|
||||
import {REMOTE_USERS_HOUR_LIMIT_END_OF_THE_DAY, REMOTE_USERS_HOUR_LIMIT_BEGINNING_OF_THE_DAY} from '@/constant';
|
||||
|
||||
export async function createNewUserProfile(client: Client4, prefix = 'user') {
|
||||
const randomUser = createRandomUser(prefix);
|
||||
@@ -42,3 +44,15 @@ export function getDefaultAdminUser() {
|
||||
|
||||
return admin as UserProfile;
|
||||
}
|
||||
|
||||
export function isOutsideRemoteUserHour(userTz: UserTimezone | undefined) {
|
||||
const timezone = (userTz?.useAutomaticTimezone ? userTz?.automaticTimezone : userTz?.manualTimezone) || 'UTC';
|
||||
|
||||
const teammateUserDate = DateTime.local().setZone(timezone);
|
||||
|
||||
const currentHour = teammateUserDate.hour;
|
||||
return (
|
||||
currentHour >= REMOTE_USERS_HOUR_LIMIT_END_OF_THE_DAY ||
|
||||
currentHour < REMOTE_USERS_HOUR_LIMIT_BEGINNING_OF_THE_DAY
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
createRandomUser,
|
||||
getAdminClient,
|
||||
initSetup,
|
||||
isOutsideRemoteUserHour,
|
||||
} from './server';
|
||||
import {hideDynamicChannelsContent, waitForAnimationEnd, waitUntil} from './test_action';
|
||||
import {pages} from './ui/pages';
|
||||
@@ -90,6 +91,7 @@ export class PlaywrightExtended {
|
||||
|
||||
// ./server
|
||||
readonly createNewUserProfile;
|
||||
readonly isOutsideRemoteUserHour;
|
||||
|
||||
// ./visual
|
||||
readonly matchSnapshot;
|
||||
@@ -131,7 +133,7 @@ export class PlaywrightExtended {
|
||||
this.ensurePluginsLoaded = ensurePluginsLoaded;
|
||||
this.initSetup = initSetup;
|
||||
this.getAdminClient = getAdminClient;
|
||||
|
||||
this.isOutsideRemoteUserHour = isOutsideRemoteUserHour;
|
||||
// ./test_action
|
||||
this.hideDynamicChannelsContent = hideDynamicChannelsContent;
|
||||
this.waitForAnimationEnd = waitForAnimationEnd;
|
||||
|
||||
@@ -7,6 +7,7 @@ import ChannelsHeader from './header';
|
||||
import ChannelsPostCreate from './post_create';
|
||||
import ChannelsPostEdit from './post_edit';
|
||||
import ChannelsPost from './post';
|
||||
import ScheduledPostIndicator from './scheduled_post_indicator';
|
||||
|
||||
import {duration, hexToRgb} from '@/util';
|
||||
import {waitUntil} from '@/test_action';
|
||||
@@ -18,14 +19,7 @@ export default class ChannelsCenterView {
|
||||
readonly header;
|
||||
readonly postCreate;
|
||||
readonly scheduledDraftOptions;
|
||||
readonly postBoxIndicator;
|
||||
readonly scheduledDraftChannelIcon;
|
||||
readonly scheduledDraftChannelInfoMessage;
|
||||
readonly scheduledDraftChannelInfoMessageLocator;
|
||||
readonly scheduledDraftDMChannelLocator;
|
||||
readonly scheduledDraftChannelInfoMessageText;
|
||||
readonly scheduledDraftDMChannelLocatorString;
|
||||
readonly scheduledDraftSeeAllLink;
|
||||
readonly scheduledPostIndicator;
|
||||
readonly postEdit;
|
||||
readonly editedPostIcon;
|
||||
readonly channelBanner;
|
||||
@@ -34,18 +28,11 @@ export default class ChannelsCenterView {
|
||||
this.container = container;
|
||||
this.page = page;
|
||||
|
||||
this.scheduledDraftChannelInfoMessageLocator = 'span:has-text("Message scheduled for")';
|
||||
this.scheduledDraftDMChannelLocatorString = 'div.ScheduledPostIndicator span a';
|
||||
this.header = new ChannelsHeader(this.container.locator('.channel-header'));
|
||||
this.postCreate = new ChannelsPostCreate(container.getByTestId('post-create'));
|
||||
this.scheduledDraftOptions = new ChannelsPostCreate(container.locator('#dropdown_send_post_options'));
|
||||
this.postEdit = new ChannelsPostEdit(container.locator('.post-edit__container'));
|
||||
this.postBoxIndicator = 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(this.scheduledDraftChannelInfoMessageLocator);
|
||||
this.scheduledDraftDMChannelLocator = container.locator(this.scheduledDraftDMChannelLocatorString);
|
||||
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all")');
|
||||
this.scheduledPostIndicator = new ScheduledPostIndicator(container.getByTestId('scheduledPostIndicator'));
|
||||
this.editedPostIcon = (postID: string) => container.locator(`#postEdited_${postID}`);
|
||||
this.channelBanner = container.getByTestId('channel_banner_container');
|
||||
}
|
||||
@@ -59,14 +46,6 @@ export default class ChannelsCenterView {
|
||||
await this.postCreate.postMessage(message, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click on "See all scheduled messages"
|
||||
*/
|
||||
async clickOnSeeAllscheduledDrafts() {
|
||||
await this.scheduledDraftSeeAllLink.isVisible();
|
||||
await this.scheduledDraftSeeAllLink.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first post in the Center
|
||||
*/
|
||||
@@ -140,22 +119,6 @@ export default class ChannelsCenterView {
|
||||
);
|
||||
}
|
||||
|
||||
async goToScheduledDraftsFromDMChannel() {
|
||||
if (await this.scheduledDraftDMChannelLocator.isVisible()) {
|
||||
await this.scheduledDraftDMChannelLocator.click();
|
||||
return;
|
||||
}
|
||||
await this.scheduledDraftSeeAllLink.isVisible();
|
||||
await this.scheduledDraftSeeAllLink.click();
|
||||
}
|
||||
|
||||
async verifyscheduledDraftChannelInfo() {
|
||||
await this.postBoxIndicator.isVisible();
|
||||
await this.scheduledDraftChannelIcon.isVisible();
|
||||
const messageLocator = this.scheduledDraftChannelInfoMessage.first();
|
||||
await expect(messageLocator).toContainText('Message scheduled for');
|
||||
}
|
||||
|
||||
async clickOnLastEditedPost(postID: string | null) {
|
||||
if (postID) {
|
||||
await this.editedPostIcon(postID).click();
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class DeleteScheduledPostModal {
|
||||
readonly container: Locator;
|
||||
readonly body: Locator;
|
||||
readonly deleteButton: Locator;
|
||||
readonly cancelButton: Locator;
|
||||
readonly closeButton: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.modal-body');
|
||||
this.deleteButton = container.locator('button:has-text("Yes, delete")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class DraftPost {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly panelHeader;
|
||||
readonly panelBody;
|
||||
|
||||
readonly postBody;
|
||||
readonly postHeader;
|
||||
readonly postImage;
|
||||
|
||||
readonly deleteButton;
|
||||
readonly editButton;
|
||||
readonly scheduleButton;
|
||||
readonly sendButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.panelHeader = container.locator('.PanelHeader');
|
||||
this.panelBody = container.locator('.DraftPanelBody');
|
||||
|
||||
this.postBody = container.locator('.post__body');
|
||||
this.postHeader = container.locator('.post__header');
|
||||
this.postImage = container.locator('.post__img');
|
||||
|
||||
this.deleteButton = container.locator('#draft_icon-trash-can-outline_delete');
|
||||
this.editButton = container.locator('#draft_icon-pencil-outline_edit');
|
||||
this.scheduleButton = container.locator('#draft_icon-clock-send-outline_reschedule');
|
||||
this.sendButton = container.locator('#draft_icon-send-outline_send');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async hover() {
|
||||
await this.container.hover();
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,14 @@ export default class ChannelsPost {
|
||||
return this.profileIcon.getByAltText(`${username} profile image`);
|
||||
}
|
||||
|
||||
async openRhs() {
|
||||
async openAThread() {
|
||||
await this.container.hover();
|
||||
await this.postMenu.toBeVisible();
|
||||
await this.postMenu.replyButton.waitFor();
|
||||
await this.postMenu.replyButton.click();
|
||||
}
|
||||
|
||||
async reply() {
|
||||
await this.container.hover();
|
||||
await this.postMenu.toBeVisible();
|
||||
await this.postMenu.reply();
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class ChannelsPostCreate {
|
||||
readonly attachmentButton;
|
||||
readonly emojiButton;
|
||||
readonly sendMessageButton;
|
||||
readonly scheduleDraftMessageButton;
|
||||
readonly scheduleMessageButton;
|
||||
readonly priorityButton;
|
||||
readonly suggestionList;
|
||||
readonly filePreview;
|
||||
@@ -33,7 +33,7 @@ export default class ChannelsPostCreate {
|
||||
this.attachmentButton = container.locator('#fileUploadButton');
|
||||
this.emojiButton = container.getByLabel('select an emoji');
|
||||
this.sendMessageButton = container.getByTestId('SendMessageButton');
|
||||
this.scheduleDraftMessageButton = container.getByLabel('Schedule message');
|
||||
this.scheduleMessageButton = container.getByLabel('Schedule message');
|
||||
this.priorityButton = container.getByLabel('Message priority');
|
||||
this.suggestionList = container.getByTestId('suggestionList');
|
||||
this.filePreview = container.locator('.file-preview__container');
|
||||
@@ -79,18 +79,6 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the message priority menu
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class ScheduleMessageMenu {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly tomorrowMenuItem;
|
||||
readonly mondayMenuItem;
|
||||
readonly nextMondayMenuItem;
|
||||
readonly recentlyUsedCustomTimeMenuItem;
|
||||
readonly customTimeMenuItem;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.tomorrowMenuItem = container.getByTestId('scheduling_time_tomorrow_9_am');
|
||||
this.mondayMenuItem = container.getByTestId('scheduling_time_monday_9_am');
|
||||
this.nextMondayMenuItem = container.getByTestId('scheduling_time_next_monday_9_am');
|
||||
this.recentlyUsedCustomTimeMenuItem = container.getByTestId('recently_used_custom_time');
|
||||
this.customTimeMenuItem = container.getByText('Choose a custom time');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async selectCustomTime() {
|
||||
await this.customTimeMenuItem.click();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class ScheduleMessageModal {
|
||||
readonly container: Locator;
|
||||
readonly dateButton: Locator;
|
||||
readonly timeButton: Locator;
|
||||
readonly timeOptionDropdown: Locator;
|
||||
readonly closeButton: Locator;
|
||||
readonly scheduleButton: Locator;
|
||||
readonly cancelButton: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.dateButton = container.locator('#customStatus__calendar-input');
|
||||
this.timeButton = container.getByTestId('time_button');
|
||||
this.timeOptionDropdown = container.getByLabel('Choose a time');
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
this.scheduleButton = container.locator('button:has-text("Schedule")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
}
|
||||
|
||||
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);
|
||||
const name = `${day}${daySuffix} ${month} (${dayOfWeek})`;
|
||||
return this.container.getByRole('button', {name});
|
||||
}
|
||||
|
||||
async selectDate(dayFromToday: number = 0) {
|
||||
await this.dateButton.click();
|
||||
|
||||
const pacificDate = new Date();
|
||||
const originDate = new 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'});
|
||||
|
||||
const dateLocator = this.dateLocator(day, month, dayOfWeek);
|
||||
|
||||
const isMonthChanged = pacificDate.getMonth() !== originDate.getMonth();
|
||||
if (!(await dateLocator.isVisible()) && isMonthChanged) {
|
||||
await this.container.getByLabel('Go to next month').click();
|
||||
}
|
||||
|
||||
await dateLocator.click();
|
||||
|
||||
// if day is less than 9 then add a 0 in front of the day
|
||||
if (day < 9) {
|
||||
return `${month} 0${day}`;
|
||||
}
|
||||
|
||||
return `${month} ${day}`;
|
||||
}
|
||||
|
||||
async selectTime(optionIndex: number = 0) {
|
||||
await this.timeButton.click();
|
||||
const timeButton = this.timeOptionDropdown.getByTestId(`time_option_${optionIndex}-button`);
|
||||
await expect(timeButton).toBeVisible();
|
||||
await timeButton.click();
|
||||
|
||||
return await timeButton.textContent();
|
||||
}
|
||||
|
||||
async scheduleMessage(dayFromToday: number = 0, timeOptionIndex: number = 0) {
|
||||
await this.toBeVisible();
|
||||
|
||||
const selectedDate = await this.selectDate(dayFromToday);
|
||||
const fromDateButton = await this.dateButton.inputValue();
|
||||
|
||||
const selectedTime = await this.selectTime(timeOptionIndex);
|
||||
await this.scheduleButton.click();
|
||||
|
||||
// if selectedDate is Today or Tomorrow then return Today or Tomorrow
|
||||
if (fromDateButton === 'Today' || fromDateButton === 'Tomorrow') {
|
||||
return {selectedDate: fromDateButton, selectedTime};
|
||||
}
|
||||
|
||||
// if selectedDate is a date in the future then return the date
|
||||
return {selectedDate, selectedTime};
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class ScheduledPost {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly panelHeader;
|
||||
readonly panelBody;
|
||||
|
||||
readonly postBody;
|
||||
readonly postHeader;
|
||||
readonly postImage;
|
||||
|
||||
readonly deleteButton;
|
||||
readonly editButton;
|
||||
readonly copyTextButton;
|
||||
readonly rescheduleButton;
|
||||
readonly sendNowButton;
|
||||
|
||||
readonly editTextBox;
|
||||
readonly saveButton;
|
||||
readonly cancelButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.panelHeader = container.locator('.PanelHeader');
|
||||
this.panelBody = container.locator('.DraftPanelBody');
|
||||
|
||||
this.postBody = container.locator('.post__body');
|
||||
this.postHeader = container.locator('.post__header');
|
||||
this.postImage = container.locator('.post__img');
|
||||
|
||||
this.deleteButton = container.locator('#draft_icon-trash-can-outline_delete');
|
||||
this.editButton = container.locator('#draft_icon-pencil-outline_edit');
|
||||
this.copyTextButton = container.locator('#draft_icon-content-copy_copy_text');
|
||||
this.rescheduleButton = container.locator('#draft_icon-clock-send-outline_reschedule');
|
||||
this.sendNowButton = container.locator('#draft_icon-send-outline_sendNow');
|
||||
|
||||
this.editTextBox = container.getByTestId('edit_textbox');
|
||||
this.saveButton = container.locator('button:has-text("Save")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async hover() {
|
||||
await this.container.hover();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class ScheduledPostIndicator {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly icon;
|
||||
readonly messageText;
|
||||
readonly seeAllLink;
|
||||
readonly scheduledMessageLink;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.icon = container.getByTestId('scheduledPostIcon');
|
||||
this.messageText = container.locator('span').first();
|
||||
this.seeAllLink = container.locator('a:has-text("See all")');
|
||||
this.scheduledMessageLink = container.locator('a:has-text("scheduled message")');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async toBeNotVisible() {
|
||||
await expect(this.container).not.toBeVisible();
|
||||
}
|
||||
|
||||
async getText() {
|
||||
return await this.messageText.innerText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Locator, expect} from '@playwright/test';
|
||||
|
||||
export default class SendMessageNowModal {
|
||||
readonly container: Locator;
|
||||
readonly body: Locator;
|
||||
readonly sendNowButton: Locator;
|
||||
readonly cancelButton: Locator;
|
||||
readonly closeButton: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.body = container.locator('.modal-body');
|
||||
this.sendNowButton = container.locator('button:has-text("Yes, send now")');
|
||||
this.cancelButton = container.locator('button:has-text("Cancel")');
|
||||
this.closeButton = container.getByRole('button', {name: 'Close'});
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,19 @@ import {Locator, expect} from '@playwright/test';
|
||||
export default class ChannelsSidebarLeft {
|
||||
readonly container: Locator;
|
||||
readonly findChannelButton;
|
||||
readonly scheduledDraftCountonLHS;
|
||||
readonly scheduledPostBadge;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.findChannelButton = container.getByRole('button', {name: 'Find Channels'});
|
||||
this.scheduledDraftCountonLHS = container.locator('span.scheduledPostBadge');
|
||||
this.scheduledPostBadge = container.locator('span.scheduledPostBadge');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async assertNoPendingScheduledDraft() {
|
||||
await expect(this.scheduledDraftCountonLHS).not.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.
|
||||
|
||||
@@ -6,6 +6,7 @@ import {Locator, expect} from '@playwright/test';
|
||||
import ChannelsPostCreate from './post_create';
|
||||
import ChannelsPostEdit from './post_edit';
|
||||
import ChannelsPost from './post';
|
||||
import ScheduledPostIndicator from './scheduled_post_indicator';
|
||||
|
||||
export default class ChannelsSidebarRight {
|
||||
readonly container: Locator;
|
||||
@@ -13,7 +14,7 @@ export default class ChannelsSidebarRight {
|
||||
readonly closeButton;
|
||||
readonly postCreate;
|
||||
readonly rhsPostBody;
|
||||
readonly postBoxIndicator;
|
||||
readonly scheduledPostIndicator;
|
||||
readonly scheduledDraftChannelInfoMessage;
|
||||
readonly scheduledDraftSeeAllLink;
|
||||
readonly scheduledDraftChannelInfoMessageText;
|
||||
@@ -25,7 +26,7 @@ export default class ChannelsSidebarRight {
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.postBoxIndicator = container.locator('div.postBoxIndicator');
|
||||
this.scheduledPostIndicator = new ScheduledPostIndicator(container.getByTestId('scheduledPostIndicator'));
|
||||
this.scheduledDraftChannelInfoMessage = container.locator('div.ScheduledPostIndicator span');
|
||||
this.scheduledDraftSeeAllLink = container.locator('a:has-text("See all")');
|
||||
this.scheduledDraftChannelInfoMessageText = container.locator('span:has-text("Message scheduled for")');
|
||||
@@ -84,11 +85,6 @@ export default class ChannelsSidebarRight {
|
||||
await expect(this.container).not.toBeVisible();
|
||||
}
|
||||
|
||||
async clickOnSeeAllscheduledDrafts() {
|
||||
await this.scheduledDraftSeeAllLink.isVisible();
|
||||
await this.scheduledDraftSeeAllLink.click();
|
||||
}
|
||||
|
||||
async toContainText(text: string) {
|
||||
await expect(this.container).toContainText(text);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ import ThreadFooter from './channels/thread_footer';
|
||||
import EmojiGifPicker from './channels/emoji_gif_picker';
|
||||
import GenericConfirmModal from './channels/generic_confirm_modal';
|
||||
import MessagePriority from './channels/message_priority';
|
||||
import ScheduledDraftMenu from './channels/scheduled_draft_menu';
|
||||
import ScheduleMessageMenu from './channels/schedule_message_menu';
|
||||
import ScheduleMessageModal from './channels/schedule_message_modal';
|
||||
import ScheduledPostIndicator from './channels/scheduled_post_indicator';
|
||||
import ScheduledDraftModal from './channels/scheduled_draft_modal';
|
||||
import UserAccountMenu from './user_account_menu';
|
||||
import ProfileModal from './channels/profile_modal';
|
||||
@@ -38,6 +40,10 @@ import DeletePostConfirmationDialog from './channels/delete_post_confirmation_di
|
||||
import RestorePostConfirmationDialog from './channels/restore_post_confirmation_dialog';
|
||||
import SystemConsoleFeatureDiscovery from './system_console/sections/system_users/feature_discovery';
|
||||
import SystemConsoleMobileSecurity from './system_console/sections/system_users/mobile_security';
|
||||
import ScheduledPost from './channels/scheduled_post';
|
||||
import SendMessageNowModal from './channels/send_message_now_modal';
|
||||
import DeleteScheduledPostModal from './channels/delete_scheduled_post_modal';
|
||||
import DraftPost from './channels/draft_post';
|
||||
|
||||
const components = {
|
||||
GlobalHeader,
|
||||
@@ -50,8 +56,10 @@ const components = {
|
||||
ChannelsPostCreate,
|
||||
ChannelsPostEdit,
|
||||
ChannelsPost,
|
||||
DraftPost,
|
||||
FindChannelsModal,
|
||||
DeletePostModal,
|
||||
DeleteScheduledPostModal,
|
||||
SettingsModal,
|
||||
PostDotMenu,
|
||||
PostMenu,
|
||||
@@ -61,8 +69,12 @@ const components = {
|
||||
PostReminderMenu,
|
||||
EmojiGifPicker,
|
||||
GenericConfirmModal,
|
||||
ScheduledDraftMenu,
|
||||
ScheduleMessageMenu,
|
||||
ScheduleMessageModal,
|
||||
ScheduledPostIndicator,
|
||||
ScheduledDraftModal,
|
||||
ScheduledPost,
|
||||
SendMessageNowModal,
|
||||
SystemConsoleSidebar,
|
||||
SystemConsoleNavbar,
|
||||
SystemUsers,
|
||||
@@ -82,6 +94,7 @@ const components = {
|
||||
export {
|
||||
components,
|
||||
GlobalHeader,
|
||||
SearchPopover,
|
||||
ChannelsCenterView,
|
||||
ChannelsSidebarLeft,
|
||||
ChannelsSidebarRight,
|
||||
@@ -90,12 +103,36 @@ export {
|
||||
ChannelsPostCreate,
|
||||
ChannelsPostEdit,
|
||||
ChannelsPost,
|
||||
DraftPost,
|
||||
FindChannelsModal,
|
||||
DeletePostModal,
|
||||
DeleteScheduledPostModal,
|
||||
SettingsModal,
|
||||
PostDotMenu,
|
||||
PostMenu,
|
||||
ThreadFooter,
|
||||
Footer,
|
||||
MainHeader,
|
||||
PostReminderMenu,
|
||||
EmojiGifPicker,
|
||||
GenericConfirmModal,
|
||||
ScheduleMessageMenu,
|
||||
ScheduleMessageModal,
|
||||
ScheduledPostIndicator,
|
||||
ScheduledDraftModal,
|
||||
ScheduledPost,
|
||||
SendMessageNowModal,
|
||||
SystemConsoleSidebar,
|
||||
SystemConsoleNavbar,
|
||||
SystemUsers,
|
||||
SystemUsersFilterPopover,
|
||||
SystemUsersFilterMenu,
|
||||
SystemUsersColumnToggleMenu,
|
||||
SystemConsoleFeatureDiscovery,
|
||||
SystemConsoleMobileSecurity,
|
||||
MessagePriority,
|
||||
UserProfilePopover,
|
||||
UserAccountMenu,
|
||||
DeletePostConfirmationDialog,
|
||||
RestorePostConfirmationDialog,
|
||||
ProfileModal,
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Page} from '@playwright/test';
|
||||
import {waitUntil} from 'async-wait-until';
|
||||
|
||||
import {ChannelsPost, components} from '@/ui/components';
|
||||
import SettingsModal from '@/ui/components/channels/settings/settings_modal';
|
||||
|
||||
import {duration} from '@/util';
|
||||
export default class ChannelsPage {
|
||||
readonly channels = 'Channels';
|
||||
|
||||
@@ -15,7 +16,6 @@ export default class ChannelsPage {
|
||||
readonly userAccountMenuButton;
|
||||
readonly searchPopover;
|
||||
readonly centerView;
|
||||
readonly scheduledDraftDropdown;
|
||||
readonly scheduledDraftModal;
|
||||
readonly sidebarLeft;
|
||||
readonly sidebarRight;
|
||||
@@ -32,6 +32,8 @@ export default class ChannelsPage {
|
||||
readonly postReminderMenu;
|
||||
readonly userAccountMenu;
|
||||
readonly emojiGifPickerPopup;
|
||||
readonly scheduleMessageMenu;
|
||||
readonly scheduleMessageModal;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
@@ -56,11 +58,14 @@ export default class ChannelsPage {
|
||||
this.postDotMenu = new components.PostDotMenu(page.getByRole('menu', {name: 'Post extra options'}));
|
||||
this.postReminderMenu = new components.PostReminderMenu(page.getByRole('menu', {name: 'Set a reminder for:'}));
|
||||
this.userAccountMenu = new components.UserAccountMenu(page.locator('#userAccountMenu'));
|
||||
this.scheduleMessageMenu = new components.ScheduleMessageMenu(page.locator('#dropdown_send_post_options'));
|
||||
|
||||
// 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'));
|
||||
this.scheduleMessageModal = new components.ScheduleMessageModal(
|
||||
page.getByRole('dialog', {name: 'Schedule message'}),
|
||||
);
|
||||
this.userProfilePopover = new components.UserProfilePopover(page.locator('.user-profile-popover'));
|
||||
|
||||
// Posts
|
||||
@@ -87,6 +92,8 @@ export default class ChannelsPage {
|
||||
}
|
||||
}
|
||||
await this.page.goto(channelsUrl);
|
||||
|
||||
return channelsUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,6 +105,30 @@ export default class ChannelsPage {
|
||||
await this.centerView.postMessage(message, files);
|
||||
}
|
||||
|
||||
async replyToLastPost(message: string) {
|
||||
const rootPost = await this.getLastPost();
|
||||
await rootPost.reply();
|
||||
|
||||
const sidebarRight = this.sidebarRight;
|
||||
await sidebarRight.toBeVisible();
|
||||
await sidebarRight.postMessage('Replying to a thread');
|
||||
|
||||
// * Verify the message has been sent
|
||||
await waitUntil(
|
||||
async () => {
|
||||
const post = await this.sidebarRight.getLastPost();
|
||||
const content = await post.container.textContent();
|
||||
|
||||
return content?.includes(message);
|
||||
},
|
||||
{timeout: duration.ten_sec},
|
||||
);
|
||||
|
||||
const lastPost = await sidebarRight.getLastPost();
|
||||
|
||||
return {rootPost, sidebarRight, lastPost};
|
||||
}
|
||||
|
||||
async openChannelSettings(): Promise<SettingsModal> {
|
||||
await this.centerView.header.openChannelMenu();
|
||||
await this.page.locator('#channelSettings[role="menuitem"]').click();
|
||||
@@ -144,4 +175,28 @@ export default class ChannelsPage {
|
||||
|
||||
return popover;
|
||||
}
|
||||
|
||||
async scheduleMessage(message: string, dayFromToday: number = 0, timeOptionIndex: number = 0) {
|
||||
await this.centerView.postCreate.writeMessage(message);
|
||||
|
||||
await expect(this.centerView.postCreate.scheduleMessageButton).toBeVisible();
|
||||
await this.centerView.postCreate.scheduleMessageButton.click();
|
||||
|
||||
await this.scheduleMessageMenu.toBeVisible();
|
||||
await this.scheduleMessageMenu.selectCustomTime();
|
||||
|
||||
return await this.scheduleMessageModal.scheduleMessage(dayFromToday, timeOptionIndex);
|
||||
}
|
||||
|
||||
async scheduleMessageFromThread(message: string, dayFromToday: number = 0, timeOptionIndex: number = 0) {
|
||||
await this.sidebarRight.postCreate.writeMessage(message);
|
||||
|
||||
await expect(this.sidebarRight.postCreate.scheduleMessageButton).toBeVisible();
|
||||
await this.sidebarRight.postCreate.scheduleMessageButton.click();
|
||||
|
||||
await this.scheduleMessageMenu.toBeVisible();
|
||||
await this.scheduleMessageMenu.selectCustomTime();
|
||||
|
||||
return await this.scheduleMessageModal.scheduleMessage(dayFromToday, timeOptionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,125 +3,52 @@
|
||||
|
||||
import {Page, expect} from '@playwright/test';
|
||||
|
||||
export default class DraftPage {
|
||||
import {components} from '@/ui/components';
|
||||
|
||||
export default class DraftsPage {
|
||||
readonly page: Page;
|
||||
|
||||
readonly draftsHeader;
|
||||
readonly tab;
|
||||
readonly confirmbutton;
|
||||
readonly datePattern;
|
||||
readonly deleteIcon;
|
||||
readonly deleteIconToolTip;
|
||||
readonly noscheduledDraftIcon;
|
||||
readonly scheduleIcon;
|
||||
readonly rescheduleIconToolTip;
|
||||
readonly draftBody;
|
||||
readonly scheduledDraftPageInfo;
|
||||
readonly scheduledDraftPanel;
|
||||
readonly scheduledDraftSendNowButton;
|
||||
readonly scheduledDraftSendNowButtonToolTip;
|
||||
readonly badge;
|
||||
readonly noDrafts;
|
||||
|
||||
readonly scheduleMessageModal;
|
||||
|
||||
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.draftsHeader = page.locator('.Drafts__header');
|
||||
this.tab = page.getByRole('tab', {name: 'Drafts'});
|
||||
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');
|
||||
this.badge = this.tab.locator('span.MuiBadge-badge');
|
||||
|
||||
this.noDrafts = page.locator('.no-results__wrapper');
|
||||
|
||||
this.scheduleMessageModal = new components.ScheduleMessageModal(
|
||||
page.getByRole('dialog', {name: 'Schedule message'}),
|
||||
);
|
||||
}
|
||||
|
||||
async goTo(teamName: string) {
|
||||
async goto(teamName: string) {
|
||||
await this.page.goto(`/${teamName}/drafts`);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
await expect(this.page).toHaveURL(/.*drafts/);
|
||||
await this.draftsHeader.isVisible();
|
||||
await expect(this.tab).toHaveAttribute('aria-selected', 'true');
|
||||
}
|
||||
|
||||
async assertBadgeCountOnTab(badgeCount: string) {
|
||||
await this.tab.isVisible();
|
||||
async getBadgeCountOnTab() {
|
||||
await expect(this.tab).toBeVisible();
|
||||
const badge = this.tab.locator('span.MuiBadge-badge');
|
||||
await expect(badge).toBeVisible();
|
||||
await expect(badge).toHaveText(badgeCount);
|
||||
return await badge.textContent();
|
||||
}
|
||||
|
||||
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();
|
||||
async getLastPost() {
|
||||
const lastPost = this.page.getByTestId('draftView').last();
|
||||
await lastPost.waitFor();
|
||||
return new components.DraftPost(lastPost);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +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';
|
||||
import ScheduledPostsPage from './scheduled_posts';
|
||||
import DraftsPage from './drafts';
|
||||
|
||||
const pages = {
|
||||
ChannelsPage,
|
||||
@@ -16,19 +16,19 @@ const pages = {
|
||||
LoginPage,
|
||||
ResetPasswordPage,
|
||||
SignupPage,
|
||||
ScheduledDraftPage,
|
||||
ScheduledPostsPage,
|
||||
SystemConsolePage,
|
||||
DraftPage,
|
||||
DraftsPage,
|
||||
};
|
||||
|
||||
export {
|
||||
pages,
|
||||
ChannelsPage,
|
||||
DraftsPage,
|
||||
LandingLoginPage,
|
||||
LoginPage,
|
||||
ResetPasswordPage,
|
||||
SignupPage,
|
||||
ScheduledDraftPage,
|
||||
ScheduledPostsPage,
|
||||
SystemConsolePage,
|
||||
DraftPage,
|
||||
};
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Page, expect} from '@playwright/test';
|
||||
|
||||
export default class ScheduledDraftPage {
|
||||
readonly page: Page;
|
||||
|
||||
readonly tab;
|
||||
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.tab = page.getByRole('tab', {name: 'Scheduled'});
|
||||
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.tab.isVisible();
|
||||
const badge = this.tab.locator('span.MuiBadge-badge');
|
||||
await expect(badge).toBeVisible();
|
||||
await expect(badge).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();
|
||||
}
|
||||
}
|
||||
80
e2e-tests/playwright/lib/src/ui/pages/scheduled_posts.ts
Обычный файл
80
e2e-tests/playwright/lib/src/ui/pages/scheduled_posts.ts
Обычный файл
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Page, expect} from '@playwright/test';
|
||||
|
||||
import {components} from '@/ui/components';
|
||||
import type {ScheduledPost} from '@/ui/components';
|
||||
|
||||
export default class ScheduledPostsPage {
|
||||
readonly page: Page;
|
||||
|
||||
readonly draftsHeader;
|
||||
readonly tab;
|
||||
readonly badge;
|
||||
readonly noScheduledDrafts;
|
||||
readonly scheduleMessageModal;
|
||||
readonly sendMessageNowModal;
|
||||
readonly deleteScheduledPostModal;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
|
||||
this.draftsHeader = page.locator('.Drafts__header');
|
||||
this.tab = page.getByRole('tab', {name: 'Scheduled'});
|
||||
this.badge = this.tab.locator('span.MuiBadge-badge');
|
||||
|
||||
this.noScheduledDrafts = page.locator('.no-results__wrapper');
|
||||
|
||||
this.scheduleMessageModal = new components.ScheduleMessageModal(
|
||||
page.getByRole('dialog', {name: 'Schedule message'}),
|
||||
);
|
||||
this.sendMessageNowModal = new components.SendMessageNowModal(
|
||||
page.getByRole('dialog', {name: 'Send message now'}),
|
||||
);
|
||||
this.deleteScheduledPostModal = new components.DeleteScheduledPostModal(
|
||||
page.getByRole('dialog', {name: 'Delete scheduled post'}),
|
||||
);
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.page).toHaveURL(/.*scheduled_posts/);
|
||||
await this.draftsHeader.isVisible();
|
||||
await expect(this.tab).toHaveAttribute('aria-selected', 'true');
|
||||
}
|
||||
|
||||
async getBadgeCountOnTab() {
|
||||
await expect(this.tab).toBeVisible();
|
||||
const badge = this.tab.locator('span.MuiBadge-badge');
|
||||
await expect(badge).toBeVisible();
|
||||
return await badge.textContent();
|
||||
}
|
||||
|
||||
async getLastPost() {
|
||||
const lastPost = this.page.getByTestId('scheduledPostView').last();
|
||||
await lastPost.waitFor();
|
||||
return new components.ScheduledPost(lastPost);
|
||||
}
|
||||
|
||||
async getLastPostID() {
|
||||
return this.page.getByTestId('scheduledPostView').last().getAttribute('data-postid');
|
||||
}
|
||||
|
||||
async getNthPost(index: number) {
|
||||
const nthPost = this.page.getByTestId('scheduledPostView').nth(index);
|
||||
await nthPost.waitFor();
|
||||
return new components.ScheduledPost(nthPost);
|
||||
}
|
||||
|
||||
async rescheduleMessage(post: ScheduledPost, dayFromToday: number = 0, timeOptionIndex: number = 0) {
|
||||
await post.hover();
|
||||
await expect(post.rescheduleButton).toBeVisible();
|
||||
await post.rescheduleButton.click();
|
||||
|
||||
return await this.scheduleMessageModal.scheduleMessage(dayFromToday, timeOptionIndex);
|
||||
}
|
||||
|
||||
async goto(teamName: string) {
|
||||
await this.page.goto(`/${teamName}/scheduled_posts`);
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user