MM-35071 Detox/E2E: Add e2e for MM-T287, MM-T290, MM-T300, MM-T2350, MM-T3151, MM-T3472 (#5347)

* MM-35071 Detox/E2E: Add e2e for MM-T287, MM-T290, MM-T2350, MM-T3151, MM-T3472

* Revert to original generic android

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Joseph Baylon 2021-04-27 16:46:50 -07:00 committed by GitHub
parent 267c0e88cc
commit f22d63644f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 406 additions and 7 deletions

View file

@ -64,6 +64,7 @@ export default class AtMention extends React.PureComponent {
leftButtons: [{
id: 'close-settings',
icon: this.closeButton,
testID: 'close.settings.button',
}],
},
};

View file

@ -53,6 +53,7 @@ class ChannelIntro extends PureComponent {
leftButtons: [{
id: 'close-settings',
icon: this.closeButton,
testID: 'close.settings.button',
}],
},
};

View file

@ -109,6 +109,7 @@ export default class Post extends PureComponent {
leftButtons: [{
id: 'close-settings',
icon: this.closeButton,
testID: 'close.settings.button',
}],
},
};

View file

@ -139,9 +139,9 @@ export default class SettingsSidebarBase extends PureComponent {
const options = {
topBar: {
leftButtons: [{
testID: 'close.settings.button',
id: 'close-settings',
icon: this.closeButton,
testID: 'close.settings.button',
}],
},
};

View file

@ -50,6 +50,7 @@ export default class TextSetting extends PureComponent {
'url',
]),
secureTextEntry: PropTypes.bool,
testID: PropTypes.string,
};
static defaultProps = {
@ -78,6 +79,7 @@ export default class TextSetting extends PureComponent {
value,
multiline,
secureTextEntry,
testID,
} = this.props;
const style = getStyleSheet(theme);
@ -88,10 +90,17 @@ export default class TextSetting extends PureComponent {
style={style.title}
id={label.id}
defaultMessage={label.defaultMessage}
testID={`${testID}.label_content`}
/>
);
} else if (typeof label === 'string') {
labelContent = <Text style={style.title}>{label}</Text>;
labelContent = (
<Text
style={style.title}
testID={`${testID}.label`}
>
{label}
</Text>);
}
let optionalContent;
@ -148,7 +157,7 @@ export default class TextSetting extends PureComponent {
const noediting = disabled ? style.disabled : null;
return (
<View>
<View testID={testID}>
<View style={style.titleContainer}>
{labelContent}
{asterisk}
@ -172,6 +181,7 @@ export default class TextSetting extends PureComponent {
keyboardType={keyboardType}
secureTextEntry={secureTextEntry}
keyboardAppearance={getKeyboardAppearanceFromTheme(theme)}
testID={`${testID}.input`}
/>
</View>
</View>

View file

@ -109,6 +109,7 @@ export default class EditProfile extends PureComponent {
id: 'update-profile',
enabled: false,
showAsAction: 'always',
testID: 'edit_profile.save.button',
};
constructor(props, context) {
@ -334,6 +335,7 @@ export default class EditProfile extends PureComponent {
theme={theme}
value={firstName}
isLandscape={isLandscape}
testID='edit_profile.text_setting.first_name'
/>
);
};
@ -357,6 +359,7 @@ export default class EditProfile extends PureComponent {
theme={theme}
value={lastName}
isLandscape={isLandscape}
testID='edit_profile.text_setting.last_name'
/>
</View>
);
@ -382,6 +385,7 @@ export default class EditProfile extends PureComponent {
theme={theme}
value={username}
isLandscape={isLandscape}
testID='edit_profile.text_setting.username'
/>
);
};
@ -444,6 +448,7 @@ export default class EditProfile extends PureComponent {
theme={theme}
value={email}
isLandscape={isLandscape}
testID='edit_profile.text_setting.email'
/>
</View>
);
@ -469,6 +474,7 @@ export default class EditProfile extends PureComponent {
value={nickname}
isLandscape={isLandscape}
optional={true}
testID='edit_profile.text_setting.nickname'
/>
);
};
@ -493,6 +499,7 @@ export default class EditProfile extends PureComponent {
value={position}
isLandscape={isLandscape}
optional={true}
testID='edit_profile.text_setting.position'
/>
);
};
@ -602,6 +609,7 @@ export default class EditProfile extends PureComponent {
<KeyboardAwareScrollView
bounces={false}
innerRef={this.scrollViewRef}
testID='edit_profile.scroll_view'
>
{displayError}
<View style={[style.scrollView]}>

View file

@ -53,6 +53,7 @@ export default class ReactionRow extends React.PureComponent {
leftButtons: [{
id: 'close-settings',
icon: this.closeButton,
testID: 'close.settings.button',
}],
},
};

