Этот коммит содержится в:
Mario Vitale
2023-03-27 16:28:42 +02:00
родитель da7a6825ce
Коммит ba6b97fb62
1142 изменённых файлов: 44 добавлений и 44 удалений

59
e2e-tests/cypress/tests/support/ui/account_settings_modal.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,59 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiOpenProfileModal`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Open the account settings modal
* @param {string} section - such as `'General'`, `'Security'`, `'Notifications'`, `'Display'`, `'Sidebar'` and `'Advanced'`
* @return the "#accountSettingsModal"
*
* @example
* cy.uiOpenProfileModal().within(() => {
* // Do something here
* });
*/
uiOpenProfileModal(section?: string): Chainable<JQuery<HTMLElement>>;
/**
* Close the account settings modal given that the modal itself is opened.
*
* @example
* cy.uiCloseAccountSettingsModal();
*/
uiCloseAccountSettingsModal(): Chainable;
/**
* Navigate to account settings and verify the user's first, last name
* @param {String} firstname - expected user firstname
* @param {String} lastname - expected user lastname
*/
verifyAccountNameSettings(firstname: string, lastname: string): Chainable;
/**
* Navigate to account display settings and change collapsed reply threads setting
* @param {String} setting - ON or OFF
*/
uiChangeCRTDisplaySetting(setting: string): Chainable;
/**
* Navigate to account display settings and change message display setting
* @param {String} setting - COMPACT or STANDARD
*/
uiChangeMessageDisplaySetting(setting: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,60 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiOpenProfileModal', (section = '') => {
// # Open profile settings modal
cy.uiOpenUserMenu('Profile');
const profileSettingsModal = () => cy.findByRole('dialog', {name: 'Profile'}).should('be.visible');
if (!section) {
return profileSettingsModal();
}
// # Click on a particular section
cy.findByRoleExtended('tab', {name: section}).should('be.visible').click();
return profileSettingsModal();
});
Cypress.Commands.add('verifyAccountNameSettings', (firstname, lastname) => {
// # Go to Profile
cy.uiOpenProfileModal();
// * Check name value
cy.get('#nameDesc').should('have.text', `${firstname} ${lastname}`);
cy.uiClose();
});
Cypress.Commands.add('uiChangeGenericDisplaySetting', (setting, option) => {
cy.uiOpenSettingsModal('Display');
cy.get(setting).scrollIntoView();
cy.get(setting).click();
cy.get('.section-max').scrollIntoView();
cy.get(option).check().should('be.checked');
cy.uiSaveAndClose();
});
/*
* Change the message display setting
* @param {String} setting - as 'STANDARD' or 'COMPACT'
*/
Cypress.Commands.add('uiChangeMessageDisplaySetting', (setting = 'STANDARD') => {
const SETTINGS = {STANDARD: '#message_displayFormatA', COMPACT: '#message_displayFormatB'};
cy.uiChangeGenericDisplaySetting('#message_displayTitle', SETTINGS[setting]);
});
/*
* Change the collapsed reply threads display setting
* @param {String} setting - as 'OFF' or 'ON'
*/
Cypress.Commands.add('uiChangeCRTDisplaySetting', (setting = 'OFF') => {
const SETTINGS = {
ON: '#collapsed_reply_threadsFormatA',
OFF: '#collapsed_reply_threadsFormatB',
};
cy.uiChangeGenericDisplaySetting('#collapsed_reply_threadsTitle', SETTINGS[setting]);
});

28
e2e-tests/cypress/tests/support/ui/announcement_bar.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,28 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiCloseAnnouncementBar`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Close the announcement bar if shown in the UI
*
* @example
* cy.uiCloseAnnouncementBar();
*/
uiCloseAnnouncementBar(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiCloseAnnouncementBar', () => {
cy.document().then((doc) => {
const announcementBar = doc.getElementsByClassName('announcement-bar')[0];
if (announcementBar) {
cy.get('.announcement-bar__close').click();
}
});
});

56
e2e-tests/cypress/tests/support/ui/boards.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,56 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiCreateEmptyBoard`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Create a board on a given menu item.
*
* @param {string} item - one of the template menu options, ex. 'Empty board'
*/
uiCreateBoard(item: string): Chainable;
/**
* Create an empty board.
* @example
* cy.uiCreateEmptyBoard();
*/
uiCreateEmptyBoard(): Chainable;
/**
* Create a board with the given title
*
* @param {string} title - title of the new board
*/
uiCreateNewBoard: (title?: string) => Chainable;
/**
* Create a new group with the given name
*
* @param {string} name - name of the new group
*/
uiAddNewGroup: (name?: string) => Chainable;
/**
* Create a card with the given title
*
* @param {string} title - title of the new card
* @param {string} columnIndex - the column index to create the card
*/
uiAddNewCard: (title?: string, columnIndex?: number) => Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,66 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import timeouts from '../../fixtures/timeouts';
/* eslint-disable cypress/no-unnecessary-waiting */
Cypress.Commands.add('uiCreateBoard', (item) => {
cy.log(`Create new board: ${item}`);
cy.uiAddBoard('Create new board');
cy.contains(item).click();
cy.contains('Use this template').click({force: true}).wait(timeouts.ONE_SEC);
});
Cypress.Commands.add('uiCreateEmptyBoard', () => {
cy.log('Create new empty board');
cy.contains('Create an empty board').click({force: true}).wait(timeouts.ONE_SEC);
});
Cypress.Commands.add('uiAddBoard', (item) => {
cy.get('.add-board-icon').should('be.visible').click();
cy.get('.menu-contents').should('be.visible');
if (item) {
cy.findByRole('button', {name: item}).click();
}
});
Cypress.Commands.add('uiCreateNewBoard', (title) => {
cy.log('**Create new empty board**');
cy.uiCreateEmptyBoard();
cy.findByPlaceholderText('Untitled board').should('be.visible');
cy.wait(timeouts.QUARTER_SEC);
if (title) {
cy.log('**Rename board**');
cy.findByPlaceholderText('Untitled board').type(`${title}{enter}`);
cy.findByRole('textbox', {name: title}).should('exist');
}
cy.wait(timeouts.HALF_SEC);
});
Cypress.Commands.add('uiAddNewGroup', (name) => {
cy.log('**Add a new group**');
cy.findByRole('button', {name: '+ Add a group'}).click();
cy.findByRole('textbox', {name: 'New group'}).should('exist');
if (name) {
cy.log('**Rename group**');
cy.findByRole('textbox', {name: 'New group'}).type(`{selectall}${name}{enter}`);
cy.findByRole('textbox', {name}).should('exist');
}
cy.wait(timeouts.HALF_SEC);
});
Cypress.Commands.add('uiAddNewCard', (title, columnIndex) => {
cy.log('**Add a new card**');
cy.findByRole('button', {name: '+ New'}).eq(columnIndex || 0).click();
cy.findByRole('dialog').should('exist');
if (title) {
cy.log('**Change card title**');
cy.findByPlaceholderText('Untitled').type(title);
}
});

67
e2e-tests/cypress/tests/support/ui/channel.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,67 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiCreateChannel`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Create a new channel in the current team.
* @param {string} options.prefix - Prefix for the name of the channel, it will be added a random string ot it.
* @param {boolean} options.isPrivate - is the channel private or public (default)?
* @param {string} options.purpose - Channel's purpose
* @param {string} options.header - Channel's header
* @param {boolean} options.isNewSidebar) - the new sidebar has a different ui flow, set this setting to true to use that. Defaults to false.
*
* @example
* cy.uiCreateChannel({prefix: 'private-channel-', isPrivate: true, purpose: 'my private channel', header: 'my private header', isNewSidebar: false});
*/
uiCreateChannel(options: Record<string, unknown>): Chainable;
/**
* Add users to the current channel.
* @param {string[]} usernameList - list of userids to add to the channel
*
* @example
* cy.uiAddUsersToCurrentChannel(['user1', 'user2']);
*/
uiAddUsersToCurrentChannel(usernameList: string[]);
/**
* Archive the current channel.
*
* @example
* cy.uiArchiveChannel();
*/
uiArchiveChannel();
/**
* Unarchive the current channel.
*
* @example
* cy.uiUnarchiveChannel();
*/
uiUnarchiveChannel();
/**
* Leave the current channel.
* @param {boolean} isPrivate - is the channel private or public (default)?
*
* @example
* cy.uiLeaveChannel(true);
*/
uiLeaveChannel(isPrivate?: boolean);
}
}

Просмотреть файл

@@ -0,0 +1,93 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getRandomId} from '../../utils';
import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiCreateChannel', ({
prefix = 'channel-',
isPrivate = false,
purpose = '',
name = '',
createBoard = false,
}) => {
cy.uiBrowseOrCreateChannel('Create New Channel').click();
cy.get('#new-channel-modal').should('be.visible');
if (isPrivate) {
cy.get('#public-private-selector-button-P').click().wait(TIMEOUTS.HALF_SEC);
} else {
cy.get('#public-private-selector-button-O').click().wait(TIMEOUTS.HALF_SEC);
}
const channelName = name || `${prefix}${getRandomId()}`;
cy.get('#input_new-channel-modal-name').should('be.visible').clear().type(channelName);
if (purpose) {
cy.get('#new-channel-modal-purpose').clear().type(purpose);
}
if (createBoard) {
cy.get('#add-board-to-channel').should('be.visible');
cy.findByTestId('add-board-to-channel-check').then((el) => {
if (el && !el.hasClass('checked')) {
el.click();
cy.get('#input_select-board-template').should('be.visible').click();
cy.get('.SelectTemplateMenu .MenuItem:contains(Roadmap) button').should('be.visible').click();
}
});
}
cy.findByText('Create channel').click();
cy.get('#new-channel-modal').should('not.exist');
cy.get('#channelIntro').should('be.visible');
return cy.wrap({name: channelName});
});
Cypress.Commands.add('uiAddUsersToCurrentChannel', (usernameList) => {
if (usernameList.length) {
cy.get('#channelHeaderDropdownIcon').click();
cy.get('#channelAddMembers').click();
cy.get('#addUsersToChannelModal').should('be.visible');
usernameList.forEach((username) => {
cy.get('#selectItems input').typeWithForce(`@${username}{enter}`);
});
cy.get('#saveItems').click();
cy.get('#addUsersToChannelModal').should('not.exist');
}
});
Cypress.Commands.add('uiArchiveChannel', () => {
cy.get('#channelHeaderDropdownIcon').click();
cy.get('#channelArchiveChannel').click();
return cy.get('#deleteChannelModalDeleteButton').click();
});
Cypress.Commands.add('uiUnarchiveChannel', () => {
cy.get('#channelHeaderDropdownIcon').should('be.visible').click();
cy.get('#channelUnarchiveChannel').should('be.visible').click();
return cy.get('#unarchiveChannelModalDeleteButton').should('be.visible').click();
});
Cypress.Commands.add('uiLeaveChannel', (isPrivate = false) => {
cy.get('#channelHeaderDropdownIcon').click();
if (isPrivate) {
cy.get('#channelLeaveChannel').click();
return cy.get('#confirmModalButton').click();
}
return cy.get('#channelLeaveChannel').click();
});
Cypress.Commands.add('goToDm', (username) => {
cy.uiAddDirectMessage().click({force: true});
// # Start typing part of a username that matches previously created users
cy.get('#selectItems input').typeWithForce(username);
cy.findByRole('dialog', {name: 'Direct Messages'}).should('be.visible').wait(TIMEOUTS.ONE_SEC);
cy.findByRole('textbox', {name: 'Search for people'}).
typeWithForce(username).
wait(TIMEOUTS.ONE_SEC).
typeWithForce('{enter}');
// # Save the selected item
return cy.get('#saveItems').click().wait(TIMEOUTS.HALF_SEC);
});

