Detox/E2E: Search Messages e2e tests in Gekidou (#6756)

* Detox/E2E: Search Messages e2e tests in Gekidou

* Migrate to Detox 20

* Fix detoxrc

* Fix assertion for search messages
This commit is contained in:
Joseph Baylon
2022-11-21 12:58:51 -08:00
committed by GitHub
parent 38331ac1d2
commit bcca6be0af
20 changed files with 2311 additions and 1022 deletions

View File

@@ -87,6 +87,7 @@ const NavigationSearch = forwardRef<SearchRef, Props>(({
searchIconColor={theme.sidebarText}
selectionColor={theme.sidebarText}
ref={ref}
testID='navigation.header.search_bar'
/>
</Animated.View>
);

View File

@@ -211,6 +211,7 @@ const OptionItem = ({
onPress={onRemove}
style={[styles.iconContainer]}
type='opacity'
testID={`${testID}.remove.button`}
>
<CompassIcon
name={'close'}

View File

@@ -73,7 +73,7 @@ export default function AddTeamSlideUp({otherTeams, title, showTitle = true}: Pr
onPress={onPressCreate}
showButton={false}
showTitle={showTitle}
testID={'team_sidebar.add_team_slide_up'}
testID='team_sidebar.add_team_slide_up'
title={title}
>
{hasOtherTeams &&
@@ -90,13 +90,13 @@ export default function AddTeamSlideUp({otherTeams, title, showTitle = true}: Pr
id='team_list.no_other_teams.title'
defaultMessage='No additional teams to join'
style={styles.title}
testID={'team_sidebar.add_team_slide_up.no_other_teams.title'}
testID='team_sidebar.add_team_slide_up.no_other_teams.title'
/>
<FormattedText
id='team_list.no_other_teams.description'
defaultMessage='To join another team, ask a Team Admin for an invitation, or create your own team.'
style={styles.description}
testID={'team_sidebar.add_team_slide_up.no_other_teams.description'}
testID='team_sidebar.add_team_slide_up.no_other_teams.description'
/>
</View>
}

View File

@@ -82,6 +82,7 @@ export default function TeamListItem({team, textColor, iconTextColor, iconBackgr
<Text
style={[styles.text, textColor && {color: textColor}]}
numberOfLines={1}
testID={`${teamListItemTestId}.team_display_name`}
>
{displayName}
</Text>

View File

@@ -43,35 +43,35 @@ const getModifiersSectionsData = (intl: IntlShape): ModifierItem[] => {
const sectionsData = [
{
term: 'From:',
testID: 'search.from_section',
testID: 'search.modifier.from',
description: formatMessage({id: 'mobile.search.modifier.from', defaultMessage: ' a specific user'}),
}, {
term: 'In:',
testID: 'search.in_section',
testID: 'search.modifier.in',
description: formatMessage({id: 'mobile.search.modifier.in', defaultMessage: ' a specific channel'}),
},
// {
// term: 'On:',
// testID: 'search.on_section',
// testID: 'search.modifier.on',
// description: formatMessage({id: 'mobile.search.modifier.on', defaultMessage: ' a specific date'}),
// },
// {
// term: 'After:',
// testID: 'search.after_section',
// testID: 'search.modifier.after',
// description: formatMessage({id: 'mobile.search.modifier.after', defaultMessage: ' after a date'}),
// }, {
// term: 'Before:',
// testID: 'search.before_section',
// testID: 'search.modifier.before',
// description: formatMessage({id: 'mobile.search.modifier.before', defaultMessage: ' before a date'}),
// },
{
term: '-',
testID: 'search.exclude_section',
testID: 'search.modifier.exclude',
description: formatMessage({id: 'mobile.search.modifier.exclude', defaultMessage: ' exclude search terms'}),
}, {
term: '""',
testID: 'search.phrases_section',
testID: 'search.modifier.phrases',
description: formatMessage({id: 'mobile.search.modifier.phrases', defaultMessage: ' messages with phrases'}),
},
];
@@ -143,6 +143,7 @@ const Modifiers = ({scrollEnabled, searchValue, setSearchValue, setTeamId, teamI
style={styles.title}
id={'screen.search.modifier.header'}
defaultMessage='Search options'
testID='search.modifier.header'
/>
{teams.length > 1 &&
<TeamPickerIcon

View File

@@ -39,7 +39,7 @@ const RecentItem = ({item, setRecentValue}: Props) => {
inline={true}
label={item.term}
onRemove={handleRemove}
testID={'search.recent_item'}
testID={`search.recent_item.${item.term}`}
type='remove'
containerStyle={styles.container}
/>

View File

@@ -335,6 +335,7 @@ const SearchScreen = ({teamId, teams}: Props) => {
style={styles.flex}
edges={EDGES}
onLayout={onLayout}
testID='search_messages.screen'
>
<Animated.View style={animated}>
<Animated.View style={headerTopStyle}>

View File

@@ -30,14 +30,14 @@ export default function SelectTeamSlideUp({teams, title, setTeamId, teamId}: Pro
<BottomSheetContent
showButton={false}
showTitle={showTitle}
testID={'search.search_team_slide_up'}
testID='search.select_team_slide_up'
title={title}
>
<TeamList
selectedTeamId={teamId}
teams={teams}
onPress={onPress}
testID='search.search_team_slide_up.team_list'
testID='search.select_team_slide_up.team_list'
/>
</BottomSheetContent>
);

View File

@@ -88,7 +88,7 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
<TouchableWithFeedback
onPress={handleTeamChange}
type='opacity'
testID={selectedTeam.id}
testID='team_picker.button'
>
<View style={[styles.teamContainer, divider && styles.border]}>
<View style={[styles.teamIcon, {width: size, height: size}]}>
@@ -99,7 +99,7 @@ const TeamPickerIcon = ({size = 24, divider = false, setTeamId, teams, teamId}:
textColor={theme.centerChannelColor}
backgroundColor={changeOpacity(theme.centerChannelColor, 0.16)}
selected={false}
testID={`${selectedTeam}.team_icon`}
testID={`team_picker.${selectedTeam.id}.team_icon`}
smallText={true}
/>
</View>

View File

@@ -1,37 +1,61 @@
{
"testRunner": "jest --forceExit --detectOpenHandles",
"runnerConfig": "e2e/config.js",
"testRunner": {
"$0": "jest",
"args": {
"config": "e2e/config.js"
}
},
"apps": {
"ios.debug": {
"type": "ios.app",
"binaryPath": "../ios/Build/Products/Debug-iphonesimulator/Mattermost.app"
},
"ios.release": {
"type": "ios.app",
"binaryPath": "../ios/Build/Products/Release-iphonesimulator/Mattermost.app",
"build": "cd ../fastlane && NODE_ENV=production bundle exec fastlane ios simulator && cd ../detox"
},
"android.debug": {
"type": "android.apk",
"binaryPath": "../android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../detox"
},
"android.release": {
"type": "android.apk",
"binaryPath": "../android/app/build/outputs/apk/release/app-release.apk",
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../detox"
}
},
"devices": {
"ios.simulator": {
"type": "ios.simulator",
"device": {
"type": "iPhone 13"
}
},
"android.emulator": {
"type": "android.emulator",
"device": {
"avdName": "detox_pixel_4_xl_api_30"
}
}
},
"configurations": {
"ios.sim.debug": {
"binaryPath": "../ios/Build/Products/Debug-iphonesimulator/Mattermost.app",
"type": "ios.simulator",
"device": {
"type": "iPhone 13"
}
"device": "ios.simulator",
"app": "ios.debug"
},
"ios.sim.release": {
"type": "ios.simulator",
"binaryPath": "../ios/Build/Products/Release-iphonesimulator/Mattermost.app",
"build": "cd ../fastlane && NODE_ENV=production bundle exec fastlane ios simulator && cd ../detox",
"device": {
"type": "iPhone 13"
}
"device": "ios.simulator",
"app": "ios.release"
},
"android.emu.debug": {
"type": "android.emulator",
"binaryPath": "../android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../detox",
"device": {
"avdName": "detox_pixel_4_xl_api_30"
}
"device": "android.emulator",
"app": "android.debug"
},
"android.emu.release": {
"type": "android.emulator",
"binaryPath": "../android/app/build/outputs/apk/release/app-release.apk",
"build": "cd .. && ./node_modules/.bin/jetify && cd android && ./gradlew clean && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../detox",
"device": {
"avdName": "detox_pixel_4_xl_api_30"
}
"device": "android.emulator",
"app": "android.release"
}
},
"artifacts": {

View File

@@ -7,16 +7,15 @@ const shard = process.env.CI_NODE_INDEX ? process.env.CI_NODE_INDEX : '';
module.exports = {
setupFilesAfterEnv: ['./test/setup.ts'],
maxWorkers: 1,
testEnvironment: './environment',
testRunner: 'jest-circus/runner',
testSequencer: './custom_sequencer.js',
testTimeout: 120000,
testRegex: '\\.e2e\\.ts$',
rootDir: '.',
testMatch: ['<rootDir>/test/**/*.e2e.ts'],
transform: {
'\\.ts?$': 'ts-jest',
},
reporters: [
'detox/runners/jest/streamlineReporter',
'detox/runners/jest/reporter',
['jest-junit', {
suiteName: 'Mobile App E2E with Detox and Jest',
outputDirectory: './artifacts',
@@ -36,6 +35,9 @@ module.exports = {
resultHtml: `${platform}-main${shard}.html`,
}],
],
globalSetup: 'detox/runners/jest/globalSetup',
globalTeardown: 'detox/runners/jest/globalTeardown',
testEnvironment: 'detox/runners/jest/testEnvironment',
verbose: true,
moduleNameMapper: {
'^@support/(.*)': '<rootDir>/support/$1',

View File

@@ -1,25 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const {
DetoxCircusEnvironment,
SpecReporter,
WorkerAssignReporter,
} = require('detox/runners/jest-circus');
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
constructor(config, context) {
super(config, context);
this.initTimeout = 300000;
// This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level.
// This is strictly optional.
this.registerListeners({
SpecReporter,
WorkerAssignReporter,
});
}
}
module.exports = CustomDetoxEnvironment;

View File

@@ -8,6 +8,9 @@ class NavigationHeader {
headerSubtitle: 'navigation.header.subtitle',
largeHeaderTitle: 'navigation.large_header.title',
largeHeaderSubtitle: 'navigation.large_header.subtitle',
searchInput: 'navigation.header.search_bar.search.input',
searchClearButton: 'navigation.header.search_bar.search.clear.button',
searchCancelButton: 'navigation.header.search_bar.search.cancel.button',
};
backButton = element(by.id(this.testID.backButton));
@@ -15,6 +18,9 @@ class NavigationHeader {
headerSubtitle = element(by.id(this.testID.headerSubtitle));
largeHeaderTitle = element(by.id(this.testID.largeHeaderTitle));
largeHeaderSubtitle = element(by.id(this.testID.largeHeaderSubtitle));
searchInput = element(by.id(this.testID.searchInput));
searchClearButton = element(by.id(this.testID.searchClearButton));
searchCancelButton = element(by.id(this.testID.searchCancelButton));
}
const navigationHeader = new NavigationHeader();

View File

@@ -33,11 +33,13 @@ import PushNotificationSettingsScreen from './push_notification_settings';
import ReactionsScreen from './reactions';
import RecentMentionsScreen from './recent_mentions';
import SavedMessagesScreen from './saved_messages';
import SearchMessagesScreen from './search_messages';
import SelectTimezoneScreen from './select_timezone';
import ServerScreen from './server';
import ServerListScreen from './server_list';
import SettingsScreen from './settings';
import TableScreen from './table';
import TeamDropdownMenuScreen from './team_dropdown_menu';
import ThemeDisplaySettingsScreen from './theme_display_settings';
import ThreadScreen from './thread';
import ThreadOptionsScreen from './thread_options';
@@ -77,11 +79,13 @@ export {
ReactionsScreen,
RecentMentionsScreen,
SavedMessagesScreen,
SearchMessagesScreen,
SelectTimezoneScreen,
ServerScreen,
ServerListScreen,
SettingsScreen,
TableScreen,
TeamDropdownMenuScreen,
ThemeDisplaySettingsScreen,
ThreadScreen,
ThreadOptionsScreen,

View File

@@ -0,0 +1,110 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
NavigationHeader,
PostList,
} from '@support/ui/component';
import {
HomeScreen,
PostOptionsScreen,
} from '@support/ui/screen';
import {timeouts, wait} from '@support/utils';
import {expect} from 'detox';
class SearchMessagesScreen {
testID = {
searchResultsScreenPrefix: 'search_results.',
searchMessagesScreen: 'search_messages.screen',
searchModifierHeader: 'search.modifier.header',
searchModifierFrom: 'search.modifier.from',
searchModifierIn: 'search.modifier.in',
searchModifierOn: 'search.modifier.on',
searchModifierAfter: 'search.modifier.after',
searchModifierBefore: 'search.modifier.before',
searchModifierExclude: 'search.modifier.exclude',
searchModifierPhrases: 'search.modifier.phrases',
teamPickerButton: 'team_picker.button',
};
searchMessagesScreen = element(by.id(this.testID.searchMessagesScreen));
searchModifierHeader = element(by.id(this.testID.searchModifierHeader));
searchModifierFrom = element(by.id(this.testID.searchModifierFrom));
searchModifierIn = element(by.id(this.testID.searchModifierIn));
searchModifierOn = element(by.id(this.testID.searchModifierOn));
searchModifierAfter = element(by.id(this.testID.searchModifierAfter));
searchModifierBefore = element(by.id(this.testID.searchModifierBefore));
searchModifierExclude = element(by.id(this.testID.searchModifierExclude));
searchModifierPhrases = element(by.id(this.testID.searchModifierPhrases));
teamPickerButton = element(by.id(this.testID.teamPickerButton));
// convenience props
largeHeaderTitle = NavigationHeader.largeHeaderTitle;
largeHeaderSubtitle = NavigationHeader.largeHeaderSubtitle;
searchInput = NavigationHeader.searchInput;
searchClearButton = NavigationHeader.searchClearButton;
searchCancelButton = NavigationHeader.searchCancelButton;
postList = new PostList(this.testID.searchResultsScreenPrefix);
getFlatPostList = () => {
return this.postList.getFlatList();
};
getPostListPostItem = (postId: string, text = '', postProfileOptions: any = {}) => {
return this.postList.getPost(postId, text, postProfileOptions);
};
getPostMessageAtIndex = (index: number) => {
return this.postList.getPostMessageAtIndex(index);
};
getTeamPickerIcon = (teamId: string) => {
return element(by.id(`team_picker.${teamId}.team_icon`));
};
getRecentSearchItem = (searchTerm: string) => {
return element(by.id(`search.recent_item.${searchTerm}`));
};
getRecentSearchItemRemoveButton = (searchTerm: string) => {
return element(by.id(`search.recent_item.${searchTerm}.remove.button`));
};
toBeVisible = async () => {
await waitFor(this.searchMessagesScreen).toExist().withTimeout(timeouts.TEN_SEC);
return this.searchMessagesScreen;
};
open = async () => {
// # Open search messages screen
await HomeScreen.searchTab.tap();
return this.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);
};
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 searchMessagesScreen = new SearchMessagesScreen();
export default searchMessagesScreen;

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {SearchMessagesScreen} from '@support/ui/screen';
import {timeouts} from '@support/utils';
import {expect} from 'detox';
class TeamDropdownMenuScreen {
testID = {
teamDropdownMenuScreen: 'search.select_team_slide_up.screen',
};
teamDropdownMenuScreen = element(by.id(this.testID.teamDropdownMenuScreen));
getTeamIcon = (teamId: string) => {
return element(by.id(`team_sidebar.team_list.team_list_item.${teamId}.team_icon`));
};
getTeamDisplayName = (teamId: string) => {
return element(by.id(`team_sidebar.team_list.team_list_item.${teamId}.team_display_name`));
};
toBeVisible = async () => {
await waitFor(this.teamDropdownMenuScreen).toExist().withTimeout(timeouts.TEN_SEC);
return this.teamDropdownMenuScreen;
};
open = async () => {
// # Open team dropdown menu screen
await SearchMessagesScreen.teamPickerButton.tap();
return this.toBeVisible();
};
close = async () => {
await this.teamDropdownMenuScreen.tap({x: 5, y: 10});
await expect(this.teamDropdownMenuScreen).not.toBeVisible();
};
}
const teamDropdownMenuScreen = new TeamDropdownMenuScreen();
export default teamDropdownMenuScreen;

View File

@@ -0,0 +1,519 @@
// 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 {
Channel,
Post,
Setup,
Team,
} from '@support/server_api';
import {
serverOneUrl,
siteOneUrl,
} from '@support/test_config';
import {Autocomplete} from '@support/ui/component';
import {
ChannelInfoScreen,
ChannelListScreen,
ChannelScreen,
EditPostScreen,
HomeScreen,
LoginScreen,
PinnedMessagesScreen,
PostOptionsScreen,
SavedMessagesScreen,
SearchMessagesScreen,
ServerScreen,
TeamDropdownMenuScreen,
ThreadScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
import {expect} from 'detox';
describe('Search - Search Messages', () => {
const serverOneDisplayName = 'Server 1';
const channelsCategory = 'channels';
let testChannel: any;
let testTeam: any;
let testUser: any;
beforeAll(async () => {
const {channel, team, user} = await Setup.apiInit(siteOneUrl);
testChannel = channel;
testTeam = team;
testUser = user;
// # Log in to server
await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName);
await LoginScreen.login(testUser);
});
beforeEach(async () => {
// * Verify on channel list screen
await ChannelListScreen.toBeVisible();
});
afterAll(async () => {
// # Log out
await HomeScreen.logout();
});
it('MM-T5294_1 - should match elements on search messages screen', async () => {
// # Open search messages screen
await SearchMessagesScreen.open();
// * Verify basic elements on search messages screen
await expect(SearchMessagesScreen.largeHeaderTitle).toHaveText('Search');
await expect(SearchMessagesScreen.searchInput).toBeVisible();
await expect(SearchMessagesScreen.searchModifierHeader).toHaveText('Search options');
await expect(SearchMessagesScreen.searchModifierFrom).toBeVisible();
await expect(SearchMessagesScreen.searchModifierIn).toBeVisible();
await expect(SearchMessagesScreen.searchModifierExclude).toBeVisible();
await expect(SearchMessagesScreen.searchModifierPhrases).toBeVisible();
// # Go back to channel list screen
await ChannelListScreen.open();
});
it('MM-T5294_2 - should be able to search messages from a specific user', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const message = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Tap on from-search-modifier, type in username, tap on user at-mention autocomplete, and tap on search key
await SearchMessagesScreen.searchModifierFrom.tap();
await SearchMessagesScreen.searchInput.typeText(testUser.username);
const {atMentionItem} = Autocomplete.getAtMentionItem(testUser.id);
await atMentionItem.tap();
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results contain messages from user
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = SearchMessagesScreen.getPostListPostItem(post.id, message);
await expect(postListPostItem).toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(`from: ${testUser.username}`).tap();
await ChannelListScreen.open();
});
it('MM-T5294_3 - should be able to search messages in a specific channel', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const message = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Tap on in-search-modifier, type in channel name, tap on channel mention autocomplete, and tap on search key
await SearchMessagesScreen.searchModifierIn.tap();
await SearchMessagesScreen.searchInput.typeText(testChannel.name);
const {channelMentionItem} = Autocomplete.getChannelMentionItem(testChannel.name);
await channelMentionItem.tap();
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results contain messages in channel
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = SearchMessagesScreen.getPostListPostItem(post.id, message);
await expect(postListPostItem).toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(`channel: ${testChannel.name}`).tap();
await ChannelListScreen.open();
});
it('MM-T5294_4 - should be able to search messages excluding search terms', async () => {
// # Open a channel screen, post a message prefix plus non-excluded term, post another message prefix plus excluded term, go back to channel list screen, and open search messages screen
const excludedTerm = getRandomId();
const messagePrefix = 'Message';
const messageWithNonExcludedTerm = `${messagePrefix} ${getRandomId()}`;
const messageWithExcludedTerm = `${messagePrefix} ${excludedTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(messageWithNonExcludedTerm);
const {post: nonExcludedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem: nonExcludedPostListPostItem} = SearchMessagesScreen.getPostListPostItem(nonExcludedPost.id, messageWithNonExcludedTerm);
await ChannelScreen.postMessage(messageWithExcludedTerm);
const {post: excludedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem: excludedPostListPostItem} = SearchMessagesScreen.getPostListPostItem(excludedPost.id, messageWithExcludedTerm);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in the message prefix, tap on excluded-search modifier, type in the excluded term, and tap on search key
await SearchMessagesScreen.searchInput.typeText(messagePrefix);
await SearchMessagesScreen.searchModifierExclude.tap();
await SearchMessagesScreen.searchInput.typeText(excludedTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results do not contain messages with excluded term
await expect(nonExcludedPostListPostItem).toBeVisible();
await expect(excludedPostListPostItem).not.toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(`${messagePrefix} -${excludedTerm}`).tap();
await ChannelListScreen.open();
});
it('MM-T5294_5 - should be able to search messages with phrases', async () => {
// # Open a channel screen, post a message prefix plus non-included term, post another message prefix plus included term, go back to channel list screen, and open search messages screen
const includedTerm = getRandomId();
const messagePrefix = 'How are';
const messageWithNonIncludedTerm = `${messagePrefix} ${getRandomId()}`;
const messageWithIncludedTerm = `${messagePrefix} ${includedTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(messageWithNonIncludedTerm);
const {post: nonIncludedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem: nonIncludedPostListPostItem} = SearchMessagesScreen.getPostListPostItem(nonIncludedPost.id, messageWithNonIncludedTerm);
await ChannelScreen.postMessage(messageWithIncludedTerm);
const {post: includedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem: includedPostListPostItem} = SearchMessagesScreen.getPostListPostItem(includedPost.id, messageWithIncludedTerm);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in the message prefix plus included term inside double quotes and tap on search key
await SearchMessagesScreen.searchModifierPhrases.tap();
await SearchMessagesScreen.searchInput.tapBackspaceKey();
await SearchMessagesScreen.searchInput.typeText(messageWithIncludedTerm);
await SearchMessagesScreen.searchModifierPhrases.tap();
await SearchMessagesScreen.searchInput.tapBackspaceKey();
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results only contain messages with included term
await expect(nonIncludedPostListPostItem).not.toBeVisible();
await expect(includedPostListPostItem).toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(`"${messageWithIncludedTerm} "`).tap();
await ChannelListScreen.open();
});
it('MM-T5294_6 - should be able to search messages using combination of modifiers', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const message = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Tap on from-search-modifier, type in username, tap on user at-mention autocomplete, tap on in-search-modifier, type in channel, tap on channel mention autocomplete, and tap on search key
await SearchMessagesScreen.searchModifierFrom.tap();
await SearchMessagesScreen.searchInput.typeText(testUser.username);
const {atMentionItem} = Autocomplete.getAtMentionItem(testUser.id);
await atMentionItem.tap();
await SearchMessagesScreen.searchModifierIn.tap();
await SearchMessagesScreen.searchInput.typeText(testChannel.name);
const {channelMentionItem} = Autocomplete.getChannelMentionItem(testChannel.name);
await channelMentionItem.tap();
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results only contain messages from user in channel
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = SearchMessagesScreen.getPostListPostItem(post.id, message);
await expect(postListPostItem).toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(`from: ${testUser.username} channel: ${testChannel.name}`).tap();
await ChannelListScreen.open();
});
it('MM-T5294_7 - should be able to search messages using recent searches', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const searchTerm = getRandomId();
const message = `Message ${searchTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in a search term that will yield results and tap on search key
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results contain searched message
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = SearchMessagesScreen.getPostListPostItem(post.id, message);
await expect(postListPostItem).toBeVisible();
// # Clear search input and tap on recent search item
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItem(searchTerm).tap();
// * Verify search results contain searched message
await expect(postListPostItem).toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
it('MM-T5294_8 - should be able to search messages on a another joined team', async () => {
// # As admin, create a second team, add user to the second team, create a new channel on second team, and add user to new channel; as user, terminate app and relaunch app
const {team: testTeamTwo} = await Team.apiCreateTeam(siteOneUrl, {prefix: 'a'});
await Team.apiAddUserToTeam(siteOneUrl, testUser.id, testTeamTwo.id);
const {channel: testChannelTwo} = await Channel.apiCreateChannel(siteOneUrl, {teamId: testTeamTwo.id});
await Channel.apiAddUserToChannel(siteOneUrl, testUser.id, testChannelTwo.id);
await device.reloadReactNative();
// * Verify on first team
await expect(ChannelListScreen.headerTeamDisplayName).toHaveText(testTeam.display_name);
// # Post a message to the new channel on second team and open search messages screen
const searchTerm = getRandomId();
const message = `Message ${searchTerm}`;
const {post} = await Post.apiCreatePost(siteOneUrl, {
channelId: testChannelTwo.id,
message,
});
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Tap on team picker button and tap on second team option
await SearchMessagesScreen.teamPickerButton.tap();
await TeamDropdownMenuScreen.getTeamIcon(testTeamTwo.id).tap();
// * Verify team picker button displays second team icon
await expect(SearchMessagesScreen.getTeamPickerIcon(testTeamTwo.id)).toBeVisible();
// # Type in a search term that will yield results for second team and tap on search key
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results contain searched message
const {postListPostItem} = SearchMessagesScreen.getPostListPostItem(post.id, message);
await expect(postListPostItem).toBeVisible();
// # Tap on team picker button and tap on first team option
await SearchMessagesScreen.teamPickerButton.tap();
await TeamDropdownMenuScreen.getTeamIcon(testTeam.id).tap();
// * Verify team picker button displays first team icon and search results do not contain searched message
await expect(SearchMessagesScreen.getTeamPickerIcon(testTeam.id)).toBeVisible();
await expect(postListPostItem).not.toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
it('MM-T5294_9 - should show empty search results screen when search result is empty', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const message = `Message ${getRandomId()}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in a search term that will yield no results and tap on search key
const searchTerm = getRandomId();
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify empty search state for search messages
await expect(element(by.text(`No matches found for “${searchTerm}`))).toBeVisible();
await expect(element(by.text('Check the spelling or try another search.'))).toExist();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
it('MM-T5294_10 - should be able to edit, reply to, and delete a searched message from search results screen', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const searchTerm = getRandomId();
const message = `Message ${searchTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in a search term that will yield results, tap on search key, open post options for searched message, and tap on edit option
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
const {post: searchedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await SearchMessagesScreen.openPostOptionsFor(searchedPost.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} = SearchMessagesScreen.getPostListPostItem(searchedPost.id, updatedMessage);
await expect(updatedPostListPostItem).toBeVisible();
await expect(postListPostItemEditedIndicator).toHaveText('(edited)');
// # Open post options for searched message and tap on reply option
await SearchMessagesScreen.openPostOptionsFor(searchedPost.id, updatedMessage);
await PostOptionsScreen.replyPostOption.tap();
// * Verify on thread screen
await ThreadScreen.toBeVisible();
// # Post a reply
const replyMessage = `${message} reply`;
await ThreadScreen.postMessage(replyMessage);
// * Verify reply is posted
const {post: replyPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = ThreadScreen.getPostListPostItem(replyPost.id, replyMessage);
await expect(postListPostItem).toBeVisible();
// # Go back to search results screen
await ThreadScreen.back();
// * Verify reply count and following button
const {postListPostItemFooterReplyCount, postListPostItemFooterFollowingButton} = SearchMessagesScreen.getPostListPostItem(searchedPost.id, updatedMessage);
await expect(postListPostItemFooterReplyCount).toHaveText('1 reply');
await expect(postListPostItemFooterFollowingButton).toBeVisible();
// # Open post options for updated searched message and delete post
await SearchMessagesScreen.openPostOptionsFor(searchedPost.id, updatedMessage);
await PostOptionsScreen.deletePost({confirm: true});
// * Verify updated searched message is deleted
await expect(postListPostItem).not.toExist();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
it('MM-T5294_11 - should be able to save/unsave a searched message from search results screen', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const searchTerm = getRandomId();
const message = `Message ${searchTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in a search term that will yield results, tap on search key, open post options for searched message, tap on save option, and open saved messages screen
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
const {post: searchedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await SearchMessagesScreen.openPostOptionsFor(searchedPost.id, message);
await PostOptionsScreen.savePostOption.tap();
await SavedMessagesScreen.open();
// * Verify searched message is displayed on saved messages screen
const {postListPostItem} = SavedMessagesScreen.getPostListPostItem(searchedPost.id, message);
await expect(postListPostItem).toBeVisible();
// # Go back to searched messages screen, open post options for searched message, tap on usave option, and open saved messages screen
await SearchMessagesScreen.open();
await SearchMessagesScreen.openPostOptionsFor(searchedPost.id, message);
await PostOptionsScreen.unsavePostOption.tap();
await SavedMessagesScreen.open();
// * Verify searched message is not displayed anymore on saved messages screen
await expect(postListPostItem).not.toExist();
// # Go back to searched messages screen, clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.open();
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
it('MM-T5294_12 - should be able to pin/unpin a searched message from search results screen', async () => {
// # Open a channel screen, post a message, go back to channel list screen, and open search messages screen
const searchTerm = getRandomId();
const message = `Message ${searchTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
// * Verify on search messages screen
await SearchMessagesScreen.toBeVisible();
// # Type in a search term that will yield results, tap on search key, open post options for searched message, tap on pin to channel option, go back to channel list screen, open the channel screen where searched message is posted, open channel info screen, and open pinned messages screen
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
const {post: searchedPost} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
await SearchMessagesScreen.openPostOptionsFor(searchedPost.id, message);
await PostOptionsScreen.pinPostOption.tap();
await ChannelListScreen.open();
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelInfoScreen.open();
await PinnedMessagesScreen.open();
// * Verify searched message is displayed on pinned messages screen
const {postListPostItem} = PinnedMessagesScreen.getPostListPostItem(searchedPost.id, message);
await expect(postListPostItem).toBeVisible();
// # Go back to searched messages screen, open post options for searched message, tap on unpin from channel option, go back to channel list screen, open the channel screen where searched message is posted, open channel info screen, and open pinned messages screen
await PinnedMessagesScreen.back();
await ChannelInfoScreen.close();
await ChannelScreen.back();
await SearchMessagesScreen.open();
await SearchMessagesScreen.openPostOptionsFor(searchedPost.id, message);
await PostOptionsScreen.unpinPostOption.tap();
await ChannelListScreen.open();
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelInfoScreen.open();
await PinnedMessagesScreen.open();
// * Verify searched message is not displayed anymore on pinned messages screen
await expect(postListPostItem).not.toExist();
// # Go back to searched messages screen, clear search input, remove recent search item, and go back to channel list screen
await PinnedMessagesScreen.back();
await ChannelInfoScreen.close();
await ChannelScreen.back();
await SearchMessagesScreen.open();
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
});

View File

@@ -25,6 +25,7 @@ import {
PostOptionsScreen,
RecentMentionsScreen,
SavedMessagesScreen,
SearchMessagesScreen,
ServerScreen,
} from '@support/ui/screen';
import {getRandomId} from '@support/utils';
@@ -115,4 +116,26 @@ describe('Smoke Test - Search', () => {
await ChannelInfoScreen.close();
await ChannelScreen.back();
});
it('MM-T4911_4 - should be able to search for a message and display on search results screen', async () => {
// # Open a channel screen, post a message, go back to channel list screen, open search messages screen, type in a search term that will yield results, and tap on search key
const searchTerm = getRandomId();
const message = `Message ${searchTerm}`;
await ChannelScreen.open(channelsCategory, testChannel.name);
await ChannelScreen.postMessage(message);
await ChannelScreen.back();
await SearchMessagesScreen.open();
await SearchMessagesScreen.searchInput.typeText(searchTerm);
await SearchMessagesScreen.searchInput.tapReturnKey();
// * Verify search results contain searched message
const {post} = await Post.apiGetLastPostInChannel(siteOneUrl, testChannel.id);
const {postListPostItem} = SearchMessagesScreen.getPostListPostItem(post.id, message);
await expect(postListPostItem).toBeVisible();
// # Clear search input, remove recent search item, and go back to channel list screen
await SearchMessagesScreen.searchClearButton.tap();
await SearchMessagesScreen.getRecentSearchItemRemoveButton(searchTerm).tap();
await ChannelListScreen.open();
});
});

2452
detox/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,37 +5,37 @@
"author": "Mattermost, Inc.",
"devDependencies": {
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-transform-modules-commonjs": "7.18.6",
"@babel/plugin-transform-runtime": "7.19.1",
"@babel/preset-env": "7.19.4",
"@jest/test-sequencer": "29.2.0",
"@types/jest": "29.1.2",
"@babel/plugin-transform-modules-commonjs": "7.19.6",
"@babel/plugin-transform-runtime": "7.19.6",
"@babel/preset-env": "7.20.2",
"@jest/test-sequencer": "29.3.1",
"@types/jest": "29.2.3",
"@types/tough-cookie": "4.0.2",
"@types/uuid": "8.3.4",
"aws-sdk": "2.1233.0",
"axios": "1.1.2",
"aws-sdk": "2.1255.0",
"axios": "1.1.3",
"axios-cookiejar-support": "4.0.3",
"babel-jest": "29.2.0",
"babel-jest": "29.3.1",
"babel-plugin-module-resolver": "4.1.0",
"client-oauth2": "4.3.3",
"deepmerge": "4.2.2",
"detox": "19.12.6",
"detox": "20.0.1",
"form-data": "4.0.0",
"jest": "29.2.0",
"jest-circus": "29.2.0",
"jest-cli": "29.2.0",
"jest": "29.3.1",
"jest-circus": "29.3.1",
"jest-cli": "29.3.1",
"jest-html-reporters": "3.0.11",
"jest-junit": "14.0.1",
"jest-stare": "2.4.1",
"junit-report-merger": "4.0.0",
"moment-timezone": "0.5.38",
"recursive-readdir": "2.2.2",
"moment-timezone": "0.5.39",
"recursive-readdir": "2.2.3",
"sanitize-filename": "1.6.3",
"shelljs": "0.8.5",
"tough-cookie": "4.1.2",
"ts-jest": "29.0.3",
"tslib": "2.4.0",
"typescript": "4.8.4",
"tslib": "2.4.1",
"typescript": "4.9.3",
"uuid": "9.0.0",
"xml2js": "0.4.23"
},