From 9d3c7c9496adc67135cceaea889efb28bab56f41 Mon Sep 17 00:00:00 2001 From: Joseph Baylon Date: Thu, 5 May 2022 10:04:33 -0700 Subject: [PATCH] Detox/E2E: Messaging e2e tests in Gekidou (#6228) --- app/components/jumbo_emoji/index.tsx | 5 +- app/components/markdown/markdown.tsx | 1 + app/screens/bottom_sheet/index.tsx | 1 + app/screens/edit_post/post_error/index.tsx | 4 +- app/screens/global_threads/index.tsx | 4 +- .../threads_list/header/index.tsx | 10 +- .../threads_list/threads_list.tsx | 3 +- .../threads_button.test.tsx.snap | 1 + .../threads_button/threads_button.tsx | 5 +- app/screens/thread/thread.tsx | 1 + detox/e2e/support/ui/component/alert.ts | 2 + detox/e2e/support/ui/component/post.ts | 3 + detox/e2e/support/ui/component/post_list.ts | 2 + detox/e2e/support/ui/screen/channel.ts | 5 +- detox/e2e/support/ui/screen/edit_post.ts | 32 ++++ detox/e2e/support/ui/screen/global_threads.ts | 31 ++++ detox/e2e/support/ui/screen/index.ts | 4 + detox/e2e/support/ui/screen/post_options.ts | 8 +- detox/e2e/support/ui/screen/server_list.ts | 4 +- detox/e2e/support/ui/screen/thread.ts | 82 ++++++++- detox/e2e/test/channels/channel_list.e2e.ts | 29 ++- .../e2e/test/messaging/message_delete.e2e.ts | 125 +++++++++++++ detox/e2e/test/messaging/message_draft.e2e.ts | 166 ++++++++++++++++++ detox/e2e/test/messaging/message_edit.e2e.ts | 153 ++++++++++++++++ detox/e2e/test/messaging/message_post.e2e.ts | 103 +++++++++++ detox/e2e/test/messaging/message_reply.e2e.ts | 133 ++++++++++++++ .../e2e/test/server_login/server_list.e2e.ts | 2 - detox/e2e/test/smoke_test/messaging.e2e.ts | 119 +++++++++++++ 28 files changed, 1007 insertions(+), 31 deletions(-) create mode 100644 detox/e2e/support/ui/screen/edit_post.ts create mode 100644 detox/e2e/support/ui/screen/global_threads.ts create mode 100644 detox/e2e/test/messaging/message_delete.e2e.ts create mode 100644 detox/e2e/test/messaging/message_draft.e2e.ts create mode 100644 detox/e2e/test/messaging/message_edit.e2e.ts create mode 100644 detox/e2e/test/messaging/message_post.e2e.ts create mode 100644 detox/e2e/test/messaging/message_reply.e2e.ts create mode 100644 detox/e2e/test/smoke_test/messaging.e2e.ts diff --git a/app/components/jumbo_emoji/index.tsx b/app/components/jumbo_emoji/index.tsx index de30554d5..bbb38ad4a 100644 --- a/app/components/jumbo_emoji/index.tsx +++ b/app/components/jumbo_emoji/index.tsx @@ -92,7 +92,10 @@ const JumboEmoji = ({baseTextStyle, isEdited, value}: JumboEmojiProps) => { ]; return ( - + {spacer} {spacer} { > {Boolean(errorLine) && ( )} {Boolean(errorExtra) && ( diff --git a/app/screens/global_threads/index.tsx b/app/screens/global_threads/index.tsx index bc93109f4..5cfbb062c 100644 --- a/app/screens/global_threads/index.tsx +++ b/app/screens/global_threads/index.tsx @@ -50,7 +50,7 @@ const GlobalThreads = ({componentId}: Props) => { edges={edges} mode='margin' style={styles.flex} - testID='global_threads' + testID='global_threads.screen' > { forceQueryAfterAppState={appState} setTab={setTab} tab={tab} - testID={'global_threads.list'} + testID={'global_threads.threads_list'} /> diff --git a/app/screens/global_threads/threads_list/header/index.tsx b/app/screens/global_threads/threads_list/header/index.tsx index 7cb63f1c3..48dc1a93d 100644 --- a/app/screens/global_threads/threads_list/header/index.tsx +++ b/app/screens/global_threads/threads_list/header/index.tsx @@ -143,14 +143,12 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => { hasUnreads ? undefined : styles.markAllReadIconDisabled, ], [styles, hasUnreads]); - const testIDPrefix = `${testID}.header`; - return ( { @@ -174,7 +172,7 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => { {hasUnreads ? ( ) : null} @@ -185,7 +183,7 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => { ); diff --git a/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap b/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap index 90d71d780..053fbab71 100644 --- a/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap +++ b/app/screens/home/channel_list/categories_list/threads_button/__snapshots__/threads_button.test.tsx.snap @@ -17,6 +17,7 @@ exports[`Threads Component should match snapshot 1`] = ` "opacity": 1, } } + testID="channel_list.threads.button" > { }, [customStyles, isActive, styles, unreads]); return ( - + { style={styles.flex} mode='margin' edges={edges} + testID='thread.screen' > {Boolean(rootPost?.id) && <> diff --git a/detox/e2e/support/ui/component/alert.ts b/detox/e2e/support/ui/component/alert.ts index 5015a5516..05d8f6ba7 100644 --- a/detox/e2e/support/ui/component/alert.ts +++ b/detox/e2e/support/ui/component/alert.ts @@ -11,6 +11,7 @@ class Alert { return isAndroid() ? element(by.text(title)) : element(by.label(title)).atIndex(0); }; + messageLengthTitle = isAndroid() ? element(by.text('Message Length')) : element(by.label('Message Length')).atIndex(0); removeServerTitle = (serverDisplayName: string) => { const title = `Are you sure you want to remove ${serverDisplayName}?`; @@ -21,6 +22,7 @@ class Alert { cancelButton = isAndroid() ? element(by.text('CANCEL')) : element(by.label('Cancel')).atIndex(1); deleteButton = isAndroid() ? element(by.text('DELETE')) : element(by.label('Delete')).atIndex(0); logoutButton = isAndroid() ? element(by.text('LOG OUT')) : element(by.label('Log out')).atIndex(1); + okButton = isAndroid() ? element(by.text('OK')) : element(by.label('OK')).atIndex(1); removeButton = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(1); } diff --git a/detox/e2e/support/ui/component/post.ts b/detox/e2e/support/ui/component/post.ts index 375fd3cfb..a841ca055 100644 --- a/detox/e2e/support/ui/component/post.ts +++ b/detox/e2e/support/ui/component/post.ts @@ -7,6 +7,7 @@ class Post { testID = { postProfilePicturePrefix: 'post_profile_picture.profile_picture.', blockQuote: 'markdown_block_quote', + editedIndicator: 'edited_indicator', emoji: 'markdown_emoji', image: 'markdown_image', message: 'markdown_text', @@ -28,6 +29,7 @@ class Post { getPost = (postItemSourceTestID: string, postId: string, postMessage: string, postProfileOptions: any = {}) => { const postItemMatcher = this.getPostItemMatcher(postItemSourceTestID, postId, postMessage); const postItemBlockQuoteMatcher = by.id(this.testID.blockQuote).withAncestor(postItemMatcher); + const postItemEditedIndicator = by.id(this.testID.editedIndicator).withAncestor(postItemMatcher); const postItemEmojiMatcher = by.id(this.testID.emoji).withAncestor(postItemMatcher); const postItemImageMatcher = by.id(this.testID.image).withAncestor(postItemMatcher); const postItemMessageMatcher = by.id(this.testID.message).withAncestor(postItemMatcher); @@ -41,6 +43,7 @@ class Post { return { postItem: element(postItemMatcher), postItemBlockQuote: element(postItemBlockQuoteMatcher), + postItemEditedIndicator: element(postItemEditedIndicator), postItemEmoji: element(postItemEmojiMatcher), postItemImage: element(postItemImageMatcher), postItemMessage: element(postItemMessageMatcher), diff --git a/detox/e2e/support/ui/component/post_list.ts b/detox/e2e/support/ui/component/post_list.ts index 268843e09..d69bba655 100644 --- a/detox/e2e/support/ui/component/post_list.ts +++ b/detox/e2e/support/ui/component/post_list.ts @@ -31,6 +31,7 @@ class PostList { const { postItem, postItemBlockQuote, + postItemEditedIndicator, postItemEmoji, postItemHeaderCommentedOn, postItemHeaderDateTime, @@ -54,6 +55,7 @@ class PostList { return { postListPostItem: postItem, postListPostItemBlockQuote: postItemBlockQuote, + postListPostItemEditedIndicator: postItemEditedIndicator, postListPostItemEmoji: postItemEmoji, postListPostItemHeaderCommentedOn: postItemHeaderCommentedOn, postListPostItemHeaderDateTime: postItemHeaderDateTime, diff --git a/detox/e2e/support/ui/screen/channel.ts b/detox/e2e/support/ui/screen/channel.ts index 41be4a968..c875c94ae 100644 --- a/detox/e2e/support/ui/screen/channel.ts +++ b/detox/e2e/support/ui/screen/channel.ts @@ -16,7 +16,7 @@ import { PostOptionsScreen, ThreadScreen, } from '@support/ui/screen'; -import {timeouts} from '@support/utils'; +import {timeouts, wait} from '@support/utils'; import {expect} from 'detox'; class ChannelScreen { @@ -27,6 +27,7 @@ class ChannelScreen { introOptionAddPeopleItem: 'channel_post_list.intro.option_item.add_people', introOptionSetHeaderItem: 'channel_post_list.intro.option_item.set_header', introOptionChannelDetailsItem: 'channel_post_list.intro.option_item.channel_details', + flatPostList: 'channel.post_list.flat_list', }; channelScreen = element(by.id(this.testID.channelScreen)); @@ -34,6 +35,7 @@ class ChannelScreen { introOptionAddPeopleItem = element(by.id(this.testID.introOptionAddPeopleItem)); introOptionSetHeaderItem = element(by.id(this.testID.introOptionSetHeaderItem)); introOptionChannelDetailsItem = element(by.id(this.testID.introOptionChannelDetailsItem)); + flatPostList = element(by.id(this.testID.flatPostList)); // convenience props backButton = NavigationHeader.backButton; @@ -102,6 +104,7 @@ class ChannelScreen { // # Open post options await postListPostItem.longPress(); await PostOptionsScreen.toBeVisible(); + await wait(timeouts.TWO_SEC); }; openReplyThreadFor = async (postId: string, text: string) => { diff --git a/detox/e2e/support/ui/screen/edit_post.ts b/detox/e2e/support/ui/screen/edit_post.ts new file mode 100644 index 000000000..cbb2d5543 --- /dev/null +++ b/detox/e2e/support/ui/screen/edit_post.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {timeouts} from '@support/utils'; + +class EditPostScreen { + testID = { + editPostScreen: 'edit_post.screen', + closeButton: 'close.edit_post.button', + saveButton: 'edit_post.save.button', + messageInput: 'edit_post.message.input', + messageInputError: 'edit_post.message.input.error', + messageInputErrorExtra: 'edit_post.message.input.error.extra', + }; + + editPostScreen = element(by.id(this.testID.editPostScreen)); + closeButton = element(by.id(this.testID.closeButton)); + saveButton = element(by.id(this.testID.saveButton)); + messageInput = element(by.id(this.testID.messageInput)); + messageInputError = element(by.id(this.testID.messageInputError)); + messageInputErrorExtra = element(by.id(this.testID.messageInputErrorExtra)); + + toBeVisible = async () => { + await waitFor(this.editPostScreen).toExist().withTimeout(timeouts.TEN_SEC); + await waitFor(this.messageInput).toBeVisible().withTimeout(timeouts.TEN_SEC); + + return this.editPostScreen; + }; +} + +const editPostScreen = new EditPostScreen(); +export default editPostScreen; diff --git a/detox/e2e/support/ui/screen/global_threads.ts b/detox/e2e/support/ui/screen/global_threads.ts new file mode 100644 index 000000000..ae2729f35 --- /dev/null +++ b/detox/e2e/support/ui/screen/global_threads.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {NavigationHeader} from '@support/ui/component'; +import {timeouts} from '@support/utils'; +import {expect} from 'detox'; + +class GlobalThreadsScreen { + testID = { + globalThreadsScreen: 'global_threads.screen', + }; + + globalThreadsScreen = element(by.id(this.testID.globalThreadsScreen)); + + // convenience props + backButton = NavigationHeader.backButton; + + toBeVisible = async () => { + await waitFor(this.globalThreadsScreen).toExist().withTimeout(timeouts.TEN_SEC); + + return this.globalThreadsScreen; + }; + + back = async () => { + await this.backButton.tap(); + await expect(this.globalThreadsScreen).not.toBeVisible(); + }; +} + +const globalThreadsScreen = new GlobalThreadsScreen(); +export default globalThreadsScreen; diff --git a/detox/e2e/support/ui/screen/index.ts b/detox/e2e/support/ui/screen/index.ts index ebb5ff4ea..9fc1c65f5 100644 --- a/detox/e2e/support/ui/screen/index.ts +++ b/detox/e2e/support/ui/screen/index.ts @@ -8,7 +8,9 @@ import ChannelDropdownMenuScreen from './channel_dropdown_menu'; import ChannelListScreen from './channel_list'; import CreateDirectMessageScreen from './create_direct_message'; import CreateOrEditChannelScreen from './create_or_edit_channel'; +import EditPostScreen from './edit_post'; import EditServerScreen from './edit_server'; +import GlobalThreadsScreen from './global_threads'; import HomeScreen from './home'; import LoginScreen from './login'; import PostOptionsScreen from './post_options'; @@ -24,7 +26,9 @@ export { ChannelListScreen, CreateDirectMessageScreen, CreateOrEditChannelScreen, + EditPostScreen, EditServerScreen, + GlobalThreadsScreen, HomeScreen, LoginScreen, PostOptionsScreen, diff --git a/detox/e2e/support/ui/screen/post_options.ts b/detox/e2e/support/ui/screen/post_options.ts index c551731b8..b60109004 100644 --- a/detox/e2e/support/ui/screen/post_options.ts +++ b/detox/e2e/support/ui/screen/post_options.ts @@ -9,6 +9,7 @@ class PostOptionsScreen { testID = { reactionEmojiPrefix: 'post_options.reaction_bar.reaction.', postOptionsScreen: 'post_options.screen', + postOptionsBackdrop: 'post_options.backdrop', pickReaction: 'post_options.reaction_bar.pick_reaction', replyPostOption: 'post_options.reply.post.option', copyLinkOption: 'post_options.copy.permalink.option', @@ -24,6 +25,7 @@ class PostOptionsScreen { }; postOptionsScreen = element(by.id(this.testID.postOptionsScreen)); + postOptionsBackdrop = element(by.id(this.testID.postOptionsBackdrop)); pickReaction = element(by.id(this.testID.pickReaction)); replyPostOption = element(by.id(this.testID.replyPostOption)); copyLinkOption = element(by.id(this.testID.copyLinkOption)); @@ -48,7 +50,7 @@ class PostOptionsScreen { }; close = async () => { - await this.postOptionsScreen.tap({x: 5, y: 10}); + await this.postOptionsBackdrop.tap({x: 5, y: 10}); await expect(this.postOptionsScreen).not.toBeVisible(); }; @@ -66,11 +68,11 @@ class PostOptionsScreen { if (confirm) { await deleteButton.tap(); await wait(timeouts.ONE_SEC); - await expect(this.postOptionsScreen).not.toBeVisible(); + await expect(this.postOptionsScreen).not.toExist(); } else { await cancelButton.tap(); await wait(timeouts.ONE_SEC); - await expect(this.postOptionsScreen).toBeVisible(); + await expect(this.postOptionsScreen).toExist(); await this.close(); } }; diff --git a/detox/e2e/support/ui/screen/server_list.ts b/detox/e2e/support/ui/screen/server_list.ts index 8636e04dc..cb32cbbb4 100644 --- a/detox/e2e/support/ui/screen/server_list.ts +++ b/detox/e2e/support/ui/screen/server_list.ts @@ -8,11 +8,13 @@ import {expect} from 'detox'; class ServerListScreen { testID = { serverListScreen: 'server_list.screen', + serverListBackdrop: 'server_list.backdrop', serverListTitle: 'server_list.title', addServerButton: 'server_list.add_a_server.button', }; serverListScreen = element(by.id(this.testID.serverListScreen)); + serverListBackdrop = element(by.id(this.testID.serverListBackdrop)); serverListTitle = element(by.id(this.testID.serverListTitle)); addServerButton = element(by.id(this.testID.addServerButton)); @@ -65,7 +67,7 @@ class ServerListScreen { }; close = async () => { - await this.serverListScreen.tap({x: 5, y: 10}); + await this.serverListBackdrop.tap({x: 5, y: 10}); await expect(this.serverListScreen).not.toBeVisible(); }; } diff --git a/detox/e2e/support/ui/screen/thread.ts b/detox/e2e/support/ui/screen/thread.ts index e30080998..a36b28cc4 100644 --- a/detox/e2e/support/ui/screen/thread.ts +++ b/detox/e2e/support/ui/screen/thread.ts @@ -1,19 +1,58 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {NavigationHeader} from '@support/ui/component'; -import {timeouts} from '@support/utils'; +import { + CameraQuickAction, + FileQuickAction, + ImageQuickAction, + InputQuickAction, + PostDraft, + PostList, + SendButton, +} from '@support/ui/component'; +import {PostOptionsScreen} from '@support/ui/screen'; +import {timeouts, wait} from '@support/utils'; import {expect} from 'detox'; class ThreadScreen { testID = { - threadScreen: 'channel.screen', + threadScreenPrefix: 'thread.', + threadScreen: 'thread.screen', + backButton: 'screen.back.button', + flatPostList: 'thread.post_list.flat_list', }; threadScreen = element(by.id(this.testID.threadScreen)); + backButton = element(by.id(this.testID.backButton)); + flatPostList = element(by.id(this.testID.flatPostList)); // convenience props - backButton = NavigationHeader.backButton; + atInputQuickAction = InputQuickAction.getAtInputQuickAction(this.testID.threadScreenPrefix); + atInputQuickActionDisabled = InputQuickAction.getAtInputQuickActionDisabled(this.testID.threadScreenPrefix); + slashInputQuickAction = InputQuickAction.getSlashInputQuickAction(this.testID.threadScreenPrefix); + slashInputQuickActionDisabled = InputQuickAction.getSlashInputQuickActionDisabled(this.testID.threadScreenPrefix); + fileQuickAction = FileQuickAction.getFileQuickAction(this.testID.threadScreenPrefix); + fileQuickActionDisabled = FileQuickAction.getFileQuickActionDisabled(this.testID.threadScreenPrefix); + imageQuickAction = ImageQuickAction.getImageQuickAction(this.testID.threadScreenPrefix); + imageQuickActionDisabled = ImageQuickAction.getImageQuickActionDisabled(this.testID.threadScreenPrefix); + cameraQuickAction = CameraQuickAction.getCameraQuickAction(this.testID.threadScreenPrefix); + cameraQuickActionDisabled = CameraQuickAction.getCameraQuickActionDisabled(this.testID.threadScreenPrefix); + postDraft = PostDraft.getPostDraft(this.testID.threadScreenPrefix); + postDraftArchived = PostDraft.getPostDraftArchived(this.testID.threadScreenPrefix); + postDraftReadOnly = PostDraft.getPostDraftReadOnly(this.testID.threadScreenPrefix); + postInput = PostDraft.getPostInput(this.testID.threadScreenPrefix); + sendButton = SendButton.getSendButton(this.testID.threadScreenPrefix); + sendButtonDisabled = SendButton.getSendButtonDisabled(this.testID.threadScreenPrefix); + + postList = new PostList(this.testID.threadScreenPrefix); + + getPostListPostItem = (postId: string, text: string, postProfileOptions: any = {}) => { + return this.postList.getPost(postId, text, postProfileOptions); + }; + + getPostMessageAtIndex = (index: number) => { + return this.postList.getPostMessageAtIndex(index); + }; toBeVisible = async () => { await waitFor(this.threadScreen).toExist().withTimeout(timeouts.TEN_SEC); @@ -25,6 +64,41 @@ class ThreadScreen { await this.backButton.tap(); await expect(this.threadScreen).not.toBeVisible(); }; + + openPostOptionsFor = async (postId: string, text: string) => { + const {postListPostItem} = this.getPostListPostItem(postId, text); + await expect(postListPostItem).toBeVisible(); + + // # Open post options + await postListPostItem.longPress(); + await PostOptionsScreen.toBeVisible(); + await wait(timeouts.TWO_SEC); + }; + + postMessage = async (message: string) => { + // # Post message + await this.postInput.tap(); + await this.postInput.replaceText(message); + await this.tapSendButton(); + }; + + tapSendButton = async () => { + // # Tap send button + await this.sendButton.tap(); + await expect(this.sendButton).not.toExist(); + await expect(this.sendButtonDisabled).toBeVisible(); + }; + + hasPostMessage = async (postId: string, postMessage: string) => { + const {postListPostItem} = this.getPostListPostItem(postId, postMessage); + await expect(postListPostItem).toBeVisible(); + }; + + hasPostMessageAtIndex = async (index: number, postMessage: string) => { + await expect( + this.getPostMessageAtIndex(index), + ).toHaveText(postMessage); + }; } const threadScreen = new ThreadScreen(); diff --git a/detox/e2e/test/channels/channel_list.e2e.ts b/detox/e2e/test/channels/channel_list.e2e.ts index 7b6d9d49d..1f5d9f478 100644 --- a/detox/e2e/test/channels/channel_list.e2e.ts +++ b/detox/e2e/test/channels/channel_list.e2e.ts @@ -9,6 +9,7 @@ import { Setup, + System, Team, } from '@support/server_api'; import { @@ -21,6 +22,7 @@ import { ChannelListScreen, CreateDirectMessageScreen, CreateOrEditChannelScreen, + GlobalThreadsScreen, HomeScreen, LoginScreen, ServerScreen, @@ -38,6 +40,12 @@ describe('Channels - Channel List', () => { let testUser: any; beforeAll(async () => { + System.apiUpdateConfig(siteOneUrl, { + ServiceSettings: { + CollapsedThreads: 'default_on', + }, + }); + const {channel, team, user} = await Setup.apiInit(siteOneUrl); testChannel = channel; testTeam = team; @@ -83,7 +91,7 @@ describe('Channels - Channel List', () => { await expect(ChannelScreen.introDisplayName).toHaveText(testChannel.display_name); // # Go back to channel list screen and tap on a second channel - await ChannelScreen.backButton.tap(); + await ChannelScreen.back(); await ChannelListScreen.toBeVisible(); await ChannelListScreen.getChannelListItemDisplayName(channelsCategory, offTopicChannelName).tap(); @@ -98,9 +106,8 @@ describe('Channels - Channel List', () => { it('MM-T4728_3 - should be able to collapse and expand categories', async () => { // # Go to a channel to make it active and go back to channel list screen - await ChannelListScreen.getChannelListItemDisplayName(channelsCategory, testChannel.name).tap(); - await ChannelScreen.toBeVisible(); - await ChannelScreen.backButton.tap(); + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.back(); // * Verify on channel list screen await ChannelListScreen.toBeVisible(); @@ -160,8 +167,15 @@ describe('Channels - Channel List', () => { await CreateOrEditChannelScreen.close(); }); - xit('MM-T4728_7 - should be able to go to threads screen', async () => { - // NOT YET IMPLEMENTED + it('MM-T4728_7 - should be able to go to global threads screen', async () => { + // # Tap on threads button + await ChannelListScreen.threadsButton.tap(); + + // * Verify on global threads screen + await GlobalThreadsScreen.toBeVisible(); + + // # Go back to channel list screen + await GlobalThreadsScreen.back(); }); xit('MM-T4728_8 - should be able to find channels', async () => { @@ -169,9 +183,10 @@ describe('Channels - Channel List', () => { }); it('MM-T4728_9 - should be able to switch between teams', async () => { - // # As admin, create a second team and add user to the second team + // # As admin, create a second team and add user to the second team; as user, terminate app and relaunch app const {team: testTeamTwo} = await Team.apiCreateTeam(siteOneUrl, {prefix: 'a'}); await Team.apiAddUserToTeam(siteOneUrl, testUser.id, testTeamTwo.id); + await device.reloadReactNative(); // * Verify on first team and team sidebar item is selected and has correct display name abbreviation await expect(ChannelListScreen.headerTeamDisplayName).toHaveText(testTeam.display_name); diff --git a/detox/e2e/test/messaging/message_delete.e2e.ts b/detox/e2e/test/messaging/message_delete.e2e.ts new file mode 100644 index 000000000..22c0d7b2f --- /dev/null +++ b/detox/e2e/test/messaging/message_delete.e2e.ts @@ -0,0 +1,125 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + HomeScreen, + LoginScreen, + PostOptionsScreen, + ServerScreen, + ThreadScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Message Delete', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4784_1 - should be able to delete a post message and confirm', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Open post options for the message that was just posted, tap delete option and confirm + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.deletePost({confirm: true}); + + // * Verify post message is deleted + await expect(postListPostItem).not.toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4784_2 - should be able to delete a post message and cancel', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Open post options for the message that was just posted, tap delete option and cancel + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.deletePost({confirm: false}); + + // * Verify post message is not deleted + await expect(postListPostItem).toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4784_3 - should be able to delete a post message from reply thread', async () => { + // # Open a channel screen, post a message, and tap on the post to open reply thread + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message); + await parentPostListPostItem.tap(); + + // * Verify on thread screen + await ThreadScreen.toBeVisible(); + + // # Post a reply, open post options for the reply message, tap delete option and confirm + const replyMessage = `${message} reply`; + await ThreadScreen.postMessage(replyMessage); + const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: replyPostListPostItem} = ChannelScreen.getPostListPostItem(replyPost.id, replyMessage); + await ThreadScreen.openPostOptionsFor(replyPost.id, replyMessage); + await PostOptionsScreen.deletePost({confirm: true}); + + // * Verify reply message is deleted + await expect(replyPostListPostItem).not.toExist(); + + // # Go back to channel list screen + await ThreadScreen.back(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/message_draft.e2e.ts b/detox/e2e/test/messaging/message_draft.e2e.ts new file mode 100644 index 000000000..99a722492 --- /dev/null +++ b/detox/e2e/test/messaging/message_draft.e2e.ts @@ -0,0 +1,166 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import {Alert} from '@support/ui/component'; +import { + ChannelScreen, + ChannelListScreen, + HomeScreen, + LoginScreen, + ServerScreen, + ThreadScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Message Draft', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + const offTopicChannelName = 'off-topic'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4781_1 - should be able to create a message draft', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postInput.tap(); + await ChannelScreen.postInput.replaceText(message); + + // * Verify message exists in post draft and is not yet added to post list + await expect(ChannelScreen.postInput).toHaveValue(message); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).not.toExist(); + + // # Switch to another channel and go back to original channel + await ChannelScreen.back(); + await ChannelScreen.open(channelsCategory, offTopicChannelName); + await ChannelScreen.back(); + await ChannelScreen.open(channelsCategory, testChannel.name); + + // * Verify message draft still exists in post draft + await expect(ChannelScreen.postInput).toHaveValue(message); + + // # Clear post draft and go back to channel list screen + await ChannelScreen.postInput.clearText(); + await ChannelScreen.back(); + }); + + it('MM-T4781_2 - should save message draft when app is closed then re-opened', async () => { + // # Open a channel screen and create a message draft + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postInput.tap(); + await ChannelScreen.postInput.replaceText(message); + + // * Verify message draft exists in post draft + await expect(ChannelScreen.postInput).toHaveValue(message); + + // # Send app to home and re-open + await device.sendToHome(); + await device.launchApp({newInstance: false}); + + // * Verify message draft still exists in post draft + await expect(ChannelScreen.postInput).toHaveValue(message); + + // # Clear post draft and go back to channel list screen + await ChannelScreen.postInput.clearText(); + await ChannelScreen.back(); + }); + + it('MM-T4781_3 - should show character count warning when message exceeds character limit', async () => { + // # Open a channel screen and create a message draft that exceeds character limit (> 16383) + let message = '1234567890'.repeat(1638) + '1234'; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postInput.tap(); + await ChannelScreen.postInput.replaceText(message); + + // * Verify warning message is displayed and send button is disabled + await expect(Alert.messageLengthTitle).toBeVisible(); + await expect(element(by.text('Your current message is too long. Current character count: 16384/16383'))).toBeVisible(); + await Alert.okButton.tap(); + await expect(ChannelScreen.sendButtonDisabled).toBeVisible(); + + // # Replace message draft with length less than the character limit (16383) + message = '1234567890'.repeat(1638) + '123'; + await ChannelScreen.postInput.replaceText(message); + + // * Verify warning message is not displayed and send button is enabled + await expect(Alert.messageLengthTitle).not.toBeVisible(); + await expect(element(by.text('Your current message is too long. Current character count: 16383/16383'))).not.toBeVisible(); + await expect(ChannelScreen.sendButton).toBeVisible(); + + // # Clear post draft and go back to channel list screen + await ChannelScreen.postInput.clearText(); + await ChannelScreen.back(); + }); + + it('MM-T4781_4 - should be able to create a message draft from reply thread', async () => { + // # Open a channel screen, post a message, and tap on the post to open reply thread + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message); + await parentPostListPostItem.tap(); + + // * Verify on thread screen + await ThreadScreen.toBeVisible(); + + // # Create a reply message draft + const replyMessage = `${message} reply`; + await ThreadScreen.postInput.tap(); + await ThreadScreen.postInput.replaceText(replyMessage); + + // * Verify reply message exists in post draft and is not yet added to post list + await expect(ThreadScreen.postInput).toHaveValue(replyMessage); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(post.id, replyMessage); + await expect(replyPostListPostItem).not.toExist(); + + // # Go back to channel screen and tap on parent post again + await ThreadScreen.back(); + await parentPostListPostItem.tap(); + + // * Verify reply message draft still exists in post draft + await expect(ThreadScreen.postInput).toHaveValue(replyMessage); + + // # Clear reply post draft and go back to channel list screen + await ThreadScreen.postInput.clearText(); + await ThreadScreen.back(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/message_edit.e2e.ts b/detox/e2e/test/messaging/message_edit.e2e.ts new file mode 100644 index 000000000..d09596440 --- /dev/null +++ b/detox/e2e/test/messaging/message_edit.e2e.ts @@ -0,0 +1,153 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + EditPostScreen, + HomeScreen, + LoginScreen, + PostOptionsScreen, + ServerScreen, + ThreadScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Message Edit', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4783_1 - should be able to edit a post message and save', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: originalPostListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(originalPostListPostItem).toExist(); + + // # Open post options for the message that was just posted and tap edit option + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.editPostOption.tap(); + + // * Verify on edit post screen + await EditPostScreen.toBeVisible(); + + // # Edit post message and tap save button + const updatedMessage = `${message} edit`; + await EditPostScreen.messageInput.replaceText(updatedMessage); + await EditPostScreen.saveButton.tap(); + + // * Verify post message is updated and displays edited indicator '(edited)' + const {postListPostItem: updatedPostListPostItem, postListPostItemEditedIndicator} = ChannelScreen.getPostListPostItem(post.id, updatedMessage); + await expect(updatedPostListPostItem).toExist(); + await expect(postListPostItemEditedIndicator).toHaveText('(edited)'); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4783_2 - should be able to edit a post message and cancel', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Open post options for the message that was just posted and tap edit option + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.editPostOption.tap(); + + // * Verify on edit post screen + await EditPostScreen.toBeVisible(); + + // # Edit post message and tap close button + const updatedMessage = `${message} edit`; + await EditPostScreen.messageInput.replaceText(updatedMessage); + await EditPostScreen.closeButton.tap(); + + // * Verify post message is not updated + await expect(postListPostItem).toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4783_3 - should be able to edit a post message from reply thread', async () => { + // # Open a channel screen, post a message, and tap on the post to open reply thread + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + const {post: parentPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: parentPostListPostItem} = ChannelScreen.getPostListPostItem(parentPost.id, message); + await parentPostListPostItem.tap(); + + // * Verify on thread screen + await ThreadScreen.toBeVisible(); + + // # Post a reply, open post options for the reply message and tap edit option + const replyMessage = `${message} reply`; + await ThreadScreen.postMessage(replyMessage); + const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ThreadScreen.openPostOptionsFor(replyPost.id, replyMessage); + await PostOptionsScreen.editPostOption.tap(); + + // * Verify on edit post screen + await EditPostScreen.toBeVisible(); + + // # Edit reply post message and tap save button + const updatedReplyMessage = `${replyMessage} edit`; + await EditPostScreen.messageInput.replaceText(updatedReplyMessage); + await EditPostScreen.saveButton.tap(); + + // * Verify reply post message is updated and displays edited indicator '(edited)' + const {postListPostItem: updatedReplyPostListPostItem, postListPostItemEditedIndicator} = ThreadScreen.getPostListPostItem(replyPost.id, updatedReplyMessage); + await expect(updatedReplyPostListPostItem).toExist(); + await expect(postListPostItemEditedIndicator).toHaveText('(edited)'); + + // # Go back to channel list screen + await ThreadScreen.back(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/message_post.e2e.ts b/detox/e2e/test/messaging/message_post.e2e.ts new file mode 100644 index 000000000..d1350542e --- /dev/null +++ b/detox/e2e/test/messaging/message_post.e2e.ts @@ -0,0 +1,103 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + HomeScreen, + LoginScreen, + ServerScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Message Post', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4782_1 - should be able to post a message when send button is tapped', async () => { + // # Open a channel screen + await ChannelScreen.open(channelsCategory, testChannel.name); + + // * Verify send button is disabled + await expect(ChannelScreen.sendButtonDisabled).toBeVisible(); + + // # Create a message draft + const message = `Message ${getRandomId()}`; + await ChannelScreen.postInput.tap(); + await ChannelScreen.postInput.replaceText(message); + + // * Verify send button is enabled + await expect(ChannelScreen.sendButton).toBeVisible(); + + // # Tap send button + await ChannelScreen.sendButton.tap(); + + // * Verify message is added to post list, cleared from post draft, and send button is disabled again + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + await expect(ChannelScreen.postInput).not.toHaveValue(message); + await expect(ChannelScreen.sendButtonDisabled).toBeVisible(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4782_2 - should be able to post a long message', async () => { + // # Open a channel screen, post a long message, and a short message after + const longMessage = 'The quick brown fox jumps over the lazy dog.'.repeat(20); + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(longMessage); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + await ChannelScreen.postMessage('short message'); + + // * Verify long message is posted and displays show more button (chevron down button) + const {postListPostItem, postListPostItemShowLessButton, postListPostItemShowMoreButton} = ChannelScreen.getPostListPostItem(post.id, longMessage); + await expect(postListPostItem).toExist(); + await expect(postListPostItemShowMoreButton).toExist(); + + // # Tap on show more button on long message post + await postListPostItemShowMoreButton.tap(); + + // * Verify long message post displays show less button (chevron up button) + await expect(postListPostItemShowLessButton).toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/messaging/message_reply.e2e.ts b/detox/e2e/test/messaging/message_reply.e2e.ts new file mode 100644 index 000000000..659c17e10 --- /dev/null +++ b/detox/e2e/test/messaging/message_reply.e2e.ts @@ -0,0 +1,133 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + HomeScreen, + LoginScreen, + PostOptionsScreen, + ServerScreen, + ThreadScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Messaging - Message Reply', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4785_1 - should be able to reply to a post via post options reply option', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Open post options for the message that was just posted, tap reply option + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.replyPostOption.tap(); + + // * Verify on reply thread screen and parent post is shown + await ThreadScreen.toBeVisible(); + const {postListPostItem: threadParentPostListPostItem} = ThreadScreen.getPostListPostItem(post.id, message); + await expect(threadParentPostListPostItem).toExist(); + + // # Reply to parent post + const replyMessage = `${message} reply`; + await ThreadScreen.postMessage(replyMessage); + + // * Verify reply message is posted + const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); + await expect(replyPostListPostItem).toExist(); + + // # Go back to channel list screen + await ThreadScreen.back(); + await ChannelScreen.back(); + }); + + it('MM-T4785_2 - should be able to open reply thread by tapping the post', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(postListPostItem).toExist(); + + // # Tap the post + await postListPostItem.tap(); + + // * Verify on reply thread screen + await ThreadScreen.toBeVisible(); + + // # Go back to channel list screen + await ThreadScreen.back(); + await ChannelScreen.back(); + }); + + it('MM-T4785_3 - should not have reply option available on reply thread post options', async () => { + // # Open a channel screen, post a message, and tap on the post + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await postListPostItem.tap(); + + // * Verify on reply thread screen + await ThreadScreen.toBeVisible(); + + // # Open post options for the parent message + await ThreadScreen.openPostOptionsFor(post.id, message); + + // * Verify reply option is not available + await expect(PostOptionsScreen.replyPostOption).not.toExist(); + + // # Go back to channel list screen + await PostOptionsScreen.close(); + await ThreadScreen.back(); + await ChannelScreen.back(); + }); +}); diff --git a/detox/e2e/test/server_login/server_list.e2e.ts b/detox/e2e/test/server_login/server_list.e2e.ts index 82d46dd34..e937185a5 100644 --- a/detox/e2e/test/server_login/server_list.e2e.ts +++ b/detox/e2e/test/server_login/server_list.e2e.ts @@ -229,8 +229,6 @@ describe('Server Login - Server List', () => { // # Log back in to first server await ServerListScreen.getServerItemLoginOption(serverOneDisplayName).tap(); - await expect(ServerScreen.headerTitleAddServer).toBeVisible(); - await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); await LoginScreen.login(serverOneUser); }); diff --git a/detox/e2e/test/smoke_test/messaging.e2e.ts b/detox/e2e/test/smoke_test/messaging.e2e.ts new file mode 100644 index 000000000..d3e1ec1ba --- /dev/null +++ b/detox/e2e/test/smoke_test/messaging.e2e.ts @@ -0,0 +1,119 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// ******************************************************************* +// - [#] indicates a test step (e.g. # Go to a screen) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element testID when selecting an element. Create one if none. +// ******************************************************************* + +import { + Post, + Setup, +} from '@support/server_api'; +import { + serverOneUrl, + siteOneUrl, +} from '@support/test_config'; +import { + ChannelScreen, + ChannelListScreen, + EditPostScreen, + HomeScreen, + LoginScreen, + PostOptionsScreen, + ServerScreen, + ThreadScreen, +} from '@support/ui/screen'; +import {getRandomId} from '@support/utils'; +import {expect} from 'detox'; + +describe('Smoke Test - Messaging', () => { + const serverOneDisplayName = 'Server 1'; + const channelsCategory = 'channels'; + let testChannel: any; + + beforeAll(async () => { + const {channel, user} = await Setup.apiInit(siteOneUrl); + testChannel = channel; + + // # Log in to server + await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); + await LoginScreen.login(user); + }); + + beforeEach(async () => { + // * Verify on channel list screen + await ChannelListScreen.toBeVisible(); + }); + + afterAll(async () => { + // # Log out + await HomeScreen.logout(); + }); + + it('MM-T4786_1 - should be able to post, edit, and delete a message', async () => { + // # Open a channel screen and post a message + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + + // * Verify message is added to post list + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: originalPostListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await expect(originalPostListPostItem).toExist(); + + // # Open post options for the message that was just posted and tap edit option + await ChannelScreen.openPostOptionsFor(post.id, message); + await PostOptionsScreen.editPostOption.tap(); + + // * Verify on edit post screen + await EditPostScreen.toBeVisible(); + + // # Edit post message and tap save button + const updatedMessage = `${message} edit`; + await EditPostScreen.messageInput.replaceText(updatedMessage); + await EditPostScreen.saveButton.tap(); + + // * Verify post message is updated and displays edited indicator '(edited)' + const {postListPostItem: updatedPostListPostItem, postListPostItemEditedIndicator} = ChannelScreen.getPostListPostItem(post.id, updatedMessage); + await expect(updatedPostListPostItem).toExist(); + await expect(postListPostItemEditedIndicator).toHaveText('(edited)'); + + // # Open post options for the updated message, tap delete option and confirm + await ChannelScreen.openPostOptionsFor(post.id, updatedMessage); + await PostOptionsScreen.deletePost({confirm: true}); + + // * Verify post message is deleted + await expect(updatedPostListPostItem).not.toExist(); + + // # Go back to channel list screen + await ChannelScreen.back(); + }); + + it('MM-T4786_2 - should be able to reply to a message', async () => { + // # Open a channel screen, post a message, and tap on the post + const message = `Message ${getRandomId()}`; + await ChannelScreen.open(channelsCategory, testChannel.name); + await ChannelScreen.postMessage(message); + const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem} = ChannelScreen.getPostListPostItem(post.id, message); + await postListPostItem.tap(); + + // * Verify on reply thread screen + await ThreadScreen.toBeVisible(); + + // # Reply to parent post + const replyMessage = `${message} reply`; + await ThreadScreen.postMessage(replyMessage); + + // * Verify reply message is posted + const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id); + const {postListPostItem: replyPostListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage); + await expect(replyPostListPostItem).toExist(); + + // # Go back to channel list screen + await ThreadScreen.back(); + await ChannelScreen.back(); + }); +});