86
e2e-tests/cypress/tests/support/ui/channel_header.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,86 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiGetChannelFavoriteButton`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Get channel header button.
*
* @example
* cy.uiGetChannelHeaderButton().click();
*/
uiGetChannelHeaderButton(): Chainable;
/**
* Get favorite button from channel header.
*
* @example
* cy.uiGetChannelFavoriteButton().click();
*/
uiGetChannelFavoriteButton(): Chainable;
/**
* Get mute button from channel header.
*
* @example
* cy.uiGetMuteButton().click();
*/
uiGetMuteButton(): Chainable;
/**
* Get member button from channel header.
*
* @example
* cy.uiGetChannelMemberButton().click();
*/
uiGetChannelMemberButton(): Chainable;
/**
* Get pin button from channel header.
*
* @example
* cy.uiGetChannelPinButton().click();
*/
uiGetChannelPinButton(): Chainable;
/**
* Get files button from channel header.
*
* @example
* cy.uiGetChannelFileButton().click();
*/
uiGetChannelFileButton(): Chainable;
/**
* Get channel menu
*
* @example
* cy.uiGetChannelMenu();
*/
uiGetChannelMenu(): Chainable;
/**
* Open channel menu
* @param {string} [menu] - such as `'View Info'`, `'Notification Preferences'`, `'Team Settings'` and other items in the main menu.
* @return the channel menu
*
* @example
* cy.uiOpenChannelMenu();
*/
uiOpenChannelMenu(menu?: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Buttons
Cypress.Commands.add('uiGetChannelHeaderButton', () => {
return cy.get('#channelHeaderDropdownButton').should('be.visible');
});
Cypress.Commands.add('uiGetChannelFavoriteButton', () => {
return cy.get('#toggleFavorite').should('be.visible');
});
Cypress.Commands.add('uiGetMuteButton', () => {
return cy.get('#toggleMute').should('be.visible');
});
Cypress.Commands.add('uiGetChannelMemberButton', () => {
return cy.get('#member_rhs').should('be.visible');
});
Cypress.Commands.add('uiGetChannelPinButton', () => {
return cy.get('#channelHeaderPinButton').should('be.visible');
});
Cypress.Commands.add('uiGetChannelFileButton', () => {
return cy.get('#channelHeaderFilesButton').should('be.visible');
});
// Menus
Cypress.Commands.add('uiGetChannelMenu', (options = {exist: true}) => {
if (options.exist) {
return cy.get('#channelHeaderDropdownMenu').
find('.dropdown-menu').
should('be.visible');
}
return cy.get('#channelHeaderDropdownMenu').should('not.exist');
});
Cypress.Commands.add('uiOpenChannelMenu', (item = '') => {
// # Click on channel header button
cy.uiGetChannelHeaderButton().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetChannelMenu();
}
// # Click on a particular item
return cy.uiGetChannelMenu().
findByText(item).
scrollIntoView().
should('be.visible').
click();
});

Просмотреть файл

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getRandomId} from '../../utils';
Cypress.Commands.add('uiCreateSidebarCategory', (categoryName = `category-${getRandomId()}`) => {
// # Click the New Category/Channel Dropdown button
cy.uiGetLHSAddChannelButton().click();
// # Click the Create New Category dropdown item
cy.get('.AddChannelDropdown').should('be.visible').contains('.MenuItem', 'Create New Category').click();
cy.findByRole('dialog', {name: 'Rename Category'}).should('be.visible').within(() => {
// # Fill in the category name and click 'Create'
cy.findByRole('textbox').should('be.visible').typeWithForce(categoryName).
invoke('val').should('equal', categoryName);
cy.findByRole('button', {name: 'Create'}).should('be.enabled').click();
});
// * Wait for the category to appear in the sidebar
cy.contains('.SidebarChannelGroup', categoryName, {matchCase: false});
return cy.wrap({displayName: categoryName});
});
Cypress.Commands.add('uiMoveChannelToCategory', (channelName, categoryName, newCategory = false, isChannelId = false) => {
// # Open the channel menu, select Move to
cy.uiGetChannelSidebarMenu(channelName, isChannelId).within(() => {
cy.findByText('Move to...').should('be.visible').trigger('mouseover');
});
// # Select the move to category
cy.findAllByRole('menu', {name: 'Move to submenu'}).should('be.visible').within(() => {
if (newCategory) {
cy.findByText('New Category').should('be.visible').click({force: true});
} else {
cy.findByText(categoryName).should('be.visible').click({force: true});
}
});
if (newCategory) {
cy.findByRole('dialog', {name: 'Rename Category'}).should('be.visible').within(() => {
// # Fill in the category name and click 'Create'
cy.findByRole('textbox').should('be.visible').typeWithForce(categoryName).
invoke('val').should('equal', categoryName);
cy.findByRole('button', {name: 'Create'}).should('be.enabled').click();
});
}
return cy.wrap({displayName: categoryName});
});

27
e2e-tests/cypress/tests/support/ui/cloud_billing.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Iframe element in Stripe
*
* @example
* cy.getIframeBody();
*/
uiGetPaymentCardInput(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiGetPaymentCardInput', () => {
return cy.
get('.__PrivateStripeElement > iframe').
its('0.contentDocument.body').should('not.be.empty').
then(cy.wrap);
});

114
e2e-tests/cypress/tests/support/ui/common.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,114 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiSave`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Click 'Save' button
*
* @example
* cy.uiSave();
*/
uiSave(): Chainable;
/**
* Click 'Cancel' button
*
* @example
* cy.uiCancel();
*/
uiCancel(): Chainable;
/**
* Click 'Close' button
*
* @example
* cy.uiClose();
*/
uiClose(): Chainable;
/**
* Click Save then Close buttons
*
* @example
* cy.uiSaveAndClose();
*/
uiSaveAndClose(): Chainable;
/**
* Get a button by its text using "cy.findByRole"
*
* @param {String} label - Button text
*
* @example
* cy.uiGetButton('Save');
*/
uiGetButton(label: string): Chainable;
/**
* Get save button
*
* @example
* cy.uiSaveButton();
*/
uiSaveButton(): Chainable;
/**
* Get cancel button
*
* @example
* cy.uiCancelButton();
*/
uiCancelButton(): Chainable;
/**
* Get close button
*
* @example
* cy.uiCloseButton();
*/
uiCloseButton(): Chainable;
/**
* Get a radio button by its text using "cy.findByRole"
*
* @example
* cy.uiGetRadioButton('Custom Theme');
*/
uiGetRadioButton(): Chainable;
/**
* Get a heading by its text using "cy.findByRole"
*
* @param {string} headingText - Heading text
*
* @example
* cy.uiGetHeading('General Settings');
*/
uiGetHeading(headingText: string): Chainable;
/**
* Get a textbox by its text using "cy.findByRole"
*
* @param {string} text - Textbox label
*
* @example
* cy.uiGetTextbox('Nickname');
*/
uiGetTextbox(text: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,55 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiSave', () => {
return cy.findByRole('button', {name: 'Save'}).scrollIntoView().click();
});
Cypress.Commands.add('uiCancel', () => {
return cy.findByRole('button', {name: 'Cancel'}).click();
});
Cypress.Commands.add('uiClose', () => {
return cy.findAllByRole('button', {name: 'Close'}).eq(0).click();
});
Cypress.Commands.add('uiSaveAndClose', () => {
cy.uiSave();
cy.uiClose();
});
Cypress.Commands.add('uiGetButton', (name) => {
return cy.findByRole('button', {name});
});
Cypress.Commands.add('uiSaveButton', () => {
return cy.uiGetButton('Save');
});
Cypress.Commands.add('uiCancelButton', () => {
return cy.uiGetButton('Cancel');
});
Cypress.Commands.add('uiCloseButton', () => {
return cy.uiGetButton('Close');
});
Cypress.Commands.add('uiGetRadioButton', (name) => {
return cy.findByRole('radio', {name}).should('be.visible');
});
Cypress.Commands.add('uiGetHeading', (name) => {
return cy.findByRole('heading', {name}).should('be.visible');
});
Cypress.Commands.add('uiGetTextbox', (name) => {
return cy.findByRole('textbox', {name}).should('be.visible');
});
Cypress.Commands.add('uiCloseOnboardingTaskList', () => {
cy.get('[data-cy=onboarding-task-list-action-button]').then(($btn) => {
if ($btn.find('i.icon-close').length) {
$btn.trigger('click');
}
});
});

39
e2e-tests/cypress/tests/support/ui/compliance_export.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Select compliance export format
* @param {string} exportFormat - compliance export format
*
* @example
* const EXPORTFORMAT = "Actiance XML";
* cy.uiEnableComplianceExport(Compliance Export Format);
*/
uiEnableComplianceExport(exportFormat: string): Chainable;
/**
* Go to Compliance Page
*/
uiGoToCompliancePage(): Chainable;
/**
* Click Run Export Compliance and wait for Success status
*/
uiExportCompliance(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,48 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiEnableComplianceExport', (exportFormat = 'csv') => {
// # Enable compliance export
cy.findByRole('radio', {name: /false/i}).click();
cy.findByRole('radio', {name: /true/i}).click();
// # Change export format
cy.findByRole('combobox', {name: /export format:/i}).select(exportFormat);
// # Save settings
cy.uiSaveConfig({confirm: true});
});
Cypress.Commands.add('uiGoToCompliancePage', () => {
cy.visit('/admin_console/compliance/export');
cy.get('.admin-console__header', {timeout: TIMEOUTS.TWO_MIN}).should('be.visible').invoke('text').should('include', 'Compliance Export');
});
Cypress.Commands.add('uiExportCompliance', () => {
// # Click the export job button
cy.findByRole('button', {name: /run compliance export job now/i}).click();
// # Small wait to ensure new row is add
cy.wait(TIMEOUTS.THREE_SEC);
// # Get the first row
cy.get('.job-table__table').find('tbody > tr').eq(0).as('firstRow');
// # Get the first table header
cy.get('.job-table__table').find('thead > tr').as('firstheader');
// # Wait until export is finished
cy.waitUntil(() => {
return cy.get('@firstRow').find('td:eq(1)').then((el) => {
return el[0].innerText.trim() === 'Success';
});
},
{
timeout: TIMEOUTS.FIVE_MIN,
interval: TIMEOUTS.ONE_SEC,
errorMsg: 'Compliance export did not finish in time',
});
});

