diff --git a/e2e-tests/playwright/support/ui/components/channels/center_view.ts b/e2e-tests/playwright/support/ui/components/channels/center_view.ts new file mode 100644 index 0000000000..26369376ba --- /dev/null +++ b/e2e-tests/playwright/support/ui/components/channels/center_view.ts @@ -0,0 +1,93 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {expect, Locator} from '@playwright/test'; + +import {components} from '@e2e-support/ui/components'; +import {waitUntil} from '@e2e-support/test_action'; +import {duration} from '@e2e-support/util'; + +export default class ChannelsCenterView { + readonly container: Locator; + + readonly header; + readonly headerMobile; + readonly postCreate; + + constructor(container: Locator) { + this.container = container; + + this.header = new components.ChannelsHeader(this.container.locator('.channel-header')); + this.headerMobile = new components.ChannelsHeaderMobile(this.container.locator('.navbar')); + this.postCreate = new components.ChannelsPostCreate(container.getByTestId('post-create')); + } + + async toBeVisible() { + await expect(this.container).toBeVisible(); + await this.postCreate.toBeVisible(); + } + + /** + * Return the first post in the Center + */ + async getFirstPost() { + const firstPost = this.container.getByTestId('postView').first(); + await firstPost.waitFor(); + return new components.ChannelsPost(firstPost); + } + + /** + * Return the last post in the Center + */ + async getLastPost() { + const lastPost = this.container.getByTestId('postView').last(); + await lastPost.waitFor(); + return new components.ChannelsPost(lastPost); + } + + /** + * Return the Nth post in the Center from the top + * @param index + * @returns + */ + async getNthPost(index: number) { + const nthPost = this.container.getByTestId('postView').nth(index); + await nthPost.waitFor(); + return new components.ChannelsPost(nthPost); + } + + /** + * Returns the Center post by post's id + * @param postId Just the ID without the prefix + */ + async getPostById(id: string) { + const postById = this.container.locator(`[id="post_${id}"]`); + await postById.waitFor(); + return new components.ChannelsPost(postById); + } + + async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) { + await waitUntil( + async () => { + const post = await this.getLastPost(); + const content = await post.container.textContent(); + return content?.includes(text); + }, + {timeout} + ); + } + + async waitUntilPostWithIdContains(id: string, text: string, timeout = duration.ten_sec) { + await waitUntil( + async () => { + const post = await this.getPostById(id); + const content = await post.container.textContent(); + + return content?.includes(text); + }, + {timeout} + ); + } +} + +export {ChannelsCenterView}; diff --git a/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts b/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts index a687220abf..50b39e6218 100644 --- a/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts +++ b/e2e-tests/playwright/support/ui/components/channels/header_mobile.ts @@ -10,13 +10,13 @@ export default class ChannelsHeaderMobile { this.container = container; } - async toggleSidebar() { - await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click(); - } - async toBeVisible() { await expect(this.container).toBeVisible(); } + + async toggleSidebar() { + await this.container.getByRole('button', {name: 'Toggle sidebar Menu Icon'}).click(); + } } export {ChannelsHeaderMobile}; diff --git a/e2e-tests/playwright/support/ui/components/channels/post_create.ts b/e2e-tests/playwright/support/ui/components/channels/post_create.ts index 82299d903d..14463e3028 100644 --- a/e2e-tests/playwright/support/ui/components/channels/post_create.ts +++ b/e2e-tests/playwright/support/ui/components/channels/post_create.ts @@ -7,30 +7,76 @@ export default class ChannelsPostCreate { readonly container: Locator; readonly input; + readonly attachmentButton; readonly emojiButton; readonly sendMessageButton; - constructor(container: Locator) { + constructor(container: Locator, isRHS = false) { this.container = container; - this.input = container.getByTestId('post_textbox'); + if (!isRHS) { + this.input = container.getByTestId('post_textbox'); + } else { + this.input = container.getByTestId('reply_textbox'); + } + this.attachmentButton = container.getByLabel('attachment'); this.emojiButton = container.getByLabel('select an emoji'); this.sendMessageButton = container.getByTestId('SendMessageButton'); } + async toBeVisible() { + await expect(this.container).toBeVisible(); + + await this.input.waitFor(); + await expect(this.input).toBeVisible(); + } + + /** + * It just writes the message in the input and doesn't send it + * @param message : Message to be written in the input + */ async writeMessage(message: string) { + await this.input.waitFor(); + await expect(this.input).toBeVisible(); + await this.input.fill(message); } + /** + * Returns the value of the message input + */ + async getInputValue() { + await expect(this.input).toBeVisible(); + return await this.input.inputValue(); + } + + /** + * Sends the message already written in the input + */ async sendMessage() { + await expect(this.input).toBeVisible(); + const messageInputValue = await this.getInputValue(); + expect(messageInputValue).not.toBe(''); + + await expect(this.sendMessageButton).toBeVisible(); + await expect(this.sendMessageButton).toBeEnabled(); + await this.sendMessageButton.click(); } - async toBeVisible() { - await expect(this.container).toBeVisible(); - await expect(this.input).toBeVisible(); + /** + * Composes and sends a message + */ + async postMessage(message: string) { + await this.writeMessage(message); + await this.sendMessage(); + } + + async openEmojiPicker() { + await expect(this.emojiButton).toBeVisible(); + await this.emojiButton.click(); } } diff --git a/e2e-tests/playwright/support/ui/components/channels/sidebar_right.ts b/e2e-tests/playwright/support/ui/components/channels/sidebar_right.ts index fd1f3586bd..f5a66d1116 100644 --- a/e2e-tests/playwright/support/ui/components/channels/sidebar_right.ts +++ b/e2e-tests/playwright/support/ui/components/channels/sidebar_right.ts @@ -8,16 +8,13 @@ import {components} from '@e2e-support/ui/components'; export default class ChannelsSidebarRight { readonly container: Locator; - readonly input; - readonly sendMessageButton; + readonly postCreate; readonly closeButton; constructor(container: Locator) { this.container = container; - this.input = container.getByTestId('reply_textbox'); - this.sendMessageButton = container.getByTestId('SendMessageButton'); - + this.postCreate = new components.ChannelsPostCreate(container.getByTestId('comment-create'), true); this.closeButton = container.locator('#rhsCloseButton'); } @@ -25,34 +22,23 @@ export default class ChannelsSidebarRight { await expect(this.container).toBeVisible(); } - async postMessage(message: string) { - await this.writeMessage(message); - await this.sendMessage(); - } - - async writeMessage(message: string) { - await this.input.fill(message); - } - - async sendMessage() { - await this.sendMessageButton.click(); - } - - /** - * Returns the value of the textbox in RHS - */ - async getInputValue() { - return await this.input.inputValue(); - } - /** * Returns the RHS post by post id * @param postId Just the ID without the prefix */ - async getRHSPostById(postId: string) { - const rhsPostId = `rhsPost_${postId}`; - const postLocator = this.container.locator(`#${rhsPostId}`); - return new components.ChannelsPost(postLocator); + async getPostById(postId: string) { + const post = this.container.locator(`[id="rhsPost_${postId}"]`); + await post.waitFor(); + return new components.ChannelsPost(post); + } + + /** + * Return the last post in the RHS + */ + async getLastPost() { + const post = this.container.getByTestId('rhsPostView').last(); + await post.waitFor(); + return new components.ChannelsPost(post); } /** diff --git a/e2e-tests/playwright/support/ui/components/index.ts b/e2e-tests/playwright/support/ui/components/index.ts index a1b86de033..bd97951652 100644 --- a/e2e-tests/playwright/support/ui/components/index.ts +++ b/e2e-tests/playwright/support/ui/components/index.ts @@ -7,6 +7,7 @@ import {ChannelsHeaderMobile} from './channels/header_mobile'; import {ChannelsAppBar} from './channels/app_bar'; import {ChannelsPostCreate} from './channels/post_create'; import {ChannelsPost} from './channels/post'; +import {ChannelsCenterView} from './channels/center_view' import {ChannelsSidebarLeft} from './channels/sidebar_left'; import {ChannelsSidebarRight} from './channels/sidebar_right'; import {DeletePostModal} from './channels/delete_post_modal'; @@ -21,38 +22,40 @@ import {ThreadFooter} from './channels/thread_footer'; const components = { BoardsSidebar, + GlobalHeader, + ChannelsCenterView, + ChannelsSidebarLeft, + ChannelsSidebarRight, ChannelsAppBar, ChannelsHeader, ChannelsHeaderMobile, ChannelsPostCreate, ChannelsPost, - ChannelsSidebarLeft, - ChannelsSidebarRight, - DeletePostModal, FindChannelsModal, - Footer, - GlobalHeader, - MainHeader, + DeletePostModal, PostDotMenu, - PostReminderMenu, PostMenu, ThreadFooter, + Footer, + MainHeader, + PostReminderMenu, }; export { components, BoardsSidebar, + GlobalHeader, + ChannelsCenterView, + ChannelsSidebarLeft, + ChannelsSidebarRight, ChannelsAppBar, ChannelsHeader, ChannelsHeaderMobile, ChannelsPostCreate, ChannelsPost, - ChannelsSidebarLeft, - ChannelsSidebarRight, FindChannelsModal, - GlobalHeader, - PostDotMenu, DeletePostModal, + PostDotMenu, PostMenu, ThreadFooter, }; diff --git a/e2e-tests/playwright/support/ui/pages/channels.ts b/e2e-tests/playwright/support/ui/pages/channels.ts index c96a841ea3..3fb729131b 100644 --- a/e2e-tests/playwright/support/ui/pages/channels.ts +++ b/e2e-tests/playwright/support/ui/pages/channels.ts @@ -3,38 +3,51 @@ import {Page} from '@playwright/test'; -import {waitUntil} from '@e2e-support/test_action'; import {components} from '@e2e-support/ui/components'; -import {duration, isSmallScreen} from '@e2e-support/util'; +import {isSmallScreen} from '@e2e-support/util'; export default class ChannelsPage { readonly channels = 'Channels'; + readonly page: Page; - readonly postCreate; - readonly findChannelsModal; + readonly globalHeader; - readonly header; - readonly headerMobile; - readonly appBar; + readonly centerView; readonly sidebarLeft; readonly sidebarRight; + readonly appBar; + + readonly findChannelsModal; + readonly deletePostModal; + readonly postDotMenu; readonly postReminderMenu; - readonly deletePostModal; constructor(page: Page) { this.page = page; - this.postCreate = new components.ChannelsPostCreate(page.locator('#post-create')); - this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'})); + + // The main areas of the app this.globalHeader = new components.GlobalHeader(page.locator('#global-header')); - this.header = new components.ChannelsHeader(page.locator('.channel-header')); - this.headerMobile = new components.ChannelsHeaderMobile(page.locator('.navbar')); - this.appBar = new components.ChannelsAppBar(page.locator('.app-bar')); + this.centerView = new components.ChannelsCenterView(page.getByTestId('channel_view')); this.sidebarLeft = new components.ChannelsSidebarLeft(page.locator('#SidebarContainer')); this.sidebarRight = new components.ChannelsSidebarRight(page.locator('#sidebar-right')); + this.appBar = new components.ChannelsAppBar(page.locator('.app-bar')); + + // Modals + this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'})); + this.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal')); + + // Menus 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.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal')); + } + + async toBeVisible() { + if (!isSmallScreen(this.page.viewportSize())) { + await this.globalHeader.toBeVisible(this.channels); + } + + await this.centerView.toBeVisible(); } async goto(teamName = '', channelName = '') { @@ -48,80 +61,6 @@ export default class ChannelsPage { await this.page.goto(channelsUrl); } - - async toBeVisible() { - if (!isSmallScreen(this.page.viewportSize())) { - await this.globalHeader.toBeVisible(this.channels); - } - await this.postCreate.toBeVisible(); - } - - async postMessage(message: string) { - await this.writeMessage(message); - await this.sendMessage(); - } - - async writeMessage(message: string) { - await this.postCreate.input.waitFor(); - await this.postCreate.writeMessage(message); - } - - async sendMessage() { - await this.postCreate.sendMessage(); - } - - async getFirstPost() { - await this.page.getByTestId('postView').first().waitFor(); - const post = await this.page.getByTestId('postView').first(); - return new components.ChannelsPost(post); - } - - async getLastPost() { - await this.page.getByTestId('postView').last().waitFor(); - const post = await this.page.getByTestId('postView').last(); - return new components.ChannelsPost(post); - } - - async getNthPost(index: number) { - await this.page.getByTestId('postView').nth(index).waitFor(); - const post = await this.page.getByTestId('postView').nth(index); - return new components.ChannelsPost(post); - } - - async getPostById(id: string) { - await this.page.locator(`[id="post_${id}"]`).waitFor(); - const post = await this.page.locator(`[id="post_${id}"]`); - return new components.ChannelsPost(post); - } - - async getRHSPostById(id: string) { - await this.page.locator(`[id="rhsPost_${id}"]`).waitFor(); - const post = await this.page.locator(`[id="rhsPost_${id}"]`); - return new components.ChannelsPost(post); - } - - async waitUntilLastPostContains(text: string, timeout = duration.ten_sec) { - await waitUntil( - async () => { - const post = await this.getLastPost(); - const content = await post.container.textContent(); - return content?.includes(text); - }, - {timeout} - ); - } - - async waitUntilPostWithIdContains(id: string, text: string, timeout = duration.ten_sec) { - await waitUntil( - async () => { - const post = await this.getPostById(id); - const content = await post.container.textContent(); - - return content?.includes(text); - }, - {timeout} - ); - } } export {ChannelsPage}; diff --git a/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts b/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts index 9ecfe64cd4..60ace2a715 100644 --- a/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts +++ b/e2e-tests/playwright/tests/accessibility/channels/intro_channel.spec.ts @@ -14,7 +14,7 @@ test('Base channel accessibility', async ({pw, pages, axe}) => { const channelsPage = new pages.ChannelsPage(page); await channelsPage.goto(); await channelsPage.toBeVisible(); - await channelsPage.postMessage('hello'); + await channelsPage.centerView.postCreate.postMessage('hello'); // # Analyze the page // Disable 'color-contrast' to be addressed by MM-53814 @@ -35,9 +35,9 @@ test('Post actions tab support', async ({pw, pages, axe}) => { const channelsPage = new pages.ChannelsPage(page); await channelsPage.goto(); await channelsPage.toBeVisible(); - await channelsPage.postMessage('hello'); + await channelsPage.centerView.postCreate.postMessage('hello'); - const post = await channelsPage.getLastPost(); + const post = await channelsPage.centerView.getLastPost(); await post.hover(); await post.postMenu.toBeVisible(); diff --git a/e2e-tests/playwright/tests/functional/channels/drafts/drafts_on_deleted_message.spec.ts b/e2e-tests/playwright/tests/functional/channels/drafts/drafts_on_deleted_message.spec.ts index 0ad7e9dbba..89e847b308 100644 --- a/e2e-tests/playwright/tests/functional/channels/drafts/drafts_on_deleted_message.spec.ts +++ b/e2e-tests/playwright/tests/functional/channels/drafts/drafts_on_deleted_message.spec.ts @@ -33,7 +33,7 @@ test('MM-T5435_1 Global Drafts link in sidebar should be hidden when another use await channelPage.goto(); await channelPage.toBeVisible(); - const lastPostByAdmin = await channelPage.getLastPost(); + const lastPostByAdmin = await channelPage.centerView.getLastPost(); await lastPostByAdmin.toBeVisible(); // # Open the last post sent by admin in RHS @@ -44,11 +44,11 @@ test('MM-T5435_1 Global Drafts link in sidebar should be hidden when another use // # Post a message as a user const sidebarRight = channelPage.sidebarRight; await sidebarRight.toBeVisible(); - await sidebarRight.postMessage('Replying to a thread'); + await sidebarRight.postCreate.postMessage('Replying to a thread'); // # Write a message in the reply thread but don't send it now so that it becomes a draft const draftMessageByUser = 'I should be in drafts by User'; - await sidebarRight.writeMessage(draftMessageByUser); + await sidebarRight.postCreate.writeMessage(draftMessageByUser); // # Close the RHS for draft to be saved await sidebarRight.close(); @@ -67,11 +67,11 @@ test('MM-T5435_1 Global Drafts link in sidebar should be hidden when another use await lastPostByAdmin.threadFooter.reply(); // * Verify drafts in user's textbox is still visible - const rhsTextboxValue = await sidebarRight.getInputValue(); + const rhsTextboxValue = await sidebarRight.postCreate.getInputValue(); expect(rhsTextboxValue).toBe(draftMessageByUser); // # Click on remove post - const deletedPostByAdminInRHS = await sidebarRight.getRHSPostById(adminPost.id); + const deletedPostByAdminInRHS = await sidebarRight.getPostById(adminPost.id); await deletedPostByAdminInRHS.remove(); // * Verify the drafts links should also be removed from sidebar @@ -90,10 +90,10 @@ test('MM-T5435_2 Global Drafts link in sidebar should be hidden when user delete await channelPage.toBeVisible(); // # Post a message in the channel - await channelPage.postMessage('Message which will be deleted'); + await channelPage.centerView.postCreate.postMessage('Message which will be deleted'); // # Start a thread by clicking on reply menuitem from post options menu - const post = await channelPage.getLastPost(); + const post = await channelPage.centerView.getLastPost(); await post.hover(); await post.postMenu.toBeVisible(); await post.postMenu.reply(); @@ -102,10 +102,10 @@ test('MM-T5435_2 Global Drafts link in sidebar should be hidden when user delete await sidebarRight.toBeVisible(); // # Post a message in the thread - await sidebarRight.postMessage('Replying to a thread'); + await sidebarRight.postCreate.postMessage('Replying to a thread'); // # Write a message in the reply thread but don't send it - await sidebarRight.writeMessage('I should be in drafts'); + await sidebarRight.postCreate.writeMessage('I should be in drafts'); // # Close the RHS for draft to be saved await sidebarRight.close(); diff --git a/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts b/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts index 542b457892..cd3be9afbf 100644 --- a/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts +++ b/e2e-tests/playwright/tests/functional/channels/search/find_channels.spec.ts @@ -38,7 +38,7 @@ test('MM-T5424 Find channel search returns only 50 results when there are more t // # Click on "Find channel" and type "test_channel" if (pw.isSmallScreen()) { - await channelsPage.headerMobile.toggleSidebar(); + await channelsPage.centerView.headerMobile.toggleSidebar(); } await channelsPage.sidebarLeft.findChannelButton.click(); diff --git a/webapp/channels/src/components/advanced_create_post/__snapshots__/advanced_create_post.test.jsx.snap b/webapp/channels/src/components/advanced_create_post/__snapshots__/advanced_create_post.test.jsx.snap index b4f00f50de..5a25d4bcd3 100644 --- a/webapp/channels/src/components/advanced_create_post/__snapshots__/advanced_create_post.test.jsx.snap +++ b/webapp/channels/src/components/advanced_create_post/__snapshots__/advanced_create_post.test.jsx.snap @@ -3,6 +3,7 @@ exports[`components/advanced_create_post Show tutorial 1`] = `