View file

@ -49,6 +49,7 @@ exports[`user_profile should match snapshot 1`] = `
"marginTop": 15,
}
}
testID="user_profile.username"
>
@fred
</Text>

View file

@ -61,6 +61,7 @@ export default class UserProfile extends PureComponent {
rightButton = {
id: 'edit-profile',
showAsAction: 'always',
testID: 'user_profile.edit.button',
};
constructor(props, context) {
@ -117,15 +118,20 @@ export default class UserProfile extends PureComponent {
if (displayName && (config.ShowFullName === 'true' || user.is_bot || showGuest)) {
return (
<View style={style.indicatorContainer}>
<Text style={style.displayName}>
<Text
style={style.displayName}
testID='user_profile.display_name'
>
{displayName}
</Text>
<BotTag
show={Boolean(user.is_bot)}
testID='user_profile.bot_tag'
theme={theme}
/>
<GuestTag
show={showGuest}
testID='user_profile.guest_tag'
theme={theme}
/>
</View>
@ -198,12 +204,14 @@ export default class UserProfile extends PureComponent {
style={style.header}
testID='user_profile.timezone_block.local_time.label'
/>
<Text style={style.text}>
<Text
style={style.text}
testID='user_profile.timezone_block.local_time.value'
>
<FormattedTime
timeZone={currentTimezone}
hour12={!militaryTime}
value={nowDate}
testID='user_profile.timezone_block.local_time.value'
/>
</Text>
</View>
@ -364,7 +372,12 @@ export default class UserProfile extends PureComponent {
testID='user_profile.profile_picture'
/>
{this.getDisplayName()}
<Text style={style.username}>{`@${user.username}`}</Text>
<Text
style={style.username}
testID='user_profile.username'
>
{`@${user.username}`}
</Text>
</View>
<View style={style.divider}/>
{this.renderDetailsBlock(style)}

View file

@ -188,6 +188,7 @@ function generateRandomUser(prefix) {
first_name: `First${randomId}`,
last_name: `Last${randomId}`,
nickname: `Nickname${randomId}`,
position: `Position${randomId}`,
};
}

View file

@ -0,0 +1,72 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ProfilePicture} from '@support/ui/component';
import {UserProfileScreen} from '@support/ui/screen';
class EditProfileScreen {
testID = {
editProfilePicturePrefix: 'edit_profile.profile_picture.',
editProfileScreen: 'edit_profile.screen',
editProfileScrollView: 'edit_profile.scroll_view',
backButton: 'screen.back.button',
saveButton: 'edit_profile.save.button',
editProfileError: 'edit_profile.error.text',
emailInput: 'edit_profile.text_setting.email.input',
emailLabel: 'edit_profile.text_setting.email.label',
firstNameInput: 'edit_profile.text_setting.first_name.input',
firstNameLabel: 'edit_profile.text_setting.first_name.label',
lastNameInput: 'edit_profile.text_setting.last_name.input',
lastNameLabel: 'edit_profile.text_setting.last_name.label',
nicknameInput: 'edit_profile.text_setting.nickname.input',
nicknameLabel: 'edit_profile.text_setting.nickname.label',
positionInput: 'edit_profile.text_setting.position.input',
positionLabel: 'edit_profile.text_setting.position.label',
usernameInput: 'edit_profile.text_setting.username.input',
usernameLabel: 'edit_profile.text_setting.username.label',
}
editProfileScreen = element(by.id(this.testID.editProfileScreen));
editProfileScrollView = element(by.id(this.testID.editProfileScrollView));
backButton = element(by.id(this.testID.backButton));
saveButton = element(by.id(this.testID.saveButton));
editProfileError = element(by.id(this.testID.editProfileError));
emailInput = element(by.id(this.testID.emailInput));
emailLabel = element(by.id(this.testID.emailLabel));
firstNameInput = element(by.id(this.testID.firstNameInput));
firstNameLabel = element(by.id(this.testID.firstNameLabel));
lastNameInput = element(by.id(this.testID.lastNameInput));
lastNameLabel = element(by.id(this.testID.lastNameLabel));
nicknameInput = element(by.id(this.testID.nicknameInput));
nicknameLabel = element(by.id(this.testID.nicknameLabel));
positionInput = element(by.id(this.testID.positionInput));
positionLabel = element(by.id(this.testID.positionLabel));
usernameInput = element(by.id(this.testID.usernameInput));
usernameLabel = element(by.id(this.testID.usernameLabel));
getProfilePicture = (userId) => {
const profilePictureItemMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.editProfilePicturePrefix, userId);
return element(profilePictureItemMatcher);
}
toBeVisible = async () => {
await expect(this.editProfileScreen).toBeVisible();
return this.editProfileScreen;
}
open = async () => {
// # Open edit profile screen
await UserProfileScreen.editButton.tap();
return this.toBeVisible();
}
back = async () => {
await this.backButton.tap();
await expect(this.editProfileScreen).not.toBeVisible();
}
}
const editProfileScreen = new EditProfileScreen();
export default editProfileScreen;

View file

@ -14,6 +14,7 @@ import CreateChannelScreen from './create_channel';
import DisplaySettingsScreen from './display_settings';
import EditChannelScreen from './edit_channel';
import EditPostScreen from './edit_post';
import EditProfileScreen from './edit_profile';
import GeneralSettingsScreen from './general_settings';
import LoginScreen from './login';
import LongPostScreen from './long_post';
@ -48,6 +49,7 @@ export {
DisplaySettingsScreen,
EditChannelScreen,
EditPostScreen,
EditProfileScreen,
GeneralSettingsScreen,
LoginScreen,
LongPostScreen,

View file

@ -1,10 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
ProfilePicture,
SettingsSidebar,
} from '@support/ui/component';
class UserProfileScreen {
testID = {
userProfilePicturePrefix: 'user_profile.profile_picture.',
userProfileScreen: 'user_profile.screen',
userProfileScrollView: 'user_profile.scroll_view',
closeSettingsButton: 'close.settings.button',
editButton: 'user_profile.edit.button',
userProfileBotTag: 'user_profile.bot_tag',
userProfileDisplayName: 'user_profile.display_name',
userProfileGuestTag: 'user_profile.bot_tag',
userProfileUsername: 'user_profile.username',
emailLabel: 'user_profile.display_block.email.label',
emailValue: 'user_profile.display_block.email.value',
firstNameLabel: 'user_profile.display_block.first_name.label',
@ -23,6 +35,12 @@ class UserProfileScreen {
userProfileScreen = element(by.id(this.testID.userProfileScreen));
userProfileScrollView = element(by.id(this.testID.userProfileScrollView));
closeSettingsButton = element(by.id(this.testID.closeSettingsButton));
editButton = element(by.id(this.testID.editButton));
userProfileBotTag = element(by.id(this.testID.userProfileBotTag));
userProfileDisplayName = element(by.id(this.testID.userProfileDisplayName));
userProfileGuestTag = element(by.id(this.testID.userProfileGuestTag));
userProfileUsername = element(by.id(this.testID.userProfileUsername));
emailLabel = element(by.id(this.testID.emailLabel));
emailValue = element(by.id(this.testID.emailValue));
firstNameLabel = element(by.id(this.testID.firstNameLabel));
@ -38,11 +56,28 @@ class UserProfileScreen {
additionalOptionsAction = element(by.id(this.testID.additionalOptionsAction));
sendMessageAction = element(by.id(this.testID.sendMessageAction));
getProfilePicture = (userId) => {
const profilePictureItemMatcher = ProfilePicture.getProfilePictureItemMatcher(this.testID.userProfilePicturePrefix, userId);
return element(profilePictureItemMatcher);
}
toBeVisible = async () => {
await expect(this.userProfileScreen).toBeVisible();
return this.userProfileScreen;
}
open = async () => {
// # Open user profile screen
await SettingsSidebar.userInfoAction.tap();
return this.toBeVisible();
}
close = async () => {
await this.closeSettingsButton.tap();
await expect(this.userProfileScreen).not.toBeVisible();
}
}
const userProfileScreen = new UserProfileScreen();

View file

@ -0,0 +1,49 @@
// 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 {
AdvancedSettingsScreen,
ChannelScreen,
GeneralSettingsScreen,
} from '@support/ui/screen';
import {
Channel,
Setup,
} from '@support/server_api';
describe('Advanced Settings', () => {
let townSquareChannel;
beforeAll(async () => {
const {team, user} = await Setup.apiInit();
({channel: townSquareChannel} = await Channel.apiGetChannelByName(team.name, 'town-square'));
// # Open channel screen
await ChannelScreen.open(user);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T300 should be able to delete documents and data', async () => {
// # Open advanced settings screen
await ChannelScreen.openSettingsSidebar();
await GeneralSettingsScreen.open();
await AdvancedSettingsScreen.open();
// # Delete documents and data
await AdvancedSettingsScreen.deleteDocumentsAndData();
// * Verify redirects to town square
await ChannelScreen.toBeVisible();
await expect(ChannelScreen.channelNavBarTitle).toHaveText(townSquareChannel.display_name);
});
});

View file

@ -0,0 +1,204 @@
// 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 {
ChannelScreen,
EditProfileScreen,
UserProfileScreen,
} from '@support/ui/screen';
import {
Channel,
Post,
Setup,
System,
} from '@support/server_api';
describe('User Profile', () => {
let testUser;
let townSquareChannel;
beforeAll(async () => {
// # Enable experimental timezone
System.apiUpdateConfig({DisplaySettings: {ExperimentalTimezone: true}});
const {team, user} = await Setup.apiInit();
testUser = user;
({channel: townSquareChannel} = await Channel.apiGetChannelByName(team.name, 'town-square'));
// # Open channel screen
await ChannelScreen.open(testUser);
});
afterAll(async () => {
await ChannelScreen.logout();
});
it('MM-T2350 should be able to view profile', async () => {
const {
firstNameValue,
getProfilePicture,
lastNameValue,
localTimeValue,
nicknameValue,
positionValue,
sendMessageAction,
userProfileDisplayName,
userProfileScrollView,
userProfileUsername,
} = UserProfileScreen;
// # Open user profile screen
await ChannelScreen.openSettingsSidebar();
await UserProfileScreen.open();
// * Verify user profile details
await expect(getProfilePicture(testUser.id)).toBeVisible();
await expect(userProfileDisplayName).toHaveText(testUser.username);
await expect(userProfileUsername).toHaveText(`@${testUser.username}`);
await expect(firstNameValue).toHaveText(testUser.first_name);
await expect(lastNameValue).toHaveText(testUser.last_name);
await expect(nicknameValue).toHaveText(testUser.nickname);
await expect(positionValue).toHaveText(testUser.position);
await userProfileScrollView.scrollTo('bottom');
await expect(localTimeValue).toBeVisible();
await expect(sendMessageAction).toBeVisible();
// # Go back to channel
await UserProfileScreen.close();
});
it('MM-T3151_1 should be able to view first name and last name when ShowFullName is enabled', async () => {
const {
firstNameValue,
lastNameValue,
} = UserProfileScreen;
// # Enable show full name
System.apiUpdateConfig({PrivacySettings: {ShowFullName: true}});
// # Open user profile screen
await ChannelScreen.openSettingsSidebar();
await UserProfileScreen.open();
await expect(firstNameValue).toBeVisible();
await expect(lastNameValue).toBeVisible();
// # Go back to channel
await UserProfileScreen.close();
});
it('MM-T3151_2 should not be able to view first name and last name when ShowFullName is disabled', async () => {
const {
firstNameValue,
lastNameValue,
} = UserProfileScreen;
// # Enable show full name
System.apiUpdateConfig({PrivacySettings: {ShowFullName: false}});
// # Open user profile screen
await ChannelScreen.openSettingsSidebar();
await UserProfileScreen.open();
await expect(firstNameValue).not.toBeVisible();
await expect(lastNameValue).not.toBeVisible();
// # Go back to channel
await UserProfileScreen.close();
});
it('MM-T287 should be able to edit profile from own profile pop-over', async () => {
const {
saveButton,
usernameInput,
} = EditProfileScreen;
const {
postMessage,
getPostListPostItem,
} = ChannelScreen;
// # Post message
const message = Date.now().toString();
await postMessage(message);
// # Open edit profile screen from channel
const {post} = await Post.apiGetLastPostInChannel(townSquareChannel.id);
const {postListPostItemProfilePicture} = await getPostListPostItem(post.id, message, {userId: testUser.id});
await postListPostItemProfilePicture.tap();
await UserProfileScreen.toBeVisible();
await EditProfileScreen.open();
// # Update username
const updatedUsername = `${testUser.username}-123`;
await usernameInput.clearText();
await usernameInput.typeText(updatedUsername);
await saveButton.tap();
// * Verify username is updated
await UserProfileScreen.toBeVisible();
await expect(UserProfileScreen.userProfileUsername).toHaveText(`@${updatedUsername}`);
// # Go back to channel
await UserProfileScreen.close();
});
it('MM-T290 should display error on invalid username', async () => {
const {
editProfileError,
saveButton,
usernameInput,
} = EditProfileScreen;
// # Open edit profile screen
await ChannelScreen.openSettingsSidebar();
await UserProfileScreen.open();
await EditProfileScreen.open();
// # Attempt to save with invalid username
const invalidUsername = `${testUser.username}+new`;
await usernameInput.clearText();
await usernameInput.typeText(invalidUsername);
await saveButton.tap();
// * Verify invalid username error is displayed
await expect(editProfileError).toHaveText('Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols ".", "-", and "_".');
// # Go back to channel
await EditProfileScreen.back();
await UserProfileScreen.close();
});
it('MM-T3472 should be able add nickname to profile', async () => {
const {
editProfileScrollView,
nicknameInput,
saveButton,
} = EditProfileScreen;
// # Open edit profile screen
await ChannelScreen.openSettingsSidebar();
await UserProfileScreen.open();
await EditProfileScreen.open();
// # Add nickname
const nickname = Date.now().toString();
await editProfileScrollView.scrollTo('bottom');
await nicknameInput.clearText();
await nicknameInput.typeText(nickname);
await saveButton.tap();
// * Verify nickname is added
await UserProfileScreen.toBeVisible();
await expect(UserProfileScreen.nicknameValue).toHaveText(nickname);
// # Go back to channel
await UserProfileScreen.close();
});
});