Detox/E2E: Messaging e2e tests in Gekidou (#6228)

This commit is contained in:
Joseph Baylon 2022-05-05 10:04:33 -07:00 committed by GitHub
parent 1760b75dbf
commit 9d3c7c9496
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1007 additions and 31 deletions

View file

@ -92,7 +92,10 @@ const JumboEmoji = ({baseTextStyle, isEdited, value}: JumboEmojiProps) => {
];
return (
<Text style={styles}>
<Text
style={styles}
testID='edited_indicator'
>
{spacer}
<FormattedText
id='post_message_view.edited'

View file

@ -210,6 +210,7 @@ const Markdown = ({
return (
<Text
style={styles}
testID='edited_indicator'
>
{spacer}
<FormattedText

View file

@ -103,6 +103,7 @@ const BottomSheet = ({closeButtonId, componentId, initialSnapIndex = 0, renderCo
sheetRef.current?.snapTo(lastSnap);
}
}}
testID={`${testID}.backdrop`}
>
<Animated.View
style={[StyleSheet.absoluteFill, backdropStyle]}

View file

@ -38,14 +38,14 @@ const PostError = ({errorLine, errorExtra}: PostErrorProps) => {
>
{Boolean(errorLine) && (
<ErrorText
testID='edit_post.error.text'
testID='edit_post.message.input.error'
error={errorLine!}
textStyle={styles.errorWrap}
/>
)}
{Boolean(errorExtra) && (
<ErrorText
testID='edit_post.error.text.extra'
testID='edit_post.message.input.error.extra'
error={errorExtra!}
textStyle={!errorLine && styles.errorWrapper}
/>

View file

@ -50,7 +50,7 @@ const GlobalThreads = ({componentId}: Props) => {
edges={edges}
mode='margin'
style={styles.flex}
testID='global_threads'
testID='global_threads.screen'
>
<NavigationHeader
showBackButton={!isTablet}
@ -68,7 +68,7 @@ const GlobalThreads = ({componentId}: Props) => {
forceQueryAfterAppState={appState}
setTab={setTab}
tab={tab}
testID={'global_threads.list'}
testID={'global_threads.threads_list'}
/>
</View>
</SafeAreaView>

View file

@ -143,14 +143,12 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => {
hasUnreads ? undefined : styles.markAllReadIconDisabled,
], [styles, hasUnreads]);
const testIDPrefix = `${testID}.header`;
return (
<View style={styles.container}>
<View style={styles.menuContainer}>
<TouchableOpacity
onPress={handleViewAllThreads}
testID={`${testIDPrefix}.all_threads`}
testID={`${testID}.all_threads.button`}
>
<View style={allThreadsContainerStyle}>
<FormattedText
@ -162,7 +160,7 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => {
</TouchableOpacity>
<TouchableOpacity
onPress={handleViewUnreadThreads}
testID={`${testIDPrefix}.unread_threads`}
testID={`${testID}.unread_threads.button`}
>
<View style={unreadsContainerStyle}>
<View>
@ -174,7 +172,7 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => {
{hasUnreads ? (
<View
style={styles.unreadsDot}
testID={`${testIDPrefix}.unreads_dot`}
testID={`${testID}.unreads_dot`}
/>
) : null}
</View>
@ -185,7 +183,7 @@ const Header = ({setTab, tab, teamId, testID, unreadsCount}: Props) => {
<TouchableOpacity
disabled={!hasUnreads}
onPress={handleMarkAllAsRead}
testID={`${testIDPrefix}.mark_all_read`}
testID={`${testID}.mark_all_as_read.button`}
>
<CompassIcon
name='playlist-check'

View file

@ -139,7 +139,7 @@ const ThreadsList = ({
setTab={setTab}
tab={tab}
teamId={teamId}
testID={testID}
testID={`${testID}.header`}
unreadsCount={unreadsCount}
/>
<FlatList
@ -151,6 +151,7 @@ const ThreadsList = ({
onEndReached={handleEndReached}
removeClippedSubviews={true}
renderItem={renderItem}
testID={`${testID}.flat_list`}
/>
</>
);

View file

@ -17,6 +17,7 @@ exports[`Threads Component should match snapshot 1`] = `
"opacity": 1,
}
}
testID="channel_list.threads.button"
>
<View
style={

View file

@ -81,7 +81,10 @@ const ThreadsButton = ({currentChannelId, unreadsAndMentions}: Props) => {
}, [customStyles, isActive, styles, unreads]);
return (
<TouchableOpacity onPress={handlePress}>
<TouchableOpacity
onPress={handlePress}
testID='channel_list.threads.button'
>
<View style={customStyles.baseContainer}>
<View style={containerStyle}>
<CompassIcon

View file

@ -38,6 +38,7 @@ const Thread = ({rootPost}: ThreadProps) => {
style={styles.flex}
mode='margin'
edges={edges}
testID='thread.screen'
>
{Boolean(rootPost?.id) &&
<>

View file

@ -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);
}

View file

@ -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),

View file

@ -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,

View file

@ -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) => {

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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();
}
};

View file

@ -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();
};
}

View file

@ -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();

View file

@ -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);

View file

@ -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();
});
});

View file

@ -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();
});
});

View file

@ -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();
});
});

View file

@ -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();
});
});

View file

@ -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();
});
});

View file

@ -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);
});

View file

@ -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();
});
});