86
e2e-tests/cypress/tests/support/ui/data_retention.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,86 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Go to Data Retention page
*/
uiGoToDataRetentionPage(): Chainable;
/**
* Click create policy button
*/
uiClickCreatePolicy(): Chainable;
/**
* Fill out custom policy form fields
* @param {string} name - policy name
* @param {string} durationDropdown - duration dropdown value (days, years, forever)
* @param {string?} durationText - duration text
*/
uiFillOutCustomPolicyFields(name: string, durationDropdown: string, durationText?: string): Chainable;
/**
* Search and add teams to custom policy
* @param {string[]} teamNames - array of team names
*/
uiAddTeamsToCustomPolicy(teamNames: string[]): Chainable;
/**
* Search and add channels to custom policy
* @param {string[]} channelNames - array of channel names
*/
uiAddChannelsToCustomPolicy(channelNames: string[]): Chainable;
/**
* Add teams to a custom policy
* @param {number} numberOfTeams - number of teams to add to the policy
*/
uiAddRandomTeamToCustomPolicy(numberOfTeams?: number): Chainable;
/**
* Add channels to a custom policy
* @param {number} numberOfTeams - number of teams to add to the policy
*/
uiAddRandomChannelToCustomPolicy(numberOfChannels?: number): Chainable;
/**
* Verify custom policy UI information
* @param {string} policyId - Custom Policy ID
* @param {string} description - The name of the policy
* @param {string} duration - How long messages last in the policy
* @param {string} appliedTo - Teams and channels the policy apples to
*/
uiVerifyCustomPolicyRow(policyId: string, description: string, duration: string, appliedTo: string): Chainable;
/**
* Click edit custom policy
* @param {string} policyId - Custom Policy ID
*/
uiClickEditCustomPolicyRow(policyId: string): Chainable;
/**
* Verify custom create policy response
* @param body - Response body
* @param {number} teamCount - Number of teams the policy applies to
* @param {number} channelCount - Number of channels the policy applies to
* @param {number} duration - How long messages last in the policy
* @param {string} displayName - The name of the policy
*/
uiVerifyPolicyResponse(body, teamCount: number, channelCount: number, duration: number, displayName: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,105 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiGoToDataRetentionPage', () => {
cy.visit('/admin_console/compliance/data_retention_settings');
cy.get('.DataRetentionSettings .admin-console__header', {timeout: TIMEOUTS.TWO_MIN}).should('be.visible').invoke('text').should('include', 'Data Retention Policies');
});
Cypress.Commands.add('uiClickCreatePolicy', () => {
cy.uiGetButton('Add policy').click();
cy.get('.DataRetentionSettings .admin-console__header', {timeout: TIMEOUTS.TWO_MIN}).should('be.visible').invoke('text').should('include', 'Custom Retention Policy');
});
Cypress.Commands.add('uiFillOutCustomPolicyFields', (name, durationDropdown, durationText = '') => {
// # Type policy name
cy.uiGetTextbox('Policy name').clear().type(name);
// # Add message retention values
cy.get('.CustomPolicy__fields #DropdownInput_message_retention').should('be.visible').click();
cy.get(`.message_retention__menu .message_retention__option span.option_${durationDropdown}`).should('be.visible').click();
if (durationText) {
cy.get('.CustomPolicy__fields input#message_retention_input').clear().type(durationText);
}
});
Cypress.Commands.add('uiAddTeamsToCustomPolicy', (teamNames) => {
cy.uiGetButton('Add teams').click();
teamNames.forEach((teamName) => {
cy.findByRole('textbox', {name: 'Search and add teams'}).typeWithForce(teamName);
cy.get('.team-info-block').then((el) => {
el.click();
});
});
cy.uiGetButton('Add').click();
});
Cypress.Commands.add('uiAddChannelsToCustomPolicy', (channelNames) => {
cy.uiGetButton('Add channels').click();
channelNames.forEach((channelName) => {
cy.findByRole('textbox', {name: 'Search and add channels'}).typeWithForce(channelName);
cy.wait(TIMEOUTS.ONE_SEC);
cy.get('.channel-info-block').then((el) => {
el.click();
});
});
cy.uiGetButton('Add').click();
});
Cypress.Commands.add('uiAddRandomTeamToCustomPolicy', (numberOfTeams = 1) => {
cy.uiGetButton('Add teams').click();
for (let i = 0; i < numberOfTeams; i++) {
cy.get('.team-info-block').first().then((el) => {
el.click();
});
}
cy.uiGetButton('Add').click();
});
Cypress.Commands.add('uiAddRandomChannelToCustomPolicy', (numberOfChannels = 1) => {
cy.uiGetButton('Add channels').click();
for (let i = 0; i < numberOfChannels; i++) {
cy.get('.channel-info-block').first().then((el) => {
el.click();
});
}
cy.uiGetButton('Add').click();
});
Cypress.Commands.add('uiVerifyCustomPolicyRow', (policyId, description, duration, appliedTo) => {
// * Assert row has correct description
cy.get(`#customDescription-${policyId}`).should('include.text', description);
// * Assert row has correct duration
cy.get(`#customDuration-${policyId}`).should('include.text', duration);
// * Assert row has correct team/channel counts
cy.get(`#customAppliedTo-${policyId}`).should('include.text', appliedTo);
});
Cypress.Commands.add('uiClickEditCustomPolicyRow', (policyId) => {
cy.get(`#customWrapper-${policyId}`).trigger('mouseover').click();
cy.findByRole('button', {name: /edit/i}).should('be.visible').click();
});
Cypress.Commands.add('uiVerifyPolicyResponse', (body, teamCount, channelCount, duration, displayName) => {
// * Assert response body exists
assert.isNotNull(body);
// * Assert response body contains an ID
assert.isNotNull(body.id);
// * Assert response body team_count matches supplied value
expect(body.team_count).to.equal(teamCount);
// * Assert response body channel_count matches supplied value
expect(body.channel_count).to.equal(channelCount);
// * Assert response body duration matches supplied value
expect(body.post_duration).to.equal(duration);
// * Assert response body display_name matches supplied value
expect(body.display_name).to.equal(displayName);
});

53
e2e-tests/cypress/tests/support/ui/emoji.ts Обычный файл
Просмотреть файл

@@ -0,0 +1,53 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChainableT} from 'tests/types';
Cypress.Commands.add('uiGetEmojiPicker', (): ChainableT<JQuery> => {
return cy.get('#emojiPicker').should('be.visible');
});
Cypress.Commands.add('uiOpenEmojiPicker', (): ChainableT<JQuery> => {
cy.findByRole('button', {name: 'select an emoji'}).click();
return cy.get('#emojiPicker').should('be.visible');
});
Cypress.Commands.add('uiOpenCustomEmoji', () => {
cy.uiOpenEmojiPicker();
cy.findByText('Custom Emoji').should('be.visible').click();
cy.url().should('include', '/emoji');
cy.get('.backstage-header').should('be.visible').and('contain', 'Custom Emoji');
});
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Open custom emoji
*
* @example
* cy.uiOpenCustomEmoji();
*/
uiGetEmojiPicker(): Chainable;
/**
* Open custom emoji
*
* @example
* cy.uiOpenCustomEmoji();
*/
uiOpenCustomEmoji(): Chainable;
/**
* Open emoji picker
*
* @example
* cy.uiOpenEmojiPicker();
*/
uiOpenEmojiPicker(): Chainable;
}
}
}

30
e2e-tests/cypress/tests/support/ui/extend_testing_library.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of the Testing Library commands
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Extends `findByRole` by matching case to `name` as insensitive but sensitive to `text` value
* @param {string} role - button, input, textbox, etc.
* @param {Object} option - text value of the target element
*
* @example
* cy.findByRoleExtended('button', {name: 'Advanced'}).should('be.visible').click();
*/
findByRoleExtended(role: string, option: {name: string}): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('findByRoleExtended', (role, {name}) => {
const re = RegExp(name, 'i');
return cy.findByRole(role, {name: re}).should('have.text', name);
});

124
e2e-tests/cypress/tests/support/ui/file_preview.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,124 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiOpenFilePreviewModal`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Get file thumbnail from a post
*
* @param {string} filename
*
* @example
* cy.uiGetFileThumbnail('image.png');
*/
uiGetFileThumbnail(filename: string): Chainable;
/**
* Get file upload preview located below post textbox
*
* @example
* cy.uiGetFileUploadPreview();
*/
uiGetFileUploadPreview(): Chainable;
/**
* Wait for file upload preview located below post textbox
*
* @example
* cy.uiGetFileUploadPreview();
*/
uiGetFileUploadPreview(): Chainable;
/**
* Get file preview modal
*
* @param {bool} option.exist - Set to false to not verify if the element exists. Otherwise, true (default) to check existence.
*
* @example
* cy.uiGetFilePreviewModal();
*/
uiGetFilePreviewModal(option: Record<string, boolean>): Chainable;
/**
* Get Public Link
*
* @param {bool} option.exist - Set to false to not verify if the element exists. Otherwise, true (default) to check existence.
*
* @example
* cy.uiGetPublicLink();
*/
uiGetPublicLink(option: Record<string, boolean>): Chainable;
/**
* Open file preview modal
*
* @param {string} filename
*
* @example
* cy.uiOpenFilePreviewModal('image.png');
*/
uiOpenFilePreviewModal(filename: string): Chainable;
/**
* Close file preview modal
*
* @example
* cy.uiCloseFilePreviewModal();
*/
uiCloseFilePreviewModal(): Chainable;
/**
* Get main content of file preview modal
*
* @example
* cy.uiGetContentFilePreviewModal();
*/
uiGetContentFilePreviewModal(): Chainable;
/**
* Get download link button from file preview modal
*
* @example
* cy.uiGetDownloadLinkFilePreviewModal();
*/
uiGetDownloadLinkFilePreviewModal(): Chainable;
/**
* Get download button from file preview modal
*
* @example
* cy.uiGetDownloadFilePreviewModal();
*/
uiGetDownloadFilePreviewModal(): Chainable;
/**
* Get arrow left button from file preview modal
*
* @example
* cy.uiGetArrowLeftFilePreviewModal();
*/
uiGetArrowLeftFilePreviewModal(): Chainable;
/**
* Get arrow right button from file preview modal
*
* @example
* cy.uiGetArrowRightFilePreviewModal();
*/
uiGetArrowRightFilePreviewModal(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,67 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiGetFileThumbnail', (filename) => {
return cy.findByLabelText(`file thumbnail ${filename.toLowerCase()}`);
});
Cypress.Commands.add('uiGetFileUploadPreview', () => {
return cy.get('.file-preview__container');
});
Cypress.Commands.add('uiWaitForFileUploadPreview', () => {
cy.waitUntil(() => cy.uiGetFileUploadPreview().then((el) => {
return el.find('.post-image.normal').length > 0;
}));
});
Cypress.Commands.add('uiGetFilePreviewModal', (options = {exist: true}) => {
if (options.exist) {
return cy.get('.file-preview-modal').should('be.visible');
}
return cy.get('.file-preview-modal').should('not.exist');
});
Cypress.Commands.add('uiGetPublicLink', (options = {exist: true}) => {
if (options.exist) {
return cy.get('.icon-link-variant').should('be.visible');
}
return cy.get('.icon-link-variant').should('not.exist');
});
Cypress.Commands.add('uiGetHeaderFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.file-preview-modal-header').should('be.visible');
});
Cypress.Commands.add('uiOpenFilePreviewModal', (filename) => {
if (filename) {
cy.uiGetFileThumbnail(filename.toLowerCase()).click();
} else {
cy.findByTestId('fileAttachmentList').children().first().click();
}
});
Cypress.Commands.add('uiCloseFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.icon-close').click();
});
Cypress.Commands.add('uiGetContentFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.file-preview-modal__content');
});
Cypress.Commands.add('uiGetDownloadLinkFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.icon-link-variant').parent();
});
Cypress.Commands.add('uiGetDownloadFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.icon-download-outline').parent();
});
Cypress.Commands.add('uiGetArrowLeftFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.icon-chevron-left').parent();
});
Cypress.Commands.add('uiGetArrowRightFilePreviewModal', () => {
return cy.uiGetFilePreviewModal().find('.icon-chevron-right').parent();
});

189
e2e-tests/cypress/tests/support/ui/global_header.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,189 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiGetProductMenuButton`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Get product switch button
*
* @example
* cy.uiGetProductMenuButton().click();
*/
uiGetProductMenuButton(): Chainable;
/**
* Get product switch menu
*
* @example
* cy.uiGetProductMenu().click();
*/
uiGetProductMenu(): Chainable;
/**
* Open product switch menu
*
* @param {string} item - menu item ex. System Console, Integrations, etc.
*
* @example
* cy.uiOpenProductMenu().click();
*/
uiOpenProductMenu(item: string): Chainable;
/**
* Get set status button
*
* @example
* cy.uiGetSetStatusButton().click();
*/
uiGetSetStatusButton(): Chainable;
/**
* Get profile header
*
* @example
* cy.uiGetProfileHeader();
*/
uiGetProfileHeader(): Chainable;
/**
* Get status menu container
*
* @param {bool} option.exist - Set to false to not verify if the element exists. Otherwise, true (default) to check existence.
* @example
* cy.uiGetStatusMenuContainer({exist: false});
*/
uiGetStatusMenuContainer(option: Record<string, boolean>): Chainable;
/**
* Get user menu
*
* @example
* cy.uiGetStatusMenu();
*/
uiGetStatusMenu(): Chainable;
/**
* Open help menu
*
* @param {string} item - menu item ex. Ask the community, Help resources, etc.
*
* @example
* cy.uiOpenHelpMenu();
*/
uiOpenHelpMenu(item: string): Chainable;
/**
* Get help button
*
* @example
* cy.uiGetHelpButton();
*/
uiGetHelpButton(): Chainable;
/**
* Get help menu
*
* @example
* cy.uiGetHelpMenu();
*/
uiGetHelpMenu(): Chainable;
/**
* Open user menu
*
* @param {string} [item] - menu item ex. Profile, Logout, etc.
*
* @example
* cy.uiOpenUserMenu();
*/
uiOpenUserMenu(item?: string): Chainable;
/**
* Get search form container
*
* @example
* cy.uiGetSearchContainer();
*/
uiGetSearchContainer(): Chainable;
/**
* Get search box
*
* @example
* cy.uiGetSearchBox();
*/
uiGetSearchBox(): Chainable;
/**
* Get at-mention button
*
* @example
* cy.uiGetRecentMentionButton();
*/
uiGetRecentMentionButton(): Chainable;
/**
* Get saved posts button
*
* @example
* cy.uiGetSavedPostButton();
*/
uiGetSavedPostButton(): Chainable;
/**
* Get settings button
*
* @example
* cy.uiGetSettingsButton();
*/
uiGetSettingsButton(): Chainable;
/**
* Get settings modal
*
* @example
* cy.uiGetSettingsModal();
*/
uiGetSettingsModal(): Chainable;
/**
* Get channel info button
*
* @example
* cy.uiGetChannelInfoButton();
*/
uiGetChannelInfoButton(): Chainable;
/**
* Open settings modal
*
* @param {string} section - ex. Display, Sidebar, etc.
*
* @example
* cy.uiOpenSettingsModal();
*/
uiOpenSettingsModal(section: string): Chainable;
/**
* User log out via user menu
*
* @example
* cy.uiLogout();
*/
uiLogout(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,155 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiGetProductMenuButton', () => {
return cy.findByRole('button', {name: 'Product switch menu'}).should('be.visible');
});
Cypress.Commands.add('uiGetProductMenu', () => {
return cy.get('.product-switcher-menu').should('be.visible');
});
Cypress.Commands.add('uiOpenProductMenu', (item = '') => {
// # Click on product switch button
cy.uiGetProductMenuButton().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetProductMenu();
}
// # Click on a particular item
return cy.uiGetProductMenu().
findByText(item).
scrollIntoView().
should('be.visible').
click();
});
Cypress.Commands.add('uiGetSetStatusButton', () => {
return cy.findByRole('button', {name: /Select to open profile and status menu\./i}).should('be.visible');
});
Cypress.Commands.add('uiGetProfileHeader', () => {
return cy.uiGetSetStatusButton().parent();
});
Cypress.Commands.add('uiGetStatusMenuContainer', (options = {exist: true}) => {
if (options.exist) {
return cy.findByRole('menu').should('exist');
}
return cy.findByRole('menu').should('not.exist');
});
Cypress.Commands.add('uiGetStatusMenu', (options = {visible: true}) => {
if (options.visible) {
return cy.uiGetStatusMenuContainer().
find('ul').
should('be.visible');
}
return cy.uiGetStatusMenuContainer().
find('ul').
should('not.be.visible');
});
Cypress.Commands.add('uiOpenHelpMenu', (item = '') => {
// # Click on help status button
cy.uiGetHelpButton().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetHelpMenu();
}
// # Click on a particular item
return cy.uiGetHelpMenu().
findByText(item).
scrollIntoView().
should('be.visible').
click();
});
Cypress.Commands.add('uiGetHelpButton', () => {
return cy.findByRole('button', {name: 'Help'}).should('be.visible');
});
Cypress.Commands.add('uiGetHelpMenu', (options = {visible: true}) => {
const dropdown = () => cy.get('#helpMenuPortal').find('.dropdown-menu');
if (options.visible) {
return dropdown().should('be.visible');
}
return dropdown().should('not.be.visible');
});
Cypress.Commands.add('uiOpenUserMenu', (item = '') => {
// # Click on user status button
cy.uiGetSetStatusButton().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetStatusMenu();
}
// # Click on a particular item
return cy.uiGetStatusMenu().
findByText(item).
scrollIntoView().
should('be.visible').
click();
});
Cypress.Commands.add('uiGetSearchContainer', () => {
return cy.get('#searchFormContainer').should('be.visible');
});
Cypress.Commands.add('uiGetSearchBox', () => {
return cy.get('#searchBox').should('be.visible');
});
Cypress.Commands.add('uiGetRecentMentionButton', () => {
return cy.findByRole('button', {name: 'Recent mentions'}).should('be.visible');
});
Cypress.Commands.add('uiGetSavedPostButton', () => {
return cy.findByRole('button', {name: 'Saved posts'}).should('be.visible');
});
Cypress.Commands.add('uiGetSettingsButton', () => {
return cy.findByRole('button', {name: 'Settings'}).should('be.visible');
});
Cypress.Commands.add('uiGetChannelInfoButton', () => {
return cy.findByRole('button', {name: 'View Info'}).should('be.visible');
});
Cypress.Commands.add('uiGetSettingsModal', () => {
// # Get settings modal
return cy.findByRole('dialog', {name: 'Settings'});
});
Cypress.Commands.add('uiOpenSettingsModal', (section = '') => {
// # Open settings modal
cy.uiGetSettingsButton().click();
if (!section) {
return cy.uiGetSettingsModal();
}
// # Click on a particular section
cy.findByRoleExtended('tab', {name: section}).should('be.visible').click();
return cy.uiGetSettingsModal();
});
Cypress.Commands.add('uiLogout', () => {
// # Click logout via user menu
cy.uiOpenUserMenu('Log Out');
cy.url().should('include', '/login');
cy.get('.login-body-message').should('be.visible');
cy.get('.login-body-card').should('be.visible');
});

31
e2e-tests/cypress/tests/support/ui/index.js Обычный файл
Просмотреть файл

@@ -0,0 +1,31 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import './account_settings_modal';
import './announcement_bar';
import './boards';
import './channel';
import './channel_header';
import './channel_sidebar';
import './cloud_billing';
import './common';
import './compliance_export';
import './data_retention';
import './extend_testing_library';
import './global_header';
import './emoji';
import './file_preview';
import './login';
import './menu';
import './mfa';
import './modal';
import './playbooks';
import './post';
import './post_dropdown_menu';
import './search';
import './sidebar_left';
import './sidebar_right';
import './suggestion_list';
import './system';
import './team';
import './tooltip';

30
e2e-tests/cypress/tests/support/ui/login.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiLogin`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Login vi UI at login page
*
* @param {UserProfile} user - user with username and password
*
* @example
* cy.uiLogin(user);
*/
uiLogin(user: UserProfile): Chainable;
}
}

11
e2e-tests/cypress/tests/support/ui/login.js Обычный файл
Просмотреть файл

@@ -0,0 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiLogin', (user = {}) => {
cy.url().should('include', '/login');
// # Type email and password, then Sign in
cy.get('#input_loginId').should('be.visible').type(user.email);
cy.get('#input_password-input').should('be.visible').type(user.password);
cy.get('#saveSetting').should('not.be.disabled').click();
});

46
e2e-tests/cypress/tests/support/ui/menu.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiOpenSystemConsoleMainMenu`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Open main menu at system console
* @param {string} item - such as `'Switch to [Team Name]'`, `'Administrator's Guide'`, `'Troubleshooting Forum'`, `'Commercial Support'`, `'About Mattermost'` and `'Log Out'`.
* @return the main menu
*
* @example
* cy.uiOpenSystemConsoleMainMenu();
*/
uiOpenSystemConsoleMainMenu(): Chainable;
/**
* Close main menu at system console
*
* @example
* cy.uiCloseSystemConsoleMainMenu();
*/
uiCloseSystemConsoleMainMenu(): Chainable;
/**
* Get main menu at system console
*
* @example
* cy.uiGetSystemConsoleMainMenu();
*/
uiGetSystemConsoleMainMenu(): Chainable;
}
}

46
e2e-tests/cypress/tests/support/ui/menu.js Обычный файл
Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const SYSTEM_CONSOLE_MAIN_MENU = 'Menu Icon';
function openMenu(name, item) {
const menu = () => cy.findByRole('button', {name}).should('be.visible');
// # Open the menu
menu().should('be.visible').click();
if (!item) {
return menu();
}
// # Click on a particular item
return cy.findByRole('menu').findByText(item).scrollIntoView().should('be.visible').click();
}
function getMenu(name) {
return cy.findByRole('button', {name}).should('be.visible');
}
Cypress.Commands.add('uiOpenSystemConsoleMainMenu', (item = '') => {
return openMenu(SYSTEM_CONSOLE_MAIN_MENU, item);
});
Cypress.Commands.add('uiCloseSystemConsoleMainMenu', () => {
return cy.uiGetSystemConsoleMainMenu().click();
});
Cypress.Commands.add('uiGetSystemConsoleMainMenu', () => {
return getMenu(SYSTEM_CONSOLE_MAIN_MENU);
});
Cypress.Commands.add('uiOpenDndStatusSubMenu', () => {
cy.uiOpenUserMenu();
// # Wait for status menu to transition in
cy.get('.MenuWrapper.status-dropdown-menu .Menu__content.dropdown-menu').should('be.visible');
// # Hover over Do Not Disturb option
cy.get('.MenuWrapper.status-dropdown-menu .Menu__content.dropdown-menu li#status-menu-dnd_menuitem').trigger('mouseover');
return cy.get('#status-menu-dnd');
});

34
e2e-tests/cypress/tests/support/ui/mfa.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiGetMFASecret`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Get MFA secret of a given user
* @param {string} userId - ID of user
*
* @returns {string} `secret` - MFA secret
*
* @example
* const headerLabel = 'What\'s New';
* cy.uiGetMFASecret('user-id').then((secret) => {
* // do something with the secret
* });
*/
uiGetMFASecret(userId: string): Chainable<string>;
}
}

32
e2e-tests/cypress/tests/support/ui/mfa.js Обычный файл
Просмотреть файл

@@ -0,0 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import authenticator from 'authenticator';
import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiGetMFASecret', (userId) => {
return cy.url().then((url) => {
if (url.includes('mfa/setup')) {
// # Complete MFA setup if we are on token setup page /mfa/setup
return cy.get('#mfa').wait(TIMEOUTS.HALF_SEC).find('.col-sm-12').then((p) => {
const secretp = p.text();
const secret = secretp.split(' ')[1];
const token = authenticator.generateToken(secret);
cy.findByPlaceholderText('MFA Code').type(token);
cy.findByText('Save').click();
cy.wait(TIMEOUTS.HALF_SEC);
cy.findByText('Okay').click();
return cy.wrap(secret);
});
}
// # If the user already has MFA enabled, reset the secret.
return cy.apiGenerateMfaSecret(userId).then((res) => {
return cy.wrap(res.code.secret);
});
});
});

30
e2e-tests/cypress/tests/support/ui/modal.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiCloseModal`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Close modal with header label
* @param {string} headerLabel - the header label
*
* @example
* const headerLabel = 'What\'s New';
* cy.uiCloseModal(headerLabel);
*/
uiCloseModal(headerLabel: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiCloseModal', (headerLabel) => {
// # Close modal with modal label
cy.get('#genericModalLabel', {timeout: TIMEOUTS.HALF_MIN}).should('have.text', headerLabel).parents().find('.modal-dialog').findByLabelText('Close').click();
});

Просмотреть файл

@@ -0,0 +1,324 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as TIMEOUTS from '../../fixtures/timeouts';
const playbookRunStartCommand = '/playbook run';
Cypress.Commands.add('startPlaybookRun', (playbookName, playbookRunName) => {
cy.get('#interactiveDialogModal').should('exist').within(() => {
// # Select playbook
cy.selectPlaybookFromDropdown(playbookName);
// # Type playbook run name
cy.findByTestId('playbookRunNameinput').type(playbookRunName, {force: true});
// # Submit
cy.get('#interactiveDialogSubmit').click();
});
cy.get('#interactiveDialogModal').should('not.exist');
});
// Opens playbook run dialog using the `/playbook run` slash command
Cypress.Commands.add('openPlaybookRunDialogFromSlashCommand', () => {
cy.uiPostMessageQuickly(playbookRunStartCommand);
});
// Starts playbook run with the `/playbook run` slash command
Cypress.Commands.add('startPlaybookRunWithSlashCommand', (playbookName, playbookRunName) => {
cy.openPlaybookRunDialogFromSlashCommand();
cy.startPlaybookRun(playbookName, playbookRunName);
});
// Selects Playbooks icon in the App Bar
Cypress.Commands.add('getPlaybooksAppBarIcon', () => {
cy.get('#channel_view').should('be.visible');
return cy.get('.app-bar').find('#app-bar-icon-playbooks .app-bar__icon-inner');
});
// Starts playbook run from the playbook run RHS
Cypress.Commands.add('startPlaybookRunFromRHS', (playbookName, playbookRunName) => {
cy.get('#channel-header').within(() => {
// open flagged posts to ensure playbook run RHS is closed
cy.get('#channelHeaderFlagButton').click();
// open the playbook run RHS
cy.getPlaybooksAppBarIcon().should('exist').click();
});
cy.get('#rhsContainer').should('exist').within(() => {
cy.findByText('Run playbook').click();
});
cy.startPlaybookRun(playbookName, playbookRunName);
});
// Create a new task from the RHS
Cypress.Commands.add('addNewTaskFromRHS', (taskname) => {
// Click add new task
cy.findByTestId('add-new-task-0').click();
// Type a name
cy.findByTestId('checklist-item-textarea-title').type(taskname);
// Save task
cy.findByTestId('checklist-item-save-button').click();
});
// Starts playbook run from the post menu
Cypress.Commands.add('startPlaybookRunFromPostMenu', (playbookName, playbookRunName) => {
// post a message as user to avoid system message
cy.findByTestId('post_textbox').clear().type('new message here{enter}');
// post a second message because cypress has trouble finding latest post when there's only one message
cy.findByTestId('post_textbox').clear().type('another new message here{enter}');
cy.clickPostActionsMenu();
cy.findByTestId('playbookRunPostMenuIcon').click();
cy.startPlaybookRun(playbookName, playbookRunName);
});
// Create playbook
Cypress.Commands.add('createPlaybook', (teamName, playbookName) => {
cy.visit('/playbooks/playbooks/new');
cy.findByTestId('save_playbook', {timeout: TIMEOUTS.HALF_MIN}).should('exist');
// # Type playbook name
cy.get('#playbook-name .editable-trigger').click();
cy.get('#playbook-name .editable-input').type(playbookName);
cy.get('#playbook-name .editable-input').type('{enter}');
// # Save playbook
cy.findByTestId('save_playbook', {timeout: TIMEOUTS.HALF_MIN}).should('not.be.disabled').click();
cy.wait(TIMEOUTS.TWO_SEC);
cy.findByTestId('save_playbook', {timeout: TIMEOUTS.HALF_MIN}).should('not.be.disabled').click();
});
// Select the playbook from the dropdown menu
Cypress.Commands.add('selectPlaybookFromDropdown', (playbookName) => {
cy.findByTestId('autoCompleteSelector').should('exist').within(() => {
cy.get('input').click().type(playbookName.toLowerCase());
cy.get('#suggestionList').contains(playbookName).click({force: true});
});
});
Cypress.Commands.add('createPost', (message) => {
// post a message as user to avoid system message
cy.findByTestId('post_textbox').clear().type(`${message}{enter}`);
});
Cypress.Commands.add('addPostToTimelineUsingPostMenu', (playbookRunName, summary, postId) => {
cy.clickPostDotMenu(postId);
cy.findByTestId('playbookRunAddToTimeline').click();
cy.get('#interactiveDialogModal').should('exist').within(() => {
// # Select playbook run
cy.findByTestId('autoCompleteSelector').should('exist').within(() => {
cy.get('input').click().type(playbookRunName);
cy.get('#suggestionList').contains(playbookRunName).click({force: true});
});
// # Type playbook run name
cy.findByTestId('summaryinput').clear().type(summary, {force: true});
// # Submit
cy.get('#interactiveDialogSubmit').click();
});
cy.get('#interactiveDialogModal').should('not.exist');
});
Cypress.Commands.add('openSelector', () => {
cy.findByText('Search for people').click({force: true});
});
Cypress.Commands.add('addInvitedUser', (userName) => {
cy.get('.invite-users-selector__menu').within(() => {
cy.findByText(userName).click({force: true});
});
});
Cypress.Commands.add('selectOwner', (userName) => {
cy.get('.assign-owner-selector__menu').within(() => {
cy.findByText(userName).click({force: true});
});
});
Cypress.Commands.add('selectChannel', (channelName) => {
cy.get('#playbook-automation-broadcast .playbooks-rselect__menu').within(() => {
cy.findByText(channelName).click({force: true});
});
});
Cypress.Commands.add('openReminderSelector', () => {
cy.get('#reminder_timer_datetime input').click({force: true});
});
Cypress.Commands.add('selectReminderTime', (timeText) => {
cy.get('#reminder_timer_datetime .playbooks-rselect__menu').within(() => {
cy.findByText(timeText).click({force: true});
});
});
/**
* Update the status of the current playbook run through the slash command.
*/
Cypress.Commands.add('updateStatus', (message, reminderQuery) => {
// # Run the slash command to update status.
cy.uiPostMessageQuickly('/playbook update');
// # Get the interactive dialog modal.
cy.getStatusUpdateDialog().within(() => {
// # remove what's there if applicable, and type the new update in the textbox.
cy.findByTestId('update_run_status_textbox').clear().type(message);
if (reminderQuery) {
cy.get('#reminder_timer_datetime').within(() => {
cy.get('input').type(reminderQuery, {delay: TIMEOUTS.TWO_HUNDRED_MILLIS, force: true}).type('{enter}', {force: true});
});
}
// # Submit the dialog.
cy.get('button.confirm').click();
});
// * Verify that the interactive dialog has gone.
cy.getStatusUpdateDialog().should('not.exist');
// # Return the post ID of the status update.
return cy.getLastPostId();
});
/**
* Edit a post through the post dot menu.
* @param {String} postId - ID of the post to delete.
* @param {String} newMessage - New content of the post.
*/
Cypress.Commands.add('editPost', (postId, newMessage) => {
// # Open the post dot menu.
cy.clickPostDotMenu(postId);
// # Click on the Edit menu option.
cy.get(`#edit_post_${postId}`).click();
// # Overwrite the post content with the new message provided.
cy.get('#edit_textbox').clear().type(newMessage);
// # Confirm the edit in the dialog.
cy.get('#editButton').click();
});
Cypress.Commands.add('getStatusUpdateDialog', () => {
return cy.findByRole('dialog', {name: /post update/i});
});
Cypress.Commands.add('getStyledComponent', (className) => {
cy.get(`[class^="${className}-"]`);
});
/**
* Get the provided pseudo-class from the previous element and return the property passed as argument
* @param {String} pseudoClass - CSS pseudo class to get.
* @param {String} property - Property that will be returned.
*
* Stolen from https://stackoverflow.com/questions/55516990/cypress-testing-pseudo-css-class-before
*/
Cypress.Commands.add('cssPseudoClass', {prevSubject: 'element'}, (el, pseudoClass, property) => {
const win = el[0].ownerDocument.defaultView;
const pseudoElem = win.getComputedStyle(el[0], pseudoClass);
return pseudoElem.getPropertyValue(property).replace(/(^")|("$)/g, '');
});
/**
* Get the :before pseudo-class from the previous element and return the property passed as argument
* @param {String} property - Property that will be returned.
*/
Cypress.Commands.add('before', {prevSubject: 'element'}, (el, property) => {
return cy.wrap(el).cssPseudoClass('before', property);
});
/**
* Get the :after pseudo-class from the previous element and return the property passed as argument
* @param {String} property - Property that will be returned.
*/
Cypress.Commands.add('after', {prevSubject: 'element'}, (el, property) => {
return cy.wrap(el).cssPseudoClass('after', property);
});
function waitUntilPermanentPost() {
cy.get('#postListContent').should('exist');
cy.waitUntil(() => cy.findAllByTestId('postView').last().then((el) => !(el[0].id.includes(':'))));
}
Cypress.Commands.add('getFirstPostId', () => {
waitUntilPermanentPost();
cy.findAllByTestId('postView').first().should('have.attr', 'id').and('not.include', ':').
invoke('replace', 'post_', '');
});
Cypress.Commands.add('assertRunDetailsPageRenderComplete', (expectedRunOwner) => {
cy.findByTestId('lhs-navigation').should('be.visible').within(() => {
cy.contains('Playbooks').should('be.visible');
cy.contains('Runs').should('be.visible');
});
cy.get('#playbooks-sidebar-right').should('be.visible').within(() => {
cy.findByTestId('assignee-profile-selector').should('contain', expectedRunOwner);
cy.findAllByTestId('timeline-item', {exact: false}).should('have.length.of.at.least', 1);
cy.findAllByTestId('profile-option', {exact: false}).should('have.length.of.at.least', 1);
});
});
Cypress.Commands.add('interceptTelemetry', () => {
cy.intercept('/plugins/playbooks/api/v0/telemetry').as('telemetry');
});
const defaultExpectTelemetryToContainOptions = {
waitForCalls: 'auto',
};
// cy.expectTelemetryToContain expects to find the given telemetry events in the order given among the
// recorded telemetry. It doesn't fail if other telemetry events happen to occur in between.
Cypress.Commands.add('expectTelemetryToContain', (items, opts) => {
const options = {...defaultExpectTelemetryToContainOptions, ...opts};
// Wait for at least as many telemetry events as requested if auto, or explicit number if passed.
if (options.waitForCalls === 'auto') {
items.forEach(() => cy.wait('@telemetry'));
} else {
for (let i = 0; i < options.waitForCalls; i++) {
cy.wait('@telemetry');
}
}
// When additional telemetry events are emitted than what is expected, the ones we want may
// still be be pending, so wait a bit more to try to let requests settle.
cy.wait(TIMEOUTS.HALF_SEC);
cy.get('@telemetry.all').then((xhrs) => {
let xhrIndex = 0;
items.forEach((item) => {
while (xhrIndex < xhrs.length) {
const xhr = xhrs[xhrIndex];
// Advance to the next xhr element regardless of whether or not we find a match.
xhrIndex++;
if (xhr.request.body.name === item.name && xhr.request.body.type === item.type) {
// Validate only passed properties
if (item.properties) {
for (const [key, value] of Object.entries(item.properties)) {
expect(xhr.request.body.properties[key]).to.eq(value, `Property ${key} does not match for event ${item.name}`);
}
}
return;
}
}
throw new Error(`failed to find telemetry event '${item.type}' '${item.name}'`);
});
});
});

254
e2e-tests/cypress/tests/support/ui/post.ts Обычный файл
Просмотреть файл

@@ -0,0 +1,254 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChainableT} from '../../types';
function uiGetPostTextBox(option = {exist: true}): ChainableT<JQuery> {
if (option.exist) {
return cy.get('#post_textbox').should('be.visible');
}
return cy.get('#post_textbox').should('not.exist');
}
Cypress.Commands.add('uiGetPostTextBox', uiGetPostTextBox);
function uiGetReplyTextBox(option = {exist: true}): ChainableT<JQuery> {
if (option.exist) {
return cy.get('#reply_textbox').should('be.visible');
}
return cy.get('#reply_textbox').should('not.exist');
}
Cypress.Commands.add('uiGetReplyTextBox', uiGetReplyTextBox);
function uiGetPostProfileImage(postId: string): ChainableT<JQuery> {
return getPost(postId).within(() => {
return cy.get('.post__img').should('be.visible');
});
}
Cypress.Commands.add('uiGetPostProfileImage', uiGetPostProfileImage);
function uiGetPostHeader(postId: string): ChainableT<JQuery> {
return getPost(postId).within(() => {
return cy.get('.post__header').should('be.visible');
});
}
Cypress.Commands.add('uiGetPostHeader', uiGetPostHeader);
function uiGetPostBody(postId: string): ChainableT<JQuery> {
return getPost(postId).within(() => {
return cy.get('.post__body').should('be.visible');
});
}
Cypress.Commands.add('uiGetPostBody', uiGetPostBody);
function uiGetPostThreadFooter(postId: string): ChainableT<JQuery> {
return getPost(postId).find('.ThreadFooter');
}
Cypress.Commands.add('uiGetPostThreadFooter', uiGetPostThreadFooter);
function uiGetPostEmbedContainer(postId: string): ChainableT<JQuery> {
return cy.uiGetPostBody(postId).
find('.file-preview__button').
should('be.visible');
}
Cypress.Commands.add('uiGetPostEmbedContainer', uiGetPostEmbedContainer);
function getPost(postId: string): ChainableT<JQuery> {
if (postId) {
return cy.get(`#post_${postId}`).should('be.visible');
}
return cy.getLastPost();
}
Cypress.Commands.add('getPost', getPost);
export function verifySavedPost(postId, message) {
// * Check that the center save icon has been updated correctly
cy.get(`#post_${postId}`).trigger('mouseover', {force: true});
cy.get(`#CENTER_flagIcon_${postId}`).
should('have.class', 'post-menu__item').
and('have.attr', 'aria-label', 'remove from saved');
// # Open the post-dotmenu
cy.clickPostDotMenu(postId, 'CENTER');
// * Check that the dotmenu item is changed accordingly
cy.findAllByTestId(`post-menu-${postId}`).eq(0).should('be.visible');
cy.findByText('Remove from Saved').scrollIntoView().should('be.visible');
cy.get(`#CENTER_dropdown_${postId}`).should('be.visible').type('{esc}');
// * Check that the post is highlighted
cy.get(`#post_${postId}`).should('have.class', 'post--pinned-or-flagged');
// * Check that the post pre-header is visible
cy.get('div.post-pre-header').should('be.visible');
// * Check that the post pre-header has the saved icon
cy.get('span.icon--post-pre-header').
should('be.visible').
within(() => {
cy.get('svg').should('have.attr', 'aria-label', 'Saved Icon');
});
// * Check that the post pre-header has the saved post link
cy.get('div.post-pre-header__text-container').
should('be.visible').
and('have.text', 'Saved').
within(() => {
cy.get('a').as('savedLink').should('be.visible');
});
// * Check that the saved posts list is not open in RHS before clicking the link in the post pre-header
cy.get('#searchContainer').should('not.exist');
// # Click the link
cy.get('@savedLink').click();
// * Check that the saved posts list is open in RHS
cy.get('#searchContainer').should('be.visible').within(() => {
cy.get('.sidebar--right__title').
should('be.visible').
and('have.text', 'Saved Posts');
// * Check that the post pre-header is not shown for the saved message in RHS
cy.get('#search-items-container').within(() => {
cy.get(`#rhsPostMessageText_${postId}`).contains(message);
cy.get('div.post-pre-header').should('not.exist');
});
});
// # Close the RHS
cy.get('#searchResultsCloseButton').should('be.visible').click();
}
export function verifyUnsavedPost(postId) {
// * Check that the center save icon has been updated correctly
cy.get(`#post_${postId}`).trigger('mouseover', {force: true});
cy.get(`#CENTER_flagIcon_${postId}`).
should('have.class', 'post-menu__item').
and('have.attr', 'aria-label', 'save');
// # Open the post-dotmenu
cy.clickPostDotMenu(postId, 'CENTER');
// * Check that the dotmenu item is changed accordingly
cy.findAllByTestId(`post-menu-${postId}`).eq(0).should('be.visible');
cy.findByText('Save').scrollIntoView().should('be.visible');
cy.get(`#CENTER_dropdown_${postId}`).should('be.visible').type('{esc}');
// * Check that the post is highlighted
cy.get(`#post_${postId}`).should('not.have.class', 'post--pinned-or-flagged');
// * Check that the post pre-header is visible
cy.get('div.post-pre-header').should('not.exist');
// * Check that the post pre-header has the saved icon
cy.get('span.icon--post-pre-header').
should('not.exist');
// * Check that the post pre-header has the saved post link
cy.get('div.post-pre-header__text-container').
should('not.exist');
// * Check that the saved posts list is not open in RHS before clicking the link in the post pre-header
cy.get('#searchContainer').should('not.exist');
// # Click the link
cy.uiGetSavedPostButton().click();
// * Check that the saved posts list is open in RHS
cy.get('#searchContainer').should('be.visible').within(() => {
cy.get('.sidebar--right__title').
should('be.visible').
and('have.text', 'Saved Posts');
// * Check that the post pre-header is not shown for the saved message in RHS
cy.get('#search-items-container').within(() => {
cy.get(`#rhsPostMessageText_${postId}`).should('not.exist');
});
});
// # Close the RHS
cy.get('#searchResultsCloseButton').should('be.visible').click();
}
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Get post profile image of a given post ID or the last post if post ID is not given
*
* @param {string} - postId (optional)
*
* @example
* cy.uiGetPostProfileImage();
*/
uiGetPostProfileImage: typeof uiGetPostProfileImage;
/**
* Get post header of a given post ID or the last post if post ID is not given
*
* @param {string} - postId (optional)
*
* @example
* cy.uiGetPostHeader();
*/
uiGetPostHeader: typeof uiGetPostHeader;
/**
* Get post body of a given post ID or the last post if post ID is not given
*
* @param {string} - postId (optional)
*
* @example
* cy.uiGetPostBody();
*/
uiGetPostBody: typeof uiGetPostBody;
/**
* Get post thread footer of a given post ID or the last post if post ID is not given
*
* @param {string} - postId (optional)
*
* @example
* cy.uiGetPostThreadFooter();
*/
uiGetPostThreadFooter: typeof uiGetPostThreadFooter;
/**
* Get post embed container of a given post ID or the last post if post ID is not given
*
* @param {string} - postId (optional)
*
* @example
* cy.uiGetPostEmbedContainer();
*/
uiGetPostEmbedContainer: typeof uiGetPostEmbedContainer;
/**
* Get post textbox
*
* @param {bool} option.exist - Set to false to check whether element should not exist. Otherwise, true (default) to check visibility.
*
* @example
* cy.uiGetPostTextBox();
*/
uiGetPostTextBox: typeof uiGetPostTextBox;
/**
* Get reply textbox
*
* @param {bool} option.exist - Set to false to check whether element should not exist. Otherwise, true (default) to check visibility.
*
* @example
* cy.uiGetReplyTextBox();
*/
uiGetReplyTextBox: typeof uiGetReplyTextBox;
getPost: typeof getPost;
}
}
}

39
e2e-tests/cypress/tests/support/ui/post_dropdown_menu.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiClickCopyLink`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Click on "Copy Link" of post dropdown menu and verifies if the link is copied into the clipboard
* Created user has an option to log in after all are setup.
* @param {string} permalink - permalink to verify if copied into the clipboard
*
* @example
* const permalink = 'http://localhost:8065/team-name/pl/post-id';
* cy.uiClickCopyLink(permalink);
*/
uiClickCopyLink(permalink: string, postId: string): Chainable;
/**
* Click dropdown menu of a post by post ID.
* @param {String} postId - post ID
* @param {String} menuItem - e.g. "Pin to channel"
* @param {String} location - 'CENTER' (default), 'SEARCH', RHS_ROOT, RHS_COMMENT
*/
uiClickPostDropdownMenu(postId: string, menuItem: string, location?: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {stubClipboard} from '../../utils';
Cypress.Commands.add('uiClickCopyLink', (permalink, postId) => {
stubClipboard().as('clipboard');
// * Verify initial state
cy.get('@clipboard').its('contents').should('eq', '');
// # Click on "Copy Link"
cy.get(`#CENTER_dropdown_${postId}`).should('be.visible').within(() => {
cy.findByText('Copy Link').scrollIntoView().should('be.visible').click();
// * Verify if it's called with correct link value
cy.get('@clipboard').its('wasCalled').should('eq', true);
cy.get('@clipboard').its('contents').should('eq', permalink);
});
});
Cypress.Commands.add('uiClickPostDropdownMenu', (postId, menuItem, location = 'CENTER') => {
cy.clickPostDotMenu(postId, location);
cy.findAllByTestId(`post-menu-${postId}`).eq(0).should('be.visible');
cy.findByText(menuItem).scrollIntoView().should('be.visible').click({force: true});
});
Cypress.Commands.add('uiPostDropdownMenuShortcut', (postId, menuText, shortcutKey, location = 'CENTER') => {
cy.clickPostDotMenu(postId, location);
cy.findByText(menuText).scrollIntoView().should('be.visible');
cy.get('body').type(shortcutKey);
});

Просмотреть файл

@@ -0,0 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiSearchPosts', (searchTerm) => {
// # Enter the search terms and hit enter to start the search
cy.get('#searchBox').clear().type(searchTerm).type('{enter}');
// * Wait for the RHS to open and the search results to appear
cy.contains('.sidebar--right__header', 'Search Results').should('be.visible');
cy.get('#searchContainer .LoadingSpinner').should('not.exist');
});
Cypress.Commands.add('uiJumpToSearchResult', (postId) => {
// # Find the post in the search results and click Jump
cy.get(`#searchResult_${postId}`).contains('a', 'Jump').click();
// * Verify the URL changes to the permalink URL
cy.url().should((url) => url.endsWith(`/${postId}`));
// * Verify that the permalinked post is highlighted in the center channel
cy.get(`#post_${postId}.post--highlight`).should('be.visible');
});

Просмотреть файл

@@ -0,0 +1,280 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChainableT} from '../../types';
Cypress.Commands.add('uiGetLHS', () => {
return cy.get('#SidebarContainer').should('be.visible');
});
Cypress.Commands.add('uiGetLHSHeader', () => {
return cy.uiGetLHS().
find('.SidebarHeaderMenuWrapper').
should('be.visible');
});
Cypress.Commands.add('uiOpenTeamMenu', (item = '') => {
// # Click on LHS header
cy.uiGetLHSHeader().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetLHSTeamMenu();
}
// # Click on a particular item
return cy.uiGetLHSTeamMenu().
findByText(item).
scrollIntoView().
should('be.visible').
click();
});
Cypress.Commands.add('uiGetLHSAddChannelButton', () => {
return cy.uiGetLHS().
findByRole('button', {name: 'Add Channel Dropdown'});
});
Cypress.Commands.add('uiGetLHSTeamMenu', () => {
return cy.uiGetLHS().find('#sidebarDropdownMenu');
});
function uiOpenSystemConsoleMenu(item = ''): ChainableT<JQuery> {
// # Click on LHS header button
cy.uiGetSystemConsoleButton().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetSystemConsoleMenu();
}
// # Click on a particular item
return cy.uiGetSystemConsoleMenu().
findByText(item).
scrollIntoView().
should('be.visible').
click();
}
Cypress.Commands.add('uiOpenSystemConsoleMenu', uiOpenSystemConsoleMenu);
function uiGetSystemConsoleButton(): ChainableT<JQuery> {
return cy.get('.admin-sidebar').
findByRole('button', {name: 'Menu Icon'});
}
Cypress.Commands.add('uiGetSystemConsoleButton', uiGetSystemConsoleButton);
function uiGetSystemConsoleMenu(): ChainableT<JQuery> {
return cy.get('.admin-sidebar').
find('.dropdown-menu').
should('be.visible');
}
Cypress.Commands.add('uiGetSystemConsoleMenu', uiGetSystemConsoleMenu);
Cypress.Commands.add('uiGetLhsSection', (section) => {
if (section === 'UNREADS') {
return cy.findByText(section).
parent().
parent().
parent();
}
return cy.findAllByRole('button', {name: section}).
first().
parent().
parent().
parent();
});
Cypress.Commands.add('uiBrowseOrCreateChannel', (item) => {
cy.findByRole('button', {name: 'Add Channel Dropdown'}).
should('be.visible').
click();
cy.get('.dropdown-menu').should('be.visible');
if (item) {
cy.findByRole('menuitem', {name: item});
}
});
Cypress.Commands.add('uiAddDirectMessage', () => {
return cy.findByRole('button', {name: 'Write a direct message'});
});
Cypress.Commands.add('uiGetFindChannels', () => {
return cy.get('#lhsNavigator').findByRole('button', {name: 'Find Channels'});
});
Cypress.Commands.add('uiOpenFindChannels', () => {
cy.uiGetFindChannels().click();
});
function uiGetSidebarThreadsButton(): ChainableT<JQuery> {
return cy.get('#sidebar-threads-button').should('be.visible');
}
Cypress.Commands.add('uiGetSidebarThreadsButton', uiGetSidebarThreadsButton);
function uiGetSidebarInsightsButton(): ChainableT<JQuery> {
return cy.get('#sidebar-insights-button').should('be.visible');
}
Cypress.Commands.add('uiGetSidebarInsightsButton', uiGetSidebarInsightsButton);
Cypress.Commands.add('uiGetChannelSidebarMenu', (channelName, isChannelId = false) => {
cy.uiGetLHS().within(() => {
if (isChannelId) {
cy.get(`#sidebarItem_${channelName}`).should('be.visible').find('button').should('exist').click({force: true});
} else {
cy.findByText(channelName).should('be.visible').parents('a').find('button').should('exist').click({force: true});
}
});
return cy.findByRole('menu', {name: 'Edit channel menu'}).should('be.visible');
});
Cypress.Commands.add('uiClickSidebarItem', (name) => {
cy.uiGetSidebarItem(name).click({force: true});
if (name === 'threads') {
cy.get('body').then((body) => {
if (body.find('#genericModalLabel').length > 0) {
cy.uiCloseModal('A new way to view and follow threads');
}
});
cy.findByRole('heading', {name: 'Followed threads'});
} else {
cy.findAllByTestId('postView').should('be.visible');
}
});
Cypress.Commands.add('uiGetSidebarItem', (channelName) => {
return cy.get(`#sidebarItem_${channelName}`);
});
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable {
/**
* Get LHS
*
* @example
* cy.uiGetLHS();
*/
uiGetLHS(): Chainable;
/**
* Get LHS header
*
* @example
* cy.uiGetLHSHeader().click();
*/
uiGetLHSHeader(): Chainable;
/**
* Open team menu
*
* @param {string} item - ex. 'Invite People', 'Team Settings', etc.
*
* @example
* cy.uiOpenTeamMenu();
*/
uiOpenTeamMenu(item?: string): Chainable;
/**
* Get LHS add channel button
*
* @example
* cy.uiGetLHSAddChannelButton().click();
*/
uiGetLHSAddChannelButton(): Chainable;
/**
* Get LHS team menu
*
* @example
* cy.uiGetLHSTeamMenu().should('not.exist);
*/
uiGetLHSTeamMenu(): Chainable;
/**
* Get LHS section
* @param {string} section - section such as UNREADS, CHANNELS, FAVORITES, DIRECT MESSAGES and other custom category
*
* @example
* cy.uiGetLhsSection('CHANNELS');
*/
uiGetLhsSection(section: string): Chainable;
/**
* Open menu to browse or create channel
* @param {string} item - dropdown menu. If set, it will do click action.
*
* @example
* cy.uiBrowseOrCreateChannel('Browse Channels');
*/
uiBrowseOrCreateChannel(item: string): Chainable;
/**
* Get "+" button to write a direct message
* @example
* cy.uiAddDirectMessage();
*/
uiAddDirectMessage(): Chainable;
/**
* Get find channels button
* @example
* cy.uiGetFindChannels();
*/
uiGetFindChannels(): Chainable;
/**
* Open find channels
* @example
* cy.uiOpenFindChannels();
*/
uiOpenFindChannels(): Chainable;
/**
* Open menu of a channel in the sidebar
* @param {string} channelName - name of channel, ex. 'town-square'
* @param {boolean} isChannelId - default false. If true, it will use channel id instead of channel name
* @example
* cy.uiGetChannelSidebarMenu('Town Square');
* cy.uiGetChannelSidebarMenu('user1212__user333', true);
*/
uiGetChannelSidebarMenu(channelName: string, isChannelId?: boolean): Chainable;
/**
* Click sidebar item by channel or thread name
* @param {string} name - channel name for channels, and threads for Global Threads
*
* @example
* cy.uiClickSidebarItem('town-square');
*/
uiClickSidebarItem(name: string): Chainable;
/**
* Get sidebar item by channel or thread name
* @param {string} name - channel name for channels, and threads for Global Threads
*
* @example
* cy.uiGetSidebarItem('town-square').find('.badge').should('be.visible');
*/
uiGetSidebarItem(name: string): Chainable;
uiOpenSystemConsoleMenu: typeof uiOpenSystemConsoleMenu;
uiGetSystemConsoleButton: typeof uiGetSystemConsoleButton;
uiGetSystemConsoleMenu: typeof uiGetSystemConsoleMenu;
uiGetSidebarThreadsButton: typeof uiGetSidebarThreadsButton;
uiGetSidebarInsightsButton: typeof uiGetSidebarInsightsButton;
}
}
}

108
e2e-tests/cypress/tests/support/ui/sidebar_right.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,108 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiGetRHS`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Get RHS container
*
* @param {bool} option.visible - Set to false to check whether RHS is not visible. Otherwise, true (default) to check visibility.
*
* @example
* cy.uiGetRHS();
*/
uiGetRHS(option?: Record<string, boolean>): Chainable;
/**
* Close RHS
*
* @example
* cy.uiCloseRHS();
*/
uiCloseRHS(): Chainable;
/**
* Expand RHS
*
* @example
* cy.uiExpandRHS();
*/
uiExpandRHS(): Chainable;
/**
* Verify if RHS is expanded
*
* @example
* cy.uiGetRHS().isExpanded();
*/
isExpanded(): Chainable;
/**
* Get "Reply" button
*
* @example
* cy.uiGetReply();
*/
uiGetReply(): Chainable;
/**
* Reply by clicking "Reply" button
*
* @example
* cy.uiReply();
*/
uiReply(): Chainable;
/**
* Get RHS container
*
* @param {bool} option.visible - Set to false to check whether Search container at RHS is not visible. Otherwise, true (default) to check visibility.
*
* @example
* cy.uiGetRHSSearchContainer();
*/
uiGetRHSSearchContainer(option: Record<string, boolean>): Chainable;
/**
* Get file filter button from RHS.
*
* @example
* cy.uiGetFileFilterButton().click();
*/
uiGetFileFilterButton(): Chainable;
/**
* Get file filter menu from RHS
*
* @param {bool} option.exist - Set to false to check whether file filter menu should not exist at RHS. Otherwise, true (default) to check visibility.
*
* @example
* cy.uiGetFileFilterMenu();
*/
uiGetFileFilterMenu(): Chainable;
/**
* Open file filter menu from RHS
* @param {string} item - such as `'Documents'`, `'Spreadsheets'`, `'Presentations'`, `'Code'`, `'Images'`, `'Audio'` and `'Videos'`.
* @return the file filter menu
*
* @example
* cy.uiOpenFileFilterMenu();
*/
uiOpenFileFilterMenu(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,74 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiGetRHS', (options = {visible: true}) => {
if (options.visible) {
return cy.get('#sidebar-right').should('be.visible');
}
return cy.get('#sidebar-right').should('not.be.exist');
});
Cypress.Commands.add('uiCloseRHS', () => {
cy.findByLabelText('Close Sidebar Icon').click();
});
Cypress.Commands.add('uiExpandRHS', () => {
cy.findByLabelText('Expand').click();
});
Cypress.Commands.add('isExpanded', {prevSubject: true}, (subject) => {
return cy.get(subject).should('have.class', 'sidebar--right--expanded');
});
Cypress.Commands.add('uiGetReply', () => {
return cy.get('#sidebar-right').findByTestId('SendMessageButton');
});
Cypress.Commands.add('uiReply', () => {
cy.uiGetReply().click();
});
// Sidebar search container
Cypress.Commands.add('uiGetRHSSearchContainer', (options = {visible: true}) => {
if (options.visible) {
return cy.get('#searchContainer').should('be.visible');
}
return cy.get('#searchContainer').should('not.exist');
});
// Sidebar files search
Cypress.Commands.add('uiGetFileFilterButton', () => {
return cy.get('.FilesFilterMenu').should('be.visible');
});
Cypress.Commands.add('uiGetFileFilterMenu', (option = {exist: true}) => {
if (option.exist) {
return cy.get('.FilesFilterMenu').
find('.dropdown-menu').
should('be.visible');
}
return cy.get('.FilesFilterMenu').
find('.dropdown-menu').
should('not.exist');
});
Cypress.Commands.add('uiOpenFileFilterMenu', (item = '') => {
// # Click on file filter button
cy.uiGetFileFilterButton().click();
if (!item) {
// # Return the menu if no item is passed
return cy.uiGetFileFilterMenu();
}
// # Click on a particular item
return cy.uiGetFileFilterMenu().
findByText(item).
should('be.visible').
click();
});

41
e2e-tests/cypress/tests/support/ui/suggestion_list.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiCheckLicenseExists`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Verify user's at-mention in the suggestion list
* @param {UserProfile} user - user object
* @param {boolean} isSelected - check if user is selected with false as default
* @param {string} sectionDividerName - name of the section in suggestion list, ex. "Channel Members"
*
* @example
* cy.uiVerifyAtMentionInSuggestionList(user, true, 'Channel Members');
*/
uiVerifyAtMentionInSuggestionList(user: UserProfile, isSelected: boolean, sectionDividerName?: string): Chainable;
/**
* Verify user's at-mention suggestion
* @param {UserProfile} user - user object
* @param {boolean} isSelected - check if user is selected with false as default
*
* @example
* cy.uiVerifyAtMentionSuggestion(user, true);
*/
uiVerifyAtMentionSuggestion(user: UserProfile, isSelected?: boolean): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiVerifyAtMentionInSuggestionList', (user, isSelected = false, sectionDividerName = null) => {
// * Verify that the suggestion list is open and visible
return cy.get('#suggestionList').should('be.visible').within(() => {
if (sectionDividerName) {
// * Verify the section name is as expected
cy.get('.suggestion-list__divider').findByText(sectionDividerName).should('be.visible');
cy.get('.suggestion-list__divider').next().findByTestId(`mentionSuggestion_${user.username}`).should('be.visible');
}
// * Verify that the user is selected
return cy.uiVerifyAtMentionSuggestion(user, isSelected);
});
});
Cypress.Commands.add('uiVerifyAtMentionSuggestion', (user, isSelected = false) => {
const {
username,
first_name: firstName,
last_name: lastName,
nickname,
} = user;
// * Verify that the user is selected
cy.findByTestId(`mentionSuggestion_${username}`).as('selectedMentionSuggestion').should('be.visible');
if (isSelected) {
cy.get('@selectedMentionSuggestion').should('have.class', 'suggestion--selected');
}
cy.get('@selectedMentionSuggestion').findByText(`@${username}`).should('be.visible');
cy.get('@selectedMentionSuggestion').findByText(`${firstName} ${lastName} (${nickname})`).should('be.visible');
return cy.findByTestId(`mentionSuggestion_${username}`);
});

44
e2e-tests/cypress/tests/support/ui/system.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiCheckLicenseExists`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Verify license exists via admin console.
*
* @example
* cy.uiCheckLicenseExists();
*/
uiCheckLicenseExists(): Chainable;
/**
* Reset system scheme permissions via System Console
*
* @example
* cy.uiResetPermissionsToDefault();
*/
uiResetPermissionsToDefault(): Chainable;
/**
* Save settings located in System Console
*
* @example
* cy.uiSaveConfig();
*/
uiSaveConfig(): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as TIMEOUTS from '../../fixtures/timeouts';
Cypress.Commands.add('uiCheckLicenseExists', () => {
// # Go to system admin then verify admin console URL, header, and content
cy.visit('/admin_console/about/license');
cy.url().should('include', '/admin_console/about/license');
cy.get('.admin-console', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').within(() => {
cy.get('.admin-console__header').should('be.visible').and('have.text', 'Edition and License');
cy.get('.admin-console__content').should('be.visible').and('not.contain', 'undefined').and('not.contain', 'Invalid');
cy.get('#remove-button').should('be.visible');
});
});
Cypress.Commands.add('uiResetPermissionsToDefault', () => {
// # Navigate to system scheme page
cy.visit('/admin_console/user_management/permissions/system_scheme');
// # Click reset to defaults and confirm
cy.findByTestId('resetPermissionsToDefault', {timeout: TIMEOUTS.HALF_MIN}).click();
cy.get('#confirmModalButton').click();
cy.uiSaveConfig();
});
Cypress.Commands.add('uiSaveConfig', ({confirm = true} = {}) => {
// # Save settings
cy.get('#saveSetting').should('be.enabled').click();
cy.wait(TIMEOUTS.HALF_SEC);
if (confirm) {
// # Wait until the UI shows the saving is done and revert the text to "Save"
cy.waitUntil(() => cy.get('#saveSetting').then((el) => {
return el[0].innerText === 'Save';
}));
} else {
cy.wait(TIMEOUTS.HALF_SEC);
}
});

26
e2e-tests/cypress/tests/support/ui/team.js Обычный файл
Просмотреть файл

@@ -0,0 +1,26 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiInviteMemberToCurrentTeam', (username) => {
// # Open member invite screen
cy.uiOpenTeamMenu('Invite People');
// # Open members section if licensed for guest accounts
cy.findByTestId('invitationModal').
then((container) => container.find('[data-testid="inviteMembersLink"]')).
then((link) => link && link.click());
// # Enter bot username and submit
cy.get('.users-emails-input__control input').typeWithForce(username).as('input');
cy.get('.users-emails-input__option ').contains(`@${username}`);
cy.get('@input').typeWithForce('{enter}');
cy.get('#inviteMembersButton').click();
// * Verify user invited to team
cy.get('.invitation-modal-confirm--sent .InviteResultRow').
should('contain.text', `@${username}`).
and('contain.text', 'This member has been added to the team.');
// # Close, return
cy.findByTestId('confirm-done').click();
});

30
e2e-tests/cypress/tests/support/ui/tooltip.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,30 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
// ***************************************************************
// Each command should be properly documented using JSDoc.
// See https://jsdoc.app/index.html for reference.
// Basic requirements for documentation are the following:
// - Meaningful description
// - Each parameter with `@params`
// - Return value with `@returns`
// - Example usage with `@example`
// Custom command should follow naming convention of having `ui` prefix, e.g. `uiGetToolTip`.
// ***************************************************************
declare namespace Cypress {
interface Chainable {
/**
* Get tooltip
*
* @param {string} text of the tooltip
*
* @example
* cy.uiGetToolTip('text');
*/
uiGetToolTip(text: string): Chainable;
}
}

Просмотреть файл

@@ -0,0 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
Cypress.Commands.add('uiGetToolTip', (text) => {
cy.findByRole('tooltip').should('contain', text);
});