MM-30076 Detox/E2E: Update UI test structure for reusability (#4931)

* update UI test structure for reusability

* fix Android tests

* improve autocomplete test

* minor cleanup
This commit is contained in:
Saturnino Abril 2020-11-03 18:11:49 +08:00 committed by GitHub
parent 2eaeb4e040
commit a819f63d99
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 1056 additions and 434 deletions

View file

@ -266,7 +266,7 @@ export default class AtMention extends PureComponent {
return (
<SectionList
testID='autocomplete.at_mention.list'
testID='at_mention_suggestion.list'
keyboardShouldPersistTaps='always'
keyExtractor={this.keyExtractor}
style={[style.listView, {maxHeight: maxListHeight}]}

View file

@ -205,6 +205,7 @@ export default class Autocomplete extends PureComponent {
return (
<View style={wrapperStyles}>
<View
testID='autocomplete'
ref={this.containerRef}
style={containerStyles}
>

View file

@ -220,7 +220,7 @@ export default class ChannelMention extends PureComponent {
return (
<SectionList
testID='autocomplete.channel_mention.list'
testID='channel_mention_suggestion.list'
keyboardShouldPersistTaps='always'
keyExtractor={this.keyExtractor}
style={[style.listView, {maxHeight: maxListHeight}]}

View file

@ -3051,7 +3051,7 @@ exports[`components/autocomplete/emoji_suggestion should match snapshot 2`] = `
},
]
}
testID="autocomplete.emoji_suggestion.list"
testID="emoji_suggestion.list"
updateCellsBatchingPeriod={50}
windowSize={21}
/>

View file

@ -226,7 +226,7 @@ export default class EmojiSuggestion extends PureComponent {
return (
<FlatList
testID='autocomplete.emoji_suggestion.list'
testID='emoji_suggestion.list'
keyboardShouldPersistTaps='always'
style={[style.listView, {maxHeight: maxListHeight}]}
extraData={this.state}

View file

@ -204,7 +204,7 @@ export default class SlashSuggestion extends PureComponent {
return (
<FlatList
testID='autocomplete.slash_suggestion'
testID='slash_suggestion.list'
keyboardShouldPersistTaps='always'
style={[style.listView, {maxHeight: maxListHeight}]}
extraData={this.state}

View file

@ -17,7 +17,6 @@ exports[`EditChannelInfo should match snapshot 1`] = `
"flex": 1,
}
}
testID="edit_channel.scroll"
>
<TouchableWithoutFeedback
onPress={[Function]}

View file

@ -53,6 +53,7 @@ export default class EditChannelInfo extends PureComponent {
oldHeader: PropTypes.string,
oldPurpose: PropTypes.string,
isLandscape: PropTypes.bool.isRequired,
testID: PropTypes.string,
};
static defaultProps = {
@ -204,6 +205,7 @@ export default class EditChannelInfo extends PureComponent {
isLandscape,
error,
saving,
testID,
} = this.props;
const {keyboardVisible, keyboardPosition} = this.state;
const bottomStyle = {
@ -241,7 +243,7 @@ export default class EditChannelInfo extends PureComponent {
<React.Fragment>
<StatusBar/>
<KeyboardAwareScrollView
testID='edit_channel.scroll'
testID={testID}
ref={this.scroll}
style={style.container}
keyboardShouldPersistTaps={'always'}

View file

@ -20,7 +20,7 @@ exports[`ErrorText should match snapshot 1`] = `
},
]
}
testID="error_text"
testID="error.text"
>
Username must begin with a letter and contain between 3 and 22 characters including numbers, lowercase letters, and the symbols
</Text>

View file

@ -29,7 +29,7 @@ export default class ErrorText extends PureComponent {
if (intl) {
return (
<FormattedText
testID='error_text'
testID='error.text'
id={intl.id}
defaultMessage={intl.defaultMessage}
values={intl.values}
@ -40,7 +40,7 @@ export default class ErrorText extends PureComponent {
return (
<Text
testID='error_text'
testID='error.text'
style={[GlobalStyles.errorLabel, style.errorLabel, textStyle]}
>
{error.message || error}

View file

@ -5,7 +5,7 @@ exports[`PasteableTextInput should render pasteable text input 1`] = `
allowFontScaling={true}
onPaste={[Function]}
rejectResponderTermination={true}
testID="post_input"
testID="post.input"
underlineColorAndroid="transparent"
>
My Text

View file

@ -57,7 +57,7 @@ export class PasteableTextInput extends React.PureComponent {
return (
<TextInput
testID='post_input'
testID='post.input'
{...props}
onPaste={this.onPaste}
ref={forwardRef}

View file

@ -316,7 +316,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"paddingTop": 6,
}
}
testID="post_input"
testID="post.input"
textContentType="none"
underlineColorAndroid="transparent"
/>
@ -531,7 +531,7 @@ exports[`PostDraft Should render the DraftInput 1`] = `
"paddingRight": 8,
}
}
testID="disabled_send_button"
testID="disabled_send.button"
>
<View
style={

View file

@ -63,7 +63,7 @@ describe('PostDraft', () => {
);
expect(toJSON()).toMatchSnapshot();
expect(getByTestId('post_input')).toBeTruthy();
expect(getByTestId('post.input')).toBeTruthy();
expect(queryByText('Close Channel')).toBeNull();
});
@ -79,7 +79,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post_input')).toBeNull();
expect(queryByTestId('post.input')).toBeNull();
// Should match text description
expect(getByText('You are viewing an ')).toBeTruthy();
@ -100,7 +100,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post_input')).toBeNull();
expect(queryByTestId('post.input')).toBeNull();
// Should match text description
expect(getByText('You are viewing an ')).toBeTruthy();
@ -121,7 +121,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post_input')).toBeNull();
expect(queryByTestId('post.input')).toBeNull();
// Should match text description
expect(getByText('This channel is read-only.')).toBeTruthy();
@ -140,7 +140,7 @@ describe('PostDraft', () => {
expect(toJSON()).toMatchSnapshot();
// Should not render text input
expect(queryByTestId('post_input')).toBeNull();
expect(queryByTestId('post.input')).toBeNull();
// Should match text description
expect(getByText('This channel is read-only.')).toBeTruthy();

View file

@ -8,7 +8,7 @@ exports[`SendAction should change theme backgroundColor to 0.3 opacity 1`] = `
"paddingRight": 8,
}
}
testID="disabled_send_button"
testID="disabled_send.button"
>
<View
style={
@ -45,7 +45,7 @@ exports[`SendAction should match snapshot 1`] = `
"paddingRight": 8,
}
}
testID="send_button"
testID="send.button"
type="opacity"
>
<View
@ -78,7 +78,7 @@ exports[`SendAction should render theme backgroundColor 1`] = `
"paddingRight": 8,
}
}
testID="send_button"
testID="send.button"
type="opacity"
>
<View

View file

@ -16,7 +16,7 @@ function SendButton(props) {
if (props.disabled) {
return (
<View
testID='disabled_send_button'
testID='disabled_send.button'
style={style.sendButtonContainer}
>
<View style={[style.sendButton, style.disableButton]}>
@ -32,7 +32,7 @@ function SendButton(props) {
return (
<TouchableWithFeedback
testID='send_button'
testID='send.button'
onPress={props.handleSendMessage}
style={style.sendButtonContainer}
type={'opacity'}

View file

@ -230,7 +230,7 @@ exports[`Reactions Should match snapshot with default emojis 1`] = `
/>
<TouchableWithoutFeedback
onPress={[MockFunction]}
testID="reaction_picker.open"
testID="open.reaction_picker"
>
<View
style={
@ -492,7 +492,7 @@ exports[`Reactions Should match snapshot with default emojis 2`] = `
/>
<TouchableWithoutFeedback
onPress={[MockFunction]}
testID="reaction_picker.open"
testID="open.reaction_picker"
>
<View
style={

View file

@ -83,7 +83,7 @@ export default class ReactionPicker extends PureComponent {
{list}
<TouchableWithoutFeedback
onPress={this.props.openReactionScreen}
testID='reaction_picker.open'
testID='open.reaction_picker'
>
<View
style={[

View file

@ -315,7 +315,6 @@ export default class Search extends PureComponent {
]}
>
<SearchBar
testID='search.bar'
autoCapitalize={this.props.autoCapitalize}
autoCorrect={false}
autoFocus={this.props.autoFocus}

View file

@ -160,7 +160,7 @@ export default class ChannelsList extends PureComponent {
return (
<View
style={styles.container}
testID='channels_list'
testID='channels.list'
>
<View style={styles.headerContainer}>
{title}

View file

@ -256,7 +256,7 @@ export default class MainSidebarBase extends Component {
showTeams={showTeams}
drawerOpened={this.state.drawerOpened}
drawerWidth={drawerWidth - offset}
testID='main_sidebar'
testID='main.sidebar'
>
{lists}
</DrawerSwiper>

View file

@ -27,7 +27,7 @@ export default class AddReaction extends PureComponent {
leftButton = {
id: 'close-add-reaction',
testID: 'screen.add_reaction.close',
testID: 'close.add_reaction.button',
};
constructor(props) {
@ -60,7 +60,10 @@ export default class AddReaction extends PureComponent {
render() {
return (
<View style={styles.container}>
<View
testID='add_reaction.screen'
style={styles.container}
>
<EmojiPicker onEmojiPress={this.handleEmojiPress}/>
</View>
);

View file

@ -67,7 +67,7 @@ export default class ChannelAndroid extends ChannelBase {
component = (
<KeyboardLayout>
<View
testID='channel_screen'
testID='channel.screen'
style={style.flex}
>
<ChannelPostList registerTypingAnimation={this.registerTypingAnimation}/>

View file

@ -107,7 +107,7 @@ export default class ChannelIOS extends ChannelBase {
return (
<MainSidebar
testID='channel_screen'
testID='channel.screen'
ref={this.mainSidebarRef}
>
<SettingsSidebar ref={this.settingsSidebarRef}>

View file

@ -7,6 +7,7 @@ exports[`channelInfo should match snapshot 1`] = `
"flex": 1,
}
}
testID="channel_info.screen"
>
<Connect(StatusBar) />
<ScrollView

View file

@ -179,7 +179,10 @@ export default class ChannelInfo extends PureComponent {
const channelIsArchived = currentChannel.delete_at !== 0;
return (
<View style={style.container}>
<View
testID='channel_info.screen'
style={style.container}
>
<StatusBar/>
<ScrollView
style={style.scrollView}

View file

@ -179,6 +179,7 @@ export default class CreateChannel extends PureComponent {
return (
<EditChannelInfo
testID='create_channel.screen'
theme={theme}
enableRightButton={this.emitCanCreateChannel}
error={error}

View file

@ -315,6 +315,7 @@ export default class EditChannel extends PureComponent {
excludeFooter={true}
>
<EditChannelInfo
testID='edit_channel.screen'
theme={theme}
enableRightButton={this.emitCanUpdateChannel}
error={error}

View file

@ -8,6 +8,7 @@ exports[`EditPost should match snapshot 1`] = `
"flex": 1,
}
}
testID="edit_post.screen"
>
<Connect(StatusBar) />
<View

View file

@ -250,7 +250,10 @@ export default class EditPost extends PureComponent {
return (
<>
<View style={style.container}>
<View
testID='edit_post.screen'
style={style.container}
>
<StatusBar/>
<View style={style.scrollView}>
{displayError}

View file

@ -339,7 +339,7 @@ export default class Login extends PureComponent {
proceed = (
<Button
testID='signin_button'
testID='signin.button'
onPress={this.preSignIn}
containerStyle={[GlobalStyles.signupButton, additionalStyle]}
>
@ -387,7 +387,7 @@ export default class Login extends PureComponent {
size={76}
style={GlobalStyles.logo}
/>
<View testID='login_screen'>
<View testID='login.screen'>
<Text style={GlobalStyles.header}>
{this.props.config.SiteName}
</Text>
@ -399,7 +399,7 @@ export default class Login extends PureComponent {
</View>
<ErrorText error={this.state.error}/>
<TextInput
testID='username_input'
testID='username.input'
autoCapitalize='none'
autoCorrect={false}
blurOnSubmit={false}
@ -415,7 +415,7 @@ export default class Login extends PureComponent {
underlineColorAndroid='transparent'
/>
<TextInput
testID='password_input'
testID='password.input'
autoCapitalize='none'
autoCorrect={false}
disableFullscreenUI={true}

View file

@ -12,6 +12,7 @@ exports[`MoreChannels should match snapshot 1`] = `
"paddingLeft": 8,
}
}
testID="more_channels.screen"
>
<View
style={null}

View file

@ -84,7 +84,7 @@ export default class MoreChannels extends PureComponent {
id: 'create-pub-channel',
text: context.intl.formatMessage({id: 'mobile.create_channel', defaultMessage: 'Create'}),
showAsAction: 'always',
testID: 'public_channels.create.button',
testID: 'public_channel.create.button',
};
this.leftButton = {
@ -454,7 +454,10 @@ export default class MoreChannels extends PureComponent {
content = (
<React.Fragment>
<View style={style.searchBar}>
<View
testID='more_channels.screen'
style={style.searchBar}
>
<View style={padding(isLandscape)}>
<SearchBar
ref={this.setSearchBarRef}

View file

@ -477,7 +477,7 @@ export default class MoreDirectMessages extends PureComponent {
}
return (
<KeyboardLayout testID='direct_channels_screen'>
<KeyboardLayout testID='direct_channels.screen'>
<StatusBar/>
<View style={style.searchBar}>
<View style={padding(isLandscape)}>

View file

@ -7,6 +7,7 @@ exports[`PostOptions should match snapshot, no option for system message to user
"flex": 1,
}
}
testID="post.options"
>
<Connect(SlideUpPanel)
allowStayMiddle={false}
@ -201,6 +202,7 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
"flex": 1,
}
}
testID="post.options"
>
<Connect(SlideUpPanel)
allowStayMiddle={false}
@ -431,6 +433,7 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
"flex": 1,
}
}
testID="post.options"
>
<Connect(SlideUpPanel)
allowStayMiddle={false}

View file

@ -413,7 +413,10 @@ export default class PostOptions extends PureComponent {
const initialPosition = getInitialPosition(deviceHeight, marginFromTop);
return (
<View style={style.container}>
<View
testID='post.options'
style={style.container}
>
<SlideUpPanel
allowStayMiddle={false}
ref={this.refSlideUpPanel}

View file

@ -712,7 +712,10 @@ export default class Search extends PureComponent {
<SafeAreaView style={style.flex}>
<KeyboardLayout>
<StatusBar/>
<View style={[style.header, paddingRes]}>
<View
testID='search.screen'
style={[style.header, paddingRes]}
>
<SearchBar
ref={this.setSearchBarRef}
placeholder={intl.formatMessage({id: 'search_bar.search', defaultMessage: 'Search'})}

View file

@ -499,7 +499,7 @@ export default class SelectServer extends PureComponent {
return (
<SafeAreaView
testID='select_server_screen'
testID='select_server.screen'
style={style.container}
>
<KeyboardAvoidingView
@ -520,7 +520,7 @@ export default class SelectServer extends PureComponent {
style={GlobalStyles.logo}
/>
<View>
<View testID='header.text'>
<FormattedText
style={[GlobalStyles.header, GlobalStyles.label]}
id='mobile.components.select_server_view.enterServerUrl'
@ -528,7 +528,7 @@ export default class SelectServer extends PureComponent {
/>
</View>
<TextInput
testID='server_url_input'
testID='server_url.input'
ref={this.inputRef}
value={url}
editable={!inputDisabled}
@ -548,7 +548,7 @@ export default class SelectServer extends PureComponent {
disableFullscreenUI={true}
/>
<Button
testID='connect_button'
testID='connect.button'
onPress={this.handleConnect}
containerStyle={[GlobalStyles.signupButton, style.connectButton]}
>

View file

@ -32,7 +32,7 @@ describe('SelectServer', () => {
const button = getByText('Connect');
fireEvent.press(button);
await waitFor(() => expect(getByTestId('error_text')).toBeTruthy());
await waitFor(() => expect(getByTestId('error.text')).toBeTruthy());
expect(getByText('Please enter a valid server URL')).toBeTruthy();
});
@ -41,13 +41,13 @@ describe('SelectServer', () => {
<SelectServer {...baseProps}/>,
);
const urlInput = getByTestId('server_url_input');
const urlInput = getByTestId('server_url.input');
fireEvent.changeText(urlInput, ' ');
const button = getByText('Connect');
fireEvent.press(button);
await waitFor(() => expect(getByTestId('error_text')).toBeTruthy());
await waitFor(() => expect(getByTestId('error.text')).toBeTruthy());
expect(getByText('Please enter a valid server URL')).toBeTruthy();
});
@ -56,13 +56,13 @@ describe('SelectServer', () => {
<SelectServer {...baseProps}/>,
);
const urlInput = getByTestId('server_url_input');
const urlInput = getByTestId('server_url.input');
fireEvent.changeText(urlInput, 'ht://invalid:8065');
const button = getByText('Connect');
fireEvent.press(button);
await waitFor(() => expect(getByTestId('error_text')).toBeTruthy());
await waitFor(() => expect(getByTestId('error.text')).toBeTruthy());
expect(getByText('URL must start with http:// or https://')).toBeTruthy();
});
@ -71,13 +71,13 @@ describe('SelectServer', () => {
<SelectServer {...baseProps}/>,
);
const urlInput = getByTestId('server_url_input');
const urlInput = getByTestId('server_url.input');
fireEvent.changeText(urlInput, 'http://localhost:8065');
const button = getByText('Connect');
fireEvent.press(button);
expect(queryByTestId('error_text')).toBeNull();
expect(queryByTestId('error.text')).toBeNull();
await waitFor(() => expect(getByText('Connecting...')).toBeTruthy());
});
});

View file

@ -1,6 +1,7 @@
{
"presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime",
["module-resolver", {
"root": ["."],

View file

@ -0,0 +1,37 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class Autocomplete {
testID = {
autocomplete: 'autocomplete',
atMentionSuggestionList: 'at_mention_suggestion.list',
channelMentionSuggestionList: 'channel_mention_suggestion.list',
dateSuggestion: 'autocomplete.date_suggestion',
emojiSuggestionList: 'emoji_suggestion.list',
slashSuggestionList: 'slash_suggestion.list',
}
autocomplete = element(by.id(this.testID.autocomplete));
atMentionSuggestionList = element(by.id(this.testID.atMentionSuggestionList));
channelMentionSuggestionList = element(by.id(this.testID.channelMentionSuggestionList));
dateSuggestion = element(by.id(this.testID.dateSuggestion));
emojiSuggestionList = element(by.id(this.testID.emojiSuggestionList));
slashSuggestionList = element(by.id(this.testID.slashSuggestionList));
getAtMentionItem = (userId) => {
return element(by.id(`autocomplete.at_mention.item.${userId}`));
}
toBeVisible = async (isVisible = true) => {
if (isVisible) {
await expect(this.autocomplete.atIndex(0)).toBeVisible();
return this.autocomplete;
}
await expect(this.autocomplete).not.toBeVisible();
return null;
}
}
const autocomplete = new Autocomplete();
export default autocomplete;

View file

@ -0,0 +1,35 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class ChannelSidebar {
testID = {
mainSidebar: 'main.sidebar',
channelItemDisplayName: 'channel_item.display_name',
channelsList: 'channels.list',
addChannel: 'action_button_sidebar.channels',
addDirectChannel: 'action_button_sidebar.direct',
}
mainSidebar = element(by.id(this.testID.mainSidebar));
channelItemDisplayName = element(by.id(this.testID.channelItemDisplayName));
channelsList = element(by.id(this.testID.channelsList));
addChannel = element(by.id(this.testID.addChannel));
addDirectChannel = element(by.id(this.testID.addDirectChannel));
toBeVisible = async () => {
await expect(element(by.id(this.testID.mainSidebar))).toBeVisible();
}
getChannelByDisplayName = (displayName) => {
return element(by.text(displayName).withAncestor(by.id(this.testID.channelsList)));
}
hasChannelAtIndex = async (index, channelDisplayName) => {
await expect(
element(by.id(this.testID.channelItemDisplayName).withAncestor(by.id(this.testID.channelsList))).atIndex(index),
).toHaveText(channelDisplayName);
}
}
const channelSidebar = new ChannelSidebar();
export default channelSidebar;

View file

@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import Autocomplete from './autocomplete';
import ChannelSidebar from './channel_sidebar';
import PostOptions from './post_options';
export {
Autocomplete,
ChannelSidebar,
PostOptions,
};

View file

@ -0,0 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class PostOptions {
testID = {
postOptions: 'post.options',
openReactionPicker: 'open.reaction_picker',
}
openReactionPicker = element(by.id(this.testID.openReactionPicker));
toBeVisible = async () => {
await expect(element(by.id(this.testID.postOptions))).toBeVisible();
}
}
const postOptions = new PostOptions();
export default postOptions;

View file

@ -1,62 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {serverUrl} from '../test_config';
// ****************************************************************
// Select Server Screen
// ****************************************************************
/**
* fulfillSelectServerScreen enters server URL on Select Server screen and then tap connect button.
* @param {String} url - server URL
*/
export async function fulfillSelectServerScreen(url) {
await expect(element(by.id('select_server_screen'))).toBeVisible();
await element(by.id('server_url_input')).replaceText(url);
// # Tap anywhere to hide keyboard
await element(by.text('Enter Server URL')).tap();
await element(by.id('connect_button')).tap();
}
// ****************************************************************
// Login Screen
// ****************************************************************
/**
* fulfillLoginScreen enters credential on Login screen and then tap "Sign in" button to log in.
* @param {Object} user - user to login with username and password
*/
export async function fulfillLoginScreen(user = {}) {
await expect(element(by.id('login_screen'))).toBeVisible();
await element(by.id('username_input')).replaceText(user.username);
// # Tap anywhere to hide keyboard
await element(by.id('login_screen')).tap({x: 10, y: 10});
await element(by.id('password_input')).replaceText(user.password);
// # Tap anywhere to hide keyboard
await element(by.id('login_screen')).tap({x: 10, y: 10});
await element(by.id('signin_button')).tap();
}
// ****************************************************************
// Channel Screen
// ****************************************************************
export async function toChannelScreen(user) {
await fulfillSelectServerScreen(serverUrl);
await fulfillLoginScreen(user);
}
export async function logoutUser() {
await element(by.id('sidebar.settings.button')).tap();
await element(by.text('Logout')).tap();
await waitFor(element(by.id('select_server_screen'))).toBeVisible();
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostOptions} from '@support/ui/component';
class AddReactionScreen {
testID = {
addReactionScreen: 'add_reaction.screen',
closeAddReactionButton: 'close.add_reaction.button',
}
addReactionScreen = element(by.id(this.testID.addReactionScreen));
closeAddReactionButton = element(by.id(this.testID.closeAddReactionButton));
toBeVisible = async () => {
await expect(this.addReactionScreen).toBeVisible();
return this.addReactionScreen;
}
open = async (message) => {
await element(by.text(message)).longPress();
await PostOptions.toBeVisible();
await PostOptions.openReactionPicker.tap();
return this.toBeVisible();
}
close = async () => {
await this.closeAddReactionButton.tap();
await expect(this.addReactionScreen).not.toBeVisible();
}
}
const addReactionScreen = new AddReactionScreen();
export default addReactionScreen;

View file

@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {LoginScreen, SelectServerScreen} from '@support/ui/screen';
class ChannelScreen {
testID = {
channelScreen: 'channel.screen',
channelDrawerButton: 'channel_drawer.button',
channelIntro: 'channel_intro.beginning.text',
channelNavBarTitle: 'channel.nav_bar.title',
channelSearchButton: 'channel.search.button',
channelTitleButton: 'channel.title.button',
disabledSendButton: 'disabled_send.button',
postInput: 'post.input',
sendButton: 'send.button',
sidebarSettingsButton: 'sidebar.settings.button',
}
channelScreen = element(by.id(this.testID.channelScreen));
channelDrawerButton = element(by.id(this.testID.channelDrawerButton));
channelIntro = element(by.id(this.testID.channelIntro));
channelNavBarTitle = element(by.id(this.testID.channelNavBarTitle));
channelSearchButton = element(by.id(this.testID.channelSearchButton));
channelTitleButton = element(by.id(this.testID.channelTitleButton));
disabledSendButton = element(by.id(this.testID.disabledSendButton));
postInput = element(by.id(this.testID.postInput));
sendButton = element(by.id(this.testID.sendButton));
sidebarSettingsButton = element(by.id(this.testID.sidebarSettingsButton));
toBeVisible = async () => {
await expect(this.channelScreen).toBeVisible();
return this.channelScreen;
}
open = async (user) => {
await SelectServerScreen.connectToServer();
await LoginScreen.login(user);
return this.toBeVisible();
}
logout = async () => {
await this.sidebarSettingsButton.tap();
await element(by.text('Logout')).tap();
await SelectServerScreen.toBeVisible();
}
}
const channelScreen = new ChannelScreen();
export default channelScreen;

View file

@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/screen';
class ChannelInfoScreen {
testID = {
channelInfoScreen: 'channel_info.screen',
channelInfoClose: 'screen.channel_info.close',
channelIconGMMemberCount: 'channel_icon.gm_member_count',
}
channelInfoScreen = element(by.id(this.testID.channelInfoScreen));
channelInfoClose = element(by.id(this.testID.channelInfoClose));
channelIconGMMemberCount = element(by.id(this.testID.channelIconGMMemberCount));
toBeVisible = async () => {
const screen = await this.channelInfoScreen;
await expect(screen).toBeVisible();
return screen;
}
open = async () => {
// # Open channel info
await ChannelScreen.channelTitleButton.tap();
return this.toBeVisible();
}
close = async () => {
await this.channelInfoClose.tap();
await expect(this.channelInfoScreen).not.toBeVisible();
}
}
const channelInfoScreen = new ChannelInfoScreen();
export default channelInfoScreen;

View file

@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/screen';
class CreateChannelScreen {
testID = {
createChannelScreen: 'create_channel.screen',
nameInput: 'edit_channel.name.input',
purposeInput: 'edit_channel.purpose.input',
headerInput: 'edit_channel.header.input',
createButton: 'edit_channel.create.button',
backButton: 'screen.back.button',
}
createChannelScreen = element(by.id(this.testID.createChannelScreen));
nameInput = element(by.id(this.testID.nameInput));
purposeInput = element(by.id(this.testID.purposeInput));
headerInput = element(by.id(this.testID.headerInput));
createButton = element(by.id(this.testID.createButton));
backButton = element(by.id(this.testID.backButton));
toBeVisible = async () => {
await expect(this.createChannelScreen).toBeVisible();
return this.createChannelScreen;
}
open = async () => {
// # Open channel info then tap 'Edit Channel'
await ChannelScreen.channelTitleButton.tap();
await element(by.text('Edit Channel')).tap();
return this.toBeVisible();
}
back = async () => {
await this.backButton.tap();
await expect(this.createChannelScreen).not.toBeVisible();
}
}
const createChannelScreen = new CreateChannelScreen();
export default createChannelScreen;

View file

@ -0,0 +1,35 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/component';
import {ChannelSidebar} from '@support/ui/screen';
class DirectChannelsScreen {
testID = {
directChannelsScreen: 'direct_channels.screen',
startButton: 'start-conversation',
}
directChannelsScreen = element(by.id(this.testID.directChannelsScreen));
startButton = element(by.id(this.testID.startButton));
getUserAtIndex = (index) => {
return element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(index);
}
toBeVisible = async () => {
await expect(this.directChannelsScreen).toBeVisible();
return this.directChannelsScreen;
}
open = async () => {
await ChannelScreen.channelDrawerButton.tap();
await ChannelSidebar.addDirectChannel.tap();
return this.toBeVisible();
}
}
const directChannelsScreen = new DirectChannelsScreen();
export default directChannelsScreen;

View file

@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/screen';
class EditChannelScreen {
testID = {
editChannelScreen: 'edit_channel.screen',
editChannelHeaderInput: 'edit_channel.header.input',
backButton: 'screen.back.button',
}
editChannelScreen = element(by.id(this.testID.editChannelScreen));
editChannelHeaderInput = element(by.id(this.testID.editChannelHeaderInput));
backButton = element(by.id(this.testID.backButton));
toBeVisible = async () => {
await expect(this.editChannelScreen).toBeVisible();
return this.editChannelScreen;
}
open = async () => {
// # Open channel menu then tap 'Edit Channel'
await ChannelScreen.channelTitleButton.tap();
await element(by.text('Edit Channel')).tap();
return this.toBeVisible();
}
back = async () => {
await this.backButton.tap();
await expect(this.editChannelScreen).not.toBeVisible();
}
}
const editChannelScreen = new EditChannelScreen();
export default editChannelScreen;

View file

@ -0,0 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {isAndroid} from '@support/utils';
class EditPostScreen {
testID = {
editPostScreen: 'edit_post.screen',
editPostInput: 'edit_post.input',
editPostClose: 'edit_post.close',
}
editPostInput = element(by.id(this.testID.editPostInput));
editPostClose = element(by.id(this.testID.editPostClose));
toBeVisible = async () => {
await expect(this.editPostScreen).toBeVisible();
return this.editPostScreen;
}
open = async (message) => {
// # Open edit post screen
await element(by.text(message)).longPress();
// # Swipe up panel on Android
if (isAndroid()) {
const slide = element(by.id('slide_up_panel'));
await slide.swipe('up');
}
await element(by.text('Edit')).tap();
}
back = async () => {
await this.backButton.tap();
await expect(this.editPostScreen).not.toBeVisible();
}
}
const editPostScreen = new EditPostScreen();
export default editPostScreen;

View file

@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AddReactionScreen from './add_reaction';
import ChannelInfoScreen from './channel_info';
import ChannelScreen from './channel';
import CreateChannelScreen from './create_channel';
import DirectChannelsScreen from './direct_channels';
import EditChannelScreen from './edit_channel';
import EditPostScreen from './edit_post';
import LoginScreen from './login';
import MoreChannelsScreen from './more_channels';
import NotificationScreen from './notification';
import SearchScreen from './search';
import SelectServerScreen from './select_server';
export {
AddReactionScreen,
ChannelScreen,
ChannelInfoScreen,
CreateChannelScreen,
DirectChannelsScreen,
EditChannelScreen,
EditPostScreen,
LoginScreen,
MoreChannelsScreen,
NotificationScreen,
SearchScreen,
SelectServerScreen,
};

View file

@ -0,0 +1,55 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {SelectServerScreen} from '@support/ui/screen';
class LoginScreen {
testID = {
loginScreen: 'login.screen',
errorText: 'error.text',
passwordInput: 'password.input',
signinButton: 'signin.button',
usernameInput: 'username.input',
}
loginScreen = element(by.id(this.testID.loginScreen));
errorText = element(by.id(this.testID.errorText));
passwordInput = element(by.id(this.testID.passwordInput));
signinButton = element(by.id(this.testID.signinButton));
usernameInput = element(by.id(this.testID.usernameInput));
toBeVisible = async () => {
await expect(this.loginScreen).toBeVisible();
return this.loginScreen;
}
open = async () => {
await SelectServerScreen.connectToServer();
return this.toBeVisible();
}
/**
* login enters credential on Login screen and then tap "Sign in" button to log in.
* @param {Object} user - user to login with username and password
*/
login = async (user = {}) => {
const screen = await this.toBeVisible();
await this.usernameInput.replaceText(user.username);
// # Tap anywhere to hide keyboard
await screen.tap({x: 10, y: 10});
await this.passwordInput.replaceText(user.password);
// # Tap anywhere to hide keyboard
await screen.tap({x: 10, y: 10});
await this.signinButton.tap();
}
}
const loginScreen = new LoginScreen();
export default loginScreen;

View file

@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/component';
import {ChannelSidebar} from '@support/ui/screen';
class MoreChannelsScreen {
testID = {
moreChannelsScreen: 'more_channels.screen',
publicChannelCreateButton: 'public_channel.create.button',
}
moreChannelsScreen = element(by.id(this.testID.moreChannelsScreen));
publicChannelCreateButton = element(by.id(this.testID.publicChannelCreateButton));
toBeVisible = async () => {
await expect(this.moreChannelsScreen).toBeVisible();
return this.moreChannelsScreen;
}
open = async () => {
await ChannelScreen.channelDrawerButton.tap();
await ChannelSidebar.addChannel.tap();
return this.toBeVisible();
}
}
const moreChannelsScreen = new MoreChannelsScreen();
export default moreChannelsScreen;

View file

@ -0,0 +1,25 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
class NotificationScreen {
testID = {
inAppNotification: 'in_app_notification',
inAppNotificationIcon: 'in_app_notification.icon',
inAppNotificationTitle: 'in_app_notification.title',
inAppNotificationMessage: 'in_app_notification.message',
}
inAppNotification = element(by.id(this.testID.inAppNotification));
inAppNotificationIcon = element(by.id(this.testID.inAppNotificationIcon));
inAppNotificationTitle = element(by.id(this.testID.inAppNotificationTitle));
inAppNotificationMessage = element(by.id(this.testID.inAppNotificationMessage));
toBeVisible = async () => {
await expect(this.inAppNotification).toBeVisible();
return this.inAppNotification;
}
}
const notificationScreen = new NotificationScreen();
export default notificationScreen;

View file

@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelScreen} from '@support/ui/screen';
import {isAndroid} from '@support/utils';
class SearchScreen {
testID = {
searchScreen: 'search.screen',
searchInput: 'searchInput',
searchFromSection: 'search_from.section',
searchInSection: 'search_in.section',
searchOnSection: 'search_on.section',
}
searchScreen = element(by.id(this.testID.searchScreen));
searchInput = element(by.id(this.testID.searchInput));
searchFromSection = element(by.id(this.testID.searchFromSection));
searchInSection = element(by.id(this.testID.searchInSection));
searchOnSection = element(by.id(this.testID.searchOnSection));
toBeVisible = async () => {
await expect(this.searchScreen).toBeVisible();
return this.searchScreen;
}
open = async () => {
await ChannelScreen.channelSearchButton.tap();
return this.toBeVisible();
}
cancel = async () => {
if (isAndroid()) {
await device.pressBack();
} else {
await element(by.text('Cancel')).atIndex(0).tap();
}
}
}
const searchScreen = new SearchScreen();
export default searchScreen;

View file

@ -0,0 +1,42 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {serverUrl} from '@support/test_config';
class SelectServerScreen {
testID = {
selectServerScreen: 'select_server.screen',
connectButton: 'connect.button',
errorText: 'error.text',
headerText: 'header.text',
serverUrlInput: 'server_url.input',
}
selectServerScreen = element(by.id(this.testID.selectServerScreen));
serverUrlInput = element(by.id(this.testID.serverUrlInput));
connectButton = element(by.id(this.testID.connectButton));
errorText = element(by.id(this.testID.errorText));
toBeVisible = async () => {
await expect(this.selectServerScreen).toBeVisible();
return this.selectServerScreen;
}
/**
* connectToServer enters server URL on Select Server screen and then tap connect button.
*/
connectToServer = async () => {
const screen = await this.toBeVisible();
await this.serverUrlInput.replaceText(serverUrl);
// # Tap anywhere to hide keyboard
await screen.tap({x: 5, y: 10});
await this.connectButton.tap();
}
}
const selectServerScreen = new SelectServerScreen();
export default selectServerScreen;

View file

@ -7,164 +7,165 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Autocomplete} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
let user;
let postInput;
const {postInput} = ChannelScreen;
const {atMentionSuggestionList} = Autocomplete;
let userAtMentionAutocomplete;
beforeAll(async () => {
({user} = await Setup.apiInit());
await toChannelScreen(user);
userAtMentionAutocomplete = Autocomplete.getAtMentionItem(user.id);
await ChannelScreen.open(user);
});
beforeEach(async () => {
await device.reloadReactNative();
// # Select post draft
await expect(element(by.id('channel_screen'))).toBeVisible();
postInput = await element(by.id('post_input'));
await postInput.tap();
// # Clear text and verify that Autocomplete disappeared
await postInput.clearText();
await Autocomplete.toBeVisible(false);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3409_1 should suggest user based on username', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type username
await postInput.typeText(user.username);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_2 should suggest user based on nickname', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type nickname
await postInput.typeText(user.nickname);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_3 should suggest user based on first name', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type first name
await postInput.typeText(user.first_name);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_4 should suggest user based on last name', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type last name
await postInput.typeText(user.last_name);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_5 should suggest user based on lowercase first name', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type lowercase first name
await postInput.typeText(user.first_name.toLowerCase());
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_6 should suggest user based on lowercase last name', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type lowercase last name
await postInput.typeText(user.last_name.toLowerCase());
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_7 should suggest user based on full name with space', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type full name
await postInput.typeText(`${user.first_name} ${user.last_name}`);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_8 should suggest user based on partial full name with space', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type partial full name
await postInput.typeText(`${user.first_name} ${user.last_name.substring(0, user.last_name.length - 6)}`);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
});
it('MM-T3409_9 should stop suggesting user after full name with trailing space', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type full name
await postInput.typeText(`${user.first_name} ${user.last_name}`);
// * Expect at mention autocomplete to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).toExist();
await expect(userAtMentionAutocomplete).toExist();
// # Type trailing space
await postInput.typeText(' ');
// * Expect at mention autocomplete not to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).not.toExist();
await expect(userAtMentionAutocomplete).not.toExist();
});
it('MM-T3409_10 should stop suggesting user when keyword is not associated with any user', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await postInput.typeText('@');
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(atMentionSuggestionList).toExist();
// # Type keyword not associated with any user
await postInput.typeText(Date.now().toString());
// * Expect at mention autocomplete not to contain associated user suggestion
await expect(element(by.id(`autocomplete.at_mention.item.${user.id}`))).not.toExist();
await expect(userAtMentionAutocomplete).not.toExist();
});
});

View file

@ -7,36 +7,39 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Autocomplete} from '@support/ui/component';
import {
ChannelScreen,
ChannelInfoScreen,
EditChannelScreen,
} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3390 should render autocomplete in channel header edit screen', async () => {
// # Open channel info modal
await element(by.id('channel.title.button')).tap();
// # Open edit channel menu
await element(by.text('Edit Channel')).tap();
// # Open edit channel modal
await EditChannelScreen.open();
// # Activate at_mention autocomplete
await element(by.id('edit_channel.header.input')).typeText('@');
await EditChannelScreen.editChannelHeaderInput.typeText('@');
// * Expect autocomplete to render
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await Autocomplete.toBeVisible();
await expect(Autocomplete.atMentionSuggestionList).toExist();
// # Go to previous screen
await element(by.id('screen.back.button')).tap();
await EditChannelScreen.back();
// # Close channel info screen
await element(by.id('screen.channel_info.close')).tap();
await ChannelInfoScreen.close();
});
});

View file

@ -7,30 +7,31 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Autocomplete} from '@support/ui/component';
import {ChannelScreen, EditPostScreen} from '@support/ui/screen';
import {isAndroid, timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3391 should render autocomplete in post edit screen', async () => {
const message = Date.now().toString();
const {postInput, sendButton} = ChannelScreen;
// # Type a message
const postInput = await element(by.id('post_input'));
await postInput.tap();
await postInput.typeText(message);
// # Tap the send button
await element(by.id('send_button')).tap();
await sendButton.tap();
// # Explicitly wait on Android before verifying error message
if (isAndroid()) {
@ -38,25 +39,19 @@ describe('Autocomplete', () => {
}
// # Open edit screen
await element(by.text(message)).longPress();
await EditPostScreen.open(message);
// # Swipe up panel on Android
if (isAndroid()) {
const slide = element(by.id('slide_up_panel'));
await slide.swipe('up');
}
const edit = element(by.text('Edit'));
await edit.tap();
const {atMentionSuggestionList} = Autocomplete;
const {editPostInput, editPostClose} = EditPostScreen;
// # Open autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await element(by.id('edit_post.input')).typeText(' @');
await expect(atMentionSuggestionList).not.toExist();
await editPostInput.typeText(' @');
// * Expect at_mention autocomplete to render
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await expect(atMentionSuggestionList).toExist();
// # Close edit post screen
await element(by.id('edit_post.close')).tap();
await editPostClose.tap();
});
});

View file

@ -7,60 +7,68 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Autocomplete} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
const {postInput} = ChannelScreen;
const {
atMentionSuggestionList,
channelMentionSuggestionList,
emojiSuggestionList,
slashSuggestionList,
} = Autocomplete;
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
});
beforeEach(async () => {
// # Select post draft
await expect(element(by.id('channel_screen'))).toBeVisible();
await element(by.id('post_input')).clearText();
await element(by.id('post_input')).tap();
// # Clear text on input
await postInput.clearText();
await postInput.tap();
});
afterAll(async () => {
await element(by.id('post_input')).clearText();
await logoutUser();
await postInput.clearText();
await ChannelScreen.logout();
});
it('MM-T3392_1 should render emoji_suggestion component', async () => {
// # Type ":" to activate emoji suggestions
await expect(element(by.id('autocomplete.emoji_suggestion.list'))).not.toExist();
await element(by.id('post_input')).typeText(':');
await expect(emojiSuggestionList).not.toExist();
await postInput.typeText(':');
// * Expect emoji suggestions to render
await expect(element(by.id('autocomplete.emoji_suggestion.list'))).toExist();
await expect(emojiSuggestionList).toExist();
});
it('MM-T3392_2 should render at_mention component', async () => {
// # Type "@" to activate at mention autocomplete
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await element(by.id('post_input')).typeText('@');
await expect(atMentionSuggestionList).not.toExist();
await postInput.typeText('@');
// * Expect at mention autocomplete to render
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await expect(atMentionSuggestionList).toExist();
});
it('MM-T3392_3 should render channel_mention component', async () => {
// # Type "~" to activate channel mention autocomplete
await expect(element(by.id('autocomplete.channel_mention.list'))).not.toExist();
await element(by.id('post_input')).typeText('~');
await expect(channelMentionSuggestionList).not.toExist();
await postInput.typeText('~');
// * Expect channel mention to render
await expect(element(by.id('autocomplete.channel_mention.list'))).toExist();
await expect(channelMentionSuggestionList).toExist();
});
it('MM-T3392_4 should render slash_suggestion component', async () => {
// # Type "/" to activate slash command suggestions
await expect(element(by.id('autocomplete.slash_suggestion'))).not.toExist();
await element(by.id('post_input')).typeText('/');
await expect(slashSuggestionList).not.toExist();
await postInput.typeText('/');
// * Expect slash suggestions to render
await expect(element(by.id('autocomplete.slash_suggestion'))).toExist();
await expect(slashSuggestionList).toExist();
});
});

View file

@ -7,54 +7,61 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {Autocomplete} from '@support/ui/component';
import {ChannelScreen, SearchScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Autocomplete', () => {
const {
atMentionSuggestionList,
channelMentionSuggestionList,
dateSuggestion,
} = Autocomplete;
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
// # Open search screen
await SearchScreen.open();
});
beforeEach(async () => {
await device.reloadReactNative();
// # Enter search screen
await element(by.id('channel.search.button')).tap();
await SearchScreen.searchInput.atIndex(0).clearText();
});
afterAll(async () => {
await device.reloadReactNative();
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3393_1 should render at_mention component', async () => {
await expect(element(by.id('autocomplete.at_mention.list'))).not.toExist();
await expect(atMentionSuggestionList).not.toExist();
// # Tap "from:" modifier
await element(by.id('search_from.section')).tap();
await SearchScreen.searchFromSection.tap();
// * Expect at mention to render
await expect(element(by.id('autocomplete.at_mention.list'))).toExist();
await expect(atMentionSuggestionList).toExist();
});
it('MM-T3393_2 should render channel_mention component', async () => {
await expect(element(by.id('autocomplete.channel_mention.list'))).not.toExist();
await expect(channelMentionSuggestionList).not.toExist();
// # Tap "in:" modifier
await element(by.id('search_in.section')).tap();
await SearchScreen.searchInSection.tap();
// * Expect channel mention to render
await expect(element(by.id('autocomplete.channel_mention.list'))).toExist();
await expect(channelMentionSuggestionList).toExist();
});
it('MM-T3393_3 should render date_suggestion component', async () => {
await expect(element(by.id('autocomplete.date_suggestion'))).not.toExist();
await expect(dateSuggestion).not.toExist();
// # Tap "on:" modifier
await element(by.id('search_on.section')).tap();
await SearchScreen.searchOnSection.tap();
// * Expect date suggestion to render
await expect(element(by.id('autocomplete.date_suggestion'))).toExist();
await expect(dateSuggestion).toExist();
});
});

View file

@ -7,47 +7,54 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {ChannelSidebar} from '@support/ui/component';
import {
ChannelScreen,
ChannelInfoScreen,
DirectChannelsScreen,
} from '@support/ui/screen';
import {timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
describe('Channel Info Header', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3406 should render correct GM member count in channel info header', async () => {
// # Open drawer
await element(by.id('channel_drawer.button')).tap();
await ChannelScreen.channelDrawerButton.tap();
await expect(element(by.text('DIRECT MESSAGES'))).toBeVisible();
// # Open Direct Channels screen
await element(by.id('action_button_sidebar.direct')).tap();
await expect(element(by.id('direct_channels_screen'))).toBeVisible();
await ChannelSidebar.addDirectChannel.tap();
await DirectChannelsScreen.toBeVisible();
// # Wait for some profiles to load
await wait(timeouts.ONE_SEC);
const {getUserAtIndex, startButton} = DirectChannelsScreen;
// # Select 3 profiles
await element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(0).tap();
await element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(1).tap();
await element(by.id('more_dms.user').withAncestor(by.id('more_dms.list'))).atIndex(2).tap();
await getUserAtIndex(0).tap();
await getUserAtIndex(1).tap();
await getUserAtIndex(2).tap();
// # Create a GM with selected profiles
await element(by.id('start-conversation')).tap();
await startButton.tap();
// # Open channel info modal
await element(by.id('channel.title.button')).tap();
await ChannelInfoScreen.open();
// * Verify GM member count is 3
await expect(element(by.id('channel_icon.gm_member_count')).atIndex(0)).toHaveText('3');
await expect(element(by.id(ChannelInfoScreen.testID.channelIconGMMemberCount)).atIndex(0)).toHaveText('3');
// # Close channel info screen
await element(by.id('screen.channel_info.close')).tap();
await ChannelInfoScreen.close();
});
});

View file

@ -9,67 +9,83 @@
import jestExpect from 'expect';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {ChannelSidebar} from '@support/ui/component';
import {
ChannelScreen,
ChannelInfoScreen,
CreateChannelScreen,
MoreChannelsScreen,
} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Channels', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3201 Create public channel', async () => {
// # Go to channel sidebar list
await element(by.id('channel_drawer.button')).tap();
await ChannelScreen.channelDrawerButton.tap();
// # Tap on + public channels
await element(by.id('action_button_sidebar.channels')).tap();
await ChannelSidebar.addChannel.tap();
// * Expect a list of public channels, initially empty
await MoreChannelsScreen.toBeVisible();
await expect(element(by.text('No more channels to join'))).toBeVisible();
// # Tap to create new channel
await element(by.id('public_channels.create.button')).tap();
await MoreChannelsScreen.publicChannelCreateButton.tap();
// * Expect a new screen to create a new public channel
const createChannelScreen = await CreateChannelScreen.toBeVisible();
await expect(element(by.text('New Public Channel'))).toBeVisible();
const {
nameInput,
purposeInput,
headerInput,
} = CreateChannelScreen;
// # Fill data
await element(by.id('edit_channel.name.input')).typeText('a');
await nameInput.typeText('a');
await attemptToTapButton('edit_channel.create.button');
// * Expect to be in the same screen since the channel name must be longer
await expect(element(by.id('edit_channel.name.input'))).toBeVisible();
await expect(nameInput).toBeVisible();
await element(by.id('edit_channel.name.input')).typeText('bc');
await element(by.id('edit_channel.purpose.input')).typeText('This sentence has');
await element(by.id('edit_channel.purpose.input')).tapReturnKey();
await element(by.id('edit_channel.purpose.input')).typeText('multiple lines');
await element(by.id('edit_channel.scroll')).scroll(200, 'down');
await expect(element(by.id('edit_channel.header.input'))).toBeVisible();
await nameInput.typeText('bc');
await purposeInput.typeText('This sentence has');
await purposeInput.tapReturnKey();
await purposeInput.typeText('multiple lines');
await createChannelScreen.scroll(200, 'down');
await expect(headerInput).toBeVisible();
const expectedChannelHeader = 'I 🌮 love 🌮 tacos 🌮';
await element(by.id('edit_channel.header.input')).replaceText(expectedChannelHeader);
await headerInput.replaceText(expectedChannelHeader);
await element(by.id('edit_channel.create.button')).tap();
await CreateChannelScreen.createButton.tap();
const expectedChannelName = 'abc';
const expectedPurpose = 'This sentence has\nmultiple lines';
// * Expect a redirection to the created channel
await expect(element(by.id('channel_intro.beginning.text'))).toHaveText('Beginning of ' + expectedChannelName);
await element(by.id('channel.title.button')).tap();
await expect(ChannelScreen.channelIntro).toHaveText('Beginning of ' + expectedChannelName);
// # Open channel info
await ChannelInfoScreen.open();
// * Expect to see channel header and purpose in channel info
await expect(element(by.text(expectedChannelHeader))).toBeVisible();
await expect(element(by.text(expectedPurpose))).toBeVisible();
// # Close channel info screen
await element(by.id('screen.channel_info.close')).tap();
await ChannelInfoScreen.close();
});
});

View file

@ -7,7 +7,7 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {AddReactionScreen, ChannelScreen, NotificationScreen} from '@support/ui/screen';
import {isAndroid, timeouts, wait} from '@support/utils';
import {Setup} from '@support/server_api';
@ -61,30 +61,30 @@ describe('in-app Notification', () => {
const {channel, team, user} = await Setup.apiInit();
testChannel = channel;
testNotification = getNotification(channel, team, user);
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3440 should render an in-app notification', async () => {
const message = Date.now().toString();
const {postInput, sendButton} = ChannelScreen;
// # Type a message
const postInput = await element(by.id('post_input'));
await postInput.tap();
await postInput.typeText(message);
// # Tap the send button
await element(by.id('send_button')).tap();
await sendButton.tap();
await wait(timeouts.HALF_SEC);
// # Open Add reaction screen
await element(by.text(message)).longPress();
await element(by.id('reaction_picker.open')).tap();
await element(by.id('screen.add_reaction.close')).tap();
await AddReactionScreen.open(message);
await AddReactionScreen.closeAddReactionButton.tap();
// # Skip on Android for now
if (isAndroid()) {
// eslint-disable-next-line no-console
console.log('Skipping on Android until https://github.com/wix/Detox/issues/2141');
@ -96,15 +96,22 @@ describe('in-app Notification', () => {
await wait(timeouts.HALF_SEC);
// * Verify in-app notification is shown
await expect(element(by.id('in_app_notification'))).toBeVisible();
await expect(element(by.id('in_app_notification.icon'))).toBeVisible();
await expect(element(by.id('in_app_notification.title'))).toHaveText(testChannel.name);
await expect(element(by.id('in_app_notification.message'))).toHaveText('This is an e2e test message');
const {
inAppNotification,
inAppNotificationIcon,
inAppNotificationTitle,
inAppNotificationMessage,
} = NotificationScreen;
await NotificationScreen.toBeVisible();
await expect(inAppNotificationIcon).toBeVisible();
await expect(inAppNotificationTitle).toHaveText(testChannel.name);
await expect(inAppNotificationMessage).toHaveText('This is an e2e test message');
// # Wait for some profiles to load
await wait(5 * timeouts.ONE_SEC);
// * Verify in-app notification is hidden
await expect(element(by.id('in_app_notification'))).not.toBeVisible();
await expect(inAppNotification).not.toBeVisible();
});
});

View file

@ -7,7 +7,8 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {ChannelSidebar} from '@support/ui/component';
import {ChannelScreen} from '@support/ui/screen';
import {isAndroid} from '@support/utils';
import {Setup} from '@support/server_api';
@ -18,36 +19,39 @@ describe('Select channel', () => {
const {user, channel} = await Setup.apiInit();
newChannel = channel;
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3412 should close the sidebar menu when selecting the same channel', async () => {
const {channelDrawerButton, channelNavBarTitle} = ChannelScreen;
// # Open channel drawer (with at least one unread channel)
await element(by.id('channel_drawer.button')).tap();
await channelDrawerButton.tap();
// * Main sidebar should be visible
await expect(element(by.id('main_sidebar'))).toBeVisible();
await ChannelSidebar.toBeVisible();
// # Tap a channel to view it
await element(by.text(newChannel.display_name).withAncestor(by.id('channels_list'))).tap();
const channelItem = ChannelSidebar.getChannelByDisplayName(newChannel.display_name);
await channelItem.tap();
// * Selected channel should be visible
await expect(element(by.id('channel.nav_bar.title'))).toHaveText(newChannel.display_name);
await expect(channelNavBarTitle).toHaveText(newChannel.display_name);
// # Open channel drawer again and select the same channel
await element(by.id('channel_drawer.button')).tap();
await element(by.text(newChannel.display_name).withAncestor(by.id('channels_list'))).tap();
await channelDrawerButton.tap();
await channelItem.tap();
// * Drawer should not be visible on Android
if (isAndroid()) {
await expect(element(by.id('main_sidebar'))).not.toBeVisible();
await expect(ChannelSidebar.mainSidebar).not.toBeVisible();
}
// * Selected channel should remain the same
await expect(element(by.id('channel.nav_bar.title'))).toHaveText(newChannel.display_name);
await expect(channelNavBarTitle).toHaveText(newChannel.display_name);
});
});

View file

@ -7,38 +7,42 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {ChannelScreen} from '@support/ui/screen';
import {Setup} from '@support/server_api';
describe('Messaging', () => {
beforeAll(async () => {
const {user} = await Setup.apiInit();
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('should post a message on tap to paper send button', async () => {
// * Verify channel screen and post input exist and send button to not exist
await expect(element(by.id('channel_screen'))).toBeVisible();
await expect(element(by.id('post_input'))).toExist();
await expect(element(by.id('send_button'))).not.toExist();
// * Verify channel screen is visible
await ChannelScreen.toBeVisible();
const {postInput, sendButton} = ChannelScreen;
// * Post input should exist while send button should not
await expect(postInput).toBeVisible();
await expect(sendButton).not.toExist();
// # Tap on post input
await element(by.id('post_input')).tap();
await postInput.tap();
// # Type text on post input
const text = Date.now().toString();
await element(by.id('post_input')).typeText(text);
await postInput.typeText(text);
// # Tap send button
await expect(element(by.id('send_button'))).toBeVisible();
await element(by.id('send_button')).tap();
await expect(sendButton).toBeVisible();
await sendButton.tap();
// * Verify text to exist
await expect(element(by.text(text))).toExist();
await expect(element(by.text(text))).toBeVisible();
});
});

View file

@ -9,7 +9,7 @@
import jestExpect from 'expect';
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {ChannelScreen} from '@support/ui/screen';
import {Channel, Post, Setup} from '@support/server_api';
describe('Messaging', () => {
@ -19,29 +19,28 @@ describe('Messaging', () => {
beforeAll(async () => {
({team, user} = await Setup.apiInit());
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T109 User can\'t send the same message repeatedly', async () => {
const message = Date.now().toString();
const {disabledSendButton, sendButton, postInput} = ChannelScreen;
// # Type a message
const postInput = await element(by.id('post_input'));
await postInput.tap();
await postInput.typeText(message);
// # Tap the send button
const sendButton = await element(by.id('send_button'));
await expect(sendButton).toBeVisible();
await sendButton.tap();
await expect(sendButton).not.toExist();
// # Then tap send button repeatedly
const disabledSendButton = await element(by.id('disabled_send_button'));
await expect(disabledSendButton).toBeVisible();
await expect(disabledSendButton).toExist();
await disabledSendButton.multiTap(3);

View file

@ -7,17 +7,32 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {Setup, System} from '@support/server_api';
import {Setup} from '@support/server_api';
import {
ChannelScreen,
LoginScreen,
SelectServerScreen,
} from '@support/ui/screen';
import {serverUrl} from '@support/test_config';
import {fulfillSelectServerScreen, logoutUser} from '@support/ui/screen';
import {isAndroid, timeouts, wait} from '@support/utils';
describe('On boarding', () => {
let config;
let user;
const {
connectButton,
errorText,
serverUrlInput,
} = SelectServerScreen;
const {
errorText: loginErrorText,
usernameInput,
passwordInput,
signinButton,
} = LoginScreen;
beforeAll(async () => {
({config} = await System.apiGetConfig());
({user} = await Setup.apiInit());
});
@ -26,30 +41,31 @@ describe('On boarding', () => {
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('should show Select server screen on initial load', async () => {
// * Verify basic elements on Select Server screen
await expect(element(by.id('select_server_screen'))).toBeVisible();
await expect(element(by.id('server_url_input'))).toBeVisible();
await expect(element(by.id('connect_button'))).toBeVisible();
await SelectServerScreen.toBeVisible();
await expect(serverUrlInput).toBeVisible();
await expect(connectButton).toBeVisible();
});
it('MM-T3383 should show error on empty server URL', async () => {
await expect(element(by.id('select_server_screen'))).toBeVisible();
const screen = await SelectServerScreen.toBeVisible();
// # Enter an empty server URL
await element(by.id('server_url_input')).typeText(' ');
await serverUrlInput.typeText(' ');
// # Tap anywhere to hide keyboard
await element(by.text('Enter Server URL')).tap();
await screen.tap({x: 5, y: 10});
// * Verify that the error message does not exist
await waitFor(element(by.id('error_text'))).not.toExist().withTimeout(timeouts.HALF_SEC);
await waitFor(errorText).not.toExist().withTimeout(timeouts.HALF_SEC);
// # Tap connect button
await element(by.id('connect_button')).tap();
await connectButton.tap();
// # Explicitly wait on Android before verifying error message
if (isAndroid()) {
@ -57,146 +73,132 @@ describe('On boarding', () => {
}
// * Verify error message
await waitFor(element(by.id('error_text'))).toBeVisible().withTimeout(timeouts.ONE_SEC);
await expect(element(by.id('error_text'))).toHaveText('Please enter a valid server URL');
await waitFor(errorText).toBeVisible().withTimeout(timeouts.ONE_SEC);
await expect(errorText).toHaveText('Please enter a valid server URL');
});
it('should show error on invalid server URL', async () => {
await expect(element(by.id('select_server_screen'))).toBeVisible();
const screen = await SelectServerScreen.toBeVisible();
// # Enter invalid server URL
const input = element(by.id('server_url_input'));
await input.clearText();
await input.typeText('http://invalid:8065');
await serverUrlInput.clearText();
await serverUrlInput.typeText(serverUrl.substring(0, serverUrl.length - 1));
// # Tap anywhere to hide keyboard
await element(by.text('Enter Server URL')).tap();
await screen.tap({x: 5, y: 10});
// * Verify that the error message does not exist
await waitFor(element(by.id('error_text'))).not.toExist().withTimeout(timeouts.HALF_SEC);
await waitFor(errorText).not.toExist().withTimeout(timeouts.HALF_SEC);
// # Tap connect button
await element(by.id('connect_button')).tap();
// # Explicitly wait on Android before verifying error message
if (isAndroid()) {
await wait(timeouts.TWO_SEC);
}
await connectButton.tap();
// * Verify error message
await waitFor(element(by.id('error_text'))).toBeVisible().withTimeout(timeouts.ONE_SEC);
await expect(element(by.id('error_text'))).toHaveText('Cannot connect to the server. Please check your server URL and internet connection.');
await waitFor(errorText).toBeVisible().withTimeout(timeouts.ONE_SEC);
await expect(errorText).toHaveText('Cannot connect to the server. Please check your server URL and internet connection.');
});
it('should move to Login screen on valid server URL', async () => {
await expect(element(by.id('select_server_screen'))).toBeVisible();
await SelectServerScreen.toBeVisible();
// # Enter valid server URL
await element(by.id('server_url_input')).replaceText(serverUrl);
await serverUrlInput.replaceText(serverUrl);
// # Tap connect button
await element(by.id('connect_button')).tap();
await connectButton.tap();
// * Verify that it goes into Login screen
await expect(element(by.id('login_screen'))).toBeVisible();
await LoginScreen.toBeVisible();
});
it('should match elements on Login screen', async () => {
await fulfillSelectServerScreen(serverUrl);
await LoginScreen.open();
// * Verify basic elements on Login screen
await expect(element(by.id('login_screen'))).toBeVisible();
await expect(usernameInput).toBeVisible();
await expect(passwordInput).toBeVisible();
await expect(element(by.id('username_input'))).toBeVisible();
await expect(element(by.id('password_input'))).toBeVisible();
await expect(element(by.id('signin_button'))).toBeVisible();
await expect(signinButton).toBeVisible();
});
it('should show error on missing any of the username or password', async () => {
await fulfillSelectServerScreen(serverUrl);
await expect(element(by.id('login_screen'))).toBeVisible();
const loginScreen = await LoginScreen.open();
// # On Login screen, enter invalid username
await element(by.id('username_input')).typeText('any');
await usernameInput.typeText('any');
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Tap "Sign in" button
await element(by.id('signin_button')).tap();
await signinButton.tap();
// * Verify that the error message is shown as expected
await expect(element(by.id('error_text'))).toBeVisible();
await expect(element(by.id('error_text'))).toHaveText('Please enter your password');
await expect(loginErrorText).toBeVisible();
await expect(loginErrorText).toHaveText('Please enter your password');
// # Clear input to username and enter invalid password
await element(by.id('username_input')).replaceText('');
await element(by.id('password_input')).typeText('any');
await usernameInput.replaceText('');
await passwordInput.typeText('any');
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Tap "Sign in" button
await element(by.id('signin_button')).tap();
await signinButton.tap();
// * Verify that the error message is shown as expected
await expect(element(by.id('error_text'))).toBeVisible();
await expect(element(by.id('error_text'))).toHaveText('Please enter your email or username');
await expect(loginErrorText).toBeVisible();
await expect(loginErrorText).toHaveText('Please enter your email or username');
});
it('should show error on incorrect credential', async () => {
await fulfillSelectServerScreen(serverUrl);
await expect(element(by.id('login_screen'))).toBeVisible();
const loginScreen = await LoginScreen.open();
// # Enter invalid username
await element(by.id('username_input')).replaceText('any');
await usernameInput.replaceText('any');
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Enter invalid password
await element(by.id('password_input')).replaceText('any');
await passwordInput.replaceText('any');
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Tap "Sign in" button
await element(by.id('signin_button')).tap();
await signinButton.tap();
// * Verify that the error message is shown as expected
await expect(element(by.id('error_text'))).toBeVisible();
await expect(element(by.id('error_text'))).toHaveText('Enter a valid email or username and/or password.');
await expect(loginErrorText).toBeVisible();
await expect(loginErrorText).toHaveText('Enter a valid email or username and/or password.');
});
it('should move to Channel screen on successful login', async () => {
await fulfillSelectServerScreen(serverUrl);
await expect(element(by.id('login_screen'))).toBeVisible();
const loginScreen = await LoginScreen.open();
// # Enter valid username
await element(by.id('username_input')).replaceText(user.username);
await usernameInput.replaceText(user.username);
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Enter valid password
await element(by.id('password_input')).replaceText(user.password);
await passwordInput.replaceText(user.password);
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Tap "Sign in" button
await element(by.id('signin_button')).tap();
await signinButton.tap();
// * Verify that it goes into Channel screen
await expect(element(by.id('channel_screen'))).toBeVisible();
await ChannelScreen.toBeVisible();
});
it('should directly go into Channel screen on reload', async () => {
// # On reload and after successful login, verify that it goes straight into Channel screen
await expect(element(by.id('channel_screen'))).toBeVisible();
await ChannelScreen.toBeVisible();
});
});

View file

@ -7,7 +7,8 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {logoutUser, toChannelScreen} from '@support/ui/screen';
import {ChannelScreen} from '@support/ui/screen';
import {ChannelSidebar} from '@support/ui/component';
import {Setup, Channel} from '@support/server_api';
describe('Unread channels', () => {
@ -25,41 +26,46 @@ describe('Unread channels', () => {
({channel: zChannel} = await Channel.apiCreateChannel({type: 'O', prefix: 'z-channel', teamId: team.id}));
await Channel.apiAddUserToChannel(user.id, zChannel.id);
await toChannelScreen(user);
await ChannelScreen.open(user);
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3187 Unread channels sort at top', async () => {
const {channelDrawerButton} = ChannelScreen;
// # Open channel drawer (with at least one unread channel)
await element(by.id('channel_drawer.button')).tap();
await channelDrawerButton.tap();
// * Channel should be visible
await ChannelSidebar.toBeVisible();
// * Verify unread channel(s) display at top of channel list (with mentions first, if any), in alphabetical order, with title "Unreads"
await expect(element(by.text('UNREADS'))).toBeVisible();
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(0)).toHaveText(aChannel.display_name);
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(1)).toHaveText(newChannel.display_name);
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(2)).toHaveText(zChannel.display_name);
await ChannelSidebar.hasChannelAtIndex(0, aChannel.display_name);
await ChannelSidebar.hasChannelAtIndex(1, newChannel.display_name);
await ChannelSidebar.hasChannelAtIndex(2, zChannel.display_name);
// # Tap an unread channel to view it
await element(by.text(aChannel.display_name)).tap();
await element(by.id('channel_drawer.button')).tap();
await element(by.text(newChannel.display_name)).tap();
await element(by.id('channel_drawer.button')).tap();
await element(by.text(zChannel.display_name)).tap();
await ChannelSidebar.getChannelByDisplayName(aChannel.display_name).tap();
await channelDrawerButton.tap();
await ChannelSidebar.getChannelByDisplayName(newChannel.display_name).tap();
await channelDrawerButton.tap();
await ChannelSidebar.getChannelByDisplayName(zChannel.display_name).tap();
// # Open channel drawer again
await element(by.id('channel_drawer.button')).tap();
await channelDrawerButton.tap();
// * Channel you just read is no longer listed in Unreads
await expect(element(by.text('UNREADS'))).not.toBeVisible();
await expect(element(by.text('PUBLIC CHANNELS'))).toBeVisible();
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(0)).toHaveText(aChannel.display_name);
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(1)).toHaveText(newChannel.display_name);
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(2)).toHaveText('Off-Topic');
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(3)).toHaveText('Town Square');
await expect(element(by.id('channel_item.display_name').withAncestor(by.id('channels_list'))).atIndex(4)).toHaveText(zChannel.display_name);
await element(by.text(aChannel.display_name)).tap();
await ChannelSidebar.hasChannelAtIndex(0, aChannel.display_name);
await ChannelSidebar.hasChannelAtIndex(1, newChannel.display_name);
await ChannelSidebar.hasChannelAtIndex(2, 'Off-Topic');
await ChannelSidebar.hasChannelAtIndex(3, 'Town Square');
await ChannelSidebar.hasChannelAtIndex(4, zChannel.display_name);
await ChannelSidebar.getChannelByDisplayName(aChannel.display_name).tap();
});
});

View file

@ -7,47 +7,38 @@
// - Use element testID when selecting an element. Create one if none.
// *******************************************************************
import {Setup, System} from '@support/server_api';
import {logoutUser} from '@support/ui/screen';
import {serverUrl} from '@support/test_config';
import {Setup} from '@support/server_api';
import {ChannelScreen, LoginScreen} from '@support/ui/screen';
describe('Smoke Tests', () => {
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3179 Log in - Email / password', async () => {
const {config} = await System.apiGetConfig();
const {user} = await Setup.apiInit();
// * Verify that it starts with the Select Server screen
await expect(element(by.id('select_server_screen'))).toBeVisible();
// # Navigate to Login screen
const loginScreen = await LoginScreen.open();
// # Type in the server URL
await element(by.id('server_url_input')).replaceText(serverUrl);
// # Tap connect button
await element(by.text('Connect')).tap();
// # Verify that it goes into Login screen
await expect(element(by.id('login_screen'))).toBeVisible();
const {usernameInput, passwordInput, signinButton} = LoginScreen;
// # Type in username
await element(by.id('username_input')).replaceText(user.username);
await usernameInput.replaceText(user.username);
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Type in password
await element(by.id('password_input')).replaceText(user.password);
await passwordInput.replaceText(user.password);
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Tap "Sign in" button
await element(by.text('Sign in')).tap();
await signinButton.tap();
// * Verify that the user has successfully logged in by checking it redirects into the Channel screen
await expect(element(by.id('channel_screen'))).toBeVisible();
await ChannelScreen.toBeVisible();
});
});

View file

@ -8,20 +8,18 @@
// *******************************************************************
import {Ldap, Setup, System, Team, User} from '@support/server_api';
import {logoutUser} from '@support/ui/screen';
import {serverUrl} from '@support/test_config';
import {ChannelScreen, LoginScreen} from '@support/ui/screen';
import ldapUsers from '@support/fixtures/ldap_users.json';
describe('Smoke Tests', () => {
const testOne = ldapUsers['test-1'];
let config;
beforeAll(async () => {
// * Verify that the server has license with LDAP feature
await System.apiRequireLicenseForFeature('LDAP');
// # Enable LDAP
({config} = await System.apiUpdateConfig({LdapSettings: {Enable: true}}));
await System.apiUpdateConfig({LdapSettings: {Enable: true}});
// * Check that LDAP server can connect and is synchronized with Mattermost server
await Ldap.apiRequireLDAPServer();
@ -30,39 +28,32 @@ describe('Smoke Tests', () => {
});
afterAll(async () => {
await logoutUser();
await ChannelScreen.logout();
});
it('MM-T3180 Log in - LDAP', async () => {
// * Verify that it starts with the Select Server screen
await expect(element(by.id('select_server_screen'))).toBeVisible();
// # Navigate to Login screen
const loginScreen = await LoginScreen.open();
// # Type in the server URL
await element(by.id('server_url_input')).replaceText(serverUrl);
// # Tap connect button
await element(by.text('Connect')).tap();
// # Verify that it goes into Login screen
await expect(element(by.id('login_screen'))).toBeVisible();
const {usernameInput, passwordInput, signinButton} = LoginScreen;
// # Type in username
await element(by.id('username_input')).replaceText(testOne.username);
await usernameInput.replaceText(testOne.username);
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Type in password
await element(by.id('password_input')).replaceText(testOne.password);
await passwordInput.replaceText(testOne.password);
// # Tap anywhere to hide keyboard
await element(by.text(config.TeamSettings.SiteName)).tap();
await loginScreen.tap({x: 5, y: 10});
// # Tap "Sign in" button
await element(by.text('Sign in')).tap();
await signinButton.tap();
// * Verify that the user has successfully logged in by checking it redirects into the Channel screen
await expect(element(by.id('channel_screen'))).toBeVisible();
await ChannelScreen.toBeVisible();
});
});

View file

@ -4,6 +4,7 @@
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
"devDependencies": {
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-transform-modules-commonjs": "7.12.1",
"@babel/plugin-transform-runtime": "7.12.1",
"@babel/preset-env": "7.12.1",