snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility @mfa
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {isMac} from '@/utils';
|
||||
|
||||
describe('Verify Accessibility Support in different sections in Settings and Profile Dialog', () => {
|
||||
const accountSettings = {
|
||||
profile: [
|
||||
{key: 'name', label: 'Full Name', type: 'text'},
|
||||
{key: 'username', label: 'Username', type: 'text'},
|
||||
{key: 'nickname', label: 'Nickname', type: 'text'},
|
||||
{key: 'position', label: 'Position', type: 'text'},
|
||||
{key: 'email', label: 'Email', type: 'text'},
|
||||
{key: 'picture', label: 'Profile Picture', type: 'image'},
|
||||
],
|
||||
security: [
|
||||
{key: 'password', label: 'Password', type: 'text'},
|
||||
{key: 'mfa', label: 'Multi-factor Authentication', type: 'optional'},
|
||||
],
|
||||
};
|
||||
|
||||
const settings = {
|
||||
notifications: [
|
||||
{key: 'desktopAndMobile', label: 'Desktop and mobile notifications', type: 'radio'},
|
||||
{key: 'desktopNotificationSound', label: 'Desktop notification sounds', type: 'radio'},
|
||||
{key: 'email', label: 'Email notifications', type: 'radio'},
|
||||
{key: 'channelMentionAutoFollow', label: 'Auto-follow threads on channel-wide mentions', type: 'radio'},
|
||||
{key: 'keywordsAndMentions', label: 'Keywords that trigger notifications', type: 'checkbox'},
|
||||
{key: 'keywordsAndHighlight', label: 'Keywords that get highlighted (without notifications)', type: 'checkbox'},
|
||||
{key: 'replyNotifications', label: 'Reply notifications', type: 'radio'},
|
||||
],
|
||||
display: [
|
||||
{key: 'theme', label: 'Theme', type: 'radio'},
|
||||
{key: 'clock', label: 'Clock Display', type: 'radio'},
|
||||
{key: 'name_format', label: 'Teammate Name Display', type: 'none'},
|
||||
{key: 'availabilityStatus', label: 'Show online availability on profile images', type: 'radio'},
|
||||
{key: 'lastactive', label: 'Share last active time', type: 'radio'},
|
||||
{key: 'timezone', label: 'Timezone', type: 'none'},
|
||||
{key: 'collapse', label: 'Default Appearance of Image Previews', type: 'radio'},
|
||||
{key: 'message_display', label: 'Message Display', type: 'radio'},
|
||||
{key: 'click_to_reply', label: 'Click to open threads', type: 'radio'},
|
||||
{key: 'channel_display_mode', label: 'Channel Display', type: 'radio'},
|
||||
{key: 'one_click_reactions_enabled', label: 'Quick reactions on messages', type: 'radio'},
|
||||
{key: 'renderEmoticonsAsEmoji', label: 'Render emoticons as emojis', type: 'radio'},
|
||||
{key: 'languages', label: 'Language', type: 'dropdown'},
|
||||
],
|
||||
sidebar: [
|
||||
{key: 'showUnreadsCategory', label: 'Group unread channels separately', type: 'multiple'},
|
||||
{key: 'limitVisibleGMsDMs', label: 'Number of direct messages to show', type: 'radio'},
|
||||
],
|
||||
advanced: [
|
||||
{key: 'advancedCtrlSend', label: `Send Messages on ${isMac() ? '⌘+ENTER' : 'CTRL+ENTER'}`, type: 'radio'},
|
||||
{key: 'wysiwygEditor', label: 'Rich text editing (Beta)', type: 'radio', optional: true},
|
||||
{key: 'formatting', label: 'Enable Post Formatting', type: 'radio'},
|
||||
{key: 'joinLeave', label: 'Enable Join/Leave Messages', type: 'radio'},
|
||||
],
|
||||
};
|
||||
let url;
|
||||
before(() => {
|
||||
// # Update Configs
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableMultifactorAuthentication: true,
|
||||
},
|
||||
SamlSettings: {
|
||||
Enable: false,
|
||||
},
|
||||
});
|
||||
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
url = offTopicUrl;
|
||||
cy.visit(offTopicUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// # Close the modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T1465_1 Verify Label & Tab behavior in section links', () => {
|
||||
// * Verify aria-label and tab support in section of Account settings modal
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
cy.findByRole('tab', {name: 'profile settings'}).should('be.visible').focus().should('be.focused');
|
||||
['profile settings', 'security'].forEach((text) => {
|
||||
// * Verify aria-label on each tab and it supports navigating to the next tab with arrow keys
|
||||
cy.focused().should('have.attr', 'aria-label', text).type('{downarrow}');
|
||||
});
|
||||
cy.uiClose();
|
||||
|
||||
// * Verify aria-label and tab support in section of Settings modal
|
||||
cy.uiOpenSettingsModal();
|
||||
cy.findByRole('tab', {name: 'notifications'}).should('be.visible').focus().should('be.focused');
|
||||
['notifications', 'display', 'sidebar', 'advanced'].forEach((text) => {
|
||||
// * Verify aria-label on each tab and it supports navigating to the next tab with arrow keys
|
||||
cy.focused().should('have.attr', 'aria-label', text).type('{downarrow}');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1465_2 Verify Accessibility Support in each section in Settings and Profile Dialog', () => {
|
||||
cy.visit(url);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Open profile settings modal
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// * Verify if the focus goes to the individual fields in Profile section
|
||||
cy.findByRole('tab', {name: 'profile settings'}).click().tab();
|
||||
verifySettings(accountSettings.profile);
|
||||
|
||||
// * Verify if the focus goes to the individual fields in Security section
|
||||
cy.findByRole('tab', {name: 'security'}).click().tab();
|
||||
verifySettings(accountSettings.security);
|
||||
cy.uiClose();
|
||||
|
||||
// # Open settings modal
|
||||
cy.uiOpenSettingsModal();
|
||||
|
||||
// * Verify if the focus goes to the individual fields in Notifications section
|
||||
cy.findByRole('tab', {name: 'notifications'}).click().tab();
|
||||
cy.focused().should('have.text', 'Learn more about notifications').tab();
|
||||
verifySettings(settings.notifications);
|
||||
|
||||
// // * Verify if the focus goes to the individual fields in Display section
|
||||
cy.findByRole('tab', {name: 'display'}).click().tab();
|
||||
verifySettings(settings.display);
|
||||
|
||||
// // * Verify if the focus goes to the individual fields in Sidebar section
|
||||
cy.findByRole('tab', {name: 'sidebar'}).click().tab();
|
||||
verifySettings(settings.sidebar);
|
||||
|
||||
// // * Verify if the focus goes to the individual fields in Advanced section
|
||||
cy.findByRole('tab', {name: 'advanced'}).click().tab();
|
||||
verifySettings(settings.advanced);
|
||||
});
|
||||
|
||||
it('MM-T1482 Input fields in Settings and Profile should read labels', () => {
|
||||
cy.visit(url);
|
||||
cy.postMessage('hello');
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
accountSettings.profile.forEach((section) => {
|
||||
if (section.type === 'text') {
|
||||
cy.get(`#${section.key}Edit`).click();
|
||||
cy.get('.setting-list-item .form-group').each(($el) => {
|
||||
if ($el.find('input').length) {
|
||||
cy.wrap($el).find('.control-label').invoke('text').then((label) => {
|
||||
cy.wrap($el).find('input').should('have.attr', 'aria-label', label);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1485 Language dropdown should read labels', () => {
|
||||
cy.visit(url);
|
||||
cy.postMessage('hello');
|
||||
cy.uiOpenSettingsModal();
|
||||
|
||||
cy.get('#displayButton').click();
|
||||
cy.get('#languagesEdit').click();
|
||||
cy.findByRole('combobox', {name: 'Dropdown selector to change the interface language'}).should('have.attr', 'aria-autocomplete', 'list').and('have.attr', 'aria-labelledby', 'changeInterfaceLanguageLabel').as('inputEl');
|
||||
cy.get('#changeInterfaceLanguageLabel').should('be.visible').and('have.text', 'Change interface language');
|
||||
|
||||
// # When space key is pressed on dropdown, it should expand and should collapse when esc key is pressed
|
||||
cy.get('@inputEl').typeWithForce(' ');
|
||||
cy.findByRole('listbox').should('have.class', 'react-select__menu-list').as('listBox');
|
||||
cy.get('@inputEl').typeWithForce('{esc}');
|
||||
cy.get('@listBox').should('not.exist');
|
||||
|
||||
// # Press down arrow twice and check aria label
|
||||
cy.get('@inputEl').typeWithForce(' ');
|
||||
cy.get('@inputEl').typeWithForce('{downarrow}{downarrow}');
|
||||
cy.get('#displayLanguage').within(($el) => {
|
||||
cy.wrap($el).findByRole('log').should('have.attr', 'aria-live', 'assertive').as('ariaEl');
|
||||
});
|
||||
cy.get('@ariaEl').within(($el) => {
|
||||
cy.wrap($el).get('#aria-focused').should('contain', 'option English (Australia) focused');
|
||||
cy.wrap($el).get('#aria-guidance').should('contain', 'Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.');
|
||||
});
|
||||
|
||||
// # Check if language setting gets changed after user presses space
|
||||
cy.get('@inputEl').typeWithForce(' ');
|
||||
cy.get('#displayLanguage').should('contain', 'English (Australia)');
|
||||
cy.get('@ariaEl').within(($el) => {
|
||||
cy.wrap($el).get('#aria-selection').should('contain', 'option English (Australia) selected');
|
||||
});
|
||||
|
||||
// # Press down arrow, then up arrow and press space
|
||||
cy.get('@inputEl').typeWithForce('{downarrow}{downarrow}{downarrow}{uparrow}');
|
||||
cy.get('@ariaEl').within(($el) => {
|
||||
cy.wrap($el).get('#aria-focused').should('contain', 'option English (US) focused');
|
||||
});
|
||||
cy.get('@inputEl').typeWithForce(' ');
|
||||
cy.get('#displayLanguage').should('contain', 'English (US)');
|
||||
cy.get('@ariaEl').within(($el) => {
|
||||
cy.wrap($el).get('#aria-selection').should('contain', 'option English (US) selected');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1488 Profile Picture should read labels', () => {
|
||||
cy.visit(url);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Go to Edit Profile picture
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
cy.get('#pictureEdit').click();
|
||||
|
||||
// * Verify image alt in profile image
|
||||
cy.get('.profile-img').should('have.attr', 'alt', 'profile image');
|
||||
|
||||
cy.get('#profileSettings').then((el) => {
|
||||
if (el.find('.profile-img__remove').length > 0) {
|
||||
cy.findByTestId('removeSettingPicture').click();
|
||||
cy.uiSave();
|
||||
cy.get('#pictureEdit').click();
|
||||
}
|
||||
});
|
||||
|
||||
// * Check Labels in different buttons
|
||||
cy.findByTestId('inputSettingPictureButton').should('have.attr', 'aria-label', 'Select');
|
||||
cy.uiSaveButton().should('have.attr', 'disabled');
|
||||
cy.findByTestId('cancelSettingPicture').should('have.attr', 'aria-label', 'Cancel');
|
||||
|
||||
// # Upload a pic and save
|
||||
cy.findByTestId('uploadPicture').attachFile('mattermost-icon.png');
|
||||
cy.uiSave();
|
||||
|
||||
// # Click on Edit Profile Picture
|
||||
cy.get('#pictureEdit').click();
|
||||
|
||||
// * Verify image alt in profile image
|
||||
cy.get('.profile-img').should('have.attr', 'alt', 'profile image');
|
||||
|
||||
// # Option to Remove Profile picture should be present
|
||||
cy.findByTestId('removeSettingPicture').within(() => {
|
||||
cy.get('.sr-only').should('have.text', 'Remove Profile Picture');
|
||||
});
|
||||
|
||||
// # Check tab behavior
|
||||
cy.findByTestId('removeSettingPicture').focus().tab({shift: true}).tab();
|
||||
cy.findByTestId('removeSettingPicture').should('have.class', 'a11y--active a11y--focused').tab();
|
||||
cy.findByTestId('inputSettingPictureButton').should('have.class', 'a11y--active a11y--focused').tab();
|
||||
cy.findByTestId('cancelSettingPicture').should('have.class', 'a11y--active a11y--focused');
|
||||
|
||||
// # Remove the profile picture and check the tab behavior
|
||||
cy.findByTestId('removeSettingPicture').click().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.findByTestId('inputSettingPictureButton').focus().tab({shift: true}).tab();
|
||||
cy.findByTestId('inputSettingPictureButton').should('have.class', 'a11y--active a11y--focused').tab();
|
||||
cy.uiSaveButton().should('be.focused').tab();
|
||||
cy.findByTestId('cancelSettingPicture').should('have.class', 'a11y--active a11y--focused');
|
||||
cy.uiSave();
|
||||
});
|
||||
|
||||
it('MM-T1496 Security Settings screen should read labels', () => {
|
||||
cy.visit(url);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Go to Security Settings
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
cy.get('#securityButton').click();
|
||||
|
||||
// * Check Tab behavior in MFA section
|
||||
cy.get('#mfaEdit').click();
|
||||
cy.get('#passwordEdit').focus().tab({shift: true}).tab().tab();
|
||||
cy.get('.setting-list button.btn').should('have.class', 'a11y--active a11y--focused').tab();
|
||||
cy.get('#cancelSetting').should('have.class', 'a11y--active a11y--focused');
|
||||
|
||||
// * Check Tab behavior in Sign-In Method if its available
|
||||
cy.get('.user-settings').then((el) => {
|
||||
if (el.find('#signinEdit').length) {
|
||||
cy.get('#signinEdit').click();
|
||||
cy.get('#mfaEdit').focus().tab({shift: true}).tab().tab();
|
||||
cy.get('.setting-list a.btn').should('have.class', 'a11y--active a11y--focused').tab();
|
||||
cy.get('#cancelSetting').should('have.class', 'a11y--active a11y--focused');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifySettings(settings) {
|
||||
settings.forEach((setting) => {
|
||||
if (setting.optional) {
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.find(`#${setting.key}Edit`).length === 0) {
|
||||
return;
|
||||
}
|
||||
cy.focused().should('have.id', `${setting.key}Edit`);
|
||||
cy.findByText(setting.label);
|
||||
cy.focused().tab();
|
||||
});
|
||||
return;
|
||||
}
|
||||
cy.focused().should('have.id', `${setting.key}Edit`);
|
||||
cy.findByText(setting.label);
|
||||
cy.focused().tab();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility
|
||||
|
||||
describe('Verify Accessibility Support in different Buttons', () => {
|
||||
before(() => {
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Post a message
|
||||
cy.postMessage('hello');
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # On a message in center channel, click then pin the post to the channel
|
||||
cy.uiClickPostDropdownMenu(postId, 'Pin to Channel');
|
||||
|
||||
// # Click pin icon next to search box
|
||||
cy.uiGetChannelPinButton().click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1459 Accessibility Support in RHS expand and close icons', () => {
|
||||
cy.clickPostCommentIcon();
|
||||
cy.get('#rhsContainer').
|
||||
should('be.visible').
|
||||
within(() => {
|
||||
// * Verify accessibility support in Sidebar Expand and Shrink icon
|
||||
cy.get('button.sidebar--right__expand').
|
||||
should('have.attr', 'aria-label', 'Expand Sidebar Icon');
|
||||
|
||||
// * Verify accessibility support in Close icon
|
||||
cy.get('#rhsCloseButton').
|
||||
should('have.attr', 'aria-label', 'Close').
|
||||
within(() => {
|
||||
cy.get('.icon-close').should('have.attr', 'aria-label', 'Close Sidebar Icon');
|
||||
});
|
||||
|
||||
// # Close the sidebar
|
||||
cy.get('#rhsCloseButton').click();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1461 Accessibility Support in different buttons in Channel Header', () => {
|
||||
// # Ensure the focus is on the Toggle Favorites button
|
||||
cy.uiGetChannelFavoriteButton().
|
||||
focus().
|
||||
tab({shift: true}).
|
||||
tab();
|
||||
|
||||
// * Verify accessibility support in Favorites button
|
||||
cy.uiGetChannelFavoriteButton().
|
||||
should('be.focused').
|
||||
and('have.attr', 'aria-label', 'add to favorites').
|
||||
click();
|
||||
|
||||
// * Verify accessibility support if Channel is added to Favorites
|
||||
cy.uiGetChannelFavoriteButton().
|
||||
should('have.attr', 'aria-label', 'remove from favorites');
|
||||
|
||||
// # Ensure the focus is on the Toggle Favorites button
|
||||
cy.uiGetChannelFavoriteButton().
|
||||
focus().
|
||||
tab({shift: true}).
|
||||
tab();
|
||||
|
||||
// # Press tab until the focus is on the Pinned messages button
|
||||
cy.focused().tab().tab().tab();
|
||||
|
||||
// * Verify accessibility support in Pinned messages button
|
||||
cy.uiGetChannelPinButton().
|
||||
should('be.focused').
|
||||
and('have.attr', 'aria-label', 'Pinned messages').
|
||||
tab().tab().tab().tab();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @accessibility
|
||||
|
||||
describe('Verify Accessibility Support in Dropdown Menus', () => {
|
||||
let offTopicUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
cy.apiLogin(sysadmin);
|
||||
|
||||
cy.apiInitSetup().then(({offTopicUrl: url}) => {
|
||||
offTopicUrl = url;
|
||||
});
|
||||
|
||||
cy.apiCreateTeam('other-team', 'Other Team');
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Visit the Off Topic channel
|
||||
cy.visit(offTopicUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
|
||||
it('MM-T1464 Accessibility Support in Channel Menu Dropdown', () => {
|
||||
// # Press tab from the Channel Favorite button
|
||||
cy.uiGetChannelFavoriteButton().
|
||||
focus().
|
||||
tab({shift: true}).
|
||||
tab().
|
||||
tab({shift: true});
|
||||
|
||||
// * Verify the aria-label in channel menu button
|
||||
cy.uiGetChannelHeaderButton().
|
||||
findByLabelText('channel menu').
|
||||
should('be.focused').click();
|
||||
|
||||
// * Verify the accessibility support in the Channel Dropdown menu
|
||||
cy.uiGetChannelMenu().
|
||||
parent().
|
||||
should('have.attr', 'aria-label', 'channel menu').
|
||||
and('have.attr', 'role', 'menu');
|
||||
|
||||
// * Verify the first option is not selected by default
|
||||
cy.uiGetChannelMenu().children().eq(0).should('not.be.focused');
|
||||
|
||||
// # Press tab
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify the accessibility support in the Channel Dropdown menu items
|
||||
const menuItems = [
|
||||
'View Info',
|
||||
'Move to...',
|
||||
'Notification Preferences',
|
||||
'Mute Channel',
|
||||
'Add Members',
|
||||
'Manage Members',
|
||||
'Edit Channel Header',
|
||||
'Edit Channel Purpose',
|
||||
'Rename Channel',
|
||||
'Convert to Private Channel',
|
||||
'Archive Channel',
|
||||
'Leave Channel',
|
||||
];
|
||||
|
||||
menuItems.forEach((item) => {
|
||||
// * Verify that the menu item is focused
|
||||
cy.uiGetChannelMenu().findByText(item).parent().should('be.focused');
|
||||
|
||||
// # Press tab for next item
|
||||
cy.focused().tab();
|
||||
});
|
||||
|
||||
// * Verify if menu is closed when we press Escape
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
cy.uiGetChannelMenu({exist: false});
|
||||
});
|
||||
|
||||
it('MM-T1476 Accessibility Support in Team Menu Dropdown', () => {
|
||||
// # Open team menu
|
||||
cy.uiGetLHSHeader().click();
|
||||
|
||||
// * Verify the accessibility support in the Main Menu Dropdown
|
||||
cy.findByRole('menu').
|
||||
should('exist').
|
||||
and('have.attr', 'aria-label', 'team menu').
|
||||
and('have.class', 'a11y__popup');
|
||||
|
||||
// * Verify the first option is not selected by default
|
||||
cy.uiGetLHSTeamMenu().find('.MenuItem').
|
||||
children().eq(0).
|
||||
should('not.be.focused').
|
||||
focus();
|
||||
|
||||
// * Verify the accessibility support in the Main Menu Dropdown items
|
||||
const menuItems = [
|
||||
{id: 'invitePeople', label: 'Invite People dialog'},
|
||||
{id: 'teamSettings', label: 'Team Settings dialog'},
|
||||
{id: 'manageMembers', label: 'Manage Members dialog'},
|
||||
{id: 'joinTeam', text: 'Join another team'},
|
||||
{id: 'leaveTeam', label: 'Leave Team dialog'},
|
||||
{id: 'createTeam', text: 'Create a team'},
|
||||
];
|
||||
|
||||
menuItems.forEach((item) => {
|
||||
// * Verify that the menu item is focused
|
||||
if (item.label) {
|
||||
cy.focused().should('have.attr', 'aria-label', item.label);
|
||||
} else {
|
||||
cy.focused().should('have.text', item.text);
|
||||
}
|
||||
|
||||
// # Press tab for next item
|
||||
cy.focused().tab();
|
||||
});
|
||||
|
||||
cy.uiGetLHSTeamMenu().find('.MenuItem').each((el) => {
|
||||
cy.wrap(el).should('have.attr', 'role', 'menuitem');
|
||||
});
|
||||
|
||||
// * Verify if menu is closed when we press Escape
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
cy.uiGetLHSTeamMenu().should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1477 Accessibility Support in Status Dropdown - KNOWN ISSUE: MM-45716', () => {
|
||||
// # Focus to set status button
|
||||
cy.uiGetSetStatusButton().focus().tab({shift: true}).tab();
|
||||
|
||||
// * Verify the aria-label in status menu button
|
||||
cy.uiGetSetStatusButton().
|
||||
should('be.focused').
|
||||
click();
|
||||
|
||||
// * Verify the accessibility support in the Status Dropdown
|
||||
cy.uiGetStatusMenuContainer().
|
||||
should('have.attr', 'aria-label', 'set status').
|
||||
and('have.class', 'a11y__popup').
|
||||
and('have.attr', 'role', 'menu');
|
||||
|
||||
// * Verify the first option is not selected by default
|
||||
cy.uiGetStatusMenuContainer().find('.dropdown-menu').children().eq(0).should('not.be.focused');
|
||||
|
||||
// # Press tab
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify first focus is on menu header which is the profile image
|
||||
cy.uiGetStatusMenuContainer().within(() => {
|
||||
cy.findByAltText('user profile image').should('be.focused');
|
||||
});
|
||||
|
||||
// # Press tab
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify the accessibility support in the Status Dropdown menu items
|
||||
const menuItems = [
|
||||
{id: 'status-menu-custom-status', label: 'Set a Custom Status dialog'},
|
||||
{id: 'status-menu-online', label: 'online'},
|
||||
{id: 'status-menu-away', label: 'away'},
|
||||
{id: 'status-menu-dnd_menuitem', label: 'do not disturb. disables all notifications'},
|
||||
{id: 'status-menu-offline', label: 'offline'},
|
||||
{id: 'accountSettings', label: 'Profile dialog'},
|
||||
{id: 'logout', label: 'Log Out'},
|
||||
];
|
||||
|
||||
menuItems.forEach((item) => {
|
||||
// * Verify that the menu item is focused
|
||||
cy.uiGetStatusMenuContainer().find(`#${item.id}`).
|
||||
should('be.visible').
|
||||
findAllByLabelText(item.label).first().
|
||||
should('be.focused');
|
||||
|
||||
// # Press tab for next item
|
||||
cy.focused().tab();
|
||||
});
|
||||
|
||||
// * Verify if menu is closed when we press Escape
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
cy.uiGetStatusMenuContainer({exist: false});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility
|
||||
|
||||
// * Verify the accessibility support in the different images
|
||||
|
||||
describe('Verify Accessibility Support in Different Images', () => {
|
||||
let otherUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, offTopicUrl, user}) => {
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
return cy.apiAddUserToTeam(team.id, otherUser.id);
|
||||
}).then(() => {
|
||||
cy.apiLogin(user);
|
||||
|
||||
// Visit the Off Topic channel
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1508 Accessibility support in different images', () => {
|
||||
// * Verify image alt in profile image
|
||||
cy.get('#userAccountMenuButton').within(() => {
|
||||
cy.findByAltText('user profile image').should('be.visible');
|
||||
});
|
||||
|
||||
// # Upload an image in the post
|
||||
cy.get('#fileUploadInput').attachFile('small-image.png');
|
||||
cy.postMessage('Image upload');
|
||||
|
||||
// * Verify accessibility in images uploaded in a post
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#${postId}_message`).within(() => {
|
||||
cy.get('img').should('be.visible').should('have.attr', 'aria-label', 'file thumbnail small-image.png');
|
||||
});
|
||||
});
|
||||
|
||||
// # Post a message as a different user
|
||||
cy.getCurrentChannelId().then((channelId) => {
|
||||
const message = `hello from ${otherUser.username}: ${Date.now()}`;
|
||||
cy.postMessageAs({sender: otherUser, message, channelId});
|
||||
});
|
||||
|
||||
// # Open profile popover
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.get('.status-wrapper').click();
|
||||
});
|
||||
|
||||
// * Verify image alt in profile popover
|
||||
cy.get('.user-profile-popover').within(() => {
|
||||
cy.get('.Avatar').should('have.attr', 'alt', `${otherUser.username} profile image`);
|
||||
});
|
||||
});
|
||||
|
||||
// # Close the profile popover
|
||||
cy.get('body').click();
|
||||
|
||||
// # Open Settings > Display > Themes
|
||||
cy.uiOpenSettingsModal('Display').within(() => {
|
||||
cy.get('#displayButton').click();
|
||||
cy.get('#displaySettingsTitle').should('exist');
|
||||
cy.get('#themeTitle').scrollIntoView().should('be.visible');
|
||||
cy.get('#themeEdit').click();
|
||||
|
||||
// * Verify image alt in Theme Images
|
||||
cy.get('#displaySettings').within(() => {
|
||||
cy.get('.appearance-section>div').children().each(($el) => {
|
||||
cy.wrap($el).get('#denim-theme-icon').should('have.text', 'Denim theme icon');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @accessibility
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
function postMessages(testChannel, otherUser, count) {
|
||||
for (let index = 0; index < count; index++) {
|
||||
// # Post Message as Current user
|
||||
const message = `hello from current user: ${getRandomId()}`;
|
||||
cy.postMessage(message);
|
||||
const otherMessage = `hello from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message: otherMessage, channelId: testChannel.id});
|
||||
}
|
||||
}
|
||||
|
||||
describe('Verify Accessibility keyboard usability across different regions in the app', () => {
|
||||
const count = 5;
|
||||
let testUser;
|
||||
let otherUser;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testUser = user;
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, otherUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(testChannel.id, otherUser.id).then(() => {
|
||||
// # Login as test user, visit test channel and post few messages
|
||||
cy.apiLogin(user);
|
||||
cy.visit(`/${team.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1513_2 Verify Keyboard support in Search Results', () => {
|
||||
// # Post few messages
|
||||
postMessages(testChannel, otherUser, count);
|
||||
|
||||
// # Search for a term
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().typeWithForce('hello').typeWithForce('{enter}');
|
||||
|
||||
// # Change the focus to search results
|
||||
cy.get('#searchContainer').within(() => {
|
||||
cy.get('button.sidebar--right__expand').focus().tab({shift: true}).tab();
|
||||
|
||||
// # focus the messages and files tabs
|
||||
cy.focused().tab().tab();
|
||||
});
|
||||
|
||||
// # verify the tabs buttons files or messages are accesible via left and right keys
|
||||
cy.get('body').type('{rightarrow}').wait(100);
|
||||
cy.get('#filesTab').should('have.class', 'a11y--active a11y--focused');
|
||||
|
||||
cy.get('body').type('{leftarrow}').wait(100);
|
||||
cy.get('#messagesTab').should('have.class', 'a11y--active a11y--focused');
|
||||
|
||||
// # move focus to the team selector
|
||||
cy.focused().tab();
|
||||
|
||||
// * Ensures the focus item has test-id of searchTeamsSelectorMenuButton
|
||||
cy.get('#searchTeamsSelectorMenuButton').should('have.class', 'a11y--active a11y--focused');
|
||||
|
||||
// # move focus to search results items
|
||||
cy.focused().tab();
|
||||
|
||||
cy.get('body').type('{downarrow}{uparrow}');
|
||||
|
||||
// # Use down arrow keys and verify if results are highlighted sequentially
|
||||
for (let index = 0; index < count; index++) {
|
||||
cy.get('.files-or-messages-panel').children('.search-item__container').eq(index).then(($el) => {
|
||||
// * Verify search result is highlighted
|
||||
cy.get($el).find('.post').should('have.class', 'a11y--active a11y--focused');
|
||||
cy.get('body').type('{downarrow}');
|
||||
});
|
||||
}
|
||||
|
||||
// # Use up arrow keys and verify if results are highlighted sequentially
|
||||
for (let index = count; index > 0; index--) {
|
||||
cy.get('.files-or-messages-panel').children('.search-item__container').eq(index).then(($el) => {
|
||||
// * Verify search result is highlighted
|
||||
cy.get($el).find('.post').should('have.class', 'a11y--active a11y--focused');
|
||||
cy.get('body').type('{uparrow}');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('MM-T1513_1 Verify Keyboard support in RHS', () => {
|
||||
// # Post Message as Current user
|
||||
const message = `hello from ${testUser.username}: ${getRandomId()}`;
|
||||
cy.postMessage(message);
|
||||
|
||||
// # Post few replies on RHS
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
for (let index = 0; index < count; index++) {
|
||||
const replyMessage = `A reply ${getRandomId()}`;
|
||||
cy.postMessageReplyInRHS(replyMessage);
|
||||
const messageFromOther = `reply from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message: messageFromOther, channelId: testChannel.id, rootId: postId});
|
||||
}
|
||||
});
|
||||
|
||||
// * Verify that the highlight order is in the reverse direction in RHS
|
||||
cy.get('#rhsContainer .post-right__content').should('have.attr', 'data-a11y-order-reversed', 'true').and('have.attr', 'data-a11y-focus-child', 'true');
|
||||
|
||||
// # Change the focus to the last post
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.uiGetReplyTextBox().focus().tab({shift: true});
|
||||
});
|
||||
cy.get('body').type('{uparrow}{downarrow}');
|
||||
|
||||
// # Use up arrow keys and verify if results are highlighted sequentially
|
||||
const total = (count * 2) + 1; // # total number of expected posts in RHS
|
||||
let row = total - 1; // # the row index which should be focused
|
||||
|
||||
for (let index = count; index > 0; index--) {
|
||||
cy.get('#rhsContainer .post-right-comments-container .post').eq(row).then(($el) => {
|
||||
// * Verify search result is highlighted
|
||||
cy.get($el).should('have.class', 'a11y--active a11y--focused');
|
||||
cy.get('body').type('{uparrow}');
|
||||
});
|
||||
row--;
|
||||
}
|
||||
|
||||
// # Use down arrow keys and verify if posts are highlighted sequentially
|
||||
for (let index = count; index > 0; index--) {
|
||||
cy.get('#rhsContainer .post-right-comments-container .post').eq(row).then(($el) => {
|
||||
// * Verify search result is highlighted
|
||||
cy.get($el).should('have.class', 'a11y--active a11y--focused');
|
||||
cy.get('body').type('{downarrow}');
|
||||
});
|
||||
row++;
|
||||
}
|
||||
});
|
||||
|
||||
it('MM-T1499 Verify Screen reader should not switch to virtual cursor mode', () => {
|
||||
// # Open RHS
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
|
||||
// * Verify Screen reader should not switch to virtual cursor mode. This is handled by adding a role=application attribute
|
||||
const regions = ['#sidebar-left', '#rhsContainer .post-right__content', '#advancedTextEditorCell'];
|
||||
regions.forEach((region) => {
|
||||
cy.get(region).should('have.attr', 'role', 'application');
|
||||
});
|
||||
cy.get('.search__form').should('have.attr', 'role', 'search');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
function postMessages(testChannel, otherUser, count) {
|
||||
for (let index = 0; index < count; index++) {
|
||||
// # Post Message as Current user
|
||||
const message = `hello from current user: ${getRandomId()}`;
|
||||
cy.postMessage(message);
|
||||
const otherMessage = `hello from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message: otherMessage, channelId: testChannel.id});
|
||||
}
|
||||
}
|
||||
|
||||
function verifyNavSupport(element, label, tabOrder) {
|
||||
cy.get(element).
|
||||
should('have.attr', 'aria-label', label).
|
||||
and('have.attr', 'data-a11y-sort-order', tabOrder).
|
||||
and('have.class', 'a11y__region a11y--active');
|
||||
}
|
||||
|
||||
describe('Verify Quick Navigation support across different regions in the app', () => {
|
||||
let otherUser;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, otherUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(testChannel.id, otherUser.id).then(() => {
|
||||
// # Login as test user, visit test channel and post few messages
|
||||
cy.apiLogin(user);
|
||||
cy.visit(`/${team.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Post few messages
|
||||
postMessages(testChannel, otherUser, 5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1460_1 Verify Navigation Support in Post List & Post Input', () => {
|
||||
// # Shift focus to the last post
|
||||
cy.uiGetPostTextBox().focus().tab({shift: true}).tab({shift: true});
|
||||
cy.get('body').type('{uparrow}{downarrow}');
|
||||
|
||||
// * Verify post region reads out correctly
|
||||
verifyNavSupport('#virtualizedPostListContent > div', 'message list main region', '1');
|
||||
|
||||
// # Shift focus to the post input
|
||||
cy.uiGetPostTextBox().focus().tab().tab({shift: true});
|
||||
|
||||
// * Verify post input region reads out correctly
|
||||
verifyNavSupport('#advancedTextEditorCell', 'message input complementary region', '2');
|
||||
});
|
||||
|
||||
it('MM-T1460_3 Verify Navigation Support in RHS Post List & RHS Post Input', () => {
|
||||
// # Open RHS and reply
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
const replyMessage = 'A reply to an older post';
|
||||
cy.postMessageReplyInRHS(replyMessage);
|
||||
});
|
||||
|
||||
// * Verify post message in RHS
|
||||
cy.uiGetRHS().within(() => {
|
||||
// # Shift the focus to the last post
|
||||
cy.uiGetReplyTextBox().focus().tab({shift: true}).type('{uparrow}');
|
||||
|
||||
// * Verify post region on RHS reads out correctly
|
||||
verifyNavSupport('.post-right__content', 'message details complementary region', '3');
|
||||
|
||||
// # Shift the focus to the RHS input
|
||||
cy.uiGetReplyTextBox().focus().tab().tab({shift: true});
|
||||
|
||||
// * Verify post input on RHS reads out correctly
|
||||
cy.get('#advancedTextEditorCell').
|
||||
should('have.attr', 'aria-label', 'message input complementary region').
|
||||
and('have.attr', 'data-a11y-sort-order', '2').
|
||||
and('have.class', 'a11y__region');
|
||||
cy.uiGetReplyTextBox().
|
||||
should('have.class', 'a11y--active a11y--focused');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1460_5 Verify Navigation Support in LHS Sidebar', () => {
|
||||
// # Change the focus to Main Menu button
|
||||
cy.uiGetLHSAddChannelButton().focus().tab({shift: true}).tab();
|
||||
|
||||
// # Change the focus to the LHS sidebar
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify nav support in LHS channel navigator
|
||||
verifyNavSupport('#lhsNavigator', 'channel navigator region', '6');
|
||||
|
||||
// # Change the focus to the LHS sidebar
|
||||
cy.focused().tab().tab().tab().tab();
|
||||
|
||||
// * Verify nav support in LHS sidebar
|
||||
verifyNavSupport('#sidebar-left', 'channel sidebar region', '7');
|
||||
});
|
||||
|
||||
it('MM-T1460_6 Verify Navigation Support in Channel Header', () => {
|
||||
// # Change the focus to Main Menu button
|
||||
cy.get('#toggleFavorite').focus().tab({shift: true}).tab();
|
||||
|
||||
// * Verify nav support in LHS sidebar header
|
||||
verifyNavSupport('#channel-header', 'channel header region', '8');
|
||||
});
|
||||
|
||||
it('MM-T1460_8 Verify Navigation Support in Search Results', () => {
|
||||
// # Search for some text
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().should('be.visible').type('hello {enter}');
|
||||
|
||||
// # Change the focus to search results
|
||||
cy.get('#searchContainer').within(() => {
|
||||
cy.get('button.sidebar--right__expand').focus().tab({shift: true}).tab();
|
||||
cy.focused().tab().tab().tab().tab();
|
||||
});
|
||||
cy.get('body').type('{downarrow}{uparrow}');
|
||||
|
||||
// * Verify nav support in Search Results Container
|
||||
verifyNavSupport('#search-items-container', 'Search Results complementary region', '3');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility
|
||||
|
||||
let previousEmoji = 'grinning';
|
||||
|
||||
function verifyArrowKeysEmojiNavigation(arrowKey, count) {
|
||||
for (let index = 0; index < count; index++) {
|
||||
cy.get('body').type(arrowKey);
|
||||
|
||||
cy.get('.emoji-picker__preview-name').invoke('text').then((selectedEmoji) => {
|
||||
expect(selectedEmoji).not.equal(previousEmoji);
|
||||
previousEmoji = selectedEmoji;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('Verify Accessibility Support in Popovers', () => {
|
||||
before(() => {
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Post a message
|
||||
cy.postMessage(`hello from test user: ${Date.now()}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1489 Accessibility Support in Emoji Popover on click of Emoji Reaction button', () => {
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # Open the Emoji Popover
|
||||
cy.clickPostReactionIcon(postId);
|
||||
|
||||
// * Verify accessibility support in Emoji Search input
|
||||
cy.get('#emojiPickerSearch').should('have.attr', 'aria-label', 'Search for an emoji');
|
||||
|
||||
// # Focus on the first emoji Category
|
||||
cy.get('#emojiPickerCategories').children().eq(0).focus().tab({shift: true}).tab();
|
||||
|
||||
const categories = [
|
||||
{ariaLabel: 'Smileys & Emotion', header: 'Smileys & Emotion'},
|
||||
{ariaLabel: 'People & Body', header: 'People & Body'},
|
||||
{ariaLabel: 'Animals & Nature', header: 'Animals & Nature'},
|
||||
{ariaLabel: 'Food & Drink', header: 'Food & Drink'},
|
||||
{ariaLabel: 'Travel & Places', header: 'Travel & Places'},
|
||||
{ariaLabel: 'Activities', header: 'Activities'},
|
||||
{ariaLabel: 'Objects', header: 'Objects'},
|
||||
{ariaLabel: 'Symbols', header: 'Symbols'},
|
||||
{ariaLabel: 'Flags', header: 'Flags'},
|
||||
];
|
||||
|
||||
// * Verify if emoji Categories gets the focus when tab is pressed
|
||||
cy.get('#emojiPickerCategories').children('.emoji-picker__category').each(($el, index) => {
|
||||
// * Verify each category
|
||||
if (index < categories.length) {
|
||||
// * Verify accessibility support in emoji category
|
||||
cy.get($el).should('have.class', 'a11y--active a11y--focused').should('have.attr', 'aria-label', categories[index].ariaLabel);
|
||||
|
||||
// * Verify if corresponding section is displayed when emoji category has focus and clicked
|
||||
cy.get($el).trigger('click').tab();
|
||||
|
||||
// * Verify if corresponding section is displayed
|
||||
cy.findByText(categories[index].header).should('be.visible');
|
||||
|
||||
// * Verify emoji navigation using arrow keys
|
||||
verifyArrowKeysEmojiNavigation('{rightarrow}', 3);
|
||||
verifyArrowKeysEmojiNavigation('{leftarrow}', 2);
|
||||
|
||||
// # Press tab
|
||||
cy.get($el).focus().tab();
|
||||
}
|
||||
});
|
||||
|
||||
// # Close the Emoji Popover
|
||||
cy.get('body').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,343 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Verify Accessibility Support in Post', () => {
|
||||
let testUser;
|
||||
let otherUser;
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(testChannel.id, otherUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as test user and visit the test channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.get('#postListContent', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
|
||||
// To be addressed in MM-70414
|
||||
it.skip('MM-T1479 Verify Reader reads out the post correctly on Center Channel', () => {
|
||||
const {lastMessage} = postMessages(testChannel, otherUser, 1);
|
||||
performActionsToLastPost();
|
||||
|
||||
// # Shift focus to the last post
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true});
|
||||
cy.get('body').type('{uparrow}{downarrow}');
|
||||
|
||||
// * Verify post message in Center Channel
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Verify reader reads out the post correctly
|
||||
verifyPostLabel(`#post_${postId}`, otherUser.username, `wrote, ${lastMessage}, 2 reactions, message is saved and pinned`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1480 Verify Reader reads out the post correctly on RHS', () => {
|
||||
const {lastMessage} = postMessages(testChannel, otherUser, 1);
|
||||
performActionsToLastPost();
|
||||
|
||||
// # Post a reply on RHS
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
const replyMessage = 'A reply to an older post';
|
||||
cy.postMessageReplyInRHS(replyMessage);
|
||||
|
||||
// * Verify post message in RHS
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// # Shift the focus to the last post
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true}).type('{uparrow}');
|
||||
|
||||
// * Verify reader reads out the post correctly
|
||||
verifyPostLabel(`#rhsPost_${postId}`, otherUser.username, `wrote, ${lastMessage}, 2 reactions, message is saved and pinned`);
|
||||
});
|
||||
|
||||
// * Verify reply message in RHS
|
||||
cy.getLastPostId().then((replyId) => {
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// # Shift the focus to the last reply message
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true}).type('{uparrow}{downarrow}');
|
||||
|
||||
// * Verify reader reads out the post correctly
|
||||
verifyPostLabel(`#rhsPost_${replyId}`, testUser.username, `replied, ${replyMessage}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1486_1 Verify different Post Focus on Center Channel', () => {
|
||||
postMessages(testChannel, otherUser, 5);
|
||||
|
||||
// # Shift focus to the last post
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true}).type('{uparrow}');
|
||||
|
||||
// * Verify if focus changes to different posts when we use up arrows
|
||||
for (let index = 1; index < 5; index++) {
|
||||
cy.getNthPostId(-index - 1).then((postId) => {
|
||||
cy.get(`#post_${postId}`).should('be.focused');
|
||||
cy.get('body').type('{uparrow}');
|
||||
});
|
||||
}
|
||||
|
||||
// * Verify if focus changes to different posts when we use down arrows
|
||||
for (let index = 5; index > 0; index--) {
|
||||
cy.getNthPostId(-index - 1).then((postId) => {
|
||||
cy.get(`#post_${postId}`).should('be.focused');
|
||||
cy.get('body').type('{downarrow}');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('MM-T1486_2 Verify different Post Focus on RHS', () => {
|
||||
// # Post Message as Current user
|
||||
const message = `hello from current user: ${getRandomId()}`;
|
||||
cy.postMessage(message);
|
||||
|
||||
// # Post few replies on RHS
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
for (let index = 0; index < 3; index++) {
|
||||
const replyMessage = `A reply ${getRandomId()}`;
|
||||
cy.postMessageReplyInRHS(replyMessage);
|
||||
const otherMessage = `reply from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message: otherMessage, channelId: testChannel.id, rootId: postId});
|
||||
}
|
||||
});
|
||||
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// # Shift focus to the last post
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true}).type('{uparrow}');
|
||||
});
|
||||
|
||||
// * Verify if focus changes to different posts when we use up arrows
|
||||
for (let index = 1; index < 5; index++) {
|
||||
cy.getNthPostId(-index - 1).then((postId) => {
|
||||
cy.get(`#rhsPost_${postId}`).should('be.focused');
|
||||
cy.get('body').type('{uparrow}');
|
||||
});
|
||||
}
|
||||
|
||||
// * Verify if focus changes to different posts when we use down arrows
|
||||
for (let index = 5; index > 1; index--) {
|
||||
cy.getNthPostId(-index - 1).then((postId) => {
|
||||
cy.get(`#rhsPost_${postId}`).should('be.focused');
|
||||
cy.get('body').type('{downarrow}');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('MM-T1486_3 Verify Tab support on Post on Center Channel', () => {
|
||||
postMessages(testChannel, otherUser, 1);
|
||||
|
||||
// # Shift focus to the last post
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true}).tab({shift: true}).tab({shift: true});
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
// * Verify focus is on profile image
|
||||
cy.get('button.status-wrapper').first().should('be.focused');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the username
|
||||
cy.get('button.user-popover').should('be.focused');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the time
|
||||
cy.get(`#CENTER_time_${postId}`).should('be.focused');
|
||||
cy.focused().tab();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
// * Verify focus is on the reactions button
|
||||
cy.get(`#recent_reaction_${i}`).should('have.class', 'emoticon--post-menu').and('have.attr', 'aria-label');
|
||||
cy.focused().tab();
|
||||
}
|
||||
|
||||
// * Verify focus is on the reactions button
|
||||
cy.get(`#CENTER_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'Add Reaction');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the save post button
|
||||
cy.get(`#CENTER_flagIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'save message');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the actions button
|
||||
cy.get(`#CENTER_actions_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'actions');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the comment button
|
||||
cy.get(`#CENTER_commentIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'reply');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the more button
|
||||
cy.get(`#CENTER_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'more');
|
||||
cy.focused().tab();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1486_4 Verify Tab support on Post on RHS', () => {
|
||||
// # Post Message as Current user
|
||||
const message = `hello from current user: ${getRandomId()}`;
|
||||
cy.postMessage(message);
|
||||
|
||||
// # Post few replies on RHS
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
const replyMessage = `A reply ${getRandomId()}`;
|
||||
cy.postMessageReplyInRHS(replyMessage);
|
||||
const otherMessage = `reply from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message: otherMessage, channelId: testChannel.id, rootId: postId});
|
||||
});
|
||||
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// # Shift focus to the last post
|
||||
cy.get('#FormattingControl_bold').focus().tab({shift: true}).tab({shift: true});
|
||||
});
|
||||
|
||||
// * Verify reverse tab on RHS
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#rhsPost_${postId}`).within(() => {
|
||||
// * Verify focus is on the time
|
||||
cy.get(`#RHS_COMMENT_time_${postId}`).should('be.focused');
|
||||
cy.focused().tab({shift: true});
|
||||
|
||||
// * Verify focus is on the username
|
||||
cy.get('button.user-popover').should('be.focused');
|
||||
cy.focused().tab().tab();
|
||||
|
||||
// * Verify focus is on most recent action
|
||||
cy.get('#recent_reaction_0').should('have.class', 'emoticon--post-menu').and('have.attr', 'aria-label');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the reactions button
|
||||
cy.get(`#RHS_COMMENT_reaction_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'Add Reaction');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the save icon
|
||||
cy.get(`#RHS_COMMENT_flagIcon_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'save message');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the actions button
|
||||
cy.get(`#RHS_COMMENT_actions_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'actions');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify focus is on the more button
|
||||
cy.get(`#RHS_COMMENT_button_${postId}`).should('be.focused').and('have.attr', 'aria-label', 'more');
|
||||
cy.focused().tab({shift: true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1462 Verify incoming messages are read', () => {
|
||||
// # Make channel as read by switching back and forth to testChannel
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.get('#postListContent', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
cy.uiGetLhsSection('CHANNELS').findByText(testChannel.display_name).click();
|
||||
cy.get('#postListContent', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
// # Submit a post as another user
|
||||
const message = `verify incoming message from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message, channelId: testChannel.id});
|
||||
cy.uiWaitUntilMessagePostedIncludes(message);
|
||||
|
||||
// # Get the element which stores the incoming messages
|
||||
cy.get('#postListContent').within(() => {
|
||||
cy.get('.sr-only').should('have.attr', 'aria-live', 'polite').as('incomingMessage');
|
||||
});
|
||||
|
||||
// * Verify incoming message is read
|
||||
cy.get('@incomingMessage').invoke('text').then((text) => {
|
||||
expect(text).contain(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function postMessages(testChannel, otherUser, count) {
|
||||
let lastMessage;
|
||||
|
||||
for (let index = 0; index < count; index++) {
|
||||
// # Post Message as Current user
|
||||
const message = `hello from current user: ${getRandomId()}`;
|
||||
cy.postMessage(message);
|
||||
lastMessage = `hello from ${otherUser.username}: ${getRandomId()}`;
|
||||
cy.postMessageAs({sender: otherUser, message: lastMessage, channelId: testChannel.id});
|
||||
}
|
||||
|
||||
return {lastMessage};
|
||||
}
|
||||
|
||||
function performActionsToLastPost() {
|
||||
// # Take some actions on the last post
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # Add grinning reaction
|
||||
cy.clickPostReactionIcon(postId);
|
||||
cy.findAllByTestId('grinning').first().trigger('mouseover', {force: true});
|
||||
cy.get('.sprite-preview').should('be.visible');
|
||||
cy.get('.emoji-picker__preview').should('be.visible').and('have.text', ':grinning:');
|
||||
cy.findAllByTestId('grinning').first().click({force: true});
|
||||
cy.get(`#postReaction-${postId}-grinning`).should('be.visible');
|
||||
|
||||
// # Add smile reaction
|
||||
cy.clickPostReactionIcon(postId);
|
||||
cy.findAllByTestId('smile').first().trigger('mouseover', {force: true});
|
||||
cy.get('.sprite-preview').should('be.visible');
|
||||
cy.get('.emoji-picker__preview').should('be.visible').and('have.text', ':smile:');
|
||||
cy.findAllByTestId('smile').first().click({force: true});
|
||||
cy.get(`#postReaction-${postId}-smile`).should('be.visible');
|
||||
|
||||
// # Save the post
|
||||
cy.clickPostSaveIcon(postId);
|
||||
|
||||
// # Pin the post
|
||||
cy.clickPostDotMenu(postId);
|
||||
cy.get(`#pin_post_${postId}`).click();
|
||||
|
||||
cy.clickPostDotMenu(postId);
|
||||
cy.get('body').type('{esc}');
|
||||
});
|
||||
}
|
||||
|
||||
function verifyPostLabel(elementId, username, labelSuffix) {
|
||||
// # Shift focus to the last post
|
||||
cy.get(elementId).as('lastPost').should('be.focused');
|
||||
|
||||
// * Verify reader reads out the post correctly
|
||||
cy.get('@lastPost').then((el) => {
|
||||
// # Get the post time
|
||||
cy.wrap(el).find('time.post__time').invoke('attr', 'datetime').then((time) => {
|
||||
const parsedTime = Cypress.dayjs(time);
|
||||
const expectedLabel = `At ${parsedTime.format('h:mm A dddd, MMMM D')}, ${username} ${labelSuffix}`;
|
||||
cy.wrap(el).should('have.attr', 'aria-label', expectedLabel);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @accessibility @smoke
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Verify Accessibility Support in Channel Sidebar Navigation', () => {
|
||||
let testUser;
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let offTopicUrl;
|
||||
|
||||
before(() => {
|
||||
let otherUser;
|
||||
let otherChannel;
|
||||
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, channel, user, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
offTopicUrl = url;
|
||||
|
||||
cy.apiCreateUser().then(({user: regularUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, regularUser.id);
|
||||
});
|
||||
|
||||
return cy.apiCreateChannel(testTeam.id, 'test', 'Test');
|
||||
}).then(({channel}) => {
|
||||
otherChannel = channel;
|
||||
return cy.apiAddUserToChannel(otherChannel.id, testUser.id);
|
||||
}).then(() => {
|
||||
return cy.apiCreateUser({prefix: 'other'});
|
||||
}).then(({user}) => {
|
||||
otherUser = user;
|
||||
return cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
}).then(() => {
|
||||
return cy.apiAddUserToChannel(testChannel.id, otherUser.id);
|
||||
}).then(() => {
|
||||
return cy.apiAddUserToChannel(otherChannel.id, otherUser.id);
|
||||
}).then(() => {
|
||||
// # Post messages to have unread messages to test user
|
||||
for (let index = 0; index < 5; index++) {
|
||||
cy.postMessageAs({sender: otherUser, message: 'This is an old message', channelId: otherChannel.id});
|
||||
}
|
||||
|
||||
// # Login as test user and visit the off-topic channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1472 Verify Tab Support in Direct Messages section', () => {
|
||||
const usersPrefixes = ['a', 'c', 'd'];
|
||||
usersPrefixes.forEach((prefix) => {
|
||||
//# Create users with prefixes in alphabetical order
|
||||
cy.apiCreateUser({prefix}).then(({user: newUser}) => {
|
||||
cy.apiCreateDirectChannel([testUser.id, newUser.id]).then(({channel}) => {
|
||||
// # Post message in The DM channel
|
||||
cy.postMessageAs({sender: newUser, message: 'test', channelId: channel.id});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// # Trigger DM with a user
|
||||
cy.uiAddDirectMessage().click();
|
||||
searchAndSelectUser('user');
|
||||
searchAndSelectUser('user');
|
||||
cy.uiGetButton('Go').click();
|
||||
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Press tab to the Create DM button and verify if the Plus button has focus
|
||||
cy.uiAddDirectMessage().
|
||||
tab({shift: true}).tab().
|
||||
should('be.focused').
|
||||
tab({shift: true}).tab({shift: true});
|
||||
|
||||
cy.focused().parent().parent().next().find('.SidebarChannel').each((el, i) => {
|
||||
if (i === 0) {
|
||||
cy.focused().findByText('DIRECT MESSAGES');
|
||||
cy.focused().tab().tab().tab();
|
||||
}
|
||||
|
||||
// * Verify if focus changes to different channels in Direct Messages section
|
||||
cy.wrap(el).find('.SidebarLink').should('be.focused');
|
||||
cy.focused().tab().tab().tab();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function searchAndSelectUser(text) {
|
||||
cy.findByRole('textbox', {name: 'Search for people'}).
|
||||
typeWithForce(text).
|
||||
wait(TIMEOUTS.ONE_SEC);
|
||||
cy.get('.more-modal__row.clickable').
|
||||
first().
|
||||
click();
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @accessibility @smoke
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Verify Accessibility Support in Channel Sidebar Navigation', () => {
|
||||
let testUser: Cypress.UserProfile;
|
||||
let testTeam: Cypress.Team;
|
||||
let testChannel: Cypress.Channel;
|
||||
let offTopicUrl: string;
|
||||
|
||||
before(() => {
|
||||
let otherUser: Cypress.UserProfile;
|
||||
let otherChannel: Cypress.Channel;
|
||||
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, channel, user, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
offTopicUrl = url;
|
||||
return cy.apiCreateChannel(testTeam.id, 'test', 'Test');
|
||||
}).then(({channel}) => {
|
||||
otherChannel = channel;
|
||||
return cy.apiAddUserToChannel(otherChannel.id, testUser.id);
|
||||
}).then(() => {
|
||||
return cy.apiCreateUser({prefix: 'other'});
|
||||
}).then(({user}) => {
|
||||
otherUser = user;
|
||||
return cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
}).then(() => {
|
||||
return cy.apiAddUserToChannel(testChannel.id, otherUser.id);
|
||||
}).then(() => {
|
||||
return cy.apiAddUserToChannel(otherChannel.id, otherUser.id);
|
||||
}).then(() => {
|
||||
// # Post messages to have unread messages to test user
|
||||
for (let index = 0; index < 5; index++) {
|
||||
cy.postMessageAs({sender: otherUser, message: 'This is an old message', channelId: otherChannel.id});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as test user and visit the off-topic channel
|
||||
cy.apiLogin(testUser);
|
||||
(cy as any).apiSaveSidebarSettingPreference();
|
||||
cy.visit(offTopicUrl);
|
||||
cy.get('#postListContent').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1470 Verify Tab Support in Channels section', () => {
|
||||
// # Create some Public Channels
|
||||
Cypress._.times(2, () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'public', 'public');
|
||||
});
|
||||
|
||||
// # Wait for few seconds
|
||||
cy.wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// # Press tab to the Add Public Channel button
|
||||
cy.uiGetLHSAddChannelButton().focus().tab().tab({shift: true});
|
||||
|
||||
// * Verify if the Plus button has focus
|
||||
cy.uiGetLHSAddChannelButton().should('be.focused');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify if the Plus button has focus
|
||||
cy.findByRole('button', {name: 'Find Channels'}).should('be.focused');
|
||||
cy.focused().tab();
|
||||
|
||||
// * Verify if focus changes to different channels in Unread section
|
||||
cy.get('.SidebarChannel.unread').each((el) => {
|
||||
cy.wrap(el).find('.unread-title').should('be.focused');
|
||||
cy.focused().tab().tab();
|
||||
});
|
||||
|
||||
cy.focused().parent().next().find('.SidebarChannel').each((el, i) => {
|
||||
if (i === 0) {
|
||||
cy.focused().findByText('CHANNELS');
|
||||
cy.focused().tab().tab();
|
||||
}
|
||||
|
||||
// * Verify if focus changes to different channels in Channels section
|
||||
cy.wrap(el).find('.SidebarLink').should('be.focused');
|
||||
cy.focused().tab().tab();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1473 Verify Tab Support in Unreads section', () => {
|
||||
// # Press tab from the Main Menu button
|
||||
cy.uiGetLHSAddChannelButton().focus().tab().tab();
|
||||
|
||||
// * Verify if focus changes to different channels in Unread section
|
||||
cy.get('.SidebarChannel.unread').each((el) => {
|
||||
cy.wrap(el).find('.unread-title').should('be.focused');
|
||||
cy.focused().tab().tab();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1474 Verify Tab Support in Favorites section', () => {
|
||||
// # Mark few channels as Favorites
|
||||
markAsFavorite('off-topic');
|
||||
markAsFavorite(testChannel.name);
|
||||
|
||||
// # Press tab from the add channel button down to all unread channels
|
||||
cy.uiGetLHSAddChannelButton().focus().tab().tab();
|
||||
cy.get('.SidebarChannel.unread').each(() => {
|
||||
cy.focused().tab().tab();
|
||||
});
|
||||
|
||||
// * Verify if focus changes to different channels in Favorite Channels section
|
||||
cy.focused().tab().tab().parent().next().find('.SidebarChannel').each((el, i) => {
|
||||
if (i === 0) {
|
||||
cy.focused().findByText('FAVORITES');
|
||||
cy.focused().tab().tab();
|
||||
}
|
||||
|
||||
cy.wrap(el).find('.SidebarLink').should('be.focused');
|
||||
cy.focused().tab().tab();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1475 Verify Up & Down Arrow support in Channel Sidebar', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'public', 'Public', 'O');
|
||||
cy.apiCreateChannel(testTeam.id, 'private', 'Private', 'P');
|
||||
|
||||
// # Mark few channels as Favorites
|
||||
markAsFavorite('off-topic');
|
||||
markAsFavorite(testChannel.name);
|
||||
|
||||
// # Press tab from the Main Menu button
|
||||
cy.uiGetLHSAddChannelButton().focus().tab().tab().tab();
|
||||
|
||||
// # Press Down Arrow and then Up Arrow
|
||||
cy.get('body').type('{downarrow}{uparrow}');
|
||||
|
||||
// * Verify if Unread Channels section has focus
|
||||
cy.uiGetLhsSection('UNREADS').should('be.focused');
|
||||
|
||||
// # Press Down Arrow and check the focus
|
||||
cy.get('body').type('{downarrow}');
|
||||
|
||||
// * Verify if Favorite Channels section has focus
|
||||
cy.uiGetLhsSection('FAVORITES').should('be.focused');
|
||||
|
||||
// # Press Down Arrow and check the focus
|
||||
cy.get('body').type('{downarrow}');
|
||||
|
||||
// * Verify if Public Channels section has focus
|
||||
cy.uiGetLhsSection('CHANNELS').should('be.focused');
|
||||
|
||||
// # Press Down Arrow and check the focus
|
||||
cy.get('body').type('{downarrow}');
|
||||
|
||||
// * Verify if Direct Messages section has focus
|
||||
cy.uiGetLhsSection('DIRECT MESSAGES').should('be.focused');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility tests for RHS getting focus after buttons actions', () => {
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
|
||||
// # Login as the test user and visit the town-square channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
cy.get('#postListContent').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Close the RHS
|
||||
cy.uiCloseRHS();
|
||||
cy.get('#sidebar-right').should('not.exist');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// # Close the RHS
|
||||
cy.uiCloseRHS();
|
||||
cy.get('#sidebar-right').should('not.exist');
|
||||
});
|
||||
|
||||
it('Focus should be on RHS when opening Recent Mentions', () => {
|
||||
// # Enter key press on the Recent Mentions button
|
||||
cy.findByRole('button', {name: /Recent mentions/i}).focus().realPress('Enter');
|
||||
|
||||
// * Verify RHS is open
|
||||
cy.get('#sidebar-right').should('be.visible');
|
||||
|
||||
// * Check that the RHS container is focused
|
||||
cy.findByLabelText('Expand Sidebar Icon').should('be.focused');
|
||||
});
|
||||
|
||||
it('Focus should be on RHS when opening Saved Messages', () => {
|
||||
// # Enter key press on the Saved Messages button
|
||||
cy.findByRole('button', {name: /Saved messages/i}).focus().realPress('Enter');
|
||||
|
||||
// * Verify RHS is open
|
||||
cy.get('#sidebar-right').should('be.visible');
|
||||
|
||||
// * Check that the RHS container is focused
|
||||
cy.findByLabelText('Expand Sidebar Icon').should('be.focused');
|
||||
});
|
||||
|
||||
it('Focus should be on RHS when opening Members', () => {
|
||||
cy.get('#channelHeaderInfo').should('exist');
|
||||
|
||||
// # Enter key press on the Members button
|
||||
cy.get('#member_rhs').should('be.visible').focus().realPress('Enter');
|
||||
|
||||
// * Verify RHS is open
|
||||
cy.get('#sidebar-right').should('be.visible');
|
||||
|
||||
// * Check that the RHS container is focused
|
||||
cy.get('.sidebar-right-container').find('#rhsCloseButton').should('be.focused');
|
||||
});
|
||||
|
||||
it('Focus should be on RHS when opening Channel files', () => {
|
||||
// # Enter key press on the Channel files button
|
||||
cy.findByRole('button', {name: /Channel files/i}).focus().realPress('Enter');
|
||||
|
||||
// * Verify RHS is open
|
||||
cy.get('#sidebar-right').should('be.visible');
|
||||
|
||||
// * Check that the RHS container is focused
|
||||
cy.findByLabelText('Expand Sidebar Icon').should('be.focused');
|
||||
});
|
||||
});
|
||||
|
||||
function markAsFavorite(channelName: string) {
|
||||
// # Visit the channel
|
||||
cy.get(`#sidebarItem_${channelName}`).click();
|
||||
cy.get('#postListContent').should('be.visible');
|
||||
|
||||
// # Remove from Favorites if already set
|
||||
cy.get('#channelHeaderInfo').then((el) => {
|
||||
if (el.find('#toggleFavorite.active').length) {
|
||||
cy.get('#toggleFavorite').click();
|
||||
}
|
||||
});
|
||||
|
||||
// # mark it as Favorite
|
||||
cy.get('#toggleFavorite').click();
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @account_settings
|
||||
|
||||
describe('Account Settings', () => {
|
||||
let testUser: Cypress.UserProfile;
|
||||
let testTeam: Cypress.Team;
|
||||
let offTopic: string;
|
||||
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({userPrefix: 'other', loginAfter: true}).then(({offTopicUrl, user, team}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
offTopic = offTopicUrl;
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2049 Account Settings link in own popover', () => {
|
||||
// # Click avatar to open profile popover
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Check if profile settings modal is open
|
||||
cy.get('#accountSettingsModal').should('be.visible');
|
||||
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2081 Password: Save should be disabled on blank', () => {
|
||||
// # Go to Profile > Security
|
||||
cy.uiOpenProfileModal('Security');
|
||||
|
||||
// * Check that the Security tab is loaded
|
||||
cy.get('#securityButton').should('be.visible');
|
||||
|
||||
// # Click the Security tab
|
||||
cy.get('#securityButton').click();
|
||||
|
||||
// # Click "Edit" to the right of "Password"
|
||||
cy.get('#passwordEdit').should('be.visible').click();
|
||||
|
||||
// # Check that save button is disabled
|
||||
cy.get('button[type="submit"]').should('be.disabled');
|
||||
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2074 New email not visible to other users until it has been confirmed', () => {
|
||||
// # Login as admin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create user
|
||||
cy.apiCreateUser({prefix: 'test'}).then(({user: newUser}) => {
|
||||
// # Add user to team
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
// # Create DM channel
|
||||
cy.apiCreateDirectChannel([testUser.id, newUser.id]).then(({channel}) => {
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopic);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// * Update email
|
||||
const oldEMail = testUser.email;
|
||||
const newEMail = 'test@example.com';
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
cy.get('#primaryEmail').should('be.visible').type(newEMail);
|
||||
cy.get('#confirmEmail').should('be.visible').type(newEMail);
|
||||
cy.get('#currentPassword').should('be.visible').type(testUser.password);
|
||||
cy.uiSaveAndClose();
|
||||
|
||||
// * Send DM
|
||||
cy.postMessageAs({sender: testUser, message: `@${newUser.username}`, channelId: channel.id});
|
||||
|
||||
// # Login to 2nd user
|
||||
cy.apiLogin(newUser);
|
||||
|
||||
// * Check if email updated
|
||||
cy.visit(`/${testTeam.name}/messages/@${testUser.username}`);
|
||||
cy.get('#channelIntro .user-popover').should('be.visible').click();
|
||||
cy.get('#user-profile-popover').should('be.visible').should('contain', oldEMail);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Profile > Profile Settings', () => {
|
||||
let otherUser: Cypress.UserProfile;
|
||||
let testChannel: Cypress.Channel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, channel, offTopicUrl}) => {
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiCreateUser().then(({user}) => {
|
||||
otherUser = user;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, user.id).then(() => {
|
||||
cy.apiAddUserToChannel(testChannel.id, user.id);
|
||||
});
|
||||
});
|
||||
|
||||
// # Go to off-topic
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T285 Status Menu stays open', () => {
|
||||
// # Click the hamburger button
|
||||
cy.uiGetSetStatusButton().click();
|
||||
|
||||
// * Menu should be visible
|
||||
cy.uiGetStatusMenu();
|
||||
|
||||
// # Post a message as other user
|
||||
cy.postMessageAs({sender: otherUser, message: 'abc', channelId: testChannel.id}).wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// * Menu should still be visible
|
||||
cy.uiGetStatusMenu();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
describe('Profile > Profile Settings > Position', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
// # Visit off-topic channel
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Post message in the main channel
|
||||
cy.postMessage('hello from master hacker');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2063 Position', () => {
|
||||
const position = 'Master hacker';
|
||||
|
||||
// # Open 'Profile' modal and view the default 'Profile Settings'
|
||||
cy.uiOpenProfileModal('Profile Settings').within(() => {
|
||||
// # Open 'Position' setting
|
||||
cy.findByRole('heading', {name: 'Position'}).should('be.visible').click();
|
||||
|
||||
// # Enter new 'Position'
|
||||
cy.findByRole('textbox', {name: 'Position'}).
|
||||
should('be.visible').
|
||||
and('be.focused').
|
||||
type(position).
|
||||
should('have.value', position);
|
||||
|
||||
// # Save and close the modal
|
||||
cy.uiSaveAndClose();
|
||||
});
|
||||
|
||||
// # Click on the profile image
|
||||
cy.get('.profile-icon > img').as('profileIconForPopover').click();
|
||||
|
||||
// # Verify that the popover is visible and contains position
|
||||
cy.contains('div.user-profile-popover', position).should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T2064 Position / 128 characters', () => {
|
||||
const longPosition = 'Master Hacker II'.repeat(8);
|
||||
|
||||
// # Open 'Profile' modal and view the default 'Profile Settings'
|
||||
cy.uiOpenProfileModal('Profile Settings').within(() => {
|
||||
const minPositionHeader = () => cy.findByRole('heading', {name: 'Position'});
|
||||
const maxPositionInput = () => cy.findByRole('textbox', {name: 'Position'});
|
||||
|
||||
// # Fill-in the position field with a value of 128 characters
|
||||
minPositionHeader().click();
|
||||
maxPositionInput().type(longPosition);
|
||||
cy.uiSave();
|
||||
maxPositionInput().should('not.exist');
|
||||
|
||||
minPositionHeader().click();
|
||||
maxPositionInput().invoke('val').then((val) => {
|
||||
// * Verify that the input value is 128 characters
|
||||
expect(val!.toString().length).to.equal(128);
|
||||
});
|
||||
|
||||
// # Try to edit the field with maximum characters
|
||||
maxPositionInput().focus().type('random');
|
||||
maxPositionInput().invoke('val').then((val) => {
|
||||
// * Verify that the position hasn't changed
|
||||
expect(val).to.equal(longPosition);
|
||||
});
|
||||
|
||||
// # Save position
|
||||
cy.uiSave();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {reUrl, getRandomId} from '@/utils';
|
||||
|
||||
describe('Profile > Profile Settings > Email', () => {
|
||||
let siteName: string;
|
||||
let testUser: Cypress.UserProfile;
|
||||
let otherUser: Cypress.UserProfile;
|
||||
let offTopicUrl: string;
|
||||
let origConfig: Cypress.AdminConfig;
|
||||
|
||||
before(() => {
|
||||
// Get config
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
origConfig = config;
|
||||
const newConfig = {
|
||||
...origConfig,
|
||||
EmailSettings: {
|
||||
...origConfig.EmailSettings,
|
||||
RequireEmailVerification: true,
|
||||
},
|
||||
};
|
||||
|
||||
cy.apiUpdateConfig(newConfig).then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
|
||||
cy.apiInitSetup().then(({user, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
offTopicUrl = url;
|
||||
|
||||
cy.apiVerifyUserEmailById(testUser.id);
|
||||
|
||||
return cy.apiCreateUser({});
|
||||
}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// # Close modal
|
||||
cy.get('body').type('{esc}');
|
||||
});
|
||||
|
||||
it('MM-T2065 Email: Can "change" to existing email address and save', () => {
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
// # Type the same email
|
||||
cy.get('#primaryEmail').should('be.visible').type(testUser.email);
|
||||
cy.get('#confirmEmail').should('be.visible').type(testUser.email);
|
||||
cy.get('#currentPassword').should('be.visible').type('SampleUs@r-1');
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Check that the email verification message is not showed.
|
||||
cy.get('.announcement-bar').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T2066 email address required', () => {
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
// # Click on the input and blur it
|
||||
cy.get('#primaryEmail').should('be.visible').click().blur();
|
||||
|
||||
// * Check that the correct error message is shown.
|
||||
cy.get('#error_primaryEmail').should('be.visible').should('have.text', 'Please enter a valid email address.');
|
||||
});
|
||||
|
||||
it('MM-T2067 email address already taken error', () => {
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
// # Type different email
|
||||
cy.get('#primaryEmail').should('be.visible').type(otherUser.email);
|
||||
cy.get('#confirmEmail').should('be.visible').type(otherUser.email);
|
||||
cy.get('#currentPassword').should('be.visible').type(otherUser.password);
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Check that the correct error message is shown.
|
||||
cy.get('#serverError').should('be.visible').should('have.text', 'An account with that email already exists.');
|
||||
});
|
||||
|
||||
it('MM-T2068 email address and confirmation don\'t match', () => {
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
// # Type some random email
|
||||
cy.get('#primaryEmail').should('be.visible').type('random@example.com');
|
||||
cy.get('#confirmEmail').should('be.visible').clear();
|
||||
cy.get('#currentPassword').should('be.visible').type('randompass');
|
||||
|
||||
// * Check that the correct error message is shown.
|
||||
cy.get('#error_confirmEmail').should('be.visible').should('have.text', 'The new emails you entered do not match.');
|
||||
});
|
||||
|
||||
// This test is a combination of 4 sub-tests because they are sub-parts of the same test.
|
||||
// Doing them individually would have a dependency with the previous test.
|
||||
// Hence, a combined single test for everything is better.
|
||||
it('MM-T2069 Email: Can update email address and verify through email notification', () => {
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
const randomId = getRandomId();
|
||||
const username = `user-${randomId}`;
|
||||
const email = `${username}@example.com`;
|
||||
|
||||
// # Type new email
|
||||
cy.get('#primaryEmail').should('be.visible').type(email);
|
||||
cy.get('#confirmEmail').should('be.visible').type(email);
|
||||
cy.get('#currentPassword').should('be.visible').type(testUser.password);
|
||||
|
||||
// # Save the settings and close
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiClose();
|
||||
|
||||
// * Verify the announcement bar
|
||||
cy.get('.announcement-bar').should('be.visible').should('contain.text', 'Check your email inbox to verify the address.');
|
||||
|
||||
// # Reload the page
|
||||
cy.reload();
|
||||
|
||||
// * Check that the email verification message is not showed.
|
||||
cy.get('.announcement-bar').should('not.exist');
|
||||
|
||||
cy.getRecentEmail({username, email}).then((data) => {
|
||||
// * Verify the subject
|
||||
expect(data.subject).to.equal(`[${siteName}] Verify new email address`);
|
||||
|
||||
// * Verify the body
|
||||
expect(data.body).to.contain('You successfully updated your email');
|
||||
const matched = data.body[6].match(reUrl);
|
||||
assert(matched.length > 0);
|
||||
|
||||
const permalink = matched[0];
|
||||
|
||||
// # Click on the link
|
||||
cy.visit(permalink);
|
||||
|
||||
// # Wait for one second for the mail to be sent out.
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
cy.getRecentEmail(testUser).then(({subject}) => {
|
||||
// * Verify the subject
|
||||
expect(subject).to.equal(`[${siteName}] Your email address has changed`);
|
||||
});
|
||||
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// * Verify new email address
|
||||
cy.get('#emailDesc').should('be.visible').should('have.text', email);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2073 - Verify email verification message after logout', () => {
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
const randomId = getRandomId();
|
||||
const username = `user-${randomId}`;
|
||||
const email = `${username}@example.com`;
|
||||
|
||||
// # Type new email
|
||||
cy.get('#primaryEmail').should('be.visible').type(email);
|
||||
cy.get('#confirmEmail').should('be.visible').type(email);
|
||||
cy.get('#currentPassword').should('be.visible').type(testUser.password);
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Close modal then logout
|
||||
cy.get('body').type('{esc}');
|
||||
cy.uiLogout();
|
||||
|
||||
// # Wait for one second for the mail to be sent out.
|
||||
cy.wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
cy.getRecentEmail({username, email}).then((data) => {
|
||||
// * Verify the subject
|
||||
expect(data.subject).to.equal(`[${siteName}] Verify new email address`);
|
||||
|
||||
// * Verify email body
|
||||
expect(data.body[1]).to.contain('You successfully updated your email');
|
||||
const matched = data.body[6].match(reUrl);
|
||||
assert(matched.length > 0);
|
||||
|
||||
const permalink = matched[0];
|
||||
cy.visit(permalink);
|
||||
|
||||
// * Verify login text
|
||||
cy.get('.AlertBanner.success').should('be.visible').within(() => {
|
||||
cy.get('.AlertBanner__title').should('contain.text', 'Email Verified');
|
||||
});
|
||||
|
||||
// # Do login
|
||||
cy.get('#input_loginId').should('be.visible').clear().type(email);
|
||||
cy.get('#input_password-input').should('be.visible').type(testUser.password);
|
||||
cy.get('#saveSetting').should('not.be.disabled').click();
|
||||
|
||||
// * Check that the email verification message is not showed.
|
||||
cy.get('.announcement-bar').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
describe('Profile > Profile Settings > Full Name', () => {
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({user, offTopicUrl}) => {
|
||||
testUser = user;
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2043 Enter first name', () => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Click "Edit" to the right of "Full Name"
|
||||
cy.get('#nameEdit').should('be.visible').click();
|
||||
|
||||
// # Clear the first name
|
||||
cy.get('#firstName').clear();
|
||||
|
||||
// # Type a new first name
|
||||
cy.get('#firstName').should('be.visible').type(testUser.first_name + '_new');
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave();
|
||||
|
||||
// * Check that the first name was correctly updated
|
||||
cy.get('#nameDesc').should('be.visible').should('contain', testUser.first_name + '_new ' + testUser.last_name);
|
||||
|
||||
// # Close the modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2042 Full Name starting blank stays blank', () => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Click "Edit" to the right of "Full Name"
|
||||
cy.get('#nameEdit').should('be.visible').click();
|
||||
|
||||
// # Clear the first name
|
||||
cy.get('#firstName').should('be.visible').clear();
|
||||
|
||||
// # Clear the last name
|
||||
cy.get('#lastName').should('be.visible').clear();
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave();
|
||||
|
||||
// # Click "Edit" to the right of "Full Name"
|
||||
cy.get('#nameEdit').should('be.visible').click();
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave();
|
||||
|
||||
// * Check that the full name was correctly cleared
|
||||
cy.findByText("Click 'Edit' to add your full name").should('be.visible');
|
||||
|
||||
// # Click "Edit" to the right of "Full Name"
|
||||
cy.get('#nameEdit').should('be.visible').click();
|
||||
|
||||
// * Check that first name is blank
|
||||
cy.get('#firstName').should('be.visible').should('have.value', '');
|
||||
|
||||
// * Check that last name is blank
|
||||
cy.get('#lastName').should('be.visible').should('have.value', '');
|
||||
|
||||
// # Close the modal
|
||||
cy.uiClose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Profile > Profile Settings> Full Name', () => {
|
||||
let firstUser: Cypress.UserProfile;
|
||||
let secondUser: Cypress.UserProfile;
|
||||
const firstName = 'This Is a Long Name';
|
||||
const lastName = 'That Should Truncate';
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user, offTopicUrl}) => {
|
||||
firstUser = user;
|
||||
cy.apiCreateUser().then(({user: user1}) => {
|
||||
secondUser = user1;
|
||||
cy.apiAddUserToTeam(team.id, secondUser.id);
|
||||
|
||||
cy.apiLogin(firstUser);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2046 Full Name - Truncated in popover', () => {
|
||||
// # Go to Profile -> Profile Settings -> Full Name -> Edit
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Open Full Name section
|
||||
cy.get('#nameDesc').click();
|
||||
|
||||
// # Enter First Name `This Is a Very Long Name`
|
||||
cy.get('#firstName').clear().type(firstName);
|
||||
|
||||
// # Enter Last Name `That Should Truncate`
|
||||
cy.get('#lastName').clear().type(lastName);
|
||||
|
||||
// # Save
|
||||
cy.get('#saveSetting').click();
|
||||
|
||||
// * Full name field shows first and last name.
|
||||
cy.contains('#nameDesc', `${firstName} ${lastName}`);
|
||||
|
||||
// # Close profile settings modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2047 Truncated in popover (visual verification)', () => {
|
||||
// # Open user profile popover
|
||||
cy.postMessage(`this is a test message ${getRandomId()}`);
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).should('be.visible');
|
||||
cy.get(`#post_${postId} img`).click();
|
||||
cy.get('div.user-profile-popover').should('be.visible');
|
||||
cy.get('button.closeButtonRelativePosition').click();
|
||||
|
||||
// * Popover user name should show truncated to 'This Is a Long Name That Should Tr...'
|
||||
cy.findByTestId(`popover-fullname-${firstUser.username}`).should('have.css', 'text-overflow', 'clip');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2048 Empty full name: @ still displays before username', () => {
|
||||
// # Open any user list ("View Members", "Add Members", "Manage Members", ..)
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// # Find a user who hasn't set their full name
|
||||
cy.get('.modal-title').should('be.visible');
|
||||
cy.get('#searchUsersInput').should('be.visible').type(secondUser.nickname);
|
||||
|
||||
// * Username is preceded with `@`, consistent with other users
|
||||
cy.contains('button.user-popover', `@${secondUser.username}`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @account_setting
|
||||
|
||||
describe('Account Settings', () => {
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2045 Full Name - Link in help text', () => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// * Ensure that the Profile tab is loaded
|
||||
cy.get('#generalSettingsTitle').should('be.visible').should('contain', 'Profile');
|
||||
|
||||
// # Click "Edit" to the right of Full Name
|
||||
cy.get('#nameEdit').click();
|
||||
|
||||
// # Click "Notifications" link in help text
|
||||
cy.get('#extraInfo').within(() => {
|
||||
cy.findByText('Notifications').click();
|
||||
});
|
||||
|
||||
// * Verify that the modal switched to "Profile" modal
|
||||
cy.findByRole('dialog', {name: 'Profile'}).should('be.visible');
|
||||
|
||||
// * Verify that the view switches to notifications tab
|
||||
cy.get('#notificationSettingsTitle').should('be.visible').should('contain', 'Notifications');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Settings > Sidebar > General', () => {
|
||||
let testUser: Cypress.UserProfile;
|
||||
let testTeam: Cypress.Team;
|
||||
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, user, offTopicUrl}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3848 No nickname is present', () => {
|
||||
// # Open 'Profile' modal and view the default 'Profile'
|
||||
cy.uiOpenProfileModal('Profile Settings').within(() => {
|
||||
// # Open 'Nickname' setting
|
||||
cy.uiGetHeading('Nickname').click();
|
||||
|
||||
// # Clear the nickname text field contents
|
||||
cy.uiGetTextbox('Nickname').should('be.visible').clear();
|
||||
|
||||
// # Save and close the modal
|
||||
cy.uiSaveAndClose();
|
||||
});
|
||||
|
||||
// # Open team menu and click "View Members"
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// # Search for username and check that no nickname is present
|
||||
cy.get('.modal-title').should('be.visible');
|
||||
cy.get('#searchUsersInput').should('be.visible').type(testUser.first_name).wait(TIMEOUTS.ONE_SEC);
|
||||
cy.findByTestId('userListItemDetails').find('.more-modal__name').should('be.visible').then((el) => {
|
||||
expect(getInnerText(el)).equal(`@${testUser.username} - ${testUser.first_name} ${testUser.last_name}`);
|
||||
});
|
||||
|
||||
// # Close Team Members modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T268 Profile > Profile Settings > Add Nickname', () => {
|
||||
const newNickname = 'victor_nick';
|
||||
|
||||
// # Open 'Profile' modal and view the default 'Profile Settings'
|
||||
cy.uiOpenProfileModal('Profile Settings').within(() => {
|
||||
// # Open 'Nickname' setting
|
||||
cy.uiGetHeading('Nickname').click();
|
||||
|
||||
// # Clear the nickname text field contents
|
||||
cy.uiGetTextbox('Nickname').should('be.visible').clear().type('victor_nick');
|
||||
|
||||
// # Save and close the modal
|
||||
cy.uiSaveAndClose();
|
||||
});
|
||||
|
||||
// # Open team menu and click "View Members"
|
||||
cy.uiOpenTeamMenu('View members');
|
||||
|
||||
// # Search for username and check that expected nickname is present
|
||||
cy.get('.modal-title').should('be.visible');
|
||||
cy.get('#searchUsersInput').should('be.visible').type(testUser.first_name).wait(TIMEOUTS.ONE_SEC);
|
||||
cy.findByTestId('userListItemDetails').find('.more-modal__name').should('be.visible').then((el) => {
|
||||
expect(getInnerText(el)).equal(`@${testUser.username} - ${testUser.first_name} ${testUser.last_name} (${newNickname})`);
|
||||
});
|
||||
|
||||
// # Close Channel Members modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2060 Nickname and username styles', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Go to Settings > Display
|
||||
cy.uiOpenSettingsModal('Display');
|
||||
|
||||
// # Click Edit button beside Teammate Name Display
|
||||
cy.get('#name_formatEdit').click();
|
||||
|
||||
// # Choose Show first and last name
|
||||
cy.get('#name_formatFormatC').check();
|
||||
|
||||
// # Click Save button to save the settings
|
||||
cy.uiSave();
|
||||
cy.uiClose();
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Verify that the modal is open
|
||||
cy.get('#addUsersToChannelModal').should('be.visible').findByText(`Add people to ${channel.display_name}`);
|
||||
|
||||
// # Type into the input box to search for a user
|
||||
cy.get('#selectItems input').typeWithForce('sys').wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// * Verify that the username span contains the '@' symbol and the dark colour
|
||||
cy.get('#multiSelectList > div > .more-modal__details > .more-modal__name > span').should('contain', '@').and('have.css', 'color', 'rgb(63, 67, 80)');
|
||||
|
||||
// # Close modal
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
// # Open DM modal from the sidebar
|
||||
cy.uiAddDirectMessage().click();
|
||||
|
||||
// # Go to direct messages modal
|
||||
cy.get('.more-modal').should('be.visible').within(() => {
|
||||
cy.findByText('Direct Messages').click();
|
||||
cy.get('#selectItems input').typeWithForce('@');
|
||||
|
||||
// * Verify that the username span contains the '@' symbol and the dark colour
|
||||
cy.get('.more-modal__details > .more-modal__name').should('contain', '@').and('have.css', 'color', 'rgb(63, 67, 80)');
|
||||
});
|
||||
|
||||
// # Exit the modal
|
||||
cy.get('body').type('{esc}');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2061 Nickname should reset on cancel of edit', () => {
|
||||
// # Open 'Profile' modal and view the default 'Profile Settings'
|
||||
cy.uiOpenProfileModal('Profile Settings').within(() => {
|
||||
// # Open 'Nickname' setting
|
||||
cy.uiGetHeading('Nickname').click();
|
||||
|
||||
// # Clear the nickname text field contents
|
||||
cy.uiGetTextbox('Nickname').should('be.visible').clear().type('nickname_edit');
|
||||
|
||||
// # Cancel the edit of nickname
|
||||
cy.uiCancelButton().click();
|
||||
});
|
||||
|
||||
// # Click edit of nickname
|
||||
cy.uiGetHeading('Nickname').click();
|
||||
|
||||
// * Check if element is present and contains old nickname
|
||||
cy.uiGetTextbox('Nickname').should('be.visible').should('contain', '');
|
||||
|
||||
// # Close the modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2062 Clear nickname and save', () => {
|
||||
// # Open 'Profile' modal and view the default 'Profile Settings'
|
||||
cy.uiOpenProfileModal('Profile Settings').within(() => {
|
||||
// # Open 'Nickname' setting
|
||||
cy.uiGetHeading('Nickname').click();
|
||||
|
||||
// # Clear the nickname
|
||||
cy.uiGetTextbox('Nickname').clear();
|
||||
|
||||
// * Check if nickname element is present and it does not contain any nickname
|
||||
cy.uiGetTextbox('Nickname').should('contain', '');
|
||||
cy.uiSaveButton().click();
|
||||
});
|
||||
|
||||
// * Verify nickname help text is visible
|
||||
cy.get('#nicknameDesc').should('be.visible').should('contain', "Click 'Edit' to add a nickname");
|
||||
|
||||
// # Close the modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
function getInnerText(el: JQuery<HTMLElement>) {
|
||||
return el[0].innerText.replace(/\n/g, '').replace(/\s/g, ' ');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Profile Settings', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin().apiInitSetup({loginAfter: true}).its('user').as('user');
|
||||
});
|
||||
|
||||
it('MM-T2079 Can remove profile pic then choose different pic without saving in between', function() {
|
||||
// # Add a profile picture (aka "old profile picture")
|
||||
setInitialPicture(this.user);
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
// # Save the id of the old picture
|
||||
getProfilePictureId().as('idOld');
|
||||
|
||||
// # Open Profile > Profile Settings > Profile Picture > Edit
|
||||
cy.uiOpenProfileModal('Profile Settings').findByRole('button', {name: /picture edit/i}).click();
|
||||
|
||||
// # Click the X to remove the old profile picture but do not click save
|
||||
cy.findByRole('button', {name: /remove profile picture/i}).click();
|
||||
|
||||
// # Select a new profile picture and click Save
|
||||
cy.findByTestId('uploadPicture').attachFile('png-image-file.png');
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
cy.findByRole('button', {name: /close/i}).click();
|
||||
|
||||
// * Verify that new profile image exists and isn't equal to the old one
|
||||
getProfilePictureId().then((idNew) => expect(idNew).to.exist.and.not.to.be.equal(this.idOld));
|
||||
});
|
||||
|
||||
it('MM-T2075_1 Profile Picture: Cancel out of adding profile picture', () => {
|
||||
verifyProfilePictureDoesNotUpdateAfterCancel();
|
||||
});
|
||||
|
||||
it('MM-T2075_2 Profile Picture: Cancel out of changing profile picture ', function() {
|
||||
// # Add a profile picture (aka "old profile picture")
|
||||
setInitialPicture(this.user);
|
||||
|
||||
verifyProfilePictureDoesNotUpdateAfterCancel();
|
||||
});
|
||||
});
|
||||
|
||||
function getProfilePictureId() {
|
||||
return cy.uiGetProfileHeader().find('.Avatar').invoke('attr', 'src').then((urlString) => {
|
||||
const url = new URL(urlString!);
|
||||
const params = new URLSearchParams(url.search);
|
||||
return params.get('_');
|
||||
});
|
||||
}
|
||||
|
||||
function setInitialPicture(user: Cypress.UserProfile) {
|
||||
cy.apiUploadFile('image', 'mattermost-icon.png', {
|
||||
url: `/api/v4/users/${user.id}/image`,
|
||||
method: 'POST',
|
||||
successStatus: 200,
|
||||
});
|
||||
}
|
||||
|
||||
function verifyProfilePictureDoesNotUpdateAfterCancel() {
|
||||
let idOld: string | null | undefined;
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
// # Save the id of the old picture
|
||||
getProfilePictureId().then((id) => {
|
||||
idOld = id;
|
||||
});
|
||||
|
||||
// # Open Profile > Profile Settings > Profile Picture > Edit
|
||||
cy.uiOpenProfileModal('Profile Settings').findByRole('button', {name: /picture edit/i}).click();
|
||||
|
||||
// # Select a new profile picture
|
||||
cy.findByTestId('uploadPicture').attachFile('png-image-file.png');
|
||||
|
||||
cy.uiCancel().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
cy.uiClose();
|
||||
|
||||
// * Verify that 'new' profile image exists and equal to the old one
|
||||
getProfilePictureId().then((idNew) => expect(idNew).to.equal(idOld));
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
// TODO: Remove import once e2e/cypress/tests/support/index.ts is converted to Typescript.
|
||||
import 'cypress-file-upload';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Profile > Profile Settings > Profile Picture', () => {
|
||||
before(() => {
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2080 Can remove profile pic', () => {
|
||||
const customImageMatch = 'image?_=';
|
||||
|
||||
// * Verify the default profile image is shown first
|
||||
cy.uiGetProfileHeader().
|
||||
find('.Avatar').
|
||||
should('have.attr', 'src').
|
||||
and('not.include', customImageMatch);
|
||||
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Click "Edit" to the right of "Profile Picture"
|
||||
cy.get('#pictureEdit').should('be.visible').click();
|
||||
|
||||
// # Upload and save profile picture
|
||||
cy.findByTestId('uploadPicture').attachFile('mattermost-icon.png');
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Close modal
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
// * Profile picture is updated
|
||||
cy.uiGetProfileHeader().
|
||||
find('.Avatar').
|
||||
should('have.attr', 'src').
|
||||
and('include', customImageMatch);
|
||||
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Click "Edit" to the right of "Profile Picture"
|
||||
cy.get('#pictureEdit').should('be.visible').click();
|
||||
|
||||
// # Remove profile picture
|
||||
cy.findByTestId('removeSettingPicture').click();
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Check that we are back in the "General" section of the Profile
|
||||
cy.get('#pictureEdit').should('be.visible');
|
||||
|
||||
// # Close modal
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
// * Verify the default profile image is shown
|
||||
cy.uiGetProfileHeader().
|
||||
find('.Avatar').
|
||||
should('have.attr', 'src').
|
||||
and('not.include', customImageMatch);
|
||||
});
|
||||
|
||||
it('MM-T2077 Profile picture: non image file shows error', () => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Click "Edit" to the right of "Profile Picture"
|
||||
cy.get('#pictureEdit').should('be.visible').click();
|
||||
|
||||
// # Upload and save profile picture
|
||||
cy.findByTestId('uploadPicture').attachFile('txt-changed-as-png.png');
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Verify error message
|
||||
cy.get('.has-error').
|
||||
should('be.visible').
|
||||
and('contain', 'Image limits check failed. Resolution is too high.');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Settings > Sidebar > General > Edit', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
let testChannel: Cypress.Channel;
|
||||
let otherUser: Cypress.UserProfile;
|
||||
let offTopicUrl: string;
|
||||
|
||||
before(() => {
|
||||
// # Login as admin and visit off-topic
|
||||
cy.apiInitSetup().then(({user, team, channel, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
offTopicUrl = url;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(testChannel.id, otherUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
});
|
||||
|
||||
it('MM-T2050 Username cannot be blank', () => {
|
||||
// # Clear the username textfield contents
|
||||
cy.get('#usernameEdit').click();
|
||||
cy.get('#username').click().clear().blur();
|
||||
|
||||
// * Check if element is present and contains expected text values
|
||||
cy.get('#error_username').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
||||
|
||||
// # Click "x" button to close Profile modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2051 Username min 3 characters', () => {
|
||||
// # Edit the username field
|
||||
cy.get('#usernameEdit').click();
|
||||
|
||||
// # Add the username to textfield contents
|
||||
cy.get('#username').clear().type('te').blur();
|
||||
|
||||
// * Check if element is present and contains expected text values
|
||||
cy.get('#error_username').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
||||
|
||||
// # Click "x" button to close Profile modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2052 Username already taken', () => {
|
||||
// # Edit the username field
|
||||
cy.get('#usernameEdit').click();
|
||||
|
||||
// # Add the username to textfield contents
|
||||
cy.get('#username').clear().type(otherUser.username);
|
||||
cy.uiSave();
|
||||
|
||||
// * Check if element is present and contains expected text values
|
||||
cy.get('#serverError').should('be.visible').should('contain', 'An account with that username already exists.');
|
||||
|
||||
// # Click "x" button to close Profile modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2053 Username w/ dot, dash, underscore still searches', () => {
|
||||
let tempUser: Cypress.UserProfile;
|
||||
|
||||
// # Create a temporary user
|
||||
cy.apiCreateUser({prefix: 'temp'}).then(({user: user1}) => {
|
||||
tempUser = user1;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, tempUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(testChannel.id, tempUser.id);
|
||||
});
|
||||
|
||||
// # Login the temporary user
|
||||
cy.apiLogin(tempUser);
|
||||
cy.visit(offTopicUrl);
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Step 1
|
||||
// # Edit the username field
|
||||
cy.get('#usernameEdit').click();
|
||||
|
||||
// # Step 2
|
||||
// # Add the username to textfield contents
|
||||
const newTempUserName = 'a-' + tempUser.username;
|
||||
cy.get('#username').clear().type(newTempUserName);
|
||||
cy.uiSaveAndClose();
|
||||
|
||||
// # Step 3
|
||||
// * Verify that we've logged in as the temp user
|
||||
cy.visit(offTopicUrl);
|
||||
cy.uiOpenUserMenu().findByText(`@${newTempUserName}`).should('exist');
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
// # Step 4
|
||||
const text = `${newTempUserName} test message!`;
|
||||
|
||||
// # Post the user name mention declared earlier
|
||||
cy.postMessage(text);
|
||||
|
||||
// # Click on the @ button
|
||||
cy.uiGetRecentMentionButton().should('be.visible').click();
|
||||
|
||||
cy.get('#search-items-container').should('be.visible').within(() => {
|
||||
// * Ensure that the mentions are visible in the RHS
|
||||
cy.findByText(newTempUserName);
|
||||
cy.findByText(`${newTempUserName} test message!`);
|
||||
});
|
||||
|
||||
// # Click on the @ button to toggle off
|
||||
cy.uiGetRecentMentionButton().should('be.visible').click();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2054 Username cannot start with dot, dash, or underscore', () => {
|
||||
// # Edit the username field
|
||||
cy.get('#usernameEdit').click();
|
||||
|
||||
const prefixes = [
|
||||
'.',
|
||||
'-',
|
||||
'_',
|
||||
];
|
||||
|
||||
for (const prefix of prefixes) {
|
||||
// # Add username to textfield contents
|
||||
cy.get('#username').clear().type(prefix).type('{backspace}.').type(otherUser.username).blur();
|
||||
|
||||
// * Check if element is present and contains expected text values
|
||||
cy.get('#error_username').should('be.visible').should('contain', 'Username must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols \'.\', \'-\', and \'_\'.');
|
||||
}
|
||||
|
||||
// # Click "x" button to close Profile modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2055 Usernames that are reserved', () => {
|
||||
// # Edit the username field
|
||||
cy.get('#usernameEdit').click();
|
||||
|
||||
const usernames = [
|
||||
'all',
|
||||
'channel',
|
||||
'here',
|
||||
'matterbot',
|
||||
];
|
||||
|
||||
for (const username of usernames) {
|
||||
// # Add username to textfield contents
|
||||
cy.get('#username').clear().type(username).blur();
|
||||
|
||||
// * Check if element is present and contains expected text values
|
||||
cy.get('#error_username').should('be.visible').should('contain', 'This username is reserved, please choose a new one.');
|
||||
}
|
||||
|
||||
// # Click "x" button to close Profile modal
|
||||
cy.uiClose();
|
||||
});
|
||||
|
||||
it('MM-T2056 Username changes when viewed by other user', () => {
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Post a message in off-topic
|
||||
cy.postMessage('Testing username update');
|
||||
|
||||
// # Login as other user
|
||||
cy.apiLogin(otherUser);
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Get last post in off-topic for verifying username
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
// # Open profile popover
|
||||
cy.get('.user-popover').click();
|
||||
});
|
||||
|
||||
// * Verify username in profile popover
|
||||
cy.get('div.user-profile-popover').within(() => {
|
||||
cy.get('#userPopoverUsername').should('be.visible').and('contain', `${testUser.username}`);
|
||||
});
|
||||
});
|
||||
|
||||
// # Login as test user
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Open profile settings modal
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Open Full Name section
|
||||
cy.get('#usernameDesc').click();
|
||||
|
||||
const randomId = getRandomId();
|
||||
|
||||
// # Save username with a randomId
|
||||
cy.get('#username').clear().type(`${otherUser.username}-${randomId}`);
|
||||
|
||||
// # save form
|
||||
cy.uiSave();
|
||||
|
||||
cy.apiLogin(otherUser);
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Get last post in off-topic for verifying username
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.get('.user-popover').click();
|
||||
});
|
||||
|
||||
// * Verify that new username is in profile popover
|
||||
cy.get('div.user-profile-popover').within(() => {
|
||||
cy.get('#userPopoverUsername').should('be.visible').and('contain', `${otherUser.username}-${randomId}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
describe('Profile > Security > View Access History', () => {
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Security');
|
||||
|
||||
// * Check that the Security tab is loaded
|
||||
cy.get('#securityButton').should('be.visible');
|
||||
|
||||
// # Click the Security tab
|
||||
cy.get('#securityButton').click();
|
||||
});
|
||||
|
||||
it('MM-T2087 View Access History', () => {
|
||||
// # Click "View Access History" link
|
||||
cy.findByText('View Access History').should('be.visible').click();
|
||||
|
||||
// * Check that the Access History modal and table are visible
|
||||
cy.get('#accessHistoryModalLabel').should('be.visible');
|
||||
cy.get('.modal-body table').should('be.visible');
|
||||
|
||||
// * Check that the Access History table has the expected length
|
||||
cy.get('.modal-body table thead tr th span').should('be.visible').should('have.length', 4);
|
||||
|
||||
// * Check that the Access History table header has the expected columns
|
||||
cy.get('.modal-body table thead tr th span').eq(0).should('be.visible').should('contain', 'Timestamp');
|
||||
cy.get('.modal-body table thead tr th span').eq(1).should('be.visible').should('contain', 'Action');
|
||||
cy.get('.modal-body table thead tr th span').eq(2).should('be.visible').should('contain', 'IP Address');
|
||||
cy.get('.modal-body table thead tr th span').eq(3).should('be.visible').should('contain', 'Session ID');
|
||||
|
||||
// * Check that the Access History table body is visible and not empty
|
||||
cy.findByTestId('auditTableBody').should('be.visible').should('not.empty');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Profile > Security > View and Log Out of Active Sessions', () => {
|
||||
const platforms = [
|
||||
'Linux',
|
||||
'Macintosh',
|
||||
'Windows',
|
||||
'Native Desktop App',
|
||||
'iPhone Native App',
|
||||
'Android Native App',
|
||||
'iPhone Native Classic App',
|
||||
'Android Native Classic App',
|
||||
];
|
||||
const platformRegex = new RegExp(`${platforms.join('|')}`, 'g');
|
||||
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl, user}) => {
|
||||
testUser = user;
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Security');
|
||||
|
||||
// * Check that the Security tab is loaded
|
||||
cy.get('#securityButton').should('be.visible');
|
||||
|
||||
// # Click the Security tab
|
||||
cy.get('#securityButton').click();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2088 View and Logout of Active Sessions (Se)', () => {
|
||||
// # Go to "View and Logout of Active Sessions"
|
||||
cy.findByText('View and Log Out of Active Sessions').should('be.visible').click();
|
||||
|
||||
// * Verify an appropriate platform is shown
|
||||
cy.get('.report__platform').contains(platformRegex);
|
||||
|
||||
// # Click "More info" for the login session
|
||||
cy.get('.report__info a').should('be.visible').and('have.text', 'More info').click();
|
||||
|
||||
// * Verify info is displayed
|
||||
cy.get('.report__info').should('contain', 'Last activity:').
|
||||
and('contain', 'First time active:').
|
||||
and('contain', 'OS:').
|
||||
and('contain', 'Browser:').
|
||||
and('contain', 'Session ID:');
|
||||
|
||||
// # Click "Log Out" to log out of the specified session
|
||||
cy.findByText('Log Out').click();
|
||||
|
||||
// * Verify we have been logged out, and we can log back in
|
||||
cy.uiLogin(testUser);
|
||||
|
||||
// * Verify we have successfully logged back in
|
||||
cy.get('#channel_view', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @account_setting
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {newTestPassword} from '@/utils';
|
||||
|
||||
describe('Profile', () => {
|
||||
let siteName: string;
|
||||
let testUser: Cypress.UserProfile;
|
||||
let offTopic: string;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({user, offTopicUrl}) => {
|
||||
testUser = user;
|
||||
offTopic = offTopicUrl;
|
||||
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Reload the page to help run each test cleanly
|
||||
cy.reload();
|
||||
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Security');
|
||||
|
||||
// * Check that the Security tab is loaded
|
||||
cy.get('#securityButton').should('be.visible');
|
||||
|
||||
// # Click the Security tab
|
||||
cy.get('#securityButton').click();
|
||||
|
||||
// # Click "Edit" to the right of "Password"
|
||||
cy.get('#passwordEdit').should('be.visible').click();
|
||||
});
|
||||
|
||||
it('MM-T2085 Password: Valid values in password change fields allow the form to save successfully', () => {
|
||||
const newPassword = newTestPassword();
|
||||
|
||||
// # Enter valid values in password change fields
|
||||
enterPasswords(testUser.password, newPassword, newPassword);
|
||||
|
||||
// * Check that there are no errors
|
||||
cy.get('#error_currentPassword').should('not.exist');
|
||||
cy.get('#error_newPassword').should('not.exist');
|
||||
cy.get('#error_confirmPassword').should('not.exist');
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave();
|
||||
|
||||
// * Check that there are no errors
|
||||
cy.get('#serverError').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T2082 Password: New password confirmation mismatch produces error', () => {
|
||||
// # Enter mismatching passwords for new password and confirm fields
|
||||
enterPasswords(testUser.password, 'MismatchPass14!', 'MISmatchPass14!');
|
||||
|
||||
// * Verify for error message: "The new passwords you entered do not match."
|
||||
cy.get('#error_confirmPassword').should('be.visible').should('have.text', 'The new passwords you entered do not match.');
|
||||
});
|
||||
|
||||
it('MM-T2083 Password: Too few characters in new password produces error', () => {
|
||||
// # Enter a New password two letters long
|
||||
enterPasswords(testUser.password, 'pw', 'pw');
|
||||
|
||||
// * Verify for error message about password length
|
||||
cy.get('#error_newPassword').should('be.visible').should('have.text', 'Your password must be 14-72 characters long.');
|
||||
});
|
||||
|
||||
it('MM-T2084 Password: Cancel out of password changes causes no changes to be made', () => {
|
||||
const newPassword = 'Changed4Testing!';
|
||||
|
||||
// # Enter new valid passwords
|
||||
enterPasswords(testUser.password, newPassword, newPassword);
|
||||
|
||||
// # Click 'Cancel'
|
||||
cy.uiCancel();
|
||||
|
||||
// * Check that user is no longer in password edit page to verify the 'Cancel' action
|
||||
cy.get('#currentPassword').should('not.exist');
|
||||
cy.get('#passwordEdit').should('be.visible');
|
||||
|
||||
// # Logout
|
||||
cy.apiLogout();
|
||||
|
||||
// * Verify that user cannot login with the cancelled password
|
||||
cy.get('#input_loginId').type(testUser.username);
|
||||
cy.get('#input_password-input').type(newPassword);
|
||||
cy.get('#saveSetting').should('not.be.disabled').click();
|
||||
cy.findByText('The email/username or password is invalid.').should('be.visible');
|
||||
|
||||
// * Verify that user can successfully login with the old password
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopic);
|
||||
cy.get('#channelHeaderTitle').should('contain', 'Off-Topic');
|
||||
});
|
||||
|
||||
it.skip('MM-T2086 Password: Timestamp and email', () => {
|
||||
const newPassword = newTestPassword();
|
||||
|
||||
// # Enter valid values in password change fields
|
||||
enterPasswords(testUser.password, newPassword, newPassword);
|
||||
|
||||
// # Get current date
|
||||
const now = moment(Date.now());
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave();
|
||||
|
||||
// # Create expected date and time formats
|
||||
const date = now.format('MMM DD, YYYY');
|
||||
const time = now.format('hh:mm A');
|
||||
|
||||
// # Create expected timestamp
|
||||
const timestamp = `Last updated ${date} at ${time}`;
|
||||
|
||||
// * Verify that password description field contains valid timestamp
|
||||
cy.get('#passwordDesc').should('have.text', timestamp);
|
||||
|
||||
// # Wait for the mail to be sent out.
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
cy.getRecentEmail(testUser).then(({subject}) => {
|
||||
// * Verify the subject
|
||||
expect(subject).to.equal(
|
||||
`[${siteName}] Your password has been updated`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function enterPasswords(currentPassword: string, newPassword: string, confirmPassword: string) {
|
||||
// # Enter Current password
|
||||
cy.get('#currentPassword').should('be.visible').type(currentPassword);
|
||||
|
||||
// # Enter New password
|
||||
cy.get('#newPassword').should('be.visible').type(newPassword);
|
||||
|
||||
// # Retype New password incorrectly
|
||||
cy.get('#confirmPassword').should('be.visible').type(confirmPassword);
|
||||
|
||||
// # Click on the input and blur it
|
||||
cy.get('#currentPassword').should('be.visible').click().blur();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// - Requires openldap and keycloak running
|
||||
// - Requires keycloak certificate at fixtures folder
|
||||
// -> copy ./mattermost-server/build/docker/keycloak/keycloak.crt to ./mattermost-webapp/e2e/cypress/tests/fixtures/keycloak.crt
|
||||
// - Requires Cypress' chromeWebSecurity to be false
|
||||
|
||||
// Group: @channels @enterprise @ldap @saml @keycloak
|
||||
|
||||
import type {LdapUser} from '@/support/ldap_server_commands';
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {getRandomId} from '@/utils';
|
||||
import {getKeycloakServerSettings} from '@/utils/config';
|
||||
|
||||
describe('AD / LDAP', () => {
|
||||
let samlLdapUser: LdapUser;
|
||||
let testTeamId: string;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.apiRequireLicenseForFeature('LDAP', 'SAML');
|
||||
|
||||
// # Create new LDAP user
|
||||
cy.createLDAPUser().then((user) => {
|
||||
samlLdapUser = user;
|
||||
});
|
||||
|
||||
// # Create new team
|
||||
cy.apiCreateTeam('saml-team', 'SAML Team').then(({team}) => {
|
||||
testTeamId = team.id;
|
||||
});
|
||||
|
||||
const samlConfig = getKeycloakServerSettings();
|
||||
cy.apiUpdateConfig(samlConfig).then(() => {
|
||||
// # Require keycloak with realm setup
|
||||
cy.apiRequireKeycloak();
|
||||
|
||||
// # Upload certificate, overwrite existing
|
||||
cy.apiUploadSAMLIDPCert('keycloak.crt');
|
||||
|
||||
// # Create Keycloak user and login for the first time
|
||||
cy.keycloakCreateUsers([samlLdapUser] as LdapUser[]);
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// # Wait for the UI to be ready which indicates SAML registration is complete
|
||||
cy.findByText('Logout').click();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiGetUserByEmail(samlLdapUser.email).then(({user}) => {
|
||||
cy.apiAddUserToTeam(testTeamId, user.id);
|
||||
cy.apiRevokeUserSessions(user.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3666 - SAML / LDAP sync with ID Attribute', () => {
|
||||
// # Login via Keycloak
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// * Check the user settings
|
||||
cy.verifyAccountNameSettings(samlLdapUser.firstname, samlLdapUser.lastname);
|
||||
|
||||
// # Update LDAP user then sync
|
||||
const randomId = getRandomId();
|
||||
const newFirstName = `Firstname${randomId}`;
|
||||
const newLastName = `Lastname${randomId}`;
|
||||
cy.updateLDAPUser({
|
||||
...samlLdapUser,
|
||||
firstname: newFirstName,
|
||||
lastname: newLastName,
|
||||
});
|
||||
const admin = getAdminAccount();
|
||||
cy.runLdapSync(admin);
|
||||
|
||||
// # Reload the page
|
||||
cy.reload();
|
||||
|
||||
// * Check the user settings is in sync with the new attributes
|
||||
cy.verifyAccountNameSettings(newFirstName, newLastName);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// - Requires openldap and keycloak running
|
||||
// - Requires keycloak certificate at fixtures folder
|
||||
// -> copy ./mattermost-server/build/docker/keycloak/keycloak.crt to ./mattermost-webapp/e2e/cypress/tests/fixtures/keycloak.crt
|
||||
// - Requires Cypress' chromeWebSecurity to be false
|
||||
|
||||
// Group: @channels @enterprise @ldap @saml @keycloak
|
||||
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import type {LdapUser} from '@/support/ldap_server_commands';
|
||||
import {generateLDAPUser} from '@/support/ldap_server_commands';
|
||||
import {getKeycloakServerSettings} from '@/utils/config';
|
||||
|
||||
describe('AD / LDAP', () => {
|
||||
const nonLDAPUser = generateLDAPUser();
|
||||
|
||||
let samlLdapUser: LdapUser;
|
||||
let testTeamId: string;
|
||||
|
||||
before(() => {
|
||||
cy.createLDAPUser().then((user) => {
|
||||
samlLdapUser = user;
|
||||
});
|
||||
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.apiRequireLicenseForFeature('LDAP', 'SAML');
|
||||
|
||||
// # Create new LDAP user
|
||||
cy.createLDAPUser().then((user) => {
|
||||
samlLdapUser = user;
|
||||
});
|
||||
|
||||
// # Create new team
|
||||
cy.apiCreateTeam('saml-team', 'SAML Team').then(({team}) => {
|
||||
testTeamId = team.id;
|
||||
});
|
||||
|
||||
const samlConfig = getKeycloakServerSettings();
|
||||
cy.apiUpdateConfig(samlConfig).then(() => {
|
||||
// # Require keycloak with realm setup
|
||||
cy.apiRequireKeycloak();
|
||||
|
||||
// # Upload certificate, overwrite existing
|
||||
cy.apiUploadSAMLIDPCert('keycloak.crt');
|
||||
|
||||
// # Create Keycloak user and login for the first time
|
||||
cy.keycloakCreateUsers([samlLdapUser, nonLDAPUser] as LdapUser[]);
|
||||
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// # Wait for the UI to be ready which indicates SAML registration is complete
|
||||
cy.findByText('Logout').click();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3664 - SAML User, Not in LDAP', () => {
|
||||
// # Login to Keycloak
|
||||
cy.doKeycloakLogin(nonLDAPUser);
|
||||
|
||||
// * Should render an error since user is not registered in LDAP
|
||||
cy.findByText('User not registered on AD/LDAP server.');
|
||||
|
||||
// # Run LDAP Sync
|
||||
const admin = getAdminAccount();
|
||||
cy.runLdapSync(admin);
|
||||
|
||||
// # REgister as LDAP user
|
||||
cy.createLDAPUser({user: nonLDAPUser});
|
||||
|
||||
// # Add user to team
|
||||
cy.apiAdminLogin();
|
||||
cy.apiGetUserByEmail(nonLDAPUser.email).then(({user}) => {
|
||||
cy.apiAddUserToTeam(testTeamId, user.id);
|
||||
});
|
||||
|
||||
// # Login to Keycloak
|
||||
cy.doKeycloakLogin(nonLDAPUser);
|
||||
|
||||
// * Should successfully login and view the default channel
|
||||
cy.postMessage('hello');
|
||||
|
||||
// * Check user setting is in sync with LDAP
|
||||
cy.verifyAccountNameSettings(nonLDAPUser.firstname, nonLDAPUser.lastname);
|
||||
});
|
||||
|
||||
it('MM-T3665 - Deactivate user in SAML', () => {
|
||||
// # Add user to team
|
||||
cy.apiAdminLogin();
|
||||
cy.apiGetUserByEmail(samlLdapUser.email).then(({user}) => {
|
||||
cy.apiAddUserToTeam(testTeamId, user.id);
|
||||
});
|
||||
|
||||
// # Login to Keycloak
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// * Should successfully login and view the default channel
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Suspend Keycloak user
|
||||
cy.keycloakSuspendUser(samlLdapUser.email);
|
||||
|
||||
// # Login to Keycloak
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// * Verify login failed
|
||||
cy.verifyKeycloakLoginFailed();
|
||||
|
||||
// # Activate user in keycloak
|
||||
cy.keycloakUnsuspendUser(samlLdapUser.email);
|
||||
|
||||
// # Login again
|
||||
cy.findByText('Password').type(samlLdapUser.password);
|
||||
cy.findAllByText('Log In').last().click();
|
||||
|
||||
// * Should successfully login and view the default channel
|
||||
cy.postMessage('hello');
|
||||
|
||||
// * Check the user settings
|
||||
cy.verifyAccountNameSettings(samlLdapUser.firstname, samlLdapUser.lastname);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// - Requires openldap and keycloak running
|
||||
// - Requires keycloak certificate at fixtures folder
|
||||
// -> copy ./mattermost-server/build/docker/keycloak/keycloak.crt to ./mattermost-webapp/e2e/cypress/tests/fixtures/keycloak.crt
|
||||
// - Requires Cypress' chromeWebSecurity to be false
|
||||
|
||||
// Group: @channels @enterprise @ldap @saml @keycloak
|
||||
|
||||
import type {LdapUser} from '@/support/ldap_server_commands';
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {getRandomId} from '@/utils';
|
||||
import {getKeycloakServerSettings} from '@/utils/config';
|
||||
|
||||
describe('AD / LDAP', () => {
|
||||
const admin = getAdminAccount();
|
||||
const samlConfig = getKeycloakServerSettings();
|
||||
|
||||
let samlLdapUser: LdapUser;
|
||||
let testTeamId: string;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.apiRequireLicenseForFeature('LDAP', 'SAML');
|
||||
|
||||
// # Create new LDAP user
|
||||
cy.createLDAPUser().then((user) => {
|
||||
samlLdapUser = user;
|
||||
});
|
||||
|
||||
// # Create new team
|
||||
cy.apiCreateTeam('saml-team', 'SAML Team').then(({team}) => {
|
||||
testTeamId = team.id;
|
||||
});
|
||||
|
||||
cy.apiUpdateConfig(samlConfig).then(() => {
|
||||
// # Require keycloak with realm setup
|
||||
cy.apiRequireKeycloak();
|
||||
|
||||
// # Upload certificate, overwrite existing
|
||||
cy.apiUploadSAMLIDPCert('keycloak.crt');
|
||||
|
||||
// # Create Keycloak user and login for the first time
|
||||
cy.keycloakCreateUsers([samlLdapUser] as LdapUser[]);
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// # Wait for the UI to be ready which indicates SAML registration is complete
|
||||
cy.findByText('Logout').click();
|
||||
|
||||
// # Add user to team
|
||||
cy.apiAdminLogin();
|
||||
cy.apiGetUserByEmail(samlLdapUser.email).then(({user}) => {
|
||||
cy.apiAddUserToTeam(testTeamId, user.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3013_1 - SAML LDAP Sync Off, user attributes pulled from SAML', () => {
|
||||
// # Login to Keycloak
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// * Check the user settings
|
||||
cy.verifyAccountNameSettings(samlLdapUser.firstname, samlLdapUser.lastname);
|
||||
|
||||
// # Run LDAP Sync
|
||||
cy.runLdapSync(admin);
|
||||
|
||||
// Refresh make sure user not logged out.
|
||||
cy.reload();
|
||||
|
||||
// * Check the user settings
|
||||
cy.verifyAccountNameSettings(samlLdapUser.firstname, samlLdapUser.lastname);
|
||||
});
|
||||
|
||||
it('MM-T3013_2 - SAML LDAP Sync On, user attributes pulled from LDAP', () => {
|
||||
const testConfig = {
|
||||
...samlConfig,
|
||||
SamlSettings: {
|
||||
...samlConfig.SamlSettings,
|
||||
EnableSyncWithLdap: true,
|
||||
},
|
||||
};
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig(testConfig);
|
||||
|
||||
// # Login to Keycloak
|
||||
cy.doKeycloakLogin(samlLdapUser);
|
||||
|
||||
// * Check the user settings
|
||||
cy.verifyAccountNameSettings(samlLdapUser.firstname, samlLdapUser.lastname);
|
||||
|
||||
// # Update LDAP user then sync
|
||||
const randomId = getRandomId();
|
||||
const newFirstName = `Firstname${randomId}`;
|
||||
const newLastName = `Lastname${randomId}`;
|
||||
cy.updateLDAPUser({
|
||||
...samlLdapUser,
|
||||
firstname: newFirstName,
|
||||
lastname: newLastName,
|
||||
});
|
||||
cy.runLdapSync(admin);
|
||||
|
||||
// # Refresh make sure user not logged out.
|
||||
cy.reload();
|
||||
|
||||
// * Check the user settings
|
||||
cy.verifyAccountNameSettings(newFirstName, newLastName);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Archived channels', () => {
|
||||
before(() => {
|
||||
// # Login as test user and visit created channel
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, channel}) => {
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1720 Cannot add reactions to existing reactions', () => {
|
||||
const messageText = 'Test add reaction in archive channels';
|
||||
|
||||
// # Post a message in the channel
|
||||
cy.postMessage(messageText);
|
||||
|
||||
// # Get the last post for reference of ID
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # Click the add reaction icon
|
||||
cy.clickPostReactionIcon(postId);
|
||||
|
||||
// # Search for the emoji in emoji search bar
|
||||
cy.findByPlaceholderText('Search emojis').should('exist').and('be.visible').
|
||||
clear().
|
||||
type('slightly_frowning_face', {delay: TIMEOUTS.QUARTER_SEC, force: true});
|
||||
|
||||
// # Choose "slightly_frowning_face" emoji
|
||||
cy.clickEmojiInEmojiPicker('slightly_frowning_face');
|
||||
|
||||
// * Should have added the reaction with count
|
||||
cy.get(`#postReaction-${postId}-slightly_frowning_face .Reaction__number--display`).should('have.text', '1');
|
||||
|
||||
// * Should have add reaction button
|
||||
cy.get(`#addReaction-${postId}`).should('exist');
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// * Should not have add reaction button
|
||||
cy.get(`#addReaction-${postId}`).should('not.exist');
|
||||
|
||||
// # Click on existing "slightly_frowning_face" emoji
|
||||
cy.get(`#postReaction-${postId}-slightly_frowning_face`).click();
|
||||
|
||||
// * Reaction count should not have incremented
|
||||
cy.get(`#postReaction-${postId}-slightly_frowning_face .Reaction__number--display`).should('have.text', '1');
|
||||
|
||||
// # Remove focus so we can open RHS
|
||||
cy.get('#channel_view').click();
|
||||
|
||||
// # Open RHS for the post
|
||||
cy.clickPostCommentIcon(postId);
|
||||
|
||||
cy.get(`#rhsPost_${postId}`).within(() => {
|
||||
// * Should not have add reaction button
|
||||
cy.get(`#addReaction-${postId}`).should('not.exist');
|
||||
|
||||
// # Click on existing "slightly_frowning_face" emoji
|
||||
cy.get(`#postReaction-${postId}-slightly_frowning_face`).click();
|
||||
|
||||
// * Reaction count should not have incremented
|
||||
cy.get(`#postReaction-${postId}-slightly_frowning_face .Reaction__number--display`).should('have.text', '1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Archive channel header spec', () => {
|
||||
before(() => {
|
||||
cy.visit('/admin_console/user_management/permissions/system_scheme');
|
||||
|
||||
// # Click reset to defaults and confirm
|
||||
cy.findByTestId('resetPermissionsToDefault').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Ensure permissions for converting channel to private is enabled
|
||||
cy.findByTestId('all_users-public_channel-convert_public_channel_to_private-checkbox').then((el) => {
|
||||
if (!el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave();
|
||||
cy.uiSaveButton().should('be.visible');
|
||||
|
||||
// # Login as test user and visit create channel
|
||||
cy.apiInitSetup({loginAfter: true}).then(({channelUrl}) => {
|
||||
cy.visit(channelUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1717 Archived channel details cannot be edited', () => {
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * The dropdown menu of the channel header should be visible;
|
||||
cy.get('#channelLeaveChannel').should('be.visible');
|
||||
|
||||
// * Channel Settings submenu should be visible;
|
||||
cy.get('#channelSettings').should('be.visible');
|
||||
|
||||
// * Archive channel menu option should be visible;
|
||||
cy.get('#channelArchiveChannel').should('be.visible');
|
||||
|
||||
// * Members menu option should be visible;
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// * Notification preferences option should be visible;
|
||||
cy.get('#channelNotificationPreferences').should('be.visible');
|
||||
|
||||
// # Close the channel dropdown menu using keyboard escape
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * Archive channel menu option should not be visible;
|
||||
cy.get('#channelArchiveChannel').should('not.exist');
|
||||
|
||||
// * Channel Settings submenu should be visible;
|
||||
cy.get('#channelSettings').should('not.exist');
|
||||
|
||||
// * Members menu option should be visible;
|
||||
// as it now displays in RHS
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// * Notification preferences option should not be visible;
|
||||
cy.get('#channelNotificationPreferences').should('not.exist');
|
||||
|
||||
// # Close the channel dropdown menu using keyboard escape
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Archive channel members spec', () => {
|
||||
before(() => {
|
||||
// # Login as test user and visit create channel
|
||||
cy.apiInitSetup({loginAfter: true, promoteNewUserAsAdmin: true}).then(({team, channel}) => {
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1719 Archived channel members cannot be managed', () => {
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * Members menu option should be visible;
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// # Close the channel dropdown menu
|
||||
cy.get('body').type('{esc}{esc}');
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// # click on view members menu option;
|
||||
cy.get('#channelMembers').should('be.visible').click();
|
||||
|
||||
// * Ensure there are no options to change channel roles or membership
|
||||
cy.uiGetRHS().findByText('Manage').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Archived channels', () => {
|
||||
before(() => {
|
||||
// # Login as test user and visit created channel
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, channel}) => {
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1718 Reaction icon should not be visible for archived channel posts', () => {
|
||||
const messageText = 'Test archive reaction';
|
||||
|
||||
// # Post a message in the channel
|
||||
cy.postMessage(messageText);
|
||||
|
||||
// # Get the last post for reference of ID
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # Click on post dot menu so we can check for reaction icon
|
||||
cy.clickPostDotMenu();
|
||||
|
||||
// * Reaction icon should be visible as channel is not archived
|
||||
cy.wait(TIMEOUTS.HALF_SEC).get(`#CENTER_reaction_${postId}`).should('be.visible');
|
||||
|
||||
// # Archive the channel
|
||||
cy.get('body').type('{esc}');
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Click on post dot menu so we can check for reaction icon
|
||||
cy.clickPostDotMenu(postId);
|
||||
|
||||
// * Reaction icon on center channel post should not be visible anymore
|
||||
cy.wait(TIMEOUTS.HALF_SEC).get(`#CENTER_reaction_${postId}`).should('not.exist');
|
||||
|
||||
// # Remove focus so we can open RHS
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
// # Open RHS for the post
|
||||
cy.clickPostCommentIcon(postId);
|
||||
|
||||
// # Click on post dot menu so we can check for reaction icon
|
||||
cy.clickPostDotMenu(postId, 'RHS_ROOT');
|
||||
|
||||
// * Reaction icon on post in RHS should not be visible anymore
|
||||
cy.wait(TIMEOUTS.HALF_SEC).get(`#RHS_ROOT_reaction_${postId}`).should('not.exist');
|
||||
|
||||
// # Search for "Test archive reaction"
|
||||
cy.get('body').type('{esc}');
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().should('be.visible').type(messageText).type('{enter}');
|
||||
|
||||
// # Click on post dot menu so we can check for reaction icon
|
||||
cy.clickPostDotMenu(postId, 'SEARCH');
|
||||
|
||||
// * Reaction icon on post in search results should not be visible anymore
|
||||
cy.wait(TIMEOUTS.HALF_SEC).get(`#searchResult_reaction_${postId}`).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import {createArchivedChannel} from './helpers';
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('archive channel search tests', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testChannel: Cypress.Channel;
|
||||
let testUser: Cypress.UserProfile;
|
||||
const testArchivedMessage = `this is an archived post ${getRandomId()}`;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit test channel
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser({prefix: 'second'}).then(({user: second}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, second.id);
|
||||
});
|
||||
cy.visit(`/${team.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1707 Unarchived channels can be searched the same as before they where archived', () => {
|
||||
// # Post some text in the channel such as "Pineapple"
|
||||
const messageText = `pineapple ${getRandomId()}`;
|
||||
|
||||
// # Archive the channel
|
||||
createArchivedChannel({prefix: 'pineapple-'}, [messageText]).then(() => {
|
||||
// # Unarchive the channel
|
||||
cy.uiUnarchiveChannel().then(() => {
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
cy.contains('#channelHeaderTitle', 'Off-Topic');
|
||||
cy.postMessage(getRandomId());
|
||||
|
||||
// # Search for the post from step 1')
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().clear().type(`${messageText}{enter}`);
|
||||
|
||||
// * Post is returned by search, since it's not archived anymore
|
||||
cy.get('#searchContainer').should('be.visible');
|
||||
cy.get('.search-item-snippet').first().contains(messageText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1708 An archived channel can be searched since archived channels are always viewable', () => {
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Open a channel other than Town Square
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
cy.contains('#channelHeaderTitle', 'Off-Topic');
|
||||
|
||||
// # Create or locate a channel you're a member of
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.get('#channelHeaderTitle').should('be.visible');
|
||||
|
||||
// # Post distinctive text in the channel such as "I like pineapples"
|
||||
cy.postMessageAs({sender: testUser, message: testArchivedMessage, channelId: testChannel.id});
|
||||
|
||||
// # Select Archive Channel from the header menu
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Search for the archived message
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().clear().type(`${testArchivedMessage}{enter}`);
|
||||
|
||||
// * Post is returned by search since archived channels are always viewable
|
||||
cy.get('#searchContainer').should('be.visible');
|
||||
cy.get('.search-item-snippet').first().contains(testArchivedMessage);
|
||||
});
|
||||
|
||||
it('MM-T1709 Archive a channel while search results are displayed in RHS', () => {
|
||||
const messageText = `search ${getRandomId()} pineapples`;
|
||||
|
||||
// # Post a unique string of text in a channel
|
||||
cy.uiCreateChannel({prefix: 'archive-while-searching'}).then(() => {
|
||||
cy.postMessage(messageText);
|
||||
|
||||
// # Search for the string of text from step 1
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().clear().type(`${messageText}{enter}`);
|
||||
|
||||
// * Post is returned by search, since it's not archived anymore
|
||||
cy.get('#searchContainer').should('be.visible');
|
||||
|
||||
// # Observe the post is displayed in RHS results
|
||||
cy.get('.search-item-snippet').first().should('contain.text', messageText);
|
||||
|
||||
// # While the RHS is still open with the results, archive the channel the post was made in
|
||||
cy.uiArchiveChannel();
|
||||
cy.get('#searchContainer').should('be.visible');
|
||||
|
||||
// * Search results should remain visible since archived channels are always viewable
|
||||
cy.get('.search-item-snippet').first().should('contain.text', messageText);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1710 archived channels are not listed on the "in:" autocomplete', () => {
|
||||
// # Archive a channel and make a mental note of the channel name
|
||||
// # Type "in:" and note the list of channels that appear
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().clear().type(`in:${testChannel.name}`);
|
||||
cy.findByTestId(testChannel.name).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
export function createArchivedChannel(channelOptions: Record<string, unknown>, messages: string[], memberUsernames?: string[]) {
|
||||
return cy.uiCreateChannel(channelOptions).then((newChannel) => {
|
||||
if (memberUsernames) {
|
||||
cy.uiAddUsersToCurrentChannel(memberUsernames);
|
||||
}
|
||||
|
||||
messages.forEach((message) => {
|
||||
cy.postMessage(message);
|
||||
});
|
||||
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Wait for sometime and verify that the archived message is shown
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
cy.get('#channelArchivedMessage', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
return cy.wrap({name: newChannel.name});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @channel
|
||||
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Archived channels', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1682 Join an archived public channel by selecting a permalink to one of its posts', () => {
|
||||
// # Log in as another user
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
// # Make a post in the new channel and get a permalink for it
|
||||
cy.postMessageAs({
|
||||
sender: getAdminAccount(),
|
||||
message: 'post',
|
||||
channelId: channel.id,
|
||||
}).then((post) => {
|
||||
const permalink = `/${testTeam.name}/pl/${post.id}`;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Log out and back in as the test user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Visit the permalink
|
||||
cy.visit(permalink);
|
||||
|
||||
// * Verify that we've logged in as the test user
|
||||
verifyUsername(testUser.username);
|
||||
|
||||
verifyViewingArchivedChannel(channel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1683 Join an archived channel by selecting a link to channel', () => {
|
||||
// # Log in as another user
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
const channelLink = `/${testTeam.name}/channels/${channel.name}`;
|
||||
|
||||
// # Visit the channel and archive it
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Visit off-topic
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Make a post linking to the archived channel
|
||||
const linkText = `link ${getRandomId()}`;
|
||||
cy.getCurrentChannelId().then((currentChannelId) => {
|
||||
cy.postMessageAs({
|
||||
sender: getAdminAccount(),
|
||||
message: `This is a link: [${linkText}](${channelLink})`,
|
||||
channelId: currentChannelId,
|
||||
});
|
||||
});
|
||||
|
||||
// # Log out and back in as the test user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Visit off-topic
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// * Verify that we've logged in as the test user
|
||||
verifyUsername(testUser.username);
|
||||
|
||||
// * Verify that the link exists and then click on it
|
||||
cy.contains('a', linkText).should('be.visible').click();
|
||||
|
||||
verifyViewingArchivedChannel(channel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifyViewingArchivedChannel(channel: Cypress.Channel) {
|
||||
// * Verify that we've switched to the correct channel and that the header contains the archived icon
|
||||
cy.get('#channelHeaderTitle').should('contain', channel.display_name);
|
||||
cy.findByTestId('channel-header-archive-icon').should('be.visible');
|
||||
|
||||
// * Verify that the channel is visible in the sidebar with the archived icon
|
||||
cy.get(`#sidebarItem_${channel.name}`).should('be.visible').
|
||||
find('.icon-archive-outline').should('be.visible');
|
||||
|
||||
// * Verify that the archived channel banner is visible at the bottom of the channel view
|
||||
cy.get('#channelArchivedMessage').should('be.visible');
|
||||
}
|
||||
|
||||
function verifyUsername(username: string) {
|
||||
// * Verify that we've logged in as the test user
|
||||
cy.uiOpenUserMenu().findByText(`@${username}`);
|
||||
|
||||
// # Close the user menu
|
||||
cy.get('body').type('{esc}');
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Leaving archived channels', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team}) => {
|
||||
testTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1685 User can leave archived public channel', () => {
|
||||
// # Create a new public channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel();
|
||||
|
||||
// * Verify that we've switched to Town Square
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1686 User can leave archived private channel', () => {
|
||||
// # Create a new private channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel', 'P').then(({channel}) => {
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel(true);
|
||||
|
||||
// * Verify that we've switched to Town Square
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @system_console @authentication
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId, newTestPassword} from '@/utils';
|
||||
|
||||
describe('Authentication', () => {
|
||||
const restrictCreationToDomains = 'mattermost.com, test.com';
|
||||
let testUser: Cypress.UserProfile;
|
||||
let testUserAlreadyInTeam: Cypress.UserProfile;
|
||||
let testTeam: Cypress.Team;
|
||||
|
||||
before(() => {
|
||||
// # Do email test if setup properly
|
||||
cy.shouldHaveEmailEnabled();
|
||||
|
||||
cy.apiInitSetup().then(({user, team}) => {
|
||||
testUserAlreadyInTeam = user;
|
||||
testTeam = team;
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
testUser = newUser;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Log in as a admin.
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
it('MM-T1756 - Restrict Domains - Multiple - success', () => {
|
||||
// # Set restricted domain
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: restrictCreationToDomains,
|
||||
},
|
||||
});
|
||||
|
||||
// # Log out and go to login page
|
||||
cy.apiLogout();
|
||||
cy.visit('/login');
|
||||
|
||||
// * Assert that create account button is visible
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@mattermost.com`);
|
||||
|
||||
cy.get('#input_password-input').type(newTestPassword());
|
||||
|
||||
cy.get('#input_name').clear().type(`test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was created successfully and we are at the select team page
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1757 - Restrict Domains - Multiple - fail', () => {
|
||||
// # Set restricted domain
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: restrictCreationToDomains,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiLogin(testUserAlreadyInTeam);
|
||||
cy.visit('/');
|
||||
|
||||
// # Open Profile
|
||||
cy.uiOpenProfileModal('Profile Settings');
|
||||
|
||||
// # Click "Edit" to the right of "Email"
|
||||
cy.get('#emailEdit').should('be.visible').click();
|
||||
|
||||
// # Type new email
|
||||
cy.get('#primaryEmail').should('be.visible').type('user-123123@example.com');
|
||||
cy.get('#confirmEmail').should('be.visible').type('user-123123@example.com');
|
||||
cy.get('#currentPassword').should('be.visible').type(testUser.password);
|
||||
|
||||
// # Save the settings
|
||||
cy.uiSave().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Assert an error exist and is what is expected
|
||||
cy.findByText('The email you provided does not belong to an accepted domain. Please contact your administrator or sign up with a different email.').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1758 - Restrict Domains - Team invite closed team', () => {
|
||||
// # Set restricted domain
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: restrictCreationToDomains,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
cy.visit(`/signup_user_complete/?id=${testTeam.invite_id}`);
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
|
||||
|
||||
cy.get('#input_password-input').type(newTestPassword());
|
||||
|
||||
cy.get('#input_name').clear().type(`test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was not created successfully
|
||||
cy.get('.AlertBanner__title').scrollIntoView().should('be.visible');
|
||||
cy.findByText('The email you provided does not belong to an accepted domain. Please contact your administrator or sign up with a different email.').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1763 - Security - Signup: Email verification not required, user immediately sees Town Square', () => {
|
||||
// # Disable email verification
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
RequireEmailVerification: false,
|
||||
},
|
||||
}).then(({config}) => {
|
||||
// # Log out and go to front page
|
||||
cy.apiLogout();
|
||||
cy.visit('/login');
|
||||
|
||||
// * Assert that create account button is visible
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
const username = `Test${getRandomId()}`;
|
||||
const email = `${username.toLowerCase()}@example.com`;
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(email);
|
||||
|
||||
cy.get('#input_password-input').type(newTestPassword());
|
||||
|
||||
cy.get('#input_name').clear().type(username);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was created successfully and we are on the team joining page
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
cy.getRecentEmail({username, email}).then(({subject}) => {
|
||||
// * Verify the subject
|
||||
expect(subject).to.include(`[${config.TeamSettings.SiteName}] You joined`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1765 - Authentication - Email - Creation with email = false', () => {
|
||||
// # Disable user sign up and enable GitLab
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
EnableSignUpWithEmail: false,
|
||||
},
|
||||
GitLabSettings: {
|
||||
Enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
cy.visit(`/signup_user_complete/?id=${testTeam.invite_id}`);
|
||||
|
||||
cy.findByText('Create your account with one of the following:').should('exist');
|
||||
cy.findByText('GitLab', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
// * Email and Password option does not exist
|
||||
cy.findByText('Email address').should('not.exist');
|
||||
cy.findByText('Choose a Password').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @system_console @authentication @mfa
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Authentication', () => {
|
||||
beforeEach(() => {
|
||||
// # Log in as admin.
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
it('MM-T1771 - Minimum password length error field shows below minimum and above 72', () => {
|
||||
cy.visit('/admin_console/authentication/password');
|
||||
|
||||
cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('88');
|
||||
|
||||
cy.uiSave();
|
||||
|
||||
// * Ensure error appears when saving a password outside of the limits
|
||||
// Note: minimum is 5 on non-FIPS builds and 14 on FIPS builds
|
||||
cy.contains(/Minimum password length must be a whole number greater than or equal to (5|14) and less than or equal to 72\./, {timeout: TIMEOUTS.ONE_MIN}).
|
||||
should('exist').
|
||||
and('be.visible');
|
||||
|
||||
cy.findByPlaceholderText('E.g.: "5"').clear().type('3');
|
||||
|
||||
cy.uiSave();
|
||||
|
||||
// * Ensure error appears when saving a password outside of the limits
|
||||
cy.contains(/Minimum password length must be a whole number greater than or equal to (5|14) and less than or equal to 72\./, {timeout: TIMEOUTS.ONE_MIN}).
|
||||
should('exist').
|
||||
and('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1772 - Change minimum password length, verify help text and error message', () => {
|
||||
cy.visit('/admin_console/authentication/password');
|
||||
|
||||
cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('15');
|
||||
|
||||
cy.uiSave();
|
||||
|
||||
cy.findByText('Your password must be 15-72 characters long.').should('be.visible');
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
|
||||
|
||||
cy.get('#input_name').clear().type(`BestUsername${getRandomId()}`);
|
||||
|
||||
cy.get('#input_password-input').clear().type('less');
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert the error is what is expected;
|
||||
cy.findByText('Your password must be 15-72 characters long.').should('be.visible');
|
||||
|
||||
cy.get('#input_password-input').clear().type('GreaterThan15Chr!');
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert that we are not shown an MFA screen and instead a Teams You Can join page
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1773 - Minimum password length field resets to default after saving invalid value', () => {
|
||||
cy.visit('/admin_console/authentication/password');
|
||||
|
||||
cy.findByPlaceholderText('E.g.: "5"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('20');
|
||||
|
||||
cy.uiSave();
|
||||
|
||||
cy.reload();
|
||||
|
||||
// * Ensure the limit 20 appears
|
||||
cy.findByPlaceholderText('E.g.: "5"').invoke('val').should('equal', '20');
|
||||
cy.findByPlaceholderText('E.g.: "5"').clear();
|
||||
|
||||
cy.uiSave();
|
||||
|
||||
// Reload to see the actual server state, since on FIPS builds saving
|
||||
// the webapp default of 5 is rejected (below the FIPS minimum of 14).
|
||||
cy.reload();
|
||||
|
||||
// * Ensure the field reflects the server's current minimum password length
|
||||
cy.apiGetConfig().then(({config: {PasswordSettings}}) => {
|
||||
cy.findByPlaceholderText('E.g.: "5"').invoke('val').should('equal', String(PasswordSettings.MinimumLength));
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1774 - Select all Password Requirements, verify help text and error on bad password', () => {
|
||||
cy.apiUpdateConfig({
|
||||
PasswordSettings: {
|
||||
Lowercase: true,
|
||||
Number: true,
|
||||
Uppercase: true,
|
||||
Symbol: true,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
|
||||
|
||||
cy.get('#input_name').clear().type(`BestUsernameInTheWorld${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
['NOLOWERCASE12345!', 'nouppercase12345!', 'NoNumberHere!!!', 'NoSymbol1234567'].forEach((option) => {
|
||||
cy.get('#input_password-input').clear().type(option);
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert the error is what is expected;
|
||||
cy.findByText('Your password must be 14-72 characters long and include both lowercase and uppercase letters, numbers, and special characters.').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1777 - Multi-factor Authentication option hidden in Profile when disabled', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableMultifactorAuthentication: false,
|
||||
},
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Security');
|
||||
|
||||
// * Assert that Multi-factor Authentication text does not exist
|
||||
cy.findByText('Multi-factor Authentication').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1779 - Multi-factor Authentication option appears in Profile when enabled', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableMultifactorAuthentication: true,
|
||||
},
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
// # Go to Profile
|
||||
cy.uiOpenProfileModal('Security');
|
||||
|
||||
// * Assert that Multi-factor Authentication text does exist
|
||||
cy.findByText('Multi-factor Authentication').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1780 - Multi-factor Authentication false: User can log in without being prompted for MFA', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableMultifactorAuthentication: true,
|
||||
EnforceMultifactorAuthentication: false,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
cy.apiLogout();
|
||||
|
||||
// # Login as a new user and visit default page
|
||||
cy.apiLogin(newUser);
|
||||
cy.visit('/');
|
||||
|
||||
// * Assert that we are not shown a MFA screen and instead a Teams You Can join page
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @system_console @authentication
|
||||
|
||||
describe('Authentication', () => {
|
||||
beforeEach(() => {
|
||||
// # Log in as a admin.
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.apiAdminLogin({failOnStatusCode: false});
|
||||
cy.apiUpdateConfig({});
|
||||
});
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
title: 'MM-T1767 - Email signin false Username signin true',
|
||||
signinWithEmail: false,
|
||||
signinWithUsername: true,
|
||||
},
|
||||
{
|
||||
title: 'MM-T1768 - Email signin true Username signin true',
|
||||
signinWithEmail: true,
|
||||
signinWithUsername: true,
|
||||
},
|
||||
{
|
||||
title: 'MM-T1769 - Email signin true Username signin false',
|
||||
signinWithEmail: true,
|
||||
signinWithUsername: false,
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach(({title, signinWithEmail, signinWithUsername}) => {
|
||||
it(title, () => {
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
EnableSignInWithEmail: signinWithEmail,
|
||||
EnableSignInWithUsername: signinWithUsername,
|
||||
},
|
||||
LdapSettings: {
|
||||
Enable: false,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// # Go to front page
|
||||
cy.visit('/login');
|
||||
|
||||
// # Remove autofocus from login input
|
||||
cy.focused().blur();
|
||||
|
||||
let expectedPlaceholderText;
|
||||
if (signinWithEmail && signinWithUsername) {
|
||||
expectedPlaceholderText = 'Email or Username';
|
||||
} else if (signinWithEmail) {
|
||||
expectedPlaceholderText = 'Email';
|
||||
} else {
|
||||
expectedPlaceholderText = 'Username';
|
||||
}
|
||||
|
||||
// * Make sure the username field has expected placeholder text
|
||||
cy.findByPlaceholderText(expectedPlaceholderText).should('exist').and('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,330 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @system_console @authentication
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {reUrl, getRandomId, newTestPassword} from '@/utils';
|
||||
|
||||
describe('Authentication', () => {
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
// # Do email test if setup properly
|
||||
cy.shouldHaveEmailEnabled();
|
||||
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
testUser = newUser;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
it('MM-T1764 - Security - Signup: Email verification required (after having created account when verification was not required)', () => {
|
||||
// # Update Configs
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
RequireEmailVerification: false,
|
||||
},
|
||||
});
|
||||
|
||||
// # Login as test user and make sure it goes to team selection
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit('/');
|
||||
cy.url().should('include', '/select_team');
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Update Configs
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
RequireEmailVerification: true,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// # Login as test user and make sure it goes to team selection
|
||||
cy.visit('/login');
|
||||
|
||||
// # Remove autofocus from login input
|
||||
cy.get('.login-body-card-title').click();
|
||||
|
||||
// # Clear email/username field and type username
|
||||
cy.apiGetClientLicense().then(({isLicensed}) => {
|
||||
cy.findByPlaceholderText(isLicensed ? 'Email, Username or AD/LDAP Username' : 'Email or Username', {timeout: TIMEOUTS.ONE_MIN}).clear().type(testUser.username);
|
||||
});
|
||||
|
||||
// # Clear password field and type password
|
||||
cy.findByPlaceholderText('Password').clear().type(testUser.password);
|
||||
|
||||
// # Hit enter to login
|
||||
cy.get('#saveSetting').should('not.be.disabled').click();
|
||||
|
||||
cy.wait(TIMEOUTS.THREE_SEC);
|
||||
|
||||
// * Assert that email verification has been sent and then resend to make sure it gets resent
|
||||
cy.findByText('Resend Email').should('be.visible').and('exist').click();
|
||||
cy.findByText('Verification email sent').should('be.visible').and('exist');
|
||||
cy.findByText('You’re almost done!').should('be.visible').and('exist');
|
||||
cy.findByText('Please verify your email address. Check your inbox for an email.').should('be.visible').and('exist');
|
||||
|
||||
cy.getRecentEmail(testUser).then(({body}) => {
|
||||
const permalink = body[6].match(reUrl)[0];
|
||||
|
||||
// # Visit permalink (e.g. click on email link), view in browser to proceed
|
||||
cy.visit(permalink);
|
||||
|
||||
// # Clear password field and type password
|
||||
cy.get('#input_password-input').clear().type(testUser.password);
|
||||
|
||||
// # Hit enter to login
|
||||
cy.get('#saveSetting').should('not.be.disabled').click();
|
||||
|
||||
// * Should show the join team stuff
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1770 - Default password settings', () => {
|
||||
cy.apiGetClientLicense().then(({isCloudLicensed}) => {
|
||||
const newConfig = {
|
||||
PasswordSettings: {
|
||||
MinimumLength: null,
|
||||
Lowercase: null,
|
||||
Number: null,
|
||||
Uppercase: null,
|
||||
Symbol: null,
|
||||
},
|
||||
ServiceSettings: {},
|
||||
};
|
||||
|
||||
if (!isCloudLicensed) {
|
||||
newConfig.ServiceSettings = {
|
||||
MaximumLoginAttempts: null,
|
||||
};
|
||||
}
|
||||
|
||||
cy.apiUpdateConfig(newConfig);
|
||||
|
||||
// * Ensure password has the default minimum length and no password requirements are checked
|
||||
// Note: default MinimumLength is 8 on non-FIPS builds and 14 on FIPS builds
|
||||
cy.apiGetConfig().then(({config: {PasswordSettings}}) => {
|
||||
expect(PasswordSettings.MinimumLength).to.be.oneOf([8, 14]);
|
||||
expect(PasswordSettings.Lowercase).equal(false);
|
||||
expect(PasswordSettings.Number).equal(false);
|
||||
expect(PasswordSettings.Uppercase).equal(false);
|
||||
expect(PasswordSettings.Symbol).equal(false);
|
||||
});
|
||||
|
||||
cy.visit('/admin_console/authentication/password');
|
||||
cy.get('.admin-console__header').should('be.visible').and('have.text', 'Password');
|
||||
|
||||
cy.findByTestId('passwordMinimumLengthinput').should('be.visible').invoke('val').then((val) => {
|
||||
expect(val).to.be.oneOf(['8', '14']);
|
||||
});
|
||||
cy.findByText('At least one lowercase letter').siblings().should('not.be.checked');
|
||||
cy.findByText('At least one uppercase letter').siblings().should('not.be.checked');
|
||||
cy.findByText('At least one number').siblings().should('not.be.checked');
|
||||
cy.findByText('At least one symbol (e.g. "~!@#$%^&*()")').siblings().should('not.be.checked');
|
||||
|
||||
if (!isCloudLicensed) {
|
||||
cy.findByTestId('maximumLoginAttemptsinput').should('be.visible').and('have.value', '10');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1783 - Username validation shows errors for various username requirements', () => {
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
|
||||
|
||||
cy.get('#input_password-input').type(newTestPassword());
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
['1user', 'te', 'user#1', 'user!1'].forEach((option) => {
|
||||
cy.get('#input_name').clear().type(option);
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Assert the error is what is expected;
|
||||
cy.get('.Input___error').scrollIntoView().should('be.visible');
|
||||
cy.findByText('Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1752 - Enable account creation - true', () => {
|
||||
// # Enable open server
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
EnableUserCreation: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Logout and go to front page
|
||||
cy.apiLogout();
|
||||
cy.visit('/login');
|
||||
|
||||
// * Assert that create account button is visible
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
|
||||
|
||||
cy.get('#input_password-input').type(newTestPassword());
|
||||
|
||||
cy.get('#input_name').clear().type(`Test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was created successfully and we are on the team joining page
|
||||
cy.findByText('Teams you can join:', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1753 - Enable account creation - false', () => {
|
||||
// # Disable user account creation
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
EnableUserCreation: false,
|
||||
},
|
||||
LdapSettings: {
|
||||
Enable: false,
|
||||
},
|
||||
}).then(({config}) => {
|
||||
expect(config.TeamSettings.EnableUserCreation).to.equal(false);
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// * Verify client config has account creation disabled
|
||||
cy.request('/api/v4/config/client').its('body.EnableUserCreation').should('eq', 'false');
|
||||
|
||||
// # Go to front page
|
||||
cy.visit('/login');
|
||||
|
||||
// # Reload so the login page picks up the client config
|
||||
cy.reload();
|
||||
|
||||
// * Assert that create account button is not visible
|
||||
cy.findByText('Don\'t have an account?').should('not.exist');
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
// * No sign up methods enabled
|
||||
cy.findByText('This server doesn’t have any sign-in methods enabled').should('be.visible').and('exist');
|
||||
});
|
||||
|
||||
it('MM-T1754 - Restrict Domains - Account creation link on signin page', () => {
|
||||
// # Enable user account creation and set restricted domain
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
RequireEmailVerification: false,
|
||||
},
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: 'test.com',
|
||||
EnableUserCreation: true,
|
||||
},
|
||||
}).then(({config}) => {
|
||||
expect(config.TeamSettings.RestrictCreationToDomains).to.equal('test.com');
|
||||
expect(config.TeamSettings.EnableUserCreation).to.equal(true);
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
|
||||
// # Go to front page
|
||||
cy.visit('/login');
|
||||
|
||||
// * Assert that create account button is visible
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
|
||||
// # Go to sign up with email page
|
||||
cy.visit('/signup_user_complete');
|
||||
|
||||
cy.get('#input_email', {timeout: TIMEOUTS.ONE_MIN}).type(`test-${getRandomId()}@example.com`);
|
||||
|
||||
cy.get('#input_password-input').type(newTestPassword());
|
||||
|
||||
cy.get('#input_name').clear().type(`Test${getRandomId()}`);
|
||||
|
||||
cy.get('#signup-body-card-form-check-terms-and-privacy').check();
|
||||
|
||||
cy.findByText('Create account').click();
|
||||
|
||||
// * Make sure account was not created successfully
|
||||
cy.findByText('The email you provided does not belong to an accepted domain. Please contact your administrator or sign up with a different email.').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1755 - Restrict Domains - Email invite', () => {
|
||||
// # Clear leftover domain restriction so a team can be created
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: '',
|
||||
EnableUserCreation: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create a team to invite from
|
||||
cy.apiCreateTeam('invite-domain', 'Invite Domain').then(({team}) => {
|
||||
// # Enable user account creation and set restricted domain
|
||||
cy.apiUpdateConfig({
|
||||
EmailSettings: {
|
||||
RequireEmailVerification: false,
|
||||
},
|
||||
ServiceSettings: {
|
||||
EnableEmailInvitations: true,
|
||||
},
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: 'test.com',
|
||||
EnableUserCreation: true,
|
||||
},
|
||||
}).then(({config}) => {
|
||||
expect(config.TeamSettings.RestrictCreationToDomains).to.equal('test.com');
|
||||
expect(config.TeamSettings.EnableUserCreation).to.equal(true);
|
||||
expect(config.ServiceSettings.EnableEmailInvitations).to.equal(true);
|
||||
});
|
||||
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
cy.get('#post_textbox').should('be.visible');
|
||||
|
||||
// # Open team menu and click on "Invite People"
|
||||
cy.uiOpenTeamMenu('Invite people');
|
||||
|
||||
const inviteEmail = `test-${getRandomId()}@mattermost.com`;
|
||||
|
||||
// # Input email
|
||||
cy.get('.users-emails-input__control').should('be.visible').within(() => {
|
||||
cy.get('input').type(inviteEmail, {force: true});
|
||||
});
|
||||
|
||||
// # Select the Add email option
|
||||
cy.get('.users-emails-input__option:not(.users-emails-input__option--no-matches)').
|
||||
should('contain', inviteEmail).
|
||||
click();
|
||||
|
||||
// * Verify the email chip is added
|
||||
cy.get('.users-emails-input__multi-value').should('contain', inviteEmail);
|
||||
|
||||
// # Click invite members button
|
||||
cy.findByTestId('inviteButton').should('be.enabled').click();
|
||||
|
||||
// * Verify message is what you expect it to be
|
||||
cy.contains('The following email addresses do not belong to an accepted domain:').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @system_console @authentication @not_cloud
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Authentication', () => {
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Log in as admin.
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
it('MM-T1762 - Invite Salt', () => {
|
||||
cy.visit('/admin_console/site_config/public_links');
|
||||
|
||||
// * Check that public link salt is masked
|
||||
cy.findByText('********************************').should('be.visible');
|
||||
|
||||
// # Click "Regenerate"
|
||||
cy.findByText('Regenerate', {timeout: TIMEOUTS.ONE_MIN}).click();
|
||||
|
||||
// * Check that new public link is generated and unmasked
|
||||
cy.findByText('********************************').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1775 - Maximum Login Attempts field resets to default after saving invalid value', () => {
|
||||
cy.visit('/admin_console/authentication/password');
|
||||
|
||||
cy.findByPlaceholderText('E.g.: "10"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('ten');
|
||||
|
||||
cy.uiSave();
|
||||
|
||||
// * Ensure error appears when saving a password outside of the limits
|
||||
cy.findByPlaceholderText('E.g.: "10"').invoke('val').should('equal', '10');
|
||||
|
||||
// * Verify that the config remain the same
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
expect(config.ServiceSettings.MaximumLoginAttempts).to.equal(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1776 - Maximum Login Attempts field successfully saves valid change', () => {
|
||||
cy.visit('/admin_console/authentication/password');
|
||||
|
||||
cy.findByPlaceholderText('E.g.: "10"', {timeout: TIMEOUTS.ONE_MIN}).clear().type('2');
|
||||
|
||||
cy.uiSaveConfig();
|
||||
|
||||
// * Ensure error appears when saving a password outside of the limits
|
||||
cy.findByPlaceholderText('E.g.: "10"').invoke('val').should('equal', '2');
|
||||
|
||||
// * Verify that the config have changed
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
expect(config.ServiceSettings.MaximumLoginAttempts).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @system_console @authentication
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Authentication', () => {
|
||||
before(() => {
|
||||
// # Enable open server
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
EnableOpenServer: false,
|
||||
},
|
||||
});
|
||||
|
||||
// # Logout and visit default page
|
||||
cy.apiLogout();
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('MM-T1760 - Enable Open Server false: Create account link is hidden', () => {
|
||||
// * Assert that create account button is not visible
|
||||
cy.findByText('Don\'t have an account?', {timeout: TIMEOUTS.TEN_SEC}).should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
import type {
|
||||
SimpleUser,
|
||||
} from './helpers';
|
||||
import {
|
||||
getPostTextboxInput,
|
||||
getQuickChannelSwitcherInput,
|
||||
startAtMention,
|
||||
verifySuggestionAtChannelSwitcher,
|
||||
verifySuggestionAtPostTextbox,
|
||||
} from './helpers';
|
||||
|
||||
export function doTestPostextbox(mention: string, ...suggestion: SimpleUser[]) {
|
||||
getPostTextboxInput();
|
||||
startAtMention(mention);
|
||||
verifySuggestionAtPostTextbox(...suggestion);
|
||||
}
|
||||
|
||||
export function doTestQuickChannelSwitcher(mention: string, ...suggestion: SimpleUser[]) {
|
||||
getQuickChannelSwitcherInput();
|
||||
startAtMention(mention);
|
||||
verifySuggestionAtChannelSwitcher(...suggestion);
|
||||
}
|
||||
|
||||
export function doTestUserChannelSection(prefix: string, testTeam: Cypress.Team, testUsers: Record<string, SimpleUser>) {
|
||||
const thor = testUsers.thor;
|
||||
const loki = testUsers.loki;
|
||||
|
||||
// # Create new channel and add user to channel
|
||||
const channelName = 'new-channel';
|
||||
cy.apiCreateChannel(testTeam.id, channelName, channelName).then(({channel}) => {
|
||||
cy.apiGetUserByEmail(thor.email).then(({user}) => {
|
||||
cy.apiAddUserToChannel(channel.id, user.id);
|
||||
});
|
||||
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
});
|
||||
|
||||
// # Start an at mention that should return 2 users (in this case, the users share a last name)
|
||||
cy.uiGetPostTextBox().
|
||||
as('input').
|
||||
clear().
|
||||
type(`@${prefix}odinson`);
|
||||
|
||||
// * Thor should be a channel member
|
||||
cy.uiVerifyAtMentionInSuggestionList(thor, true);
|
||||
|
||||
// * Loki should NOT be a channel member
|
||||
cy.uiVerifyAtMentionInSuggestionList(loki, false);
|
||||
}
|
||||
|
||||
export function doTestDMChannelSidebar(testUsers: Record<string, SimpleUser>) {
|
||||
const thor = testUsers.thor;
|
||||
|
||||
// # Open of the add direct message modal
|
||||
cy.uiAddDirectMessage().click({force: true});
|
||||
|
||||
// # Type username into input
|
||||
cy.get('.more-direct-channels').
|
||||
find('input').
|
||||
should('exist').
|
||||
type(thor.username, {force: true});
|
||||
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: '/api/v4/users/search',
|
||||
}).as('searchUsers');
|
||||
|
||||
cy.wait('@searchUsers').then((interception) => {
|
||||
expect(interception.response?.body).to.have.length(1);
|
||||
});
|
||||
|
||||
// * There should only be one result
|
||||
cy.get('#moreDmModal').find('.more-modal__row').
|
||||
as('result').
|
||||
its('length').
|
||||
should('equal', 1);
|
||||
|
||||
// * Result should have appropriate text
|
||||
cy.get('@result').
|
||||
find('.more-modal__name').
|
||||
should('have.text', `@${thor.username} - ${thor.first_name} ${thor.last_name} (${thor.nickname})`);
|
||||
|
||||
cy.get('@result').
|
||||
find('.more-modal__description').
|
||||
should('have.text', thor.email);
|
||||
|
||||
// # Click on the result to add user
|
||||
cy.get('@result').click({force: true});
|
||||
|
||||
// # Click "Go"
|
||||
cy.uiGetButton('Go').click();
|
||||
|
||||
// # Should land on direct message channel for that user
|
||||
cy.get('#channelHeaderTitle').should('have.text', thor.username + ' ');
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @autocomplete
|
||||
|
||||
import {doTestQuickChannelSwitcher} from '../common_test';
|
||||
import type {SimpleUser} from '../helpers';
|
||||
import {createSearchData} from '../helpers';
|
||||
|
||||
import {getRandomLetter} from '@/utils';
|
||||
|
||||
describe('Autocomplete with Database - Users', () => {
|
||||
const prefix = getRandomLetter(3);
|
||||
let testUsers: Record<string, SimpleUser>;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetClientLicense().then(({isCloudLicensed}) => {
|
||||
if (!isCloudLicensed) {
|
||||
cy.shouldHaveElasticsearchDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
createSearchData(prefix).then((searchData) => {
|
||||
testUsers = searchData.users;
|
||||
|
||||
cy.apiLogin(searchData.sysadmin);
|
||||
|
||||
// # Navigate to the new teams town square
|
||||
cy.visit(`/${searchData.team.name}/channels/town-square`);
|
||||
|
||||
// # Open quick channel switcher
|
||||
cy.typeCmdOrCtrl().type('k');
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('search for user in channel switcher', () => {
|
||||
describe('by @username', () => {
|
||||
it('MM-T4071_1 Full username returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}ironman`, testUsers.ironman);
|
||||
});
|
||||
|
||||
it('MM-T4071_2 Unique partial username returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}doc`, testUsers.doctorstrange);
|
||||
});
|
||||
|
||||
it('MM-T4071_3 Partial username returns all users that match', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}i`, testUsers.ironman);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by @firstname', () => {
|
||||
it('MM-T4072_1 Full first name returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}tony`, testUsers.ironman);
|
||||
});
|
||||
|
||||
it('MM-T4072_2 Unique partial first name returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}wa`, testUsers.deadpool);
|
||||
});
|
||||
|
||||
it('MM-T4072_3 Partial first name returns all users that match', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}ste`, testUsers.captainamerica, testUsers.doctorstrange);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by @lastname', () => {
|
||||
it('MM-T4073_1 Full last name returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}stark`, testUsers.ironman);
|
||||
});
|
||||
|
||||
it('MM-T4073_2 Unique partial last name returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}ban`, testUsers.hulk);
|
||||
});
|
||||
|
||||
it('MM-T4073_3 Partial last name returns all users that match', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}ba`, testUsers.hawkeye, testUsers.hulk);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by @nickname', () => {
|
||||
it('MM-T4074_1 Full nickname returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}ronin`, testUsers.hawkeye);
|
||||
});
|
||||
|
||||
it('MM-T4074_2 Unique partial nickname returns single user', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}gam`, testUsers.hulk);
|
||||
});
|
||||
|
||||
it('MM-T4074_3 Partial nickname returns all users that match', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}pro`, testUsers.captainamerica, testUsers.ironman);
|
||||
});
|
||||
});
|
||||
|
||||
describe('special characters in usernames are returned', () => {
|
||||
it('MM-T4075_1 Username with dot', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}dot.dot`, testUsers.dot);
|
||||
});
|
||||
|
||||
it('MM-T4075_2 Username dash', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}dash-dash`, testUsers.dash);
|
||||
});
|
||||
|
||||
it('MM-T4075_3 Username underscore', () => {
|
||||
doTestQuickChannelSwitcher(`@${prefix}under_score`, testUsers.underscore);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @autocomplete
|
||||
|
||||
import {doTestPostextbox} from '../common_test';
|
||||
import type {SimpleUser} from '../helpers';
|
||||
import {createSearchData} from '../helpers';
|
||||
|
||||
import {getRandomLetter} from '@/utils';
|
||||
|
||||
describe('Autocomplete with Database - Users', () => {
|
||||
const prefix = getRandomLetter(3);
|
||||
let testUsers: Record<string, SimpleUser>;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetClientLicense().then(({isCloudLicensed}) => {
|
||||
if (!isCloudLicensed) {
|
||||
cy.shouldHaveElasticsearchDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
createSearchData(prefix).then((searchData) => {
|
||||
testUsers = searchData.users;
|
||||
|
||||
cy.apiLogin(searchData.sysadmin);
|
||||
|
||||
// # Navigate to the new teams town square
|
||||
cy.visit(`/${searchData.team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search for user in message input box', () => {
|
||||
describe('by @username', () => {
|
||||
it('MM-T4076_1 Full username returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}ironman`, testUsers.ironman);
|
||||
});
|
||||
|
||||
it('MM-T4076_2 Unique partial username returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}doc`, testUsers.doctorstrange);
|
||||
});
|
||||
|
||||
it('MM-T4076_3 Partial username returns all users that match', () => {
|
||||
doTestPostextbox(`@${prefix}i`, testUsers.ironman);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by @firstname', () => {
|
||||
it('MM-T4077_1 Full first name returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}tony`, testUsers.ironman);
|
||||
});
|
||||
|
||||
it('MM-T4077_2 Unique partial first name returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}wa`, testUsers.deadpool);
|
||||
});
|
||||
|
||||
it('MM-T4077_3 Partial first name returns all users that match', () => {
|
||||
doTestPostextbox(`@${prefix}ste`, testUsers.captainamerica, testUsers.doctorstrange);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by @lastname', () => {
|
||||
it('MM-T4078_1 Full last name returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}stark`, testUsers.ironman);
|
||||
});
|
||||
|
||||
it('MM-T4078_2 Unique partial last name returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}ban`, testUsers.hulk);
|
||||
});
|
||||
|
||||
it('MM-T4078_3 Partial last name returns all users that match', () => {
|
||||
doTestPostextbox(`@${prefix}ba`, testUsers.hawkeye, testUsers.hulk);
|
||||
});
|
||||
});
|
||||
|
||||
describe('by @nickname', () => {
|
||||
it('MM-T4079_1 Full nickname returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}ronin`, testUsers.hawkeye);
|
||||
});
|
||||
|
||||
it('MM-T4079_2 Unique partial nickname returns single user', () => {
|
||||
doTestPostextbox(`@${prefix}gam`, testUsers.hulk);
|
||||
});
|
||||
|
||||
it('MM-T4079_3 Partial nickname returns all users that match', () => {
|
||||
doTestPostextbox(`@${prefix}pro`, testUsers.captainamerica, testUsers.ironman);
|
||||
});
|
||||
});
|
||||
|
||||
describe('special characters in usernames are returned', () => {
|
||||
it('MM-T4080_1 Username with dot', () => {
|
||||
doTestPostextbox(`@${prefix}dot.dot`, testUsers.dot);
|
||||
});
|
||||
|
||||
it('MM-T4080_2 Username with dash', () => {
|
||||
doTestPostextbox(`@${prefix}dash-dash`, testUsers.dash);
|
||||
});
|
||||
|
||||
it('MM-T4080_3 Username with underscore', () => {
|
||||
doTestPostextbox(`@${prefix}under_score`, testUsers.underscore);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @autocomplete
|
||||
|
||||
import {doTestDMChannelSidebar, doTestUserChannelSection} from '../common_test';
|
||||
import type {SimpleUser} from '../helpers';
|
||||
import {createSearchData} from '../helpers';
|
||||
|
||||
import {getRandomLetter} from '@/utils';
|
||||
|
||||
describe('Autocomplete with Database - Users', () => {
|
||||
const prefix = getRandomLetter(3);
|
||||
let testUsers: Record<string, SimpleUser>;
|
||||
let testTeam: Cypress.Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetClientLicense().then(({isCloudLicensed}) => {
|
||||
if (!isCloudLicensed) {
|
||||
cy.shouldHaveElasticsearchDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
createSearchData(prefix).then((searchData) => {
|
||||
testUsers = searchData.users;
|
||||
testTeam = searchData.team;
|
||||
|
||||
cy.apiLogin(searchData.sysadmin);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T4081 Users in correct in/out of channel sections', () => {
|
||||
doTestUserChannelSection(prefix, testTeam, testUsers);
|
||||
});
|
||||
|
||||
it('MM-T4082 DM can be opened with a user not on your team or in your DM channel sidebar', () => {
|
||||
doTestDMChannelSidebar(testUsers);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {newTestPassword} from '@/utils';
|
||||
|
||||
export type SimpleUser = Pick<Cypress.UserProfile, 'username' | 'first_name' | 'last_name' | 'nickname' | 'password' | 'email'>;
|
||||
|
||||
function createPrivateChannel(teamId: string, userToAdd: Cypress.UserProfile | null = null) {
|
||||
// # Create a private channel as sysadmin
|
||||
return createChannel('P', teamId, userToAdd);
|
||||
}
|
||||
|
||||
function createPublicChannel(teamId: string, userToAdd: Cypress.UserProfile | null = null) {
|
||||
// # Create a public channel as sysadmin
|
||||
return createChannel('O', teamId, userToAdd);
|
||||
}
|
||||
|
||||
function createSearchData(prefix: string) {
|
||||
return cy.apiCreateCustomAdmin({loginAfter: true, hideAdminTrialModal: true}).then(({sysadmin}) => {
|
||||
const users = getTestUsers(prefix);
|
||||
|
||||
cy.apiLogin(sysadmin);
|
||||
|
||||
// # Create new team for tests
|
||||
return cy.apiCreateTeam('search', 'Search').then(({team}) => {
|
||||
// # Create pool of users for tests
|
||||
Cypress._.forEach(users, (testUser) => {
|
||||
cy.apiCreateUser({user: testUser}).then(({user}) => {
|
||||
cy.apiAddUserToTeam(team.id, user.id);
|
||||
});
|
||||
});
|
||||
|
||||
return cy.wrap({sysadmin, team, users});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getTestUsers(prefix = ''): Record<string, SimpleUser> {
|
||||
if (Cypress.expose('searchTestUsers')) {
|
||||
return JSON.parse(Cypress.expose('searchTestUsers'));
|
||||
}
|
||||
|
||||
return {
|
||||
ironman: generatePrefixedUser({
|
||||
username: 'ironman',
|
||||
first_name: 'Tony',
|
||||
last_name: 'Stark',
|
||||
nickname: 'protoncannon',
|
||||
}, prefix),
|
||||
hulk: generatePrefixedUser({
|
||||
username: 'hulk',
|
||||
first_name: 'Bruce',
|
||||
last_name: 'Banner',
|
||||
nickname: 'gammaray',
|
||||
}, prefix),
|
||||
hawkeye: generatePrefixedUser({
|
||||
username: 'hawkeye',
|
||||
first_name: 'Clint',
|
||||
last_name: 'Barton',
|
||||
nickname: 'ronin',
|
||||
}, prefix),
|
||||
deadpool: generatePrefixedUser({
|
||||
username: 'deadpool',
|
||||
first_name: 'Wade',
|
||||
last_name: 'Wilson',
|
||||
nickname: 'merc',
|
||||
}, prefix),
|
||||
captainamerica: generatePrefixedUser({
|
||||
username: 'captainamerica',
|
||||
first_name: 'Steve',
|
||||
last_name: 'Rogers',
|
||||
nickname: 'professional',
|
||||
}, prefix),
|
||||
doctorstrange: generatePrefixedUser({
|
||||
username: 'doctorstrange',
|
||||
first_name: 'Stephen',
|
||||
last_name: 'Strange',
|
||||
nickname: 'sorcerersupreme',
|
||||
}, prefix),
|
||||
thor: generatePrefixedUser({
|
||||
username: 'thor',
|
||||
first_name: 'Thor',
|
||||
last_name: 'Odinson',
|
||||
nickname: 'mjolnir',
|
||||
}, prefix),
|
||||
loki: generatePrefixedUser({
|
||||
username: 'loki',
|
||||
first_name: 'Loki',
|
||||
last_name: 'Odinson',
|
||||
nickname: 'trickster',
|
||||
}, prefix),
|
||||
dot: generatePrefixedUser({
|
||||
username: 'dot.dot',
|
||||
first_name: 'z1First',
|
||||
last_name: 'z1Last',
|
||||
nickname: 'z1Nick',
|
||||
}, prefix),
|
||||
dash: generatePrefixedUser({
|
||||
username: 'dash-dash',
|
||||
first_name: 'z2First',
|
||||
last_name: 'z2Last',
|
||||
nickname: 'z2Nick',
|
||||
}, prefix),
|
||||
underscore: generatePrefixedUser({
|
||||
username: 'under_score',
|
||||
first_name: 'z3First',
|
||||
last_name: 'z3Last',
|
||||
nickname: 'z3Nick',
|
||||
}, prefix),
|
||||
};
|
||||
}
|
||||
|
||||
function getPostTextboxInput() {
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetPostTextBox().
|
||||
as('input').
|
||||
clear();
|
||||
}
|
||||
|
||||
function getQuickChannelSwitcherInput() {
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).
|
||||
should('be.visible').
|
||||
as('input').
|
||||
clear();
|
||||
}
|
||||
|
||||
function searchAndVerifyChannel(channel: Cypress.Channel, shouldExist = true) {
|
||||
const name = channel.display_name;
|
||||
searchForChannel(name);
|
||||
|
||||
if (shouldExist) {
|
||||
// * Channel should appear in suggestions list
|
||||
cy.get('#suggestionList').findByTestId(channel.name).should('be.visible');
|
||||
} else {
|
||||
// * Suggestion list and channel item should not appear
|
||||
cy.get('#suggestionList').should('not.exist');
|
||||
cy.findByTestId(channel.name).should('not.exist');
|
||||
}
|
||||
}
|
||||
|
||||
function searchAndVerifyUser(user: Cypress.UserProfile) {
|
||||
// # Start @ mentions autocomplete with username
|
||||
cy.uiGetPostTextBox().
|
||||
as('input').
|
||||
clear().
|
||||
type(`@${user.username}`);
|
||||
|
||||
// * Suggestion list should appear
|
||||
cy.get('#suggestionList', {timeout: TIMEOUTS.FIVE_SEC}).should('be.visible');
|
||||
|
||||
// * Verify user appears in results post-change
|
||||
return cy.uiVerifyAtMentionSuggestion(user);
|
||||
}
|
||||
|
||||
function searchForChannel(name: string) {
|
||||
// # Open up channel switcher
|
||||
cy.typeCmdOrCtrl().type('k').wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// # Clear out and type in the name
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).
|
||||
should('be.visible').
|
||||
as('input').
|
||||
clear().
|
||||
type(name);
|
||||
}
|
||||
|
||||
function startAtMention(string: string) {
|
||||
// # Get the expected input
|
||||
cy.get('@input').clear().type(string);
|
||||
|
||||
// * Suggestion list should appear
|
||||
cy.get('#suggestionList').should('be.visible');
|
||||
}
|
||||
|
||||
function verifySuggestionAtPostTextbox(...expectedUsers: SimpleUser[]) {
|
||||
expectedUsers.forEach((user) => {
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiVerifyAtMentionSuggestion(user);
|
||||
});
|
||||
}
|
||||
|
||||
function verifySuggestionAtChannelSwitcher(...expectedUsers: SimpleUser[]) {
|
||||
expectedUsers.forEach((user) => {
|
||||
cy.findByTestId(user.username).
|
||||
should('be.visible').
|
||||
and('have.text', `${user.first_name} ${user.last_name} (${user.nickname})@${user.username}`);
|
||||
});
|
||||
}
|
||||
|
||||
function createChannel(channelType: string, teamId: string, userToAdd: Cypress.UserProfile | null = null) {
|
||||
// # Create a channel as sysadmin
|
||||
return cy.externalRequest({
|
||||
user: getAdminAccount(),
|
||||
method: 'POST',
|
||||
path: 'channels',
|
||||
data: {
|
||||
team_id: teamId,
|
||||
name: 'test-channel' + Date.now(),
|
||||
display_name: 'Test Channel ' + Date.now(),
|
||||
type: channelType,
|
||||
header: '',
|
||||
purpose: '',
|
||||
},
|
||||
}).then(({data: channel}) => {
|
||||
if (userToAdd) {
|
||||
// # Get user profile by email
|
||||
return cy.apiGetUserByEmail(userToAdd.email).then(({user}) => {
|
||||
// # Add user to channel
|
||||
cy.externalAddUserToChannel(user.id, channel.id).then(() => {
|
||||
// # Explicitly wait to give some time to index before searching
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// # Explicitly wait to give some time to index before searching
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
}
|
||||
|
||||
function generatePrefixedUser(user: Omit<SimpleUser, 'password' | 'email'>, prefix: string) {
|
||||
return {
|
||||
username: withPrefix(user.username, prefix),
|
||||
password: newTestPassword(),
|
||||
first_name: withPrefix(user.first_name, prefix),
|
||||
last_name: withPrefix(user.last_name, prefix),
|
||||
email: createEmail(user.username, prefix),
|
||||
nickname: withPrefix(user.nickname, prefix),
|
||||
};
|
||||
}
|
||||
|
||||
function withPrefix(name: string, prefix: string) {
|
||||
return prefix + name;
|
||||
}
|
||||
|
||||
function createEmail(name: string, prefix: string) {
|
||||
return `${prefix}${name}@sample.mattermost.com`;
|
||||
}
|
||||
|
||||
export {
|
||||
createPrivateChannel,
|
||||
createPublicChannel,
|
||||
createSearchData,
|
||||
getTestUsers,
|
||||
getPostTextboxInput,
|
||||
getQuickChannelSwitcherInput,
|
||||
searchAndVerifyChannel,
|
||||
searchAndVerifyUser,
|
||||
searchForChannel,
|
||||
startAtMention,
|
||||
verifySuggestionAtChannelSwitcher,
|
||||
verifySuggestionAtPostTextbox,
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @messaging @benchmark
|
||||
|
||||
import {reportBenchmarkResults} from '@/utils/benchmark';
|
||||
|
||||
describe('Message', () => {
|
||||
before(() => {
|
||||
// # Create new team and new user and visit off-topic channel
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('Current user posting message in empty channel', () => {
|
||||
// # Wait for posts to load
|
||||
cy.get('#postListContent', {timeout: 10000}).should('be.visible');
|
||||
|
||||
// # Reset selector measurements
|
||||
cy.window().then((win) => {
|
||||
win.resetTrackedSelectors();
|
||||
|
||||
// # Post message "One"
|
||||
cy.postMessage('One');
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
const divPostId = `#postMessageText_${postId}`;
|
||||
|
||||
// * Check that the message was created
|
||||
cy.get(divPostId).find('p').should('have.text', 'One');
|
||||
|
||||
reportBenchmarkResults(cy, win);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Typing one character into create post textbox', () => {
|
||||
// # Wait for posts to load
|
||||
cy.get('#postListContent', {timeout: 10000}).should('be.visible');
|
||||
|
||||
// # Reset selector measurements
|
||||
cy.window().then((win) => {
|
||||
win.resetTrackedSelectors();
|
||||
|
||||
// # Push a character key such as "A"
|
||||
cy.uiGetPostTextBox().type('A').then(() => {
|
||||
reportBenchmarkResults(cy, win);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts @mfa
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Bot accounts ownership and API', () => {
|
||||
let newTeam: Team;
|
||||
let newUser: UserProfile;
|
||||
let newChannel: Channel;
|
||||
let botId: string;
|
||||
let botName: string;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnforceMultifactorAuthentication: false,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
cy.apiInitSetup().then(({team, user, channel, townSquareUrl}) => {
|
||||
newTeam = team;
|
||||
newUser = user;
|
||||
newChannel = channel;
|
||||
|
||||
cy.visit(townSquareUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
|
||||
// # Create a test bot
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
botId = bot.user_id;
|
||||
botName = bot.display_name!;
|
||||
cy.apiPatchUserRoles(bot.user_id, ['system_admin', 'system_user']);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1862 Only system admin are able to create bots', () => {
|
||||
// # Open product switch menu and click "Integrations"
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Confirm integrations are visible
|
||||
cy.url().should('include', `/${newTeam.name}/integrations`);
|
||||
cy.get('.backstage-header').findByText('Integrations').should('be.visible');
|
||||
|
||||
// # Login as a regular user
|
||||
cy.apiLogin(newUser);
|
||||
|
||||
cy.visit(`/${newTeam.name}/channels/town-square`);
|
||||
|
||||
// # Click product switch button
|
||||
cy.uiOpenProductMenu();
|
||||
|
||||
// * Confirm "Integrations" is not visible
|
||||
cy.uiGetProductMenu().should('not.contain', 'Integrations');
|
||||
});
|
||||
|
||||
it('MM-T1863 Only System Admin are able to create bots (API)', () => {
|
||||
// # Login as a regular user
|
||||
cy.apiLogin(newUser);
|
||||
|
||||
// # Try to create a new bot as a regular user
|
||||
const botName3 = 'stay-enabled-bot-' + Date.now();
|
||||
|
||||
cy.request({
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
url: '/api/v4/bots',
|
||||
method: 'POST',
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
username: botName3,
|
||||
display_name: 'some text',
|
||||
description: 'some text',
|
||||
},
|
||||
}).then((response) => {
|
||||
// * Validate that request was denied
|
||||
expect(response.status).to.equal(403);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1864 Create bot (API)', () => {
|
||||
// * This call will fail if bot was not created
|
||||
cy.apiCreateBot();
|
||||
});
|
||||
|
||||
it('MM-T1865 Create post as bot', () => {
|
||||
// # Create token for the bot
|
||||
cy.apiCreateToken(botId).then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
cy.apiCreatePost(newChannel.id, msg1, '', {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token);
|
||||
|
||||
// # Re-login to validate post presence
|
||||
cy.apiAdminLogin();
|
||||
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
|
||||
|
||||
// * Validate post was created
|
||||
cy.findByText(msg1).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('MM-T1866 Create two posts in a row to the same channel', () => {
|
||||
// # Create token for the bot
|
||||
cy.apiCreateToken(botId).then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
const msg2 = 'this is a bot message2 ' + botName;
|
||||
cy.apiCreatePost(newChannel.id, msg1, '', {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token).then(({body: post1}) => {
|
||||
cy.apiCreatePost(newChannel.id, msg2, '', {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token).then(({body: post2}) => {
|
||||
// # Re-login to validate post presence
|
||||
cy.apiAdminLogin();
|
||||
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
|
||||
|
||||
// * Validate posts were created
|
||||
cy.get(`#postMessageText_${post1.id}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
cy.get(`#postMessageText_${post2.id}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg2);
|
||||
|
||||
// * Validate first post has an image
|
||||
cy.get(`#post_${post1.id}`).find('.Avatar').should('be.visible');
|
||||
|
||||
// * Validate that the second one doesn't
|
||||
cy.get(`#post_${post2.id}`).should('have.class', 'same--user');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1867 Post as a bot and include an @ mention', () => {
|
||||
// # Create token for the bot
|
||||
cy.apiCreateToken(botId).then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
cy.apiCreatePost(newChannel.id, msg1 + ' to @sysadmin', '', {}, token);
|
||||
|
||||
// # Re-login to validate post presence
|
||||
cy.apiAdminLogin();
|
||||
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
|
||||
// * Assert that the last message posted contains highlighted mention
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).find('.mention--highlight').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1868 BOT has a member role and is not in target channel and team', () => {
|
||||
// # Create a test bot (member)
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
// # Create token for the bot
|
||||
cy.apiCreateToken(bot.user_id).then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
|
||||
// # Try posting
|
||||
cy.apiCreatePost(newChannel.id, 'this is a bot message ' + bot.username, '', {}, token, false).then((response) => {
|
||||
// * Validate that posting was not allowed
|
||||
expect(response.status).to.equal(403);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1869 BOT has a system admin role and is not in target channel and team', () => {
|
||||
const botName3 = 'stay-enabled-bot-' + Date.now();
|
||||
|
||||
// # Create token for the bot
|
||||
cy.apiCreateToken(botId).then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
|
||||
// # Try posting
|
||||
cy.apiCreatePost(newChannel.id, 'this is a bot message ' + botName3, '', {}, token).then((response) => {
|
||||
// * Validate that posting was allowed
|
||||
expect(response.status).to.equal(201);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts @mfa
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Bot accounts ownership and API', () => {
|
||||
let newTeam: Team;
|
||||
let newUser: UserProfile;
|
||||
let newChannel: Channel;
|
||||
let botId: string;
|
||||
let botUsername: string;
|
||||
let botName: string;
|
||||
let adminUser: UserProfile;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin().then(({user}) => {
|
||||
adminUser = user;
|
||||
});
|
||||
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnforceMultifactorAuthentication: false,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
cy.apiInitSetup().then(({team, user, channel, townSquareUrl}) => {
|
||||
newTeam = team;
|
||||
newUser = user;
|
||||
newChannel = channel;
|
||||
|
||||
cy.visit(townSquareUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
|
||||
// # Create a test bot
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
botId = bot.user_id;
|
||||
botUsername = bot.username;
|
||||
botName = bot.display_name!;
|
||||
cy.apiPatchUserRoles(bot.user_id, ['system_admin', 'system_user']);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1870 BOT has a system admin role and can also post to private channels they do not belong to', () => {
|
||||
const channelName = 'channel' + Date.now();
|
||||
|
||||
// # Create private channel that bot doesn't belong to
|
||||
cy.apiCreateChannel(newTeam.id, channelName, channelName, 'P').then(({channel}) => {
|
||||
// # Create token for the bot
|
||||
cy.apiCreateToken(botId).then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
|
||||
// # Create a post
|
||||
cy.apiCreatePost(channel.id, msg1 + ' to @sysadmin', '', {}, token!);
|
||||
|
||||
// # Re-login to validate post presence
|
||||
cy.apiAdminLogin();
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('MM-T1872 Bot can post to DM channel', () => {
|
||||
// # Create DM channel that bot doesn't belong to
|
||||
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
|
||||
// # Create token for the bot
|
||||
cy.apiAccessToken(botId, 'some text').then(({token}) => {
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
|
||||
// # Post test message
|
||||
cy.postBotMessage({message: msg1, token: token!, channelId: channel.id});
|
||||
|
||||
// # Validate post presence
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1874 Bots can post when MFA is enforced', () => {
|
||||
// # Create token for the bot
|
||||
cy.apiAccessToken(botId, 'some text').then(({token}) => {
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
cy.postBotMessage({channelId: newChannel.id, message: msg1, props: {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token: token!});
|
||||
|
||||
// # Visit test channel
|
||||
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
|
||||
|
||||
// * Validate post was created
|
||||
cy.findByText(msg1).should('be.visible');
|
||||
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnforceMultifactorAuthentication: true,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
const msg2 = 'this is a bot message2 ' + botName;
|
||||
cy.postBotMessage({channelId: newChannel.id, message: msg2, props: {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token: token!});
|
||||
|
||||
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
|
||||
|
||||
// * Validate post was created
|
||||
cy.findByText(msg2).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1875 A bot cannot create another bot', () => {
|
||||
// # Create token for the bot
|
||||
cy.apiAccessToken(botId, 'some text').then(({token}) => {
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
|
||||
// # Try to create a new bot
|
||||
cy.request({
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest', Authorization: `Bearer ${token}`},
|
||||
url: '/api/v4/bots',
|
||||
method: 'POST',
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
username: botName + '333',
|
||||
display_name: 'some text',
|
||||
description: 'some text',
|
||||
},
|
||||
}).then((response) => {
|
||||
// * Validate that request was denied
|
||||
expect(response.status).to.equal(403);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1877 Reactivate a deactivated bot', () => {
|
||||
// # Create private channel that bot doesn't belong to
|
||||
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
|
||||
// # Create token for the bot
|
||||
cy.apiAccessToken(botId, 'some text').then(({token}) => {
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
|
||||
// # Create a post
|
||||
cy.postBotMessage({channelId: channel.id, message: msg1, token: token!});
|
||||
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
});
|
||||
|
||||
// # Disable the bot
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().parent().findByText('Disable').click();
|
||||
|
||||
// # Try to post again
|
||||
const msg2 = 'this is a bot message2 ' + botName;
|
||||
|
||||
// # Logout to allow posting as bot
|
||||
cy.apiLogout();
|
||||
|
||||
// # Create a post
|
||||
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!, failOnStatus: false}).then(({status}) => {
|
||||
// * Validate that posting failed
|
||||
expect(status).to.equal(401);
|
||||
});
|
||||
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Enable the bot again
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().parent().findByText('Enable').click();
|
||||
|
||||
// # Try to post again
|
||||
|
||||
// * Validate that posting works
|
||||
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!});
|
||||
|
||||
// * Validate post presence
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1878 Disable token can not be used to post', () => {
|
||||
// # Create DM channel that bot doesn't belong to
|
||||
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
|
||||
// # Create token for the bot
|
||||
cy.apiAccessToken(botId, 'some text').then(({token, id}) => {
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
|
||||
// # Create a post
|
||||
cy.postBotMessage({channelId: channel.id, message: msg1, token: token!});
|
||||
|
||||
// # Validate post presence
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
});
|
||||
|
||||
// # Disable the bot token
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
cy.findByText(`${botName} (@${botUsername})`).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
cy.get(`#${id}_deactivate`).click();
|
||||
});
|
||||
|
||||
// # Try to post again
|
||||
const msg2 = 'this is a bot message2 ' + botName;
|
||||
|
||||
// # Create a post
|
||||
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!, failOnStatus: false}).then(({status}) => {
|
||||
// * Validate that posting failed
|
||||
expect(status).to.equal(401);
|
||||
});
|
||||
|
||||
// # Enable the bot token again
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
cy.findAllByText('Bot Accounts');
|
||||
|
||||
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
cy.get(`#${id}_activate`).click().wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// # Try to post again
|
||||
// * Validate that posting works
|
||||
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!});
|
||||
|
||||
// # Validate post presence
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1880 Deleted token can not be used to post', () => {
|
||||
// # Create private channel that bot doesn't belong to
|
||||
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
|
||||
// # Create token for the bot
|
||||
cy.apiAccessToken(botId, 'some text').then(({token, id}) => {
|
||||
const msg1 = 'this is a bot message ' + botName;
|
||||
|
||||
// # Create a post
|
||||
cy.postBotMessage({channelId: channel.id, message: msg1, token: token!});
|
||||
|
||||
// # Validate post presence
|
||||
cy.visit(`/${newTeam.name}/channels/` + channel.name);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Validate post was created
|
||||
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
|
||||
});
|
||||
|
||||
// # Disable the bot token
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Delete token
|
||||
cy.get(`#${id}_delete`).click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Try to post again
|
||||
const msg2 = 'this is a bot message2 ' + botName;
|
||||
|
||||
// # Create a post
|
||||
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!, failOnStatus: false}).then(({status}) => {
|
||||
// * Validate that posting failed
|
||||
expect(status).to.equal(401);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @not_cloud @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Bot accounts ownership and API', () => {
|
||||
let newTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
|
||||
cy.apiInitSetup({
|
||||
promoteNewUserAsAdmin: true,
|
||||
loginAfter: true,
|
||||
}).then(({team}) => {
|
||||
newTeam = team;
|
||||
});
|
||||
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
DisableBotsWhenOwnerIsDeactivated: true,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
});
|
||||
|
||||
it('MM-T1861 Bots do not re-enable if the owner is re-activated', () => {
|
||||
// # Create another admin account
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
// # Login as the new admin
|
||||
cy.apiLogin(sysadmin);
|
||||
|
||||
// # Create a new bot as the new admin
|
||||
cy.apiCreateBot({prefix: 'stay-enabled-bot'}).then(({bot}) => {
|
||||
// # Login again as main admin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Deactivate the newly created admin
|
||||
cy.apiDeactivateUser(sysadmin.id);
|
||||
|
||||
// # Get bot list
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// # Search for the other bot
|
||||
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
|
||||
|
||||
// * Validate that the plugin is disabled since its owner is deactivated
|
||||
cy.get('.bot-list__disabled').scrollIntoView().should('be.visible');
|
||||
|
||||
// # Re-activate the newly created admin
|
||||
cy.apiActivateUser(sysadmin.id);
|
||||
|
||||
// # Repeat the test to confirm it stays disabled
|
||||
|
||||
// # Get bot list
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// # Search for the other bot
|
||||
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
|
||||
|
||||
// * Validate that the plugin is disabled even though its owner is activated
|
||||
cy.get('.bot-list__disabled').scrollIntoView().should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Bot} from '@mattermost/types/bots';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
|
||||
describe('Bot channel intro and avatar', () => {
|
||||
let team: Team;
|
||||
let bot: Bot;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then((out) => {
|
||||
team = out.team;
|
||||
});
|
||||
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Create bot
|
||||
bot = await client.createBot(createBotPatch());
|
||||
await client.addToTeam(team.id, bot.user_id);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1839 Bots have default profile image visible', () => {
|
||||
// # Open bot DM channel
|
||||
cy.visit(`/${team.name}/messages/@${bot.username}`);
|
||||
|
||||
// # Get channel intro and bot-post Avatars
|
||||
cy.get<HTMLImageElement[]>(`#channelIntro .profile-icon > img.Avatar, img.Avatar[alt="${bot.username} profile image"]`).
|
||||
should(($imgs) => {
|
||||
// * Verify imgs downloaded
|
||||
expect($imgs[0].naturalWidth).to.be.greaterThan(0);
|
||||
expect($imgs[1].naturalWidth).to.be.greaterThan(0);
|
||||
}).
|
||||
each(($img) => {
|
||||
// * Verify img visible and has src
|
||||
cy.wrap($img).
|
||||
should('be.visible').
|
||||
and('have.attr', 'src').
|
||||
then((url) => cy.request({url, encoding: 'binary'} as unknown as Partial<Cypress.RequestOptions>)).
|
||||
then(({body}) => {
|
||||
// * Verify matches expected default bot avatar
|
||||
cy.fixture('bot-default-avatar.png', 'binary').should('deep.equal', body);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Create bot', () => {
|
||||
it('MM-T1810 Create a Bot via the UI', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableUserAccessTokens: true,
|
||||
},
|
||||
});
|
||||
|
||||
createBot();
|
||||
});
|
||||
|
||||
it('MM-T1811 Create a Bot when personal access tokens are set to False', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableUserAccessTokens: false,
|
||||
},
|
||||
});
|
||||
|
||||
createBot();
|
||||
});
|
||||
});
|
||||
|
||||
function createBot() {
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
// # Go to town-square channel
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Go to bot integrations page
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
cy.get('a.integration-option[href$="/bots"]').click();
|
||||
cy.get('#addBotAccount').click();
|
||||
|
||||
// # Fill and submit form
|
||||
cy.get('#username').type(`bot-${getRandomId()}`);
|
||||
cy.get('#displayName').type('Test Bot');
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
// * Verify confirmation page
|
||||
cy.url().
|
||||
should('include', `/${team.name}/integrations/confirm`).
|
||||
should('match', /token=[a-zA-Z0-9]{26}/);
|
||||
|
||||
// * Verify confirmation form/token
|
||||
cy.get('div.backstage-form').
|
||||
should('include.text', 'Setup Successful').
|
||||
should((confirmation) => {
|
||||
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
|
||||
});
|
||||
cy.get('#doneButton').click();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @not_cloud @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {createBotInteractive} from './helpers';
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Bot accounts - CRUD Testing', () => {
|
||||
let newTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.shouldHavePluginUploadEnabled();
|
||||
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
EmailSettings: {
|
||||
SMTPServer: '',
|
||||
},
|
||||
PluginSettings: {
|
||||
Enable: true,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
// # Create a test bot
|
||||
cy.apiCreateBot();
|
||||
|
||||
// # Create and visit new channel
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
newTeam = team;
|
||||
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1849 Create a Personal Access Token when email config is invalid', () => {
|
||||
// # Create a test bot and validate that token is created
|
||||
const botUsername = `bot-${getRandomId()}`;
|
||||
createBotInteractive(newTeam, botUsername);
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// # Add a new token to the bot
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(`Test Bot (@${botUsername})`).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Click the 'Create token' button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Create New Token').should('be.visible').click();
|
||||
|
||||
// # Add description
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('input').click().type('description!');
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('saveSetting').click();
|
||||
|
||||
// # Click Close button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Close').should('be.visible').click();
|
||||
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// * Check that token is visible
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findAllByText(/Token ID:/).should('have.length', 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Bot} from '@mattermost/types/bots';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {createBotInteractive} from './helpers';
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Bot accounts - CRUD Testing', () => {
|
||||
let newTeam: Team;
|
||||
let testBot: Bot & {fullDisplayName: string};
|
||||
|
||||
before(() => {
|
||||
// # Create and visit new channel
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
newTeam = team;
|
||||
});
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Create a test bot
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
testBot = bot;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1841 Long description text', () => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(testBot.fullDisplayName).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Click the edit button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Edit').should('be.visible').click();
|
||||
|
||||
// * Validate redirect to edit screen
|
||||
cy.url().should('include', `/${newTeam.name}/integrations/bots/edit`);
|
||||
|
||||
// # type long string
|
||||
const longDescription = 'A'.repeat(1020); // 1024 is the limit
|
||||
cy.get('#description').clear().type(longDescription);
|
||||
|
||||
// * Validate that it's fully typed
|
||||
cy.get('#description').should('have.value', longDescription);
|
||||
|
||||
// # type some more characters
|
||||
cy.get('#description').type('{end}12345');
|
||||
|
||||
// * Validate that it's partially updated
|
||||
cy.get('#description').should('have.value', longDescription + '1234');
|
||||
|
||||
// # Update the bot
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
// * Validate that bot saved correctly
|
||||
cy.url().should('include', `/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Validate that the description exists
|
||||
cy.findAllByText(longDescription + '1234').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1842 Change BOT role', () => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(testBot.fullDisplayName).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Click the edit button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Edit').should('be.visible').click();
|
||||
|
||||
// * Validate redirect to edit screen
|
||||
cy.url().should('include', `/${newTeam.name}/integrations/bots/edit`);
|
||||
|
||||
// # Select sysadmin
|
||||
cy.get('select').select('System Admin');
|
||||
|
||||
// * Validate that permissions are set and read only
|
||||
cy.get('#postChannels').should('be.checked').should('be.disabled');
|
||||
cy.get('#postAll').should('be.checked').should('be.disabled');
|
||||
|
||||
// # Update the bot
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
// * Validate that bot saved correctly
|
||||
cy.url().should('include', `/${newTeam.name}/integrations/bots`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1843 ID along with actual token is created', () => {
|
||||
// # Create the bot and validate token is visible
|
||||
createBotInteractive(newTeam);
|
||||
cy.get('#doneButton').click();
|
||||
});
|
||||
|
||||
it('MM-T1844 Token is hidden when you return to the page but ID is still visible', () => {
|
||||
// # Create the bot and validate token is visible
|
||||
|
||||
const botUsername = `bot-${getRandomId()}`;
|
||||
|
||||
createBotInteractive(newTeam, botUsername).then((text) => {
|
||||
// # Close the Add dialog
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(`Test Bot (@${botUsername})`).then((el) => {
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// * Validate that token is NOT visible on the next page
|
||||
const token = text.substr(text.indexOf('Token: ') + 7, 26);
|
||||
cy.findByText(new RegExp(token)).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1845 Create a new token via the UI', () => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(testBot.fullDisplayName).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Click the 'Create token' button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Create New Token').should('be.visible').click();
|
||||
|
||||
// # Try saving without description
|
||||
cy.findByTestId('saveSetting').click();
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('input').scrollIntoView();
|
||||
|
||||
// * Check for error message
|
||||
cy.get('#clientError').should('be.visible');
|
||||
|
||||
// # Add description
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('input').click().type(testBot.username + 'description!');
|
||||
|
||||
// # Save and check that no error is visible
|
||||
cy.findByTestId('saveSetting').click();
|
||||
|
||||
cy.get('#clientError').should('not.exist');
|
||||
|
||||
cy.findAllByText(testBot.username + 'description!').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1848 Delete Token', () => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(testBot.fullDisplayName).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Click the 'Create token' button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Create New Token').should('be.visible').click();
|
||||
|
||||
// # Add description
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('input').click().type('description!');
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('saveSetting').click();
|
||||
|
||||
// # Click Close button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Close').should('be.visible').click();
|
||||
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// * Check that token is visible
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText(/Token ID:/).should('be.visible');
|
||||
|
||||
// # Click Delete button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText('Delete').should('be.visible').click();
|
||||
|
||||
// * Validate that confirmation dialog is visible and click the delete button
|
||||
cy.get('#confirmModalButton').should('be.visible').click();
|
||||
|
||||
// * Check that token is not visible
|
||||
cy.wrap(el[0].parentElement!.parentElement!).findByText(/Token ID:/).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
describe('Bot display name', () => {
|
||||
let offTopicChannel: Channel;
|
||||
let otherSysadmin: UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.intercept('**/api/v4/**').as('resources');
|
||||
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnableUserAccessTokens: false,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
otherSysadmin = sysadmin;
|
||||
cy.apiLogin(otherSysadmin);
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
cy.apiGetChannelByName(team.name, 'off-topic').then(({channel}) => {
|
||||
offTopicChannel = channel;
|
||||
});
|
||||
cy.visit(`/${team.name}/channels/off-topic`);
|
||||
cy.wait('@resources');
|
||||
|
||||
// # Wait for the page to fully load before continuing
|
||||
cy.get('#sidebar-header-container').should('be.visible').and('have.text', team.display_name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1813 Display name for bots stays current', () => {
|
||||
cy.makeClient({user: otherSysadmin}).then((client) => {
|
||||
// # Create a bot and get bot user id
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
const botUserId = bot.user_id;
|
||||
const firstMessage = 'This is the first message from a bot that will change its name';
|
||||
const secondMessage = 'This is the second message from a bot that has changed its name';
|
||||
|
||||
// # Get token from bot's id
|
||||
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
|
||||
//# Add bot to team
|
||||
cy.apiAddUserToTeam(offTopicChannel.team_id, botUserId);
|
||||
|
||||
// # Post message as bot through api with auth token
|
||||
const props = {attachments: [{pretext: 'Some Pretext', text: 'Some Text'}]};
|
||||
cy.postBotMessage({token: token!, message: firstMessage, props, channelId: offTopicChannel.id}).
|
||||
its('id').
|
||||
should('exist').
|
||||
as('botPost');
|
||||
cy.uiWaitUntilMessagePostedIncludes(firstMessage);
|
||||
|
||||
// # Go to the channel
|
||||
cy.get('#sidebarItem_off-topic').click({force: true});
|
||||
|
||||
// * Verify bot display name
|
||||
cy.get('@botPost').then((postIdA) => {
|
||||
cy.get(`#post_${postIdA} button.user-popover`).click();
|
||||
|
||||
cy.get('div.user-profile-popover').
|
||||
should('be.visible');
|
||||
|
||||
cy.findByTestId(`popover-fullname-${bot.username}`).
|
||||
should('have.text', bot.display_name);
|
||||
}).then(() => {
|
||||
// # Change display name after prior verification
|
||||
cy.wrap(client.patchBot(bot.user_id, {display_name: `NEW ${bot.display_name}`})).then(() => {
|
||||
cy.postBotMessage({token: token!, message: secondMessage, props, channelId: offTopicChannel.id}).
|
||||
its('id').
|
||||
should('exist').
|
||||
as('newBotPost');
|
||||
cy.uiWaitUntilMessagePostedIncludes(secondMessage);
|
||||
|
||||
// * Verify changed display name
|
||||
cy.get('@newBotPost').then(() => {
|
||||
cy.get('div.user-profile-popover').
|
||||
should('be.visible');
|
||||
|
||||
cy.findByTestId(`popover-fullname-${bot.username}`).
|
||||
should('have.text', `NEW ${bot.display_name}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import * as MESSAGES from '@/fixtures/messages';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Edit bot', () => {
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, townSquareUrl}) => {
|
||||
testTeam = team;
|
||||
|
||||
cy.visit(townSquareUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1840 Description allows for special character', () => {
|
||||
const userName = `bot-${getRandomId()}`;
|
||||
const description = MESSAGES.LARGE.concat('!@#$%&*');
|
||||
|
||||
// # Create bot
|
||||
createBot(userName, testTeam.name);
|
||||
|
||||
// * Set alias for bot entry in bot list, this also checks that the bot entry exists
|
||||
cy.get('.backstage-list__item').contains('.backstage-list__item', userName).as('botEntry');
|
||||
|
||||
cy.get('@botEntry').then((el) => {
|
||||
// # Find the edit link for the bot
|
||||
const editLink = el.find('.item-actions>a');
|
||||
|
||||
if (editLink.text() === 'Edit') {
|
||||
// # Click the edit link for the bot
|
||||
cy.wrap(editLink).click();
|
||||
|
||||
// * Check that user name is as expected
|
||||
cy.get('#username').should('have.value', userName);
|
||||
|
||||
// * Check that details are empty
|
||||
cy.get('#displayName').should('have.value', '');
|
||||
cy.get('#description').should('have.value', '');
|
||||
|
||||
// # Set long description
|
||||
cy.get('#description').clear().type(description);
|
||||
|
||||
// # Click update button
|
||||
cy.get('#saveBot').click();
|
||||
}
|
||||
});
|
||||
|
||||
// * Get bot entry in bot list by username
|
||||
cy.get('@botEntry').then((el) => {
|
||||
cy.wrap(el).scrollIntoView();
|
||||
|
||||
// * Confirm long description is as expected
|
||||
cy.wrap(el.find('.bot-details__description')).should('have.text', description);
|
||||
});
|
||||
});
|
||||
|
||||
function createBot(userName: string, teamName: string) {
|
||||
// # Go to bot integrations page
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
cy.get('a.integration-option[href$="/bots"]').click();
|
||||
cy.get('#addBotAccount').click();
|
||||
|
||||
// # Fill and submit form
|
||||
cy.get('#username').type(userName);
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
// * Verify confirmation page
|
||||
cy.url().
|
||||
should('include', `/${teamName}/integrations/confirm`).
|
||||
should('match', /token=[a-zA-Z0-9]{26}/);
|
||||
|
||||
// * Verify confirmation form/token
|
||||
cy.get('div.backstage-form').
|
||||
should('include.text', 'Setup Successful').
|
||||
should((confirmation) => {
|
||||
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
|
||||
});
|
||||
cy.get('#doneButton').click();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Edit bot username', () => {
|
||||
let team: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then((out) => {
|
||||
team = out.team;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2923 Edit bot username.', () => {
|
||||
// # Visit bot config
|
||||
cy.visit('/admin_console/integrations/bot_accounts');
|
||||
|
||||
// # Verify that the setting is enabled
|
||||
cy.findByTestId('ServiceSettings.EnableBotAccountCreationtrue', {timeout: TIMEOUTS.ONE_MIN}).should('be.checked');
|
||||
|
||||
// # Visit the integrations
|
||||
goToCreateBot();
|
||||
|
||||
const initialBotName = `bot-${getRandomId()}`;
|
||||
|
||||
// # Fill and submit form
|
||||
cy.get('#username').clear().type(initialBotName);
|
||||
cy.get('#displayName').clear().type('Test Bot');
|
||||
cy.get('#saveBot').click();
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// * Set alias for bot entry in bot list, this also checks that the bot entry exists
|
||||
cy.get('.backstage-list__item').contains('.backstage-list__item', initialBotName).as('botEntry');
|
||||
|
||||
cy.get('@botEntry').then((el) => {
|
||||
// # Find the edit link for the bot
|
||||
const editLink = el.find('.item-actions>a');
|
||||
|
||||
if (editLink.text() === 'Edit') {
|
||||
// # Click the edit link for the bot
|
||||
cy.wrap(editLink).click();
|
||||
|
||||
// * Check that user name is as expected
|
||||
cy.get('#username').should('have.value', initialBotName);
|
||||
|
||||
// * Check that the display name is correct
|
||||
cy.get('#displayName').should('have.value', 'Test Bot');
|
||||
|
||||
// * Check that description is empty
|
||||
cy.get('#description').should('have.value', '');
|
||||
|
||||
const newBotName = `bot-${getRandomId()}`;
|
||||
|
||||
// * Enter the new user name
|
||||
cy.get('#username').clear().type(newBotName);
|
||||
|
||||
// # Click update button
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
return cy.wrap(newBotName);
|
||||
}
|
||||
return cy.wrap(null);
|
||||
}).then((newBotName) => {
|
||||
if (!newBotName) {
|
||||
throw new Error('Expected edited bot username to be returned');
|
||||
}
|
||||
|
||||
// * Set alias for bot entry in bot list, this also checks that the bot entry exists
|
||||
cy.get('.backstage-list__item').contains('.backstage-list__item', newBotName).as('newbotEntry');
|
||||
|
||||
// * Get bot entry in bot list by username
|
||||
cy.get('@newbotEntry').then((el) => {
|
||||
cy.wrap(el).scrollIntoView();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1838 Bot naming convention is enforced', () => {
|
||||
goToCreateBot();
|
||||
|
||||
// # Attempt invalid bot usernames
|
||||
tryUsername('be', NAMING_WARNING_STANDARD);
|
||||
tryUsername('@be', NAMING_WARNING_STANDARD);
|
||||
tryUsername('abe.', NAMING_WARNING_ENDING_PERIOD);
|
||||
|
||||
// # Attempt valid bot username
|
||||
const validBotName = `abe-the-bot-${getRandomId()}`;
|
||||
tryUsername(validBotName);
|
||||
});
|
||||
|
||||
const NAMING_WARNING_STANDARD = 'Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.';
|
||||
const NAMING_WARNING_ENDING_PERIOD = 'Bot usernames cannot have a period as the last character';
|
||||
|
||||
function tryUsername(name: string, warningMessage?: string) {
|
||||
cy.get('#username').clear().type(name);
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
if (warningMessage) {
|
||||
// * Verify expected warning
|
||||
cy.get('.backstage-form__footer .has-error').should('have.text', warningMessage);
|
||||
} else {
|
||||
// * Verify confirmation page
|
||||
cy.url().
|
||||
should('include', `/${team.name}/integrations/confirm`).
|
||||
should('match', /token=[a-zA-Z0-9]{26}/);
|
||||
|
||||
// * Verify confirmation form/token
|
||||
cy.get('div.backstage-form').
|
||||
should('include.text', 'Setup Successful').
|
||||
and('include.text', name).
|
||||
and((confirmation) => {
|
||||
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
|
||||
});
|
||||
|
||||
// # back to start
|
||||
goToCreateBot();
|
||||
}
|
||||
}
|
||||
|
||||
function goToCreateBot() {
|
||||
cy.visit(`/${team.name}/integrations/bots`);
|
||||
|
||||
// * Assert that adding bots possible
|
||||
cy.get('#addBotAccount', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
export function createBotInteractive(team: Team, username = `bot-${getRandomId()}`) {
|
||||
// # Visit the Integrations > Bot Accounts page
|
||||
cy.visit(`/${team.name}/integrations/bots`);
|
||||
|
||||
// # Click add bot
|
||||
cy.get('#addBotAccount').click();
|
||||
|
||||
// # Fill and submit form
|
||||
cy.get('#username').type(username);
|
||||
cy.get('#displayName').type('Test Bot');
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
// * Verify confirmation page
|
||||
cy.url({timeout: TIMEOUTS.ONE_MIN}).
|
||||
should('include', `/${team.name}/integrations/confirm`).
|
||||
should('match', /token=[a-zA-Z0-9]{26}/);
|
||||
|
||||
// * Verify confirmation form/token
|
||||
cy.get('div.backstage-form').
|
||||
should('include.text', 'Setup Successful').
|
||||
should((confirmation) => {
|
||||
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
|
||||
});
|
||||
|
||||
return cy.get('div.backstage-form').invoke('text');
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Bot} from '@mattermost/types/bots';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
import {generateRandomUser} from '@/support/api/user';
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Bots in lists', () => {
|
||||
let team: Team;
|
||||
let channel: Channel;
|
||||
let bots: Bot[];
|
||||
let createdUsers: UserProfile[];
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then((out) => {
|
||||
team = out.team;
|
||||
channel = out.channel;
|
||||
});
|
||||
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Create bots
|
||||
bots = await Promise.all([
|
||||
client.createBot(createBotPatch()),
|
||||
client.createBot(createBotPatch()),
|
||||
client.createBot(createBotPatch()),
|
||||
]);
|
||||
|
||||
// # Create users
|
||||
createdUsers = await Promise.all([
|
||||
client.createUser(generateRandomUser() as UserProfile, '', ''),
|
||||
client.createUser(generateRandomUser() as UserProfile, '', ''),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
...bots,
|
||||
...createdUsers,
|
||||
].map(async (user) => {
|
||||
// * Verify username exists
|
||||
cy.wrap(user).its('username');
|
||||
|
||||
// # Add to team and channel
|
||||
await client.addToTeam(team.id, (user as Bot).user_id ?? (user as UserProfile).id);
|
||||
await client.addToChannel((user as Bot).user_id ?? (user as UserProfile).id, channel.id);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1834 Bots are not listed on “Users” list in System Console > Users', () => {
|
||||
// # Go to system console > users
|
||||
cy.visit('/admin_console/user_management/users');
|
||||
|
||||
bots.forEach(({username}) => {
|
||||
// # Search for bot
|
||||
cy.get('#input_searchTerm').clear().type(`${username}`).wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// * Verify bot not in list
|
||||
cy.get('.noRows').should('have.text', 'No data');
|
||||
|
||||
// * Verify pseudo checksum total of non bot users
|
||||
cy.get('.adminConsoleListTabletOptionalHead > span').should('have.text', '0 users').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Bot} from '@mattermost/types/bots';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
import {generateRandomUser} from '@/support/api/user';
|
||||
|
||||
describe('Bots in lists', () => {
|
||||
let team: Team;
|
||||
let channel: Channel;
|
||||
let testUser: UserProfile;
|
||||
|
||||
const STATUS_PRIORITY: Record<string, number> = {
|
||||
online: 0,
|
||||
away: 1,
|
||||
dnd: 2,
|
||||
offline: 3,
|
||||
ooo: 3,
|
||||
};
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then((out) => {
|
||||
team = out.team;
|
||||
channel = out.channel;
|
||||
testUser = out.user;
|
||||
});
|
||||
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Create bots
|
||||
const bots = await Promise.all([
|
||||
client.createBot(createBotPatch() as Partial<Bot>),
|
||||
client.createBot(createBotPatch() as Partial<Bot>),
|
||||
client.createBot(createBotPatch() as Partial<Bot>),
|
||||
]);
|
||||
|
||||
// # Create users
|
||||
const createdUsers = await Promise.all([
|
||||
client.createUser(generateRandomUser() as UserProfile, '', ''),
|
||||
client.createUser(generateRandomUser() as UserProfile, '', ''),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
...bots,
|
||||
...createdUsers,
|
||||
].map(async (user) => {
|
||||
// * Verify username exists
|
||||
cy.wrap(user).its('username');
|
||||
|
||||
// # Add to team and channel
|
||||
await client.addToTeam(team.id, (user as Bot).user_id ?? (user as UserProfile).id);
|
||||
await client.addToChannel((user as Bot).user_id ?? (user as UserProfile).id, channel.id);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1835 Channel Members list for BOTs', () => {
|
||||
cy.makeClient({user: testUser}).then((client) => {
|
||||
// # Login as regular user and visit a channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel members
|
||||
cy.get('.channel-header__trigger').click();
|
||||
cy.findByText('Manage members').click();
|
||||
|
||||
cy.get('.more-modal__row .more-modal__name').then(async ($query) => {
|
||||
// # Extract usernames from jQuery collection
|
||||
const usernames = $query.toArray().map(({innerText}) => innerText.split('\n')[0]);
|
||||
|
||||
// # Get users
|
||||
const profiles = await client.getProfilesByUsernames(usernames);
|
||||
const statuses = await client.getStatusesByIds(profiles.map((user) => user.id));
|
||||
const users = Cypress._.zip(profiles, statuses).map(([profile, status]) => ({...profile, ...status}));
|
||||
|
||||
// # Sort 'em
|
||||
const sortedUsers = Cypress._.sortBy(users, [
|
||||
({is_bot: isBot}) => (isBot ? 1 : 0), // users first
|
||||
({status}) => STATUS_PRIORITY[status ?? 'offline'],
|
||||
({username}) => username,
|
||||
]);
|
||||
|
||||
// * Verify order of member-dropdown users against API-sourced/data-sorted version
|
||||
cy.wrap(usernames).should('deep.equal', sortedUsers.map(({username}) => username));
|
||||
});
|
||||
|
||||
// * Verify no statuses on bots
|
||||
cy.get('.more-modal__row--bot .status-wrapper .status').should('not.exist');
|
||||
|
||||
// * Verify bot badges
|
||||
cy.get('.more-modal__row--bot .Tag').then(($tags) => {
|
||||
$tags.toArray().forEach((tagEl) => {
|
||||
cy.wrap(tagEl).then(() => tagEl.scrollIntoView());
|
||||
cy.wrap(tagEl).should('be.visible').and('have.text', 'BOT');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
import {createChannelPatch} from '@/support/api/channel';
|
||||
|
||||
describe('Managing bots in Teams and Channels', () => {
|
||||
let team: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: 'sample.mattermost.com',
|
||||
},
|
||||
});
|
||||
cy.apiInitSetup({loginAfter: true}).then((out) => {
|
||||
team = out.team;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1814 Add a BOT to a team', () => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Go to channel
|
||||
const channel = await client.getChannelByName(team.id, 'town-square');
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Invite bot to team (asserts the modal success message)
|
||||
const bot = await client.createBot(createBotPatch());
|
||||
cy.uiInviteMemberToCurrentTeam(bot.username);
|
||||
|
||||
// * Verify system message in-channel
|
||||
cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the team by you.`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1815 Add a BOT to a team that has email restricted', () => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Go to channel
|
||||
const channel = await client.getChannelByName(team.id, 'town-square');
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Invite bot to team
|
||||
const bot = await client.createBot(createBotPatch());
|
||||
cy.uiInviteMemberToCurrentTeam(bot.username);
|
||||
|
||||
// * Verify system message in-channel
|
||||
cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the team by you.`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1816 Add a BOT to a channel', () => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Go to channel
|
||||
const channel = await client.createChannel(createChannelPatch(team.id, 'a-chan', 'A Channel') as Channel);
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Add bot to team
|
||||
const bot = await client.createBot(createBotPatch());
|
||||
await client.addToTeam(team.id, bot.user_id);
|
||||
|
||||
// # Add bot to channel in team
|
||||
cy.uiInviteUsersToCurrentChannel([bot.username]);
|
||||
|
||||
// * Verify system message in-channel
|
||||
cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the channel by you.`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1817 Add a BOT to a channel that is not on the Team', () => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Go to channel
|
||||
const channel = await client.createChannel(createChannelPatch(team.id, 'a-chan', 'A Channel') as Channel);
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Invite bot to team
|
||||
const bot = await client.createBot(createBotPatch());
|
||||
cy.postMessage(`/invite @${bot.username} `);
|
||||
|
||||
// * Verify system message in-channel
|
||||
cy.uiWaitUntilMessagePostedIncludes(`You can add @${bot.username} to this channel once they are members of`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1818 No ephemeral post about Adding a bot to a channel When Bot is mentioned', () => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Go to channel
|
||||
const channel = await client.createChannel(createChannelPatch(team.id, 'a-chan', 'A Channel') as Channel);
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # And bot to team
|
||||
const bot = await client.createBot(createBotPatch());
|
||||
cy.apiAddUserToTeam(team.id, bot.user_id);
|
||||
|
||||
// # Mention bot
|
||||
const message = `hey @${bot.username}, tell me a rhyme..`;
|
||||
cy.postMessage(message);
|
||||
|
||||
// * Verify no ephemeral post is shown asking if you want to invite the bot to the server
|
||||
cy.uiGetNthPost(-1).should('contain.text', message);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts @plugin @not_cloud
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {matterpollPlugin} from '@/utils/plugins';
|
||||
|
||||
describe('Managing bot accounts', () => {
|
||||
let newTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.shouldHavePluginUploadEnabled();
|
||||
|
||||
// # Create and visit new channel
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
newTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
it('MM-T1859 Bot is kept active when owner is disabled', () => {
|
||||
// # Visit bot config
|
||||
cy.visit('/admin_console/integrations/bot_accounts');
|
||||
|
||||
// # Click 'false' to disable
|
||||
cy.findByTestId('ServiceSettings.DisableBotsWhenOwnerIsDeactivatedfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('saveSetting').should('be.enabled').click();
|
||||
|
||||
// # Create another admin account
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
// # Login as the new admin
|
||||
cy.apiLogin(sysadmin);
|
||||
|
||||
// # Create a new bot as the new admin
|
||||
cy.apiCreateBot({prefix: 'stay-enabled-bot'}).then(({bot}) => {
|
||||
// # Login again as main admin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Deactivate the newly created admin
|
||||
cy.apiDeactivateUser(sysadmin.id);
|
||||
|
||||
// # Get bot list
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// # Search for the other bot
|
||||
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.display_name!);
|
||||
|
||||
// * Validate that the plugin is still active, even though its owner is disabled
|
||||
cy.get('.bot-list__disabled').should('not.exist');
|
||||
cy.findByText(bot.fullDisplayName).scrollIntoView().should('be.visible');
|
||||
|
||||
cy.visit(`/${newTeam.name}/messages/@sysadmin`);
|
||||
|
||||
// # Get last post message text
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).as('postMessageText');
|
||||
});
|
||||
|
||||
// * Verify entire message
|
||||
cy.get('@postMessageText').
|
||||
should('be.visible').
|
||||
and('contain.text', `${sysadmin.username} was deactivated. They managed the following bot accounts`).
|
||||
and('contain.text', bot.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('MM-T1853 Bots managed plugins can be created when Enable Bot Account Creation is set to false', () => {
|
||||
// # Upload and enable "matterpoll" plugin
|
||||
cy.apiUploadAndEnablePlugin(matterpollPlugin);
|
||||
|
||||
// # Visit bot config
|
||||
cy.visit('/admin_console/integrations/bot_accounts');
|
||||
|
||||
// # Click 'false' to disable
|
||||
cy.findByTestId('ServiceSettings.EnableBotAccountCreationfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('saveSetting').should('be.enabled').click();
|
||||
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Validate that plugin installed ok
|
||||
cy.contains('Matterpoll (@matterpoll)', {timeout: TIMEOUTS.ONE_MIN});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Managing bot accounts', () => {
|
||||
let newTeam: Team;
|
||||
|
||||
before(() => {
|
||||
// # Create and visit new channel
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
newTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnableBotAccountCreation: true,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
});
|
||||
|
||||
it('MM-T1851 No option to create BOT accounts when Enable Bot Account Creation is set to False.', () => {
|
||||
// # Visit bot config
|
||||
cy.visit('/admin_console/integrations/bot_accounts');
|
||||
|
||||
// # Click 'false' to disable
|
||||
cy.findByTestId('ServiceSettings.EnableBotAccountCreationfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('saveSetting').should('be.enabled').click();
|
||||
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Assert that adding bots is not possible
|
||||
cy.get('#addBotAccount', {timeout: TIMEOUTS.ONE_MIN}).should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1852 Bot creation via API is not permitted when Enable Bot Account Creation is set to False', () => {
|
||||
// # Visit bot config
|
||||
cy.visit('/admin_console/integrations/bot_accounts');
|
||||
|
||||
// # Click 'false' to disable
|
||||
cy.findByTestId('ServiceSettings.EnableBotAccountCreationfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('saveSetting').should('be.enabled').click().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Validate that creating bot fails
|
||||
|
||||
cy.request({
|
||||
headers: {'X-Requested-With': 'XMLHttpRequest'},
|
||||
url: '/api/v4/bots',
|
||||
method: 'POST',
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
username: `bot-${getRandomId()}`,
|
||||
display_name: 'test bot',
|
||||
description: 'test bot',
|
||||
},
|
||||
}).then((response) => {
|
||||
expect(response.status).to.equal(403);
|
||||
expect(response.body.message).to.equal('Bot creation has been disabled.');
|
||||
return cy.wrap(response);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1854 Bots can be create when Enable Bot Account Creation is set to True.', () => {
|
||||
// # Visit bot config
|
||||
cy.visit('/admin_console/integrations/bot_accounts');
|
||||
|
||||
// * Check that creation is enabled
|
||||
cy.findByTestId('ServiceSettings.EnableBotAccountCreationtrue', {timeout: TIMEOUTS.ONE_MIN}).should('be.checked');
|
||||
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Assert that adding bots is possible
|
||||
cy.get('#addBotAccount', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1856 Disable Bot', () => {
|
||||
cy.apiCreateBot({prefix: 'test-bot'}).then(({bot}) => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// # Filter bot
|
||||
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().then((el) => {
|
||||
// # Click the disable button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(3)').should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Check that the bot is in the 'disabled' section
|
||||
cy.get('.bot-list__disabled').scrollIntoView().findByText(bot.fullDisplayName).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1857 Enable Bot', () => {
|
||||
cy.apiCreateBot({prefix: 'test-bot'}).then(({bot}) => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().then((el) => {
|
||||
// # Click the disable button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(3)').should('be.visible').click();
|
||||
});
|
||||
|
||||
// # Filter bot
|
||||
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
|
||||
|
||||
// # Re-enable the bot
|
||||
cy.get('.bot-list__disabled').scrollIntoView().findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().then((el) => {
|
||||
// # Click the enable button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(1)').should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Check that the bot is in the 'enabled' section
|
||||
cy.findByText(bot.fullDisplayName).scrollIntoView().should('be.visible');
|
||||
cy.get('.bot-list__disabled').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1858 Search active and disabled Bot accounts', () => {
|
||||
cy.apiCreateBot({prefix: 'hello-bot'}).then(({bot}) => {
|
||||
// # Visit the integrations
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// * Check that the previously created bot is listed
|
||||
cy.findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).then((el) => {
|
||||
// # Make sure it's on the screen
|
||||
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
|
||||
|
||||
// # Click the disable button
|
||||
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(3)').should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Validate that disabled section appears
|
||||
cy.get('.bot-list__disabled').scrollIntoView().should('be.visible');
|
||||
|
||||
// # Search for the other bot
|
||||
cy.apiCreateBot({prefix: 'other-bot'}).then(({bot: otherBot}) => {
|
||||
cy.get('#searchInput').type(otherBot.username);
|
||||
|
||||
// * Validate that disabled section disappears
|
||||
cy.get('.bot-list__disabled').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1860 Bot is disabled when owner is deactivated', () => {
|
||||
// # Create another admin account
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
// # Login as the new admin
|
||||
cy.apiLogin(sysadmin);
|
||||
|
||||
// # Create a new bot as the new admin
|
||||
cy.apiCreateBot({prefix: 'stay-enabled-bot'}).then(({bot}) => {
|
||||
// # Login again as main admin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Deactivate the newly created admin
|
||||
cy.apiDeactivateUser(sysadmin.id);
|
||||
|
||||
// # Get bot list
|
||||
cy.visit(`/${newTeam.name}/integrations/bots`);
|
||||
|
||||
// # Search for the other bot
|
||||
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.display_name!);
|
||||
|
||||
// * Validate that the plugin is disabled since it's owner is deactivate
|
||||
cy.get('.bot-list__disabled').scrollIntoView().findByText(bot.fullDisplayName).scrollIntoView().should('be.visible');
|
||||
|
||||
cy.visit(`/${newTeam.name}/messages/@sysadmin`);
|
||||
|
||||
// # Get last post message text
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).as('postMessageText');
|
||||
});
|
||||
|
||||
// * Verify entire message
|
||||
cy.get('@postMessageText').
|
||||
should('be.visible').
|
||||
and('contain.text', `${sysadmin.username} was deactivated. They managed the following bot accounts`).
|
||||
and('contain.text', bot.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
describe('Bot post message', () => {
|
||||
let offTopicChannel: Channel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
cy.apiGetChannelByName(team.name, 'off-topic').then(({channel}) => {
|
||||
offTopicChannel = channel;
|
||||
});
|
||||
cy.visit(`/${team.name}/channels/off-topic`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1812 Post as a bot when personal access tokens are false', () => {
|
||||
// # Create a bot and get bot user id
|
||||
cy.apiCreateBot().then(({bot}) => {
|
||||
const botUserId = bot.user_id;
|
||||
const message = 'This is a message from a bot.';
|
||||
|
||||
// # Get token from bot's id
|
||||
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
|
||||
//# Add bot to team
|
||||
cy.apiAddUserToTeam(offTopicChannel.team_id, botUserId);
|
||||
|
||||
// # Post message as bot through api with auth token
|
||||
const props = {attachments: [{pretext: 'Some Pretext', text: 'Some Text'}]};
|
||||
cy.postBotMessage({token: token!, message, props, channelId: offTopicChannel.id}).
|
||||
its('id').
|
||||
should('exist').
|
||||
as('botPost');
|
||||
|
||||
// * Verify bot message
|
||||
cy.uiWaitUntilMessagePostedIncludes(message);
|
||||
cy.get('@botPost').then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).
|
||||
should('be.visible').
|
||||
and('have.text', message);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
|
||||
describe('Managing bots in Teams and Channels', () => {
|
||||
let team: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictCreationToDomains: 'sample.mattermost.com',
|
||||
},
|
||||
});
|
||||
cy.apiInitSetup({loginAfter: true}).then((out) => {
|
||||
team = out.team;
|
||||
});
|
||||
});
|
||||
it('MM-T1819 Promote a BOT to team admin', () => {
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Go to channel
|
||||
const channel = await client.getChannelByName(team.id, 'off-topic');
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Add bot to team
|
||||
const bot = await client.createBot(createBotPatch());
|
||||
await client.addToTeam(team.id, bot.user_id);
|
||||
|
||||
// # Open team menu and click 'Manage Members'
|
||||
cy.uiOpenTeamMenu('Manage members');
|
||||
|
||||
// # Find bot
|
||||
cy.get('.more-modal__list').find('.more-modal__row').its('length').should('be.gt', 0);
|
||||
cy.get('#searchUsersInput').type(bot.username);
|
||||
|
||||
// # Wait for loading screen
|
||||
cy.get('#teamMembersModal .loading-screen').should('be.visible');
|
||||
|
||||
// # Find bot member dropdown
|
||||
cy.get(`#teamMembersDropdown_${bot.username}`).as('memberDropdown').should('contain.text', 'Member').click();
|
||||
|
||||
// # Promote bot to team admin
|
||||
cy.get('li').contains('Make Team Admin').click();
|
||||
|
||||
// * Verify bot was promoted
|
||||
cy.get('@memberDropdown').should('contain.text', 'Team Admin');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts @not_cloud
|
||||
|
||||
import type {Bot} from '@mattermost/types/bots';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
import {generateRandomUser} from '@/support/api/user';
|
||||
|
||||
describe('Bot accounts', () => {
|
||||
let team: Team;
|
||||
let channel: Channel;
|
||||
let testUser: UserProfile;
|
||||
let bots: Bot[];
|
||||
let createdUsers: UserProfile[];
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
|
||||
cy.apiInitSetup().then((out) => {
|
||||
team = out.team;
|
||||
channel = out.channel;
|
||||
testUser = out.user;
|
||||
});
|
||||
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Create bots
|
||||
bots = await Promise.all([
|
||||
client.createBot(createBotPatch()),
|
||||
client.createBot(createBotPatch()),
|
||||
client.createBot(createBotPatch()),
|
||||
]);
|
||||
|
||||
// # Create users
|
||||
createdUsers = await Promise.all([
|
||||
client.createUser(generateRandomUser() as UserProfile, '', ''),
|
||||
client.createUser(generateRandomUser() as UserProfile, '', ''),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
...bots,
|
||||
...createdUsers,
|
||||
].map(async (user) => {
|
||||
// * Verify username exists
|
||||
cy.wrap(user).its('username');
|
||||
|
||||
// # Add to team and channel
|
||||
await client.addToTeam(team.id, (user as Bot).user_id ?? (user as UserProfile).id);
|
||||
await client.addToChannel((user as Bot).user_id ?? (user as UserProfile).id, channel.id);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
});
|
||||
|
||||
it('MM-T1836 Bot accounts display', () => {
|
||||
// # Login as regular user and visit a channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/messages/@${bots[0].username}`);
|
||||
|
||||
cy.get('.SidebarChannelGroup:contains(DIRECT MESSAGES) .SidebarChannel.active > .SidebarLink').then(($link) => {
|
||||
// * Verify DM label
|
||||
cy.wrap($link).find('.SidebarChannelLinkLabel').should('have.text', bots[0].username);
|
||||
|
||||
// * Verify bot icon exists
|
||||
cy.wrap($link).find('.Avatar').should('exist').
|
||||
invoke('attr', 'src').
|
||||
then((url) => cy.request({url, encoding: 'binary'})).
|
||||
then(({body}) => {
|
||||
// * Verify it matches default bot avatar
|
||||
cy.fixture('bot-default-avatar.png', 'binary').should('deep.equal', body);
|
||||
});
|
||||
});
|
||||
|
||||
cy.postMessage('Bump bot chat recency');
|
||||
|
||||
// # Open a new DM
|
||||
cy.visit(`/${team.name}/messages/@${createdUsers[0].username}`);
|
||||
cy.postMessage('Hello, regular user');
|
||||
|
||||
// * Verify Bots and Regular users as siblings in DMs
|
||||
cy.get('.SidebarChannelGroup:contains(DIRECT MESSAGES) .SidebarChannel.active').siblings('.SidebarChannel').then(($siblings) => {
|
||||
cy.wrap($siblings).contains('.SidebarChannelLinkLabel', bots[0].username);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @bot_accounts
|
||||
|
||||
import type {Bot} from '@mattermost/types/bots';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {createBotPatch} from '@/support/api/bots';
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Bot tags', () => {
|
||||
let me: UserProfile;
|
||||
let team: Team;
|
||||
let channel: Channel;
|
||||
let postId: string;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then((out) => {
|
||||
team = out.team;
|
||||
channel = out.channel;
|
||||
});
|
||||
|
||||
let meId: string;
|
||||
|
||||
cy.getCurrentUserId().then((id) => {
|
||||
meId = id;
|
||||
});
|
||||
|
||||
cy.makeClient().then(async (client) => {
|
||||
// # Setup state
|
||||
me = await client.getUser(meId);
|
||||
const bot = await client.createBot(createBotPatch() as Partial<Bot>);
|
||||
await client.addToTeam(team.id, bot.user_id);
|
||||
await client.addToChannel(bot.user_id, channel.id);
|
||||
|
||||
const {token} = await client.createUserAccessToken(bot.user_id, 'Create token');
|
||||
const message = `Message for @${me.username}. Signed, @${bot.username}.`;
|
||||
|
||||
// # Post message as bot through api with auth token
|
||||
const props = {attachments: [{pretext: 'Some Pretext', text: 'Some Text'}]};
|
||||
|
||||
cy.postBotMessage({token: token!, message, props, channelId: channel.id}).then(async ({id}) => {
|
||||
postId = id;
|
||||
await client.pinPost(postId);
|
||||
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
cy.get(`#post_${postId}`).trigger('mouseover', {force: true});
|
||||
cy.wait(TIMEOUTS.HALF_SEC).get(`#CENTER_flagIcon_${postId}`).click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1831 BOT tag is visible in search results', () => {
|
||||
// # Open search
|
||||
cy.uiSearchPosts(`Message for @${me.username}`);
|
||||
|
||||
// * Verify bot badge
|
||||
cy.get('.sidebar--right__title').should('contain.text', 'Search Results');
|
||||
rhsPostHasBotBadge(postId);
|
||||
});
|
||||
|
||||
it('MM-T1832 BOT tag is visible in Recent Mentions', () => {
|
||||
// # Open mentions
|
||||
cy.uiGetRecentMentionButton().click();
|
||||
|
||||
// * Verify bot badge
|
||||
cy.get('.sidebar--right__title').should('contain.text', 'Recent Mentions');
|
||||
rhsPostHasBotBadge(postId);
|
||||
});
|
||||
|
||||
it('MM-T1833 BOT tag is visible in Pinned Messages', () => {
|
||||
// # Open pinned posts
|
||||
cy.uiGetChannelPinButton().click();
|
||||
|
||||
// * Verify bot badge
|
||||
cy.get('.sidebar--right__title').should('contain.text', 'Pinned messages');
|
||||
rhsPostHasBotBadge(postId);
|
||||
});
|
||||
|
||||
it('MM-T3659 BOT tag is visible in Saved Messages', () => {
|
||||
// # Open saved posts
|
||||
cy.uiGetSavedPostButton().click();
|
||||
|
||||
// * Verify bot badge
|
||||
cy.get('.sidebar--right__title').should('contain.text', 'Saved messages');
|
||||
rhsPostHasBotBadge(postId);
|
||||
});
|
||||
});
|
||||
|
||||
function rhsPostHasBotBadge(postId: string) {
|
||||
cy.get(`.post#searchResult_${postId} .Tag`).should('be.visible').and('have.text', 'BOT');
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Leave an archived channel', () => {
|
||||
let testTeam: Team;
|
||||
let offTopicUrl: string;
|
||||
const channelType = {
|
||||
all: 'Channel Type: All',
|
||||
public: 'Channel Type: Public',
|
||||
archived: 'Channel Type: Archived',
|
||||
};
|
||||
before(() => {
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, offTopicUrl: url}) => {
|
||||
testTeam = team;
|
||||
offTopicUrl = url;
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Post a message to the channel
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1680 Open archived channel from search results with permalink view in another channel is open', () => {
|
||||
// # Visit the test team
|
||||
cy.visit(`/${testTeam.name}`);
|
||||
|
||||
// # Create a new channel
|
||||
cy.uiCreateChannel({isNewSidebar: true});
|
||||
|
||||
// # Make a post
|
||||
const archivedPostText = `archived${getRandomId()}`;
|
||||
cy.postMessage(archivedPostText);
|
||||
cy.getLastPostId().as('archivedPostId');
|
||||
|
||||
// # Archive the newly created channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Switch away from the archived channel
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Make a post outside of the archived channel
|
||||
const otherPostText = `post${getRandomId()}`;
|
||||
cy.postMessage(otherPostText);
|
||||
cy.getLastPostId().as('otherPostId');
|
||||
|
||||
// # Search for the new post and jump to it from the search results
|
||||
cy.uiSearchPosts(otherPostText);
|
||||
cy.get<string>('@otherPostId').then((otherPostId) => cy.uiJumpToSearchResult(otherPostId));
|
||||
|
||||
// # Search for a post in the archived channel
|
||||
cy.uiSearchPosts(archivedPostText);
|
||||
|
||||
// # Open it in the RHS
|
||||
cy.get<string>('@archivedPostId').then((archivedPostId) => {
|
||||
cy.clickPostCommentIcon(archivedPostId, 'SEARCH');
|
||||
|
||||
// * Verify that the RHS has switched from search results to the thread
|
||||
cy.get('#searchContainer').should('not.exist');
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
// * Verify that the thread is visible and marked as archived
|
||||
cy.get(`#rhsPost_${archivedPostId}`).should('be.visible');
|
||||
cy.get('#rhsContainer .channel-archived-warning__container').should('be.visible');
|
||||
cy.get('#rhsContainer .channel-archived-warning__content').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1697 - Browse Public channels shows archived channels option', () => {
|
||||
// # Create public channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Click on browse channels
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible');
|
||||
|
||||
// # Click on dropdown
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Click archived channels
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Modal should contain created channel
|
||||
cy.get('#moreChannelsList').should('contain', channel.display_name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-T1699 - Browse Channels for all channel types shows archived channels option', () => {
|
||||
let archivedPrivateChannel: Channel;
|
||||
let archivedPublicChannel: Channel;
|
||||
|
||||
// # Create private channel
|
||||
cy.uiCreateChannel({isPrivate: true, isNewSidebar: true}).as('channel').then((channel) => {
|
||||
archivedPrivateChannel = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedPrivateChannel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
});
|
||||
|
||||
// # Create public channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
archivedPublicChannel = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedPublicChannel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
});
|
||||
|
||||
// # Click on browse channels from menu
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
// # All channel list opens by default
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Click on archived channels
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Channel list should contain newly created channels
|
||||
cy.get('#moreChannelsList').should('contain', archivedPrivateChannel.name);
|
||||
cy.get('#moreChannelsList').should('contain', archivedPublicChannel.display_name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-T1700 - All archived public channels are shown Important', () => {
|
||||
let archivedPublicChannel1: Channel;
|
||||
let archivedPublicChannel2: Channel;
|
||||
|
||||
// # Create public channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
archivedPublicChannel1 = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedPublicChannel1.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
});
|
||||
|
||||
// # Create second public channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
archivedPublicChannel2 = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedPublicChannel2.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Leave channel
|
||||
cy.uiLeaveChannel();
|
||||
|
||||
// # User should be redirected to last viewed channel
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${archivedPublicChannel1.name}`);
|
||||
});
|
||||
|
||||
// # Click on browse channels from menu
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
// # All channels are shown by default
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Go to archived channels
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Channel list should contain both archived public channels
|
||||
cy.get('#moreChannelsList').should('contain', archivedPublicChannel1.display_name);
|
||||
cy.get('#moreChannelsList').should('contain', archivedPublicChannel2.display_name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-T1701 - Only Private channels you are a member of are displayed', () => {
|
||||
let archivedPrivateChannel1: Channel;
|
||||
let archivedPrivateChannel2: Channel;
|
||||
|
||||
// # Create private channel
|
||||
cy.uiCreateChannel({isPrivate: true, isNewSidebar: true}).as('channel').then((channel) => {
|
||||
archivedPrivateChannel1 = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedPrivateChannel1.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
});
|
||||
|
||||
// # Create another private channel
|
||||
cy.uiCreateChannel({isPrivate: true, isNewSidebar: true}).as('channel').then((channel) => {
|
||||
archivedPrivateChannel2 = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedPrivateChannel2.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel();
|
||||
});
|
||||
|
||||
// # Leave channel modal is visible
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Click on browse channels from menu
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// # More channels modal opens
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
// # Show all channels is visible by default
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Go to archived channels
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # Channel list should contain only the private channel user is a member of
|
||||
cy.get('#moreChannelsList').should('contain', archivedPrivateChannel1.name);
|
||||
cy.get('#moreChannelsList').should('not.contain', archivedPrivateChannel2.name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-T1703 - User can open archived channels', () => {
|
||||
let archivedChannel: Channel;
|
||||
|
||||
// # Create a public channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
archivedChannel = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${archivedChannel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
});
|
||||
|
||||
// # Click on browse channels from menu
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// # More channels modal opens and lands on all channels
|
||||
cy.get('#browseChannelsModal').should('be.visible').then(() => {
|
||||
cy.findByText(channelType.all).should('be.visible').click();
|
||||
|
||||
// # Go to archived channels
|
||||
cy.findByText('Archived channels').click();
|
||||
|
||||
// # More channels list should contain the archived channel
|
||||
cy.get('#moreChannelsList').should('contain', archivedChannel.display_name);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import type {Channel, ServerChannel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Leave an archived channel', () => {
|
||||
let testTeam: Team;
|
||||
let testUser: UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
});
|
||||
|
||||
it('MM-T1687 App does not crash when another user archives a channel', () => {
|
||||
cy.makeClient({user: getAdminAccount()}).then(async (client) => {
|
||||
// # Have another user create a private channel
|
||||
const channelName = `channel${getRandomId()}`;
|
||||
const channelTest = {
|
||||
display_name: channelName,
|
||||
name: channelName,
|
||||
team_id: testTeam.id,
|
||||
type: 'P',
|
||||
} as Channel;
|
||||
|
||||
cy.wrap(client.createChannel(channelTest)).then(async (channel: unknown) => {
|
||||
const ch = channel as ServerChannel;
|
||||
// # Then invite us to it
|
||||
await client.addToChannel(testUser.id, ch.id);
|
||||
|
||||
return ch;
|
||||
}).then((channel) => {
|
||||
// * Verify that the newly created channel is in the sidebar
|
||||
cy.get(`#sidebarItem_${channel.name}`).should('be.visible');
|
||||
|
||||
cy.wrap(channel);
|
||||
}).then(async (channel) => {
|
||||
const ch = channel as ServerChannel;
|
||||
// # Then archive the channel
|
||||
await client.deleteChannel(ch.id);
|
||||
|
||||
// * Verify that the channel is no longer in the sidebar and that the app hasn't crashed
|
||||
cy.get(`#sidebarItem_${ch.name}`).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1688 archived channels only appear in search results as long as the user does not leave them', () => {
|
||||
// # Create a new channel
|
||||
cy.uiCreateChannel({isNewSidebar: true}).then(({name: channelName}) => {
|
||||
// # Make a post
|
||||
const archivedPostText = `archived${getRandomId()}`;
|
||||
cy.postMessage(archivedPostText);
|
||||
cy.getLastPostId().then((archivedPostId) => {
|
||||
// # Archive the newly created channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Switch away from the archived channel
|
||||
cy.get('#sidebarItem_off-topic').click();
|
||||
|
||||
// * Verify that the channel is no longer in the sidebar
|
||||
cy.get(`#sidebarItem_${channelName}`).should('not.exist');
|
||||
|
||||
// # Search for the post
|
||||
cy.uiSearchPosts(archivedPostText);
|
||||
|
||||
// * Verify that the post is shown in the search results
|
||||
cy.get(`#searchResult_${archivedPostId}`).should('be.visible');
|
||||
|
||||
// # Switch back to the archived channel through the permalink
|
||||
cy.uiJumpToSearchResult(archivedPostId);
|
||||
|
||||
// * Wait for the permalink URL to disappear
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${channelName}`);
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel();
|
||||
cy.url().should('include', `/${testTeam.name}/channels/off-topic`);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Search for the post again
|
||||
cy.uiSearchPosts(archivedPostText);
|
||||
|
||||
// * Verify that the post is no longer shown in the search results
|
||||
cy.get(`#searchResult_${archivedPostId}`).should('not.exist');
|
||||
cy.get('#search-items-container .no-results__wrapper').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {createPrivateChannel} from '../enterprise/elasticsearch_autocomplete/helpers';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
const channelType = {
|
||||
all: 'Channel Type: All',
|
||||
public: 'Channel Type: Public',
|
||||
archived: 'Channel Type: Archived',
|
||||
};
|
||||
|
||||
describe('Channels', () => {
|
||||
let testUser: UserProfile;
|
||||
let otherUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
let testChannel: Channel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.apiCreateUser().then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
});
|
||||
|
||||
// # Go to town square
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-19337 Verify UI of Browse channels modal with archived selection as admin', () => {
|
||||
// * Verify browse channels modal as admin shows archived channels option
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
cy.get('#menuWrapper').should('be.visible').and('have.text', channelType.all);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-19337 Verify UI of Browse channels modal with archived selection as regular user', () => {
|
||||
// # Login as regular user and verify browse channels modal shows archived channels option
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
cy.get('#menuWrapper').should('be.visible').and('have.text', channelType.all);
|
||||
});
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
});
|
||||
|
||||
it('MM-19337 Enable users to view archived channels', () => {
|
||||
// # Login as new user and go to "/"
|
||||
cy.apiLogin(otherUser);
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// * Dropdown should be visible, defaulting to "All Channels"
|
||||
cy.get('#menuWrapper').should('be.visible').and('contain', channelType.all).wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1).within(() => {
|
||||
cy.findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
|
||||
// * Channel test should be visible as a public channel in the list
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
// # Click to join the channel
|
||||
cy.findByText(testChannel.display_name).scrollIntoView().should('be.visible').click();
|
||||
});
|
||||
});
|
||||
|
||||
// # Verify that the modal is not closed
|
||||
cy.get('#browseChannelsModal').should('exist');
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Login as channel admin and go directly to the channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click channel header to open channel menu
|
||||
cy.get('#channelHeaderTitle').should('contain', testChannel.display_name).click();
|
||||
|
||||
// * Verify that the menu is opened
|
||||
cy.get('#channelHeaderDropdownMenu').should('be.visible').within(() => {
|
||||
// # Archive the channel
|
||||
cy.findByText('Archive Channel').should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Verify that the delete/archive channel modal is opened
|
||||
cy.get('#deleteChannelModal').should('be.visible').within(() => {
|
||||
// # Confirm archive
|
||||
cy.findByText('Archive').should('be.visible').click();
|
||||
});
|
||||
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible');
|
||||
|
||||
// # CLick dropdown to open selection
|
||||
cy.get('#menuWrapper').should('be.visible').click();
|
||||
|
||||
// # Click on archived channels item
|
||||
cy.findByText('Archived channels').should('be.visible').click();
|
||||
|
||||
// * Menu text should be updated to reflect the selection
|
||||
cy.get('#menuWrapper').should('contain', channelType.archived);
|
||||
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type(testChannel.display_name).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').children().should('have.length', 1).within(() => {
|
||||
cy.findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
|
||||
// * Test channel should be visible as a archived channel in the list
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
// # Click to view archived channel
|
||||
cy.findByText(testChannel.display_name).scrollIntoView().should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Assert that channel is archived and new messages can't be posted.
|
||||
cy.get('#channelArchivedMessage').should('contain', 'You are viewing an archived channel. New messages cannot be posted.');
|
||||
cy.uiGetPostTextBox({exist: false});
|
||||
|
||||
// # Switch to another channel
|
||||
cy.get('#sidebarItem_town-square').click();
|
||||
|
||||
// * Assert that archived channel doesn't show up in LHS list
|
||||
cy.get('#sidebar-left').should('not.contain', testChannel.display_name);
|
||||
});
|
||||
|
||||
it('MM-19337 Increase channel member count when user joins a channel', () => {
|
||||
// # Login as new user and go to "/"
|
||||
cy.apiLogin(otherUser);
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
let newChannel: Channel;
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-to-leave', 'Channel to leave').then(({channel}) => {
|
||||
newChannel = channel;
|
||||
cy.visit(`/${testTeam.name}/channels/${newChannel.name}`);
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel();
|
||||
|
||||
// * Verify that we've switched to Town Square
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
});
|
||||
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible').within(() => {
|
||||
// * Verify that channel has zero members
|
||||
cy.findByTestId(`channelMemberCount-${newChannel.name}`).should('be.visible').and('contain', 0);
|
||||
|
||||
// # Click the channel row to join the channel
|
||||
cy.findByTestId(`ChannelRow-${newChannel.name}`).scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Verify that channel has one member
|
||||
cy.findByTestId(`channelMemberCount-${newChannel.name}`).should('be.visible').and('contain', 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1702 Search works when changing public/all options in the dropdown', () => {
|
||||
cy.apiAdminLogin();
|
||||
let newChannel: Channel;
|
||||
let testArchivedChannel: Channel;
|
||||
let testPrivateArchivedChannel: Channel;
|
||||
|
||||
cy.apiCreateTeam('team', 'Test NoMember').then(({team}) => {
|
||||
cy.apiCreateChannel(team.id, 'not-archived-channel', 'Not Archived Channel').then(({channel}) => {
|
||||
newChannel = channel;
|
||||
cy.visit(`/${team.name}/channels/${newChannel.name}`);
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel();
|
||||
|
||||
// * Verify that we've switched to Town Square
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
});
|
||||
|
||||
cy.apiCreateChannel(team.id, 'archived-channel', 'Archived Channel').then(({channel}) => {
|
||||
testArchivedChannel = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${team.name}/channels/${testArchivedChannel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
|
||||
// # Leave the channel
|
||||
cy.uiLeaveChannel();
|
||||
|
||||
// * Verify that we've switched to Town Square
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
});
|
||||
|
||||
createPrivateChannel(team.id).then((channel) => {
|
||||
testPrivateArchivedChannel = channel;
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${team.name}/channels/${testPrivateArchivedChannel.name}`);
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiArchiveChannel();
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// * Dropdown should be visible, defaulting to "All channels"
|
||||
cy.get('#menuWrapper').should('be.visible').within((el) => {
|
||||
cy.wrap(el).should('contain', channelType.all);
|
||||
});
|
||||
|
||||
// * Hide Archived is on by default, so search only returns active matches
|
||||
cy.get('#hideArchivedPreferenceCheckbox').should('be.visible').and('have.attr', 'aria-checked', 'true');
|
||||
cy.get('#searchChannelsTextbox').should('be.visible').type('iv').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 1);
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
cy.findByText(newChannel.display_name).should('be.visible');
|
||||
cy.findByText(testArchivedChannel.display_name).should('not.exist');
|
||||
});
|
||||
|
||||
// # Uncheck Hide Archived so archived matches appear in All search results
|
||||
cy.get('#hideArchivedPreferenceCheckbox').click();
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
|
||||
cy.get('#moreChannelsList').should('be.visible').within(() => {
|
||||
cy.findByText(newChannel.display_name).should('be.visible');
|
||||
cy.findByText(testArchivedChannel.display_name).should('be.visible');
|
||||
});
|
||||
|
||||
cy.get('#browseChannelsModal').should('be.visible');
|
||||
|
||||
// * Users should be able to switch to "Archived Channels" list
|
||||
cy.get('#menuWrapper').should('be.visible').and('contain', channelType.all).click().wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Click on archived channels item
|
||||
cy.findByText('Archived channels').should('be.visible').click();
|
||||
|
||||
// * Modal menu should be updated accordingly
|
||||
cy.get('#menuWrapper').should('contain', channelType.archived);
|
||||
|
||||
cy.get('#searchChannelsTextbox').clear();
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 2);
|
||||
cy.get('#moreChannelsList').within(() => {
|
||||
cy.findByText(testArchivedChannel.display_name).should('be.visible');
|
||||
cy.findByText(testPrivateArchivedChannel.display_name).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
function verifyNoChannelToJoinMessage(isVisible: boolean) {
|
||||
cy.findByText('No public channels').should(isVisible ? 'be.visible' : 'not.exist');
|
||||
}
|
||||
|
||||
function ensureHideJoinedCheckboxEnabled(shouldBeChecked: boolean) {
|
||||
cy.get('#hideJoinedPreferenceCheckbox').then(($checkbox) => {
|
||||
cy.wrap($checkbox).findByText('Hide Joined').should('be.visible');
|
||||
cy.wrap($checkbox).find('div.get-app__checkbox').invoke('attr', 'class').then(($classList) => {
|
||||
if ($classList!.split(' ').includes('checked') !== Boolean(shouldBeChecked)) {
|
||||
// We click on the button only when the XOR operands do not match
|
||||
// e.g. checkbox is checked, but should not be checked; and vice-versa
|
||||
cy.wrap($checkbox).click();
|
||||
cy.wrap($checkbox).find('div.get-app__checkbox').should(`${shouldBeChecked ? '' : 'not.'}have.class`, 'checked');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('browse public channels', () => {
|
||||
let testUser: UserProfile;
|
||||
let otherUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.apiCreateUser().then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # create 30 channels
|
||||
for (let i = 0; i < 30; i++) {
|
||||
cy.apiCreateChannel(testTeam.id, 'public-channel', 'public-channel');
|
||||
}
|
||||
|
||||
// # Go to town square
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1664 Channels do not disappear from Browse Channels modal', () => {
|
||||
// # Login as other user
|
||||
cy.apiLogin(otherUser);
|
||||
|
||||
// # Go to town square
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// * Assert that the browse channel modal is visible
|
||||
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
|
||||
// # Click on dropdown
|
||||
cy.findByText('Channel Type: All').should('be.visible').click();
|
||||
|
||||
// # Click archived channels
|
||||
cy.findByText('Public channels').should('be.visible').click();
|
||||
|
||||
// # Click hide joined checkbox if not already checked
|
||||
ensureHideJoinedCheckboxEnabled(true);
|
||||
|
||||
// * Assert that the moreChannelsList is visible and the number of channels is 31
|
||||
cy.get('#moreChannelsList').should('be.visible').children().should('have.length', 31);
|
||||
|
||||
// # scroll to the middle channel
|
||||
cy.get('#moreChannelsList .more-modal__row').eq(15).scrollIntoView();
|
||||
|
||||
// * Assert that the "No more channels to join" message does not exist
|
||||
verifyNoChannelToJoinMessage(false);
|
||||
|
||||
// # scroll to the last channel
|
||||
cy.get('#moreChannelsList .more-modal__row').last().scrollIntoView();
|
||||
|
||||
// * Assert that the "No more channels to join" message does not exist
|
||||
verifyNoChannelToJoinMessage(false);
|
||||
|
||||
// # scroll to the first channel
|
||||
cy.get('#moreChannelsList .more-modal__row').first().scrollIntoView();
|
||||
|
||||
// * Assert that the "No more channels to join" message does not exist
|
||||
verifyNoChannelToJoinMessage(false);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Go to town square
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Go to LHS and click 'Browse channels'
|
||||
cy.uiBrowseOrCreateChannel('Browse channels');
|
||||
|
||||
// * Assert the moreChannelsModel is visible
|
||||
cy.findByRole('dialog', {name: 'Browse Channels'}).should('be.visible').then(() => {
|
||||
// # Click on dropdown
|
||||
cy.findByText('Channel Type: All').should('be.visible').click();
|
||||
|
||||
// # Click archived channels
|
||||
cy.findByText('Public channels').should('be.visible').click();
|
||||
|
||||
// # Click hide joined checkbox if not already checked
|
||||
ensureHideJoinedCheckboxEnabled(true);
|
||||
|
||||
// * Assert that the "No more channels to join" message is visible
|
||||
verifyNoChannelToJoinMessage(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings
|
||||
|
||||
describe('Channel Settings - Channel Header', () => {
|
||||
let testTeam;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
cy.apiLogin(user);
|
||||
|
||||
// # Visit town-square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('Create channel, modify header, and verify preview button functionality', () => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-channel', 'New Channel').then(({channel}) => {
|
||||
// # Visit the newly created channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Select the "Edit Channel Header" option from the dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify modal is open
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
cy.get('#genericModalLabel').should('contain', 'Channel Settings');
|
||||
|
||||
// # Type something in the header edit box
|
||||
cy.get('#channel_settings_header_textbox').clear().type('This is the new header content');
|
||||
|
||||
// * Verify the "Preview" button exists and is not active
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
should('not.have.class', 'active');
|
||||
|
||||
// * Verify that before hitting the preview button, the style on the textbox is `display: block`
|
||||
cy.get('#channel_settings_header_textbox').should('have.css', 'display', 'block');
|
||||
|
||||
// # Click the "Preview" button
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').click();
|
||||
|
||||
// * Verify that the display is now none on the textbox element
|
||||
cy.get('#channel_settings_header_textbox').should('have.css', 'display', 'none');
|
||||
|
||||
// * Verify the "Preview" button has class active
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
should('have.class', 'active');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,541 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @rhs @channel_info
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {stubClipboard} from '@/utils';
|
||||
|
||||
describe('Channel Info RHS', () => {
|
||||
let testTeam: Team;
|
||||
let testChannel: Channel;
|
||||
let groupChannel: Channel;
|
||||
let directChannel: Channel;
|
||||
let directUser: UserProfile;
|
||||
let admin: UserProfile;
|
||||
let user: UserProfile;
|
||||
const otherUsers: UserProfile[] = [];
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, user: newAdmin}) => {
|
||||
testTeam = team;
|
||||
admin = newAdmin;
|
||||
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Public Channel', 'O').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
cy.apiAddUserToChannel(channel.id, newAdmin.id);
|
||||
});
|
||||
|
||||
// User we'll use for our permissions tests
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
user = newUser;
|
||||
});
|
||||
|
||||
// Users used for GM/DM
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
otherUsers.push(newUser);
|
||||
cy.apiPatchUser(newUser.id, {position: 'Upside down'} as UserProfile).then(({user: patchedUser}) => {
|
||||
cy.apiCreateDirectChannel([newAdmin.id, newUser.id]).then(({channel}) => {
|
||||
directChannel = channel;
|
||||
});
|
||||
directUser = patchedUser;
|
||||
});
|
||||
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
|
||||
cy.apiCreateUser().then(({user: newUser2}) => {
|
||||
otherUsers.push(newUser2);
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
cy.apiCreateGroupChannel([newAdmin.id, newUser.id, newUser2.id]).then(({channel}) => {
|
||||
groupChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// # Change permission so that regular users can't change channels or add members
|
||||
cy.apiGetRolesByNames(['channel_user']).then(({roles}) => {
|
||||
const role = roles[0];
|
||||
const permissions = role.permissions.filter((permission) => {
|
||||
return !(['manage_public_channel_members', 'manage_private_channel_members', 'manage_public_channel_properties', 'manage_private_channel_properties'].includes(permission));
|
||||
});
|
||||
|
||||
if (permissions.length !== role.permissions.length) {
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
}
|
||||
});
|
||||
|
||||
cy.apiLogin(admin);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to open the RHS', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * RHS Container shoud exist
|
||||
ensureRHSIsOpenOnChannelInfo(testChannel);
|
||||
});
|
||||
|
||||
describe('regular channel', () => {
|
||||
describe('top buttons', () => {
|
||||
it('should show correct tooltips for all buttons', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify tooltips appear with correct text
|
||||
cy.uiGetRHS().findByText('Favorite').trigger('mouseenter');
|
||||
cy.findByText('Add this channel to favorites').should('be.visible');
|
||||
cy.uiGetRHS().findByText('Favorite').trigger('mouseleave');
|
||||
|
||||
cy.uiGetRHS().findByText('Mute').trigger('mouseenter');
|
||||
cy.findByText('Mute notifications for this channel').should('be.visible');
|
||||
cy.uiGetRHS().findByText('Mute').trigger('mouseleave');
|
||||
|
||||
cy.uiGetRHS().findByText('Add People').trigger('mouseenter');
|
||||
cy.findByText('Add team members to this channel').should('be.visible');
|
||||
cy.uiGetRHS().findByText('Add People').trigger('mouseleave');
|
||||
|
||||
cy.uiGetRHS().findByText('Copy Link').trigger('mouseenter');
|
||||
cy.findByText('Copy link to this channel').should('be.visible');
|
||||
cy.uiGetRHS().findByText('Copy Link').trigger('mouseleave');
|
||||
});
|
||||
|
||||
it('should be able to toggle favorite on a channel', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that we can toggle the favorite status
|
||||
cy.uiGetRHS().findByText('Favorite').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Favorited').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Favorite').should('be.visible');
|
||||
});
|
||||
|
||||
it('should be able to toggle mute on a channel', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that we can toggle the mute status
|
||||
cy.uiGetRHS().findByText('Mute').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Muted').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Mute').should('be.visible');
|
||||
});
|
||||
|
||||
it('should be able to add people', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that the modal appears
|
||||
cy.uiGetRHS().findByText('Add People').should('be.visible').click();
|
||||
cy.get('.channel-invite').should('be.visible');
|
||||
});
|
||||
|
||||
it('should NOT be able to add people without permission', () => {
|
||||
// # Login as simple user
|
||||
cy.apiLogout().then(() => {
|
||||
cy.apiLogin(user);
|
||||
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that the modal appears
|
||||
cy.uiGetRHS().findByText('Add People').should('not.exist');
|
||||
|
||||
// # log back in as admin
|
||||
cy.apiLogout().then(() => {
|
||||
cy.apiLogin(admin);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to copy link', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify initial state
|
||||
cy.get('@clipboard').its('contents').should('eq', '');
|
||||
|
||||
// # Click on "Copy Link"
|
||||
cy.uiGetRHS().findByText('Copy Link').parent().should('be.visible').trigger('click');
|
||||
|
||||
// * Text should change to Copied
|
||||
cy.uiGetRHS().findByText('Copied').should('be.visible');
|
||||
|
||||
// * Verify if it's called with correct link value
|
||||
cy.get('@clipboard').its('contents').should('eq', `${Cypress.config('baseUrl')}/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
});
|
||||
describe('about area', () => {
|
||||
it('should display purpose', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(testChannel.id, {
|
||||
purpose: 'purpose for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('purpose for the tests').should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should display header', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(testChannel.id, {
|
||||
header: 'header for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
|
||||
});
|
||||
});
|
||||
it('should be able to rename channel from About area', () => {
|
||||
// # Create a dedicated channel for renaming to avoid affecting other tests
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-to-rename', 'Channel To Rename', 'O').then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, admin.id);
|
||||
|
||||
// # Go to the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open Channel Info RHS
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click edit on channel name (first Edit in About)
|
||||
cy.uiGetRHS().findAllByLabelText('Edit').first().click({force: true});
|
||||
|
||||
// * Rename Channel modal appears
|
||||
cy.findByRole('heading', {name: /rename channel/i}).should('be.visible');
|
||||
|
||||
// # Fill display name and URL
|
||||
cy.findByPlaceholderText(/enter display name/i).clear().type('Renamed Channel');
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type('renamed-channel');
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// # Save
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
|
||||
// * URL updated
|
||||
cy.location('pathname').should('include', `/${testTeam.name}/channels/renamed-channel`);
|
||||
|
||||
// * Header shows new name
|
||||
cy.get('#channelHeaderTitle').should('contain', 'Renamed Channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('bottom menu', () => {
|
||||
it('should be able to manage notifications', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Notification Preferences"
|
||||
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Notification Preferences').scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Ensures the modal is there
|
||||
cy.get('.ChannelNotificationModal').should('be.visible');
|
||||
});
|
||||
it('should open Channel Settings from RHS menu', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Close RHS if it's open, then click on the channel info button
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.find('#rhsCloseButton').length > 0) {
|
||||
cy.get('#rhsCloseButton').click();
|
||||
}
|
||||
cy.get('#channel-info-btn').should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Channel Settings item is visible in RHS menu
|
||||
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Channel Settings').scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Channel Settings modal opens
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
});
|
||||
it('should be able to view files and come back', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Files"
|
||||
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Files').scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Ensure we see the files RHS
|
||||
cy.uiGetRHS().findByText('No files yet').should('be.visible');
|
||||
|
||||
// # Click the Back Icon
|
||||
cy.uiGetRHS().get('[aria-label="Back Icon"]').click();
|
||||
|
||||
// * Make sure we are back in the channel info rhs
|
||||
ensureRHSIsOpenOnChannelInfo(testChannel);
|
||||
});
|
||||
it('should be able to view pinned message and come back', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Go to test channel
|
||||
cy.uiPostMessageQuickly('Hello channel info rhs spec').then(() => {
|
||||
cy.getNthPostId(-1).then((postId) => {
|
||||
cy.clickPostDotMenu(postId);
|
||||
cy.get(`#pin_post_${postId}`).click();
|
||||
});
|
||||
});
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Pinned Messages"
|
||||
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Pinned messages').scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Ensure we see the Pinned Post RHS
|
||||
cy.uiGetRHS().findByText('Hello channel info rhs spec').first().should('be.visible');
|
||||
|
||||
// # Click the Back Icon
|
||||
cy.uiGetRHS().get('[aria-label="Back Icon"]').click();
|
||||
|
||||
// * Make sure we are back in the channel info rhs
|
||||
ensureRHSIsOpenOnChannelInfo(testChannel);
|
||||
});
|
||||
it('should be able to view channel members and come back', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Members"
|
||||
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Members').scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Ensure we see the members
|
||||
cy.uiGetRHS().contains('sysadmin').should('be.visible');
|
||||
cy.uiGetRHS().contains(`${admin.username}`).should('be.visible');
|
||||
|
||||
// # Click the Back Icon
|
||||
cy.uiGetRHS().get('[aria-label="Back Icon"]').click();
|
||||
|
||||
// * Make sure we are back in the channel info rhs
|
||||
ensureRHSIsOpenOnChannelInfo(testChannel);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('group channel', () => {
|
||||
describe('top buttons', () => {
|
||||
it('should be able to toggle favorite', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that we can toggle the favorite status
|
||||
cy.uiGetRHS().findByText('Favorite').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Favorited').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Favorite').should('be.visible');
|
||||
});
|
||||
|
||||
it('should be able to toggle mute', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that we can toggle the mute status
|
||||
cy.uiGetRHS().findByText('Mute').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Muted').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Mute').should('be.visible');
|
||||
});
|
||||
|
||||
it('should be able to add people', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that the modal appears
|
||||
cy.uiGetRHS().findByText('Add People').should('be.visible').click();
|
||||
cy.get('.more-direct-channels').should('be.visible');
|
||||
});
|
||||
|
||||
it('should NOT be able to copy link', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Copy Link"
|
||||
cy.uiGetRHS().get('Copy Link').should('not.exist');
|
||||
});
|
||||
});
|
||||
describe('about area', () => {
|
||||
it('should display other users', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
otherUsers.forEach((otherUser) => {
|
||||
cy.uiGetRHS().contains(otherUser.username);
|
||||
});
|
||||
});
|
||||
it('should display header', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(groupChannel.id, {
|
||||
header: 'header for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('bottom menu', () => {
|
||||
it('should be able to manage notifications', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/${groupChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Notification Preferences"
|
||||
cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Notification Preferences').scrollIntoView().should('be.visible').click();
|
||||
|
||||
// * Ensures the modal is there
|
||||
cy.get('.ChannelNotificationModal').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('direct channel', () => {
|
||||
describe('top buttons', () => {
|
||||
it('should be able to toggle favorite', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${directUser.username}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that we can toggle the favorite status
|
||||
cy.uiGetRHS().findByText('Favorite').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Favorited').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Favorite').should('be.visible');
|
||||
});
|
||||
|
||||
it('should be able to toggle mute', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${directUser.username}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that we can toggle the mute status
|
||||
cy.uiGetRHS().findByText('Mute').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Muted').should('be.visible').click();
|
||||
cy.uiGetRHS().findByText('Mute').should('be.visible');
|
||||
});
|
||||
|
||||
it('should NOT be able to add people', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${directUser.username}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// * Verify that the modal appears
|
||||
cy.uiGetRHS().findByText('Add People').should('not.exist');
|
||||
});
|
||||
|
||||
it('should NOT be able to copy link', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${directUser.username}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on "Copy Link"
|
||||
cy.uiGetRHS().get('Copy Link').should('not.exist');
|
||||
});
|
||||
});
|
||||
describe('about area', () => {
|
||||
it('should display other user name and position', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${directUser.username}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.uiGetRHS().contains(directUser.username);
|
||||
cy.uiGetRHS().contains(directUser.position);
|
||||
});
|
||||
it('should display header', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${directUser.username}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
cy.apiPatchChannel(directChannel.id, {
|
||||
header: 'header for the tests',
|
||||
}).then(() => {
|
||||
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function ensureRHSIsOpenOnChannelInfo(testChannel: Cypress.Channel) {
|
||||
cy.get('#rhsContainer').then((rhsContainer) => {
|
||||
cy.wrap(rhsContainer).findByText('Info').should('be.visible');
|
||||
cy.wrap(rhsContainer).find('.sidebar--right__title__subtitle').should('contain', testChannel.display_name);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
const {generateRandomUser} = require('../../../support/api/user');
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @rhs @channel_members
|
||||
|
||||
function openChannelMembersRhs(testTeam: Team, testChannel: Channel) {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Click on the channel info button
|
||||
cy.get('#channel-info-btn').click();
|
||||
|
||||
// # Click on the Members menu
|
||||
cy.uiGetRHS().findByText('Members').should('be.visible').click();
|
||||
}
|
||||
|
||||
describe('Channel members RHS', () => {
|
||||
let testTeam: Team;
|
||||
let testChannel: Channel;
|
||||
let admin: UserProfile;
|
||||
let user: UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, user: newAdmin}) => {
|
||||
testTeam = team;
|
||||
admin = newAdmin;
|
||||
|
||||
// User we'll use for our permissions tests
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
user = newUser;
|
||||
cy.apiAddUserToTeam(team.id, newUser.id).then(() => {
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Public Channel', 'O').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
cy.apiAddUserToChannel(channel.id, newAdmin.id);
|
||||
cy.apiAddUserToChannel(channel.id, newUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// # Change permission so that regular users can't change channels or add members
|
||||
cy.apiGetRolesByNames(['channel_user']).then(({roles}) => {
|
||||
const role = roles[0];
|
||||
const permissions = role.permissions.filter((permission) => {
|
||||
return !(['manage_public_channel_members', 'manage_private_channel_members', 'manage_public_channel_properties', 'manage_private_channel_properties'].includes(permission));
|
||||
});
|
||||
|
||||
if (permissions.length !== role.permissions.length) {
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
}
|
||||
});
|
||||
|
||||
cy.apiLogin(admin);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to open the RHS from channel info', () => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// * RHS Container should exist
|
||||
ensureChannelMembersRHSExists(testChannel);
|
||||
});
|
||||
|
||||
it('should be able to open the RHS from the members icon', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open the RHS by clicking on the members icon
|
||||
cy.get('#channelHeaderInfo').within(() => {
|
||||
cy.get('#member_rhs').should('be.visible').click({force: true});
|
||||
});
|
||||
|
||||
// * RHS Container should exist
|
||||
ensureChannelMembersRHSExists(testChannel);
|
||||
});
|
||||
|
||||
it('should display the number of members', () => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// * Verify that we can toggle the favorite status
|
||||
cy.uiGetRHS().findByText('3 members').should('be.visible');
|
||||
});
|
||||
|
||||
it('should go back to previous RHS when switching from a channel to a DM', () => {
|
||||
cy.apiCreateUser({}).then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// * Ensure we are in the members subpanel
|
||||
cy.uiGetRHS().get('.sidebar--right__title').findByText('Members').should('be.visible');
|
||||
|
||||
// # Visit the DM page
|
||||
cy.uiGotoDirectMessageWithUser(newUser);
|
||||
|
||||
// * We should be in Channel Info RHS now
|
||||
cy.uiGetRHS().get('.sidebar--right__title').findByText('Info').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should close the RHS when switching from a channel to a DM', () => {
|
||||
cy.apiCreateUser({}).then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
// # Open the Channel Members RHS directly from the channel header button
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.get('#member_rhs').click();
|
||||
|
||||
// * Ensure we are in the members subpanel
|
||||
cy.uiGetRHS().get('.sidebar--right__title').findByText('Members').should('be.visible');
|
||||
|
||||
// # Visit the DM page
|
||||
cy.uiGotoDirectMessageWithUser(newUser);
|
||||
|
||||
// * The RHS must not exist
|
||||
cy.get('#sidebar-right').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should display search when number of members is 20 or above', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'search-test-channel', 'Search Test Channel', 'O').then(({channel}) => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, channel);
|
||||
|
||||
// * Ensure the search bar is not there
|
||||
cy.uiGetRHS().findByPlaceholderText('Search members').should('not.exist');
|
||||
|
||||
// # Close RHS
|
||||
cy.uiCloseRHS();
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(channel.id, newUser.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cy.apiGetUsers({in_channel: channel.id}).then(({users}) => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, channel);
|
||||
|
||||
// # Search for first user
|
||||
cy.uiGetRHS().findByTestId('channel-member-rhs-search').should('be.visible').type(users[0].username);
|
||||
|
||||
// * we should see them, but nobody else
|
||||
cy.uiGetRHS().contains(`${users[0].username}`).should('be.visible');
|
||||
cy.uiGetRHS().findByText(`${users[1].username}`).should('not.exist');
|
||||
|
||||
// # erase the field
|
||||
cy.uiGetRHS().get('[aria-label="cancel members search"]').should('be.visible').click();
|
||||
cy.uiGetRHS().contains(`${users[0].username}`).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide deactivated members', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'hide-test-channel', 'Hide Test Channel', 'O').then(({channel}) => {
|
||||
let testUser: UserProfile;
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(channel.id, newUser.id).then(() => {
|
||||
testUser = newUser;
|
||||
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, channel);
|
||||
|
||||
// * Ensure the member is visible
|
||||
cy.uiGetRHS().contains(`${testUser.username}`).should('be.visible');
|
||||
|
||||
// # Deactivate the user
|
||||
cy.apiDeactivateUser(testUser.id);
|
||||
|
||||
// * Ensure the user is not visible anymore
|
||||
cy.uiGetRHS().findByText(`${testUser.username}`).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as an admin', () => {
|
||||
before(() => {
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(admin);
|
||||
});
|
||||
|
||||
it('should be able to open the RHS from the channel menu', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
|
||||
// * RHS Container should be open in edit mode
|
||||
cy.get('#rhsContainer').then((rhsContainer) => {
|
||||
cy.wrap(rhsContainer).findByText('Members').should('be.visible');
|
||||
cy.wrap(rhsContainer).findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to invite new members', () => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// # Click on the Add button
|
||||
cy.uiGetRHS().findByText('Add').should('be.visible').click();
|
||||
|
||||
// * The modal should appear
|
||||
cy.get('.channel-invite').should('be.visible');
|
||||
});
|
||||
|
||||
it('should be able to manage members', () => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// # Click on the Manage button
|
||||
cy.uiGetRHS().findByText('Manage').should('be.visible').click();
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
// * Can see user with their roles, and change it
|
||||
cy.uiGetRHS().findByTestId(`memberline-${user.id}`).should('be.visible').within(() => {
|
||||
cy.contains(`${user.username}`).should('be.visible');
|
||||
cy.findByText('Member').should('be.visible').click();
|
||||
cy.findByText('Make Channel Admin').should('be.visible').click();
|
||||
});
|
||||
|
||||
// the user line is going to be removed and re-added in another category,
|
||||
// cypress struggle to realize this so we have to wait a few ms
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
// * Can see the user with his new admin role, and change it back
|
||||
cy.uiGetRHS().findByTestId(`memberline-${user.id}`).should('be.visible').within(() => {
|
||||
cy.findByText('Admin').should('be.visible').click();
|
||||
cy.findByText('Make Channel Member').should('be.visible').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as an non-admin', () => {
|
||||
before(() => {
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(user);
|
||||
});
|
||||
|
||||
it('should be able to open the RHS from the channel menu', () => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
|
||||
// * RHS Container should be open in edit mode
|
||||
ensureChannelMembersRHSExists(testChannel);
|
||||
});
|
||||
|
||||
it('should not be able to invite new members', () => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// # Click on the Add button
|
||||
cy.uiGetRHS().findByText('Add').should('not.exist');
|
||||
});
|
||||
|
||||
it('should not be able to manage members', () => {
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, testChannel);
|
||||
|
||||
// # Click on the Manage button
|
||||
cy.uiGetRHS().findByText('Manage').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to find users not in the initial list', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'big-search-test-channel', 'Big Search Test Channel', 'O').then(({channel}) => {
|
||||
// # create 100 random users
|
||||
for (let i = 0; i < 100; i++) {
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(channel.id, newUser.id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// # create a user that will not be listed by default
|
||||
const lastUser = generateRandomUser();
|
||||
lastUser.username = 'zzzzzzz' + Date.now();
|
||||
cy.apiCreateUser({user: lastUser}).then(({user: newUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, newUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(channel.id, newUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
// # Open the Channel Members RHS
|
||||
openChannelMembersRhs(testTeam, channel);
|
||||
|
||||
// # make sure that last user is not present in the list
|
||||
cy.uiGetRHS().findByText(`${lastUser.username}`).should('not.exist');
|
||||
|
||||
// # Search for the user user
|
||||
cy.uiGetRHS().findByTestId('channel-member-rhs-search').should('be.visible').type(lastUser.username);
|
||||
|
||||
// * the user is now existing
|
||||
cy.uiGetRHS().contains(`${lastUser.username}`).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function ensureChannelMembersRHSExists(testChannel: Cypress.Channel) {
|
||||
cy.get('#rhsContainer').then((rhsContainer) => {
|
||||
cy.wrap(rhsContainer).findByText('Members').should('be.visible');
|
||||
cy.wrap(rhsContainer).findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Channel', () => {
|
||||
let testTeam: Team;
|
||||
let ownChannel: Channel;
|
||||
let otherChannel: Channel;
|
||||
let testUser: UserProfile;
|
||||
let offTopicUrl: string;
|
||||
|
||||
const myChannelsDividerText = 'My Channels';
|
||||
const otherChannelsDividerText = 'Other Channels';
|
||||
|
||||
before(() => {
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup().then(({team, channel, user, offTopicUrl: url}) => {
|
||||
testTeam = team;
|
||||
ownChannel = channel;
|
||||
testUser = user;
|
||||
offTopicUrl = url;
|
||||
|
||||
cy.apiCreateChannel(testTeam.id, 'delta-test', 'Delta Channel').then((out) => {
|
||||
otherChannel = out.channel;
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('Channel autocomplete should have both lists populated correctly', () => {
|
||||
// # Type "~"
|
||||
cy.uiGetPostTextBox().clear().type('~').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#loadingSpinner').should('not.exist');
|
||||
|
||||
// * Should open up suggestion list for channels
|
||||
// * Should match each channel item
|
||||
cy.get('#suggestionList').should('be.visible').children().as('suggestionList');
|
||||
|
||||
// * Should render "MY CHANNELS" suggestion list group
|
||||
cy.get('@suggestionList').get('[role="presentation"]').contains(myChannelsDividerText, {matchCase: false});
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', ownChannel.display_name);
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', 'Off-Topic');
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', 'Town Square');
|
||||
|
||||
// * Should render "OTHER CHANNELS" suggestion list group
|
||||
cy.get('@suggestionList').get('[role="presentation"]').contains(otherChannelsDividerText, {matchCase: false});
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', otherChannel.display_name);
|
||||
});
|
||||
|
||||
it('Joining a channel should alter channel mention autocomplete lists accordingly', () => {
|
||||
// # Join a channel by /join slash command
|
||||
cy.uiGetPostTextBox().clear().wait(TIMEOUTS.HALF_SEC).type(`/join ~${otherChannel.name}`).type('{enter}').wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Verify that it redirects into the channel
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${otherChannel.name}`);
|
||||
|
||||
// # Type "~"
|
||||
cy.uiGetPostTextBox().type('~').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#loadingSpinner').should('not.exist');
|
||||
|
||||
// * Should open up suggestion list for channels
|
||||
// * Should match each channel
|
||||
cy.get('#suggestionList').should('be.visible').children().as('suggestionList');
|
||||
|
||||
// * Should render "MY CHANNELS" suggestion list group
|
||||
cy.get('@suggestionList').get('[role="presentation"]').contains(myChannelsDividerText, {matchCase: false});
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', ownChannel.display_name);
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', otherChannel.display_name);
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', 'Off-Topic');
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', 'Town Square');
|
||||
});
|
||||
|
||||
it('Getting removed from a channel should alter channel mention autocomplete lists accordingly', () => {
|
||||
// # Remove test user from the test channel
|
||||
cy.apiAdminLogin();
|
||||
cy.removeUserFromChannel(otherChannel.id, testUser.id).then((res) => {
|
||||
expect(res.status).to.equal(200);
|
||||
|
||||
// # Login as test user and visit the test team
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Type "~"
|
||||
cy.uiGetPostTextBox().clear().type('~').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.get('#loadingSpinner').should('not.exist');
|
||||
|
||||
// * Should open up suggestion list for channels
|
||||
// * Should match each channel item
|
||||
cy.get('#suggestionList').should('be.visible').children().as('suggestionList');
|
||||
|
||||
// * Should render "MY CHANNELS" suggestion list group
|
||||
cy.get('@suggestionList').get('[role="presentation"]').contains(myChannelsDividerText, {matchCase: false});
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', ownChannel.display_name);
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', 'Off-Topic');
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', 'Town Square');
|
||||
|
||||
// * Should render "OTHER CHANNELS" suggestion list group
|
||||
cy.get('@suggestionList').get('[role="presentation"]').contains(otherChannelsDividerText, {matchCase: false});
|
||||
cy.get('@suggestionList').get('[role="option"]').should('contain', otherChannel.display_name);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @not_cloud
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
const timestamp = Date.now();
|
||||
|
||||
describe('channel name tooltips', () => {
|
||||
let loggedUser: UserProfile;
|
||||
let longUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
|
||||
// # Login as new user and visit off-topic
|
||||
cy.apiInitSetup().then(({team, user, offTopicUrl}) => {
|
||||
testTeam = team;
|
||||
loggedUser = user;
|
||||
|
||||
cy.apiCreateUser({prefix: `thisIsALongUsername${timestamp}`}).then(({user: user1}) => {
|
||||
longUser = user1;
|
||||
cy.apiAddUserToTeam(testTeam.id, loggedUser.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(loggedUser);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should show tooltip on hover - open/public channel with long name', () => {
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'channel-test',
|
||||
`Public channel with a long name-${timestamp}`,
|
||||
).then(({channel}) => {
|
||||
verifyChannel(channel);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should show tooltip on hover - private channel with long name', () => {
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'channel-test',
|
||||
`Private channel with a long name-${timestamp}`,
|
||||
'P',
|
||||
).then(({channel}) => {
|
||||
verifyChannel(channel);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not show tooltip on hover - open/public channel with short name', () => {
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'channel-test',
|
||||
'Public channel',
|
||||
).then(({channel}) => {
|
||||
verifyChannel(channel, false);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should not show tooltip on hover - private channel with short name', () => {
|
||||
// # Create new test channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'channel-test',
|
||||
'Private channel',
|
||||
'P',
|
||||
).then(({channel}) => {
|
||||
verifyChannel(channel, false);
|
||||
});
|
||||
});
|
||||
|
||||
it('Should show tooltip on hover - user with a long username', () => {
|
||||
// # Open a DM with the user
|
||||
cy.findByRole('button', {name: 'Write a direct message'}).click();
|
||||
cy.get('.more-direct-channels #selectItems input').as('searchBox').typeWithForce(longUser.username);
|
||||
|
||||
// * Verify that the user is selected in the results list before typing enter
|
||||
cy.get('div.more-modal__row').
|
||||
should('have.length', 1).
|
||||
and('have.class', 'clickable').
|
||||
and('have.class', 'more-modal__row--selected').
|
||||
and('contain.text', longUser.username.toLowerCase());
|
||||
|
||||
cy.get('@searchBox').typeWithForce('{enter}');
|
||||
cy.uiGetButton('Go').click();
|
||||
|
||||
// # Hover on the channel name
|
||||
cy.get(`#sidebarItem_${Cypress._.sortBy([loggedUser.id, longUser.id]).join('__')}`).scrollIntoView().should('be.visible').trigger('mouseenter');
|
||||
|
||||
// * Verify that the tooltip is displayed
|
||||
cy.findByRole('tooltip').should('be.visible');
|
||||
|
||||
// # Move cursor away from channel
|
||||
cy.get(`#sidebarItem_${Cypress._.sortBy([loggedUser.id, longUser.id]).join('__')}`).scrollIntoView().should('be.visible').trigger('mouseleave');
|
||||
});
|
||||
});
|
||||
|
||||
function verifyChannel(channel: Channel, verifyExistence = true) {
|
||||
// # Wait for Channel to be created
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Hover on the channel name
|
||||
cy.get(`#sidebarItem_${channel.name}`).should('be.visible').trigger('mouseenter');
|
||||
|
||||
// * Verify that the tooltip is displayed
|
||||
if (verifyExistence) {
|
||||
cy.findByRole('tooltip').should('be.visible').and('have.text', channel.display_name);
|
||||
} else {
|
||||
cy.findByRole('tooltip').should('not.exist');
|
||||
}
|
||||
|
||||
// # Move cursor away from channel
|
||||
cy.get(`#sidebarItem_${channel.name}`).should('be.visible').trigger('mouseleave');
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Channel routing', () => {
|
||||
let testUser: UserProfile;
|
||||
let otherUser1: UserProfile;
|
||||
let otherUser2: UserProfile;
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser({prefix: 'otherA'}).then(({user: newUser}) => {
|
||||
otherUser1 = newUser;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
});
|
||||
|
||||
cy.apiCreateUser({prefix: 'otherB'}).then(({user: newUser}) => {
|
||||
otherUser2 = newUser;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
});
|
||||
|
||||
// # Login as test user and go to town square
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should go to town square channel view', () => {
|
||||
// * Check if the channel is loaded correctly
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', 'Town Square');
|
||||
});
|
||||
|
||||
it('should go to private channel view', () => {
|
||||
// # Create a private channel
|
||||
cy.apiCreateChannel(testTeam.id, 'private-channel', 'Private channel', 'P').then(({channel}) => {
|
||||
// # Go to the newly created channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', 'Private channel');
|
||||
|
||||
// # Remove the created channel
|
||||
cy.apiDeleteChannel(channel.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('should go to self direct channel using the multiple ways to go', () => {
|
||||
// # Create a self direct channel
|
||||
cy.apiCreateDirectChannel([testUser.id, testUser.id]).then(({channel: ownDMChannel}) => {
|
||||
// # Visit the channel using the channel name
|
||||
cy.visit(`/${testTeam.name}/channels/${testUser.id}__${testUser.id}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', `${testUser.username} (you)`);
|
||||
|
||||
// # Visit the channel using the channel id
|
||||
cy.visit(`/${testTeam.name}/channels/${ownDMChannel.id}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', `${testUser.username} (you)`);
|
||||
|
||||
// # Visit the channel using the username
|
||||
cy.visit(`/${testTeam.name}/messages/@${testUser.username}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', `${testUser.username} (you)`);
|
||||
|
||||
// # Visit the channel using the user email
|
||||
cy.visit(`/${testTeam.name}/messages/${testUser.email}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', `${testUser.username} (you)`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should go to other user direct channel using multiple ways to go', () => {
|
||||
// # Create a direct channel between two users
|
||||
cy.apiCreateDirectChannel([testUser.id, otherUser1.id]).then(({channel: dmChannel}) => {
|
||||
// # Visit the channel using the channel name
|
||||
cy.visit(`/${testTeam.name}/channels/${testUser.id}__${otherUser1.id}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', otherUser1.username);
|
||||
|
||||
// # Visit the channel using the channel id
|
||||
cy.visit(`/${testTeam.name}/channels/${dmChannel.id}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', otherUser1.username);
|
||||
|
||||
// # Visit the channel using the target username
|
||||
cy.visit(`/${testTeam.name}/messages/@${otherUser1.username}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', otherUser1.username);
|
||||
|
||||
// # Visit the channel using the target user email
|
||||
cy.visit(`/${testTeam.name}/messages/${otherUser1.email}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', otherUser1.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should go group channel using group id', () => {
|
||||
const userGroupIds = [testUser.id, otherUser1.id, otherUser2.id];
|
||||
|
||||
// # Create a group channel for 3 users
|
||||
cy.apiCreateGroupChannel(userGroupIds).then(({channel: gmChannel}) => {
|
||||
// # Visit the channel using the name using the channels route
|
||||
cy.visit(`/${testTeam.name}/channels/${gmChannel.name}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
const displayName = gmChannel.display_name.split(', ').filter(((username) => username !== testUser.username)).join(', ');
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', displayName);
|
||||
|
||||
// # Visit the channel using the name using the messages route
|
||||
cy.visit(`/${testTeam.name}/messages/${gmChannel.name}`);
|
||||
|
||||
// * Check you can go to the channel without problem
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', displayName);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings @permissions
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
describe('Channel Settings Menu Permissions', () => {
|
||||
let testTeam: Team;
|
||||
let testChannel: Channel;
|
||||
let admin: UserProfile;
|
||||
let user: UserProfile;
|
||||
|
||||
before(() => {
|
||||
// # Create a test team, channel, and admin user
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, user: newAdmin, channel}) => {
|
||||
testTeam = team;
|
||||
admin = newAdmin;
|
||||
testChannel = channel;
|
||||
|
||||
// # Create a regular user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
user = newUser;
|
||||
cy.apiAddUserToTeam(team.id, newUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(channel.id, newUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
// # Change permission so that regular users can't access channel settings
|
||||
cy.apiGetRolesByNames(['channel_user']).then(({roles}) => {
|
||||
const role = roles[0];
|
||||
const permissions = role.permissions.filter((permission) => {
|
||||
return !(['manage_public_channel_properties', 'manage_private_channel_properties', 'manage_public_channel_banner', 'manage_private_channel_banner', 'delete_public_channel', 'delete_private_channel'].includes(permission));
|
||||
});
|
||||
|
||||
if (permissions.length !== role.permissions.length) {
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
}
|
||||
});
|
||||
|
||||
cy.apiLogin(admin);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1001: Channel Settings menu is visible for users with permissions', () => {
|
||||
// # Login as the admin user
|
||||
cy.apiLogin(admin);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open channel header menu
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
|
||||
// * Verify Channel Settings option is visible
|
||||
cy.findByText('Channel Settings').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T1002: Channel Settings menu is hidden for users without permissions', () => {
|
||||
// # Login as the regular user
|
||||
cy.apiLogin(user);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open channel header menu
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
|
||||
// * Verify Channel Settings option is not visible
|
||||
cy.findByText('Channel Settings').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,460 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
describe('Channel Settings Modal', () => {
|
||||
let testTeam: Team;
|
||||
let testUser: UserProfile;
|
||||
let testChannel: Channel;
|
||||
let originalTestChannel: Channel;
|
||||
|
||||
before(() => {
|
||||
// Setup test data
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create a test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'test-channel', 'Test Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
originalTestChannel = {...channel};
|
||||
cy.apiAddUserToChannel(channel.id, user.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Visit the channel before each test
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
|
||||
// 2DO: update tests ids once QA has defined the test plan and zephyr tests
|
||||
it('MM-T1: Can open and close the channel settings modal', () => {
|
||||
// # Open channel settings modal from channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify modal is open
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
cy.get('#genericModalLabel').should('contain', 'Channel Settings');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * Verify modal is closed
|
||||
cy.get('.ChannelSettingsModal').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T2: Can navigate between tabs', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify Info tab is active by default
|
||||
cy.get('#infoButton').should('have.class', 'active');
|
||||
cy.get('.ChannelSettingsModal__infoTab').should('be.visible');
|
||||
|
||||
// # Click on Archive tab
|
||||
cy.get('#archiveButton').click();
|
||||
|
||||
// * Verify Archive tab is active
|
||||
cy.get('#archiveButton').should('have.class', 'active');
|
||||
cy.get('.ChannelSettingsModal__archiveTab').should('be.visible');
|
||||
|
||||
// # Click back on Info tab
|
||||
cy.get('#infoButton').click();
|
||||
|
||||
// * Verify Info tab is active again
|
||||
cy.get('#infoButton').should('have.class', 'active');
|
||||
cy.get('.ChannelSettingsModal__infoTab').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T3: Can edit channel name and URL', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Get original channel name, url and alias it
|
||||
cy.get('#input_channel-settings-name').invoke('val').as('originalName');
|
||||
cy.get('.url-input-label').invoke('val').as('originalUrl');
|
||||
|
||||
// # Edit channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('Updated Channel Name');
|
||||
|
||||
// * Verify URL remains unchanged (does not auto-update when editing existing channels)
|
||||
cy.get('@originalUrl').then((originalUrl) => {
|
||||
cy.get('.url-input-label').should('contain', originalUrl);
|
||||
});
|
||||
|
||||
// # Click Save
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * Verify channel header shows updated name
|
||||
cy.get('#channelHeaderTitle').should('contain', 'Updated Channel Name');
|
||||
|
||||
// # Open channel settings modal to restore original values
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// * Verify the channel name is reset to the original value
|
||||
cy.get('#input_channel-settings-name').clear().type(originalTestChannel.display_name);
|
||||
|
||||
// # Try to change URL to match the original url
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type(originalTestChannel.name);
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// # Save changes
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * Verify channel header shows updated name
|
||||
cy.get('#channelHeaderTitle').should('contain', originalTestChannel.display_name);
|
||||
});
|
||||
|
||||
it('MM-T31: Channel name changes do not auto-update URL, URL only changes when explicitly modified', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'url-behavior-test', 'URL Behavior Test').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Store original values
|
||||
cy.get('#input_channel-settings-name').invoke('val').as('originalName');
|
||||
cy.get('.url-input-label').invoke('val').as('originalUrl');
|
||||
|
||||
// # Verify changing channel name does NOT auto-update URL
|
||||
|
||||
// # Change the channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('New Channel Name');
|
||||
|
||||
// * Verify URL remains unchanged after name change
|
||||
cy.get('@originalUrl').then((originalUrl) => {
|
||||
cy.get('.url-input-label').should('contain', originalUrl);
|
||||
});
|
||||
|
||||
// # Save the name change
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// * Verify URL is still the original URL after saving
|
||||
cy.get('@originalUrl').then((originalUrl) => {
|
||||
cy.get('.url-input-label').should('contain', originalUrl);
|
||||
});
|
||||
|
||||
// # Verify URL only changes when explicitly modified
|
||||
|
||||
// # Click the URL edit button to enable URL editing
|
||||
cy.get('.url-input-button').click();
|
||||
|
||||
// # Modify the URL explicitly
|
||||
cy.get('.url-input-container input').clear().type('explicitly-changed-url');
|
||||
|
||||
// # Confirm the URL change
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// * Verify URL has been updated to the new value
|
||||
cy.get('.url-input-label').should('contain', 'explicitly-changed-url');
|
||||
|
||||
// # Save the URL change
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * Verify the URL has actually changed by checking the browser URL
|
||||
cy.url().should('include', 'explicitly-changed-url');
|
||||
|
||||
// # Verify subsequent name changes still don't affect the explicitly set URL
|
||||
|
||||
// # Open channel settings modal again
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Store the current URL (which should be the explicitly changed one)
|
||||
cy.get('.url-input-label').invoke('val').as('currentUrl');
|
||||
|
||||
// # Change the channel name again
|
||||
cy.get('#input_channel-settings-name').clear().type('Another Name Change');
|
||||
|
||||
// * Verify URL remains the explicitly set URL, not auto-generated from new name
|
||||
cy.get('@currentUrl').then((currentUrl) => {
|
||||
cy.get('.url-input-label').should('contain', currentUrl);
|
||||
});
|
||||
|
||||
// * Verify URL does NOT contain auto-generated value from new name
|
||||
cy.get('.url-input-label').should('not.contain', 'another-name-change');
|
||||
|
||||
// # Save the second name change
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// * Final verification: URL should still be the explicitly set one
|
||||
cy.get('@currentUrl').then((currentUrl) => {
|
||||
cy.get('.url-input-label').should('contain', currentUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T4: Shows error for invalid channel name', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Set empty channel name
|
||||
cy.get('#input_channel-settings-name').clear();
|
||||
|
||||
// # Try Save changes
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify error is shown
|
||||
cy.get('.Input_fieldset').should('have.class', 'Input_fieldset___error');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
|
||||
it('MM-T6: Can edit channel purpose and header', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Edit channel purpose
|
||||
cy.get('#channel_settings_purpose_textbox').clear().type('This is a test purpose');
|
||||
|
||||
// # Edit channel header
|
||||
cy.get('#channel_settings_header_textbox').clear().type('This is a test header');
|
||||
|
||||
// # Save changes
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * Verify channel header shows updated header
|
||||
cy.get('#channelHeaderDescription').should('contain', 'This is a test header');
|
||||
});
|
||||
|
||||
it('MM-T7: Shows error when purpose exceeds character limit', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Enter purpose that exceeds character limit (250 characters)
|
||||
const longPurpose = 'a'.repeat(260);
|
||||
cy.get('#channel_settings_purpose_textbox').clear().type(longPurpose);
|
||||
|
||||
// * Verify error is shown
|
||||
cy.findAllByTestId('channel_settings_purpose_textbox').should('have.class', 'textarea--has-errors');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
|
||||
it('MM-T8: Shows error when header exceeds character limit', () => {
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Enter header that exceeds character limit (1024 characters)
|
||||
const longHeader = 'a'.repeat(1050);
|
||||
cy.findByTestId('channel_settings_header_textbox').clear().type(longHeader);
|
||||
|
||||
// * Verify error is shown
|
||||
cy.findByTestId('channel_settings_header_textbox').should('have.class', 'textarea--has-errors');
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
|
||||
it('MM-T9: Can archive a channel and stay in archived channel view', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'archived-channel-test', 'Archived Channel Test').then(({channel}) => {
|
||||
// # Visit the channel to be archived
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Click on Archive tab
|
||||
cy.get('#archiveButton').click();
|
||||
|
||||
// # Click Archive button
|
||||
cy.get('#channelSettingsArchiveChannelButton').click();
|
||||
|
||||
// * Verify confirmation modal appears
|
||||
cy.get('#archiveChannelConfirmModal').should('be.visible');
|
||||
|
||||
// # Confirm archive
|
||||
cy.findByRole('button', {name: 'Confirm'}).click();
|
||||
|
||||
// * Verify we stay in the archived channel (no redirect)
|
||||
cy.url().should('include', channel.name);
|
||||
|
||||
// * Verify archived channel message is displayed
|
||||
cy.contains('You are viewing an archived channel').should('be.visible');
|
||||
|
||||
// * Verify channel is still visible in sidebar while viewing it
|
||||
cy.get('.SidebarChannel').contains('Archived Channel Test').should('exist');
|
||||
|
||||
// # Navigate to a different channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify archived channel disappears from sidebar after navigating away
|
||||
cy.get('.SidebarChannel').contains('Archived Channel Test').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T10: Warns when switching tabs with unsaved changes', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'unsaved-test', 'Unsaved Test').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Make a change to the channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('Changed Name');
|
||||
|
||||
// # Try to switch to Archive tab
|
||||
cy.get('#archiveButton').click();
|
||||
|
||||
// * Verify we're still on the Info tab due to unsaved changes
|
||||
cy.get('#infoButton').should('have.class', 'active');
|
||||
|
||||
// * Verify save changes panel shows error
|
||||
cy.get('.SaveChangesPanel').should('have.class', 'error');
|
||||
});
|
||||
});
|
||||
|
||||
// MM-T11: Can reset changes without saving
|
||||
it('MM-T11: Can reset changes without saving', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'reset-test', 'Reset Test').then(({channel}) => {
|
||||
// # Visit the channel page using the channel name returned from the API
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Get original channel name and alias it
|
||||
cy.get('#input_channel-settings-name').invoke('val').as('originalName');
|
||||
|
||||
// # Change the channel name
|
||||
cy.get('#input_channel-settings-name').clear().type('Temporary Name');
|
||||
|
||||
// # Click the Reset button in the SaveChangesPanel
|
||||
cy.get('.SaveChangesPanel button').contains('Reset').click();
|
||||
|
||||
// * Verify the channel name is reset to the original value
|
||||
cy.get('@originalName').then((originalName) => {
|
||||
cy.get('#input_channel-settings-name').should('have.value', originalName);
|
||||
});
|
||||
|
||||
// * Verify the SaveChangesPanel is no longer visible
|
||||
cy.get('.SaveChangesPanel').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
// MM-T12: Can preview purpose and header with markdown
|
||||
it('MM-T12: Can preview purpose and header with markdown', () => {
|
||||
// # Create a new channel for this test
|
||||
cy.apiCreateChannel(testTeam.id, 'markdown-test', 'Markdown Test').then(({channel}) => {
|
||||
// # Visit the channel page using the channel name returned from the API
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Add markdown to purpose
|
||||
cy.get('#channel_settings_purpose_textbox').clear().type('This is **bold** and _italic_ text');
|
||||
|
||||
// # Click preview button for purpose (assumes preview button is inside the AdvancedTextbox container)
|
||||
cy.get('#channel_settings_purpose_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
click();
|
||||
|
||||
// * Verify that markdown is rendered in preview: check for bold and italic formatting
|
||||
cy.get('.textbox-preview-area').
|
||||
should('contain', 'This is').
|
||||
find('strong').
|
||||
should('contain', 'bold');
|
||||
cy.get('.textbox-preview-area').
|
||||
find('em').
|
||||
should('contain', 'italic');
|
||||
|
||||
// # Add markdown to header
|
||||
cy.get('#channel_settings_header_textbox').clear().type('Visit [Mattermost](https://mattermost.com)');
|
||||
|
||||
// # Click preview button for header
|
||||
cy.get('#channel_settings_header_textbox').
|
||||
parents('.AdvancedTextbox').
|
||||
find('#PreviewInputTextButton').
|
||||
click();
|
||||
|
||||
// * Verify that markdown is rendered in preview: check for a link with text "Mattermost"
|
||||
cy.get('.textbox-preview-area').
|
||||
should('contain', 'Visit').
|
||||
find('a').
|
||||
should('contain', 'Mattermost');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T13: Validates URL when editing channel name', () => {
|
||||
// # Create two channels to test URL conflict error
|
||||
cy.apiCreateChannel(testTeam.id, 'first-channel', 'First Channel').then(({channel: channel1}) => {
|
||||
cy.apiCreateChannel(testTeam.id, 'second-channel', 'Second Channel').then(({channel}) => {
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Try to change URL to match the first channel
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type(channel1.name);
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// * Verify error is shown
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {
|
||||
beMuted,
|
||||
beUnmuted,
|
||||
} from '@/support/assertions';
|
||||
|
||||
describe('Channel Settings', () => {
|
||||
let testTeam: Team;
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Private Channel', 'P').then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, user.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(user);
|
||||
|
||||
// # Visit town-square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T882 Channel URL validation works properly', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Go to channel dropdown > Channel Settings Modal
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Change channel name (this should NOT affect the URL anymore)
|
||||
cy.get('#input_channel-settings-name').clear().type('town-square');
|
||||
|
||||
// # Now explicitly change the URL to trigger the validation error
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type('town-square');
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// # Try to save
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// # Error is displayed and URL is unchanged
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
cy.get('.url-input-error').should('be.visible').and('contain.text', 'A channel with that name already exists on the same team.');
|
||||
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Enter a new URL and save
|
||||
cy.get('.url-input-container input').clear().type('another-town-square');
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * URL is updated and no errors are displayed
|
||||
cy.url().should('include', `/${testTeam.name}/channels/another-town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T887 Channel dropdown menu - Mute / Unmute', () => {
|
||||
// # Visit off-topic
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Go to channel dropdown > Mute channel
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelHeaderDropdownMenu').should('exist').
|
||||
findByText('Mute Channel').should('be.visible').click();
|
||||
|
||||
// # Verify channel is muted
|
||||
cy.get('#sidebarItem_off-topic').should(beMuted);
|
||||
|
||||
// # Verify mute bell icon is visible
|
||||
cy.get('#toggleMute').should('be.visible');
|
||||
|
||||
// # Verify that off topic is last in the list of channels
|
||||
cy.uiGetLhsSection('CHANNELS').find('.SidebarChannel').
|
||||
last().should('contain', 'Off-Topic').
|
||||
get('a').should('have.class', 'muted');
|
||||
|
||||
// # Click Unmute channel while menu is open
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelHeaderDropdownMenu').should('exist').
|
||||
findByText('Unmute Channel').should('be.visible').click();
|
||||
|
||||
// # Verify channel is unmuted
|
||||
cy.get('#sidebarItem_off-topic').should(beUnmuted);
|
||||
|
||||
// # Verify mute bell icon is not visible
|
||||
cy.get('#toggleMute').should('not.exist');
|
||||
|
||||
// # Verify that off topic is not last in the list of channels
|
||||
cy.uiGetLhsSection('CHANNELS').find('.SidebarChannel').
|
||||
last().should('not.contain', 'Off-Topic');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @smoke
|
||||
|
||||
describe('Channel Switcher', () => {
|
||||
let testTeam: Team;
|
||||
let offTopicUrl: string;
|
||||
const channelNamePrefix = 'aswitchchannel';
|
||||
const channelDisplayNamePrefix = 'ASwitchChannel';
|
||||
|
||||
let channelB: Channel;
|
||||
let channelC: Channel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({channelPrefix: {name: `${channelNamePrefix}-a`, displayName: `${channelDisplayNamePrefix} A`}}).then(({team, user, offTopicUrl: url}) => {
|
||||
testTeam = team;
|
||||
offTopicUrl = url;
|
||||
|
||||
// # Add some channels
|
||||
cy.apiCreateChannel(testTeam.id, `${channelNamePrefix}-b`, `${channelDisplayNamePrefix} B`, 'O').then(({channel}) => {
|
||||
channelB = channel;
|
||||
});
|
||||
cy.apiCreateChannel(testTeam.id, `${channelNamePrefix}-c`, `${channelDisplayNamePrefix} C`, 'O').then(({channel}) => {
|
||||
channelC = channel;
|
||||
});
|
||||
|
||||
// # Login as test user and go to off-topic
|
||||
cy.apiLogin(user);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2031_1 - should switch channels by keyboard', () => {
|
||||
// # Press CTRL+K (Windows) or CMD+K(Mac)
|
||||
cy.typeCmdOrCtrl().type('K', {release: true});
|
||||
|
||||
// # Start typing channel name in the "Switch Channels" modal message box
|
||||
// # Use up/down arrow keys to highlight second channel
|
||||
// # Press ENTER
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).type(`${channelDisplayNamePrefix}`);
|
||||
cy.findByRole('option', {name: channelB.display_name}).should('be.visible');
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).type('{downarrow}{downarrow}{enter}');
|
||||
|
||||
// * Expect channel title to match title
|
||||
cy.get('#channelHeaderTitle').
|
||||
should('be.visible').
|
||||
and('contain.text', channelB.display_name);
|
||||
|
||||
// * Expect url to match url
|
||||
cy.url().should('contain', `${channelB.name}`);
|
||||
});
|
||||
|
||||
it('MM-T2031_2 - should switch channels by mouse', () => {
|
||||
// # Press CTRL+K (Windows) or CMD+K(Mac)
|
||||
cy.typeCmdOrCtrl().type('K', {release: true});
|
||||
|
||||
// # Start typing channel name in the "Switch Channels" modal message box
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).type(`${channelDisplayNamePrefix} `);
|
||||
|
||||
cy.get(`[data-testid^=${channelC.name}] > span`).click();
|
||||
|
||||
// * Expect channel title to match title
|
||||
cy.get('#channelHeaderTitle').
|
||||
should('be.visible').
|
||||
and('contain.text', channelC.display_name);
|
||||
|
||||
// * Expect url to match url
|
||||
cy.url().should('contain', `${channelC.name}`);
|
||||
});
|
||||
|
||||
it('MM-T2031_3 - should show empty result', () => {
|
||||
// # Press CTRL+K (Windows) or CMD+K(Mac)
|
||||
cy.typeCmdOrCtrl().type('K', {release: true});
|
||||
|
||||
// # Type invalid channel name in the "Switch Channels" modal message box
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).type('there-is-no-spoon');
|
||||
|
||||
// * Expect 'nothing found' message
|
||||
cy.get('.no-results__title > span').should('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T2031_4 - should close on esc and outside click', () => {
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Press CTRL+K (Windows) or CMD+K(Mac)
|
||||
cy.typeCmdOrCtrl().type('K', {release: true});
|
||||
|
||||
// # Press ESC
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).type('{esc}');
|
||||
|
||||
// * Expect the dialog to be closed
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).should('not.exist');
|
||||
|
||||
// * Expect staying in the same channel
|
||||
cy.url().should('contain', 'off-topic');
|
||||
|
||||
// # Press CTRL+K (Windows) or CMD+K(Mac)
|
||||
cy.typeCmdOrCtrl().type('K', {release: true});
|
||||
|
||||
// # Click outside of the modal
|
||||
cy.get('.modal').click({force: true});
|
||||
|
||||
// * Expect the dialog to be closed
|
||||
cy.findByRole('combobox', {name: 'quick switch input'}).should('not.exist');
|
||||
|
||||
// * Expect staying in the same channel
|
||||
cy.url().should('contain', 'off-topic');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import type {ChainableT} from '@/types';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings @not_cloud
|
||||
|
||||
// Make sure that the current channel is Town Square and that the
|
||||
// channel identified by the passed name is no longer in the channel
|
||||
// sidebar
|
||||
function verifyChannelWasProperlyClosed(channelName: string) {
|
||||
// * Make sure that we have switched channels
|
||||
cy.get('#channelHeaderTitle').should('contain', 'Town Square');
|
||||
|
||||
// * Make sure the old DM no longer exists
|
||||
cy.get('#sidebarItem_' + channelName).should('not.exist');
|
||||
}
|
||||
|
||||
describe('Close direct messages', () => {
|
||||
let testUser: UserProfile;
|
||||
let otherUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
otherUser = newUser;
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
});
|
||||
|
||||
// # Login as test user and go to town square
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('Through channel header dropdown menu', () => {
|
||||
createAndVisitDMChannel([testUser.id, otherUser.id]).then((channel) => {
|
||||
// # Open channel header dropdown menu and click on Close Direct Message
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Close Direct Message').click();
|
||||
|
||||
verifyChannelWasProperlyClosed(channel.name);
|
||||
});
|
||||
});
|
||||
|
||||
function createAndVisitDMChannel(userIds: string[]): ChainableT<Channel> {
|
||||
return cy.apiCreateDirectChannel(userIds).then(({channel}) => {
|
||||
// # Visit the new channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Verify channel's display name
|
||||
cy.get('#channelHeaderTitle').should('contain', channel.display_name);
|
||||
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('Close group messages', () => {
|
||||
let testUser: UserProfile;
|
||||
let otherUser1: UserProfile;
|
||||
let otherUser2: UserProfile;
|
||||
let testTeam: Team;
|
||||
|
||||
before(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.apiCreateUser({prefix: 'aaa'}).then(({user: newUser}) => {
|
||||
otherUser1 = newUser;
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
});
|
||||
cy.apiCreateUser({prefix: 'bbb'}).then(({user: newUser}) => {
|
||||
otherUser2 = newUser;
|
||||
cy.apiAddUserToTeam(team.id, newUser.id);
|
||||
});
|
||||
|
||||
// # Login as test user and go to town square
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('Through channel header dropdown menu', () => {
|
||||
createAndVisitGMChannel([otherUser1, otherUser2]).then((channel) => {
|
||||
// # Open channel header dropdown menu and click on Close Direct Message
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Close Group Message').click();
|
||||
|
||||
verifyChannelWasProperlyClosed(channel.name);
|
||||
});
|
||||
});
|
||||
|
||||
function createAndVisitGMChannel(users: UserProfile[] = []) {
|
||||
const userIds = users.map((user) => user.id);
|
||||
return cy.apiCreateGroupChannel(userIds).then(({channel}) => {
|
||||
// # Visit the new channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Verify channel's display name
|
||||
const displayName = users.
|
||||
map((member) => member.username).
|
||||
sort((a, b) => a.localeCompare(b, 'en', {numeric: true})).
|
||||
join(', ');
|
||||
cy.get('#channelHeaderTitle').should('contain', displayName);
|
||||
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,571 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Channel Type Conversion (Public to Private Only)', () => {
|
||||
let testUser: UserProfile;
|
||||
let testTeam: Team;
|
||||
const admin = getAdminAccount();
|
||||
|
||||
// Helper functions for permission management
|
||||
const saveConfig = () => {
|
||||
cy.waitUntil(() =>
|
||||
cy.get('#saveSetting').then((button) => {
|
||||
// Check if the button text is exactly "Save"
|
||||
if (button.text().trim() === 'Save') {
|
||||
button.click();
|
||||
return true; // signals waitUntil that it succeeded
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const enablePermission = (permissionCheckBoxTestId: string) => {
|
||||
cy.findByTestId(permissionCheckBoxTestId).then((el) => {
|
||||
if (!el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removePermission = (permissionCheckBoxTestId: string) => {
|
||||
cy.findByTestId(permissionCheckBoxTestId).then((el) => {
|
||||
if (el.hasClass('checked')) {
|
||||
el.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const promoteToChannelAdmin = (userId: string, channelId: string, admin: Pick<UserProfile, 'username' | 'password'>) => {
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'put',
|
||||
path: `channels/${channelId}/members/${userId}/schemeRoles`,
|
||||
data: {
|
||||
scheme_user: true,
|
||||
scheme_admin: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const resetPermissionsToDefault = () => {
|
||||
// # Login as sysadmin and navigate to system scheme page
|
||||
cy.apiAdminLogin();
|
||||
cy.visit('/admin_console/user_management/permissions/system_scheme');
|
||||
|
||||
// # Click reset to defaults and confirm
|
||||
cy.findByTestId('resetPermissionsToDefault').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Save
|
||||
saveConfig();
|
||||
cy.wait(1000);
|
||||
cy.visit('/admin_console/user_management/permissions/system_scheme');
|
||||
};
|
||||
|
||||
// Helper functions for permission setup
|
||||
const setupPermissions = (config: {
|
||||
resetToDefault?: boolean;
|
||||
publicToPrivate?: boolean;
|
||||
removeFromTeamAdmin?: boolean;
|
||||
}) => {
|
||||
if (config.resetToDefault) {
|
||||
resetPermissionsToDefault();
|
||||
}
|
||||
|
||||
// Public to private conversion permissions
|
||||
if (config.publicToPrivate) {
|
||||
enablePermission('all_users-public_channel-convert_public_channel_to_private-checkbox');
|
||||
} else if (config.publicToPrivate === false) {
|
||||
removePermission('all_users-public_channel-convert_public_channel_to_private-checkbox');
|
||||
}
|
||||
|
||||
// Remove public to private conversion from team admin if specified
|
||||
if (config.removeFromTeamAdmin) {
|
||||
removePermission('team_admin-public_channel-convert_public_channel_to_private-checkbox');
|
||||
}
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
saveConfig();
|
||||
};
|
||||
|
||||
// Helper functions for channel management
|
||||
const createAndVisitPublicChannel = (teamName: string, channelId: string, displayName: string): Cypress.Chainable<Channel> => {
|
||||
return cy.apiCreateChannel(testTeam.id, channelId, displayName).then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
cy.visit(`/${teamName}/channels/${channel.name}`);
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
};
|
||||
|
||||
const createAndVisitPrivateChannel = (teamName: string, channelId: string, displayName: string): Cypress.Chainable<Channel> => {
|
||||
return cy.apiCreateChannel(testTeam.id, channelId, displayName, 'P').then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
cy.visit(`/${teamName}/channels/${channel.name}`);
|
||||
return cy.wrap(channel);
|
||||
});
|
||||
};
|
||||
|
||||
const visitChannel = (teamName: string, channelName: string) => {
|
||||
cy.visit(`/${teamName}/channels/${channelName}`);
|
||||
};
|
||||
|
||||
// Helper functions for UI interaction
|
||||
const openChannelSettingsModal = () => {
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
};
|
||||
|
||||
const closeChannelSettingsModal = () => {
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
cy.get('.ChannelSettingsModal').should('not.exist');
|
||||
};
|
||||
|
||||
// Helper functions for UI interaction
|
||||
const saveChannelSettings = () => {
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
};
|
||||
|
||||
const verifySettingsSaved = () => {
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
};
|
||||
|
||||
// Helper functions for channel conversion
|
||||
const convertChannelToPrivate = () => {
|
||||
// Select private channel type
|
||||
cy.get('#public-private-selector-button-P').click();
|
||||
cy.get('#public-private-selector-button-P').should('have.class', 'selected');
|
||||
|
||||
// Save changes - this will trigger the confirmation modal
|
||||
saveChannelSettings();
|
||||
|
||||
// Handle the confirmation modal that appears when converting from public to private
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// Verify settings were saved
|
||||
verifySettingsSaved();
|
||||
|
||||
// Verify the modal completely closed to avoid flakiness
|
||||
cy.get('#confirmModal').should('not.exist');
|
||||
};
|
||||
|
||||
// Function kept for potential future use but not used in current tests
|
||||
// since private to public conversion is no longer allowed
|
||||
// const convertChannelToPublic = () => {
|
||||
// cy.get('#public-private-selector-button-O').click();
|
||||
// cy.get('#public-private-selector-button-O').should('have.class', 'selected');
|
||||
// };
|
||||
|
||||
// Helper functions for verification
|
||||
const verifyChannelIsPrivate = (channelName: string) => {
|
||||
cy.get('.SidebarChannel').contains(channelName).parent().find('.icon-lock-outline').should('exist');
|
||||
};
|
||||
|
||||
// Function kept for potential future use but not used in current tests
|
||||
// since private to public conversion is no longer allowed
|
||||
// const verifyChannelIsPublic = (channelName: string) => {
|
||||
// cy.get('.SidebarChannel').contains(channelName).parent().find('.icon-lock-outline').should('not.exist');
|
||||
// };
|
||||
|
||||
const verifyConversionOptionDisabled = (toPrivate = true) => {
|
||||
if (toPrivate) {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is disabled - it might have a disabled attribute or a disabled class
|
||||
cy.get('#public-private-selector-button-P').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.true;
|
||||
});
|
||||
} else {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is disabled - it might have a disabled attribute or a disabled class
|
||||
cy.get('#public-private-selector-button-O').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const verifyConversionOptionEnabled = (toPrivate = true) => {
|
||||
if (toPrivate) {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is enabled - it should not have disabled attributes or classes
|
||||
cy.get('#public-private-selector-button-P').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.false;
|
||||
});
|
||||
} else {
|
||||
// Wait for the UI to fully load and stabilize
|
||||
cy.wait(500);
|
||||
|
||||
// Check if the button is enabled - it should not have disabled attributes or classes
|
||||
cy.get('#public-private-selector-button-O').then(($el) => {
|
||||
const isDisabled = $el.hasClass('disabled') || $el.prop('disabled') === true || $el.attr('aria-disabled') === 'true';
|
||||
expect(isDisabled).to.be.false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to make user a channel admin
|
||||
const makeUserChannelAdmin = (channelId: string, userId: string) => {
|
||||
cy.apiAddUserToChannel(channelId, userId);
|
||||
promoteToChannelAdmin(userId, channelId, admin);
|
||||
};
|
||||
|
||||
// Helper function to make user a team admin
|
||||
const makeUserTeamAdmin = (teamId: string, userId: string) => {
|
||||
cy.apiUpdateTeamMemberSchemeRole(teamId, userId, {scheme_admin: true, scheme_user: true});
|
||||
};
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset to a clean state before each test
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('Basic Conversion Functionality', () => {
|
||||
it('MM-T3348-1 - Can convert a public channel to private', () => {
|
||||
// # Setup permissions
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'public-to-private', 'Public To Private').then((channel) => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348-5 - Cannot convert a private channel back to public', () => {
|
||||
// # Setup permissions
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'private-stays-private', 'Private Stays Private').then((channel) => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
|
||||
// # Open channel settings modal again
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Role-Based Channel Type Conversion', () => {
|
||||
it('MM-T3350-1 - System admin cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Login as system admin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create and visit a private channel
|
||||
createAndVisitPrivateChannel(testTeam.name, 'sysadmin-private-stays-private', 'SysAdmin Private Channel').then(() => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3350-2 - Channel admin cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
setupPermissions({resetToDefault: true});
|
||||
|
||||
// # Login as regular user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create and visit a private channel
|
||||
createAndVisitPrivateChannel(testTeam.name, 'channel-admin-private', 'Channel Admin Private').then((channel) => {
|
||||
// # Make test user a channel admin
|
||||
makeUserChannelAdmin(channel.id, testUser.id);
|
||||
|
||||
// # Visit the channel again after role change
|
||||
visitChannel(testTeam.name, channel.name);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3350-3 - Team admin cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
setupPermissions({resetToDefault: true});
|
||||
|
||||
// # Make test user a team admin
|
||||
makeUserTeamAdmin(testTeam.id, testUser.id);
|
||||
|
||||
// # Login as team admin
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create and visit a private channel
|
||||
createAndVisitPrivateChannel(testTeam.name, 'team-admin-private', 'Team Admin Private').then(() => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option to public is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3350-4 - Regular user cannot convert a private channel to public', () => {
|
||||
// # Reset permissions to default
|
||||
resetPermissionsToDefault();
|
||||
|
||||
// # Create a new regular user specifically for this test
|
||||
cy.apiCreateUser().then(({user: regularUser}) => {
|
||||
// # Add user to the team
|
||||
cy.apiAddUserToTeam(testTeam.id, regularUser.id);
|
||||
|
||||
// # Have admin create a private channel
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'admin-created-private', 'Admin Created Private', 'P').then(({channel}) => {
|
||||
// # Add the regular user to the channel (as a regular member, not admin)
|
||||
cy.apiAddUserToChannel(channel.id, regularUser.id);
|
||||
|
||||
// # Login as the regular user
|
||||
cy.apiLogin(regularUser);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(false);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Permission-Based Tests', () => {
|
||||
it('MM-T3348-2 - Regular user without permission cannot convert public to private', () => {
|
||||
// # Setup permissions - remove public to private conversion permission
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: false});
|
||||
|
||||
// # Create a new regular user specifically for this test
|
||||
cy.apiCreateUser().then(({user: regularUser}) => {
|
||||
// # Add user to the team
|
||||
cy.apiAddUserToTeam(testTeam.id, regularUser.id);
|
||||
|
||||
// # Have admin create a public channel
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'admin-created-public', 'Admin Created Public').then(({channel}) => {
|
||||
// # Add the regular user to the channel (as a regular member, not admin)
|
||||
cy.apiAddUserToChannel(channel.id, regularUser.id);
|
||||
|
||||
// # Login as the regular user
|
||||
cy.apiLogin(regularUser);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(true);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348-3 - Team admin can convert public to private by default', () => {
|
||||
// # Setup permissions - reset to default
|
||||
setupPermissions({resetToDefault: true});
|
||||
|
||||
// # Make test user a team admin
|
||||
makeUserTeamAdmin(testTeam.id, testUser.id);
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'team-admin-convert', 'Team Admin Convert').then((channel) => {
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is enabled
|
||||
verifyConversionOptionEnabled(true);
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3348-4 - Team admin cannot convert when permission is removed', () => {
|
||||
// # Setup permissions - remove permission from team admin
|
||||
setupPermissions({
|
||||
resetToDefault: true,
|
||||
removeFromTeamAdmin: true,
|
||||
});
|
||||
|
||||
// # Make test user a team admin
|
||||
makeUserTeamAdmin(testTeam.id, testUser.id);
|
||||
|
||||
// # Have admin create a public channel
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'admin-created-team-admin-no-perm', 'Admin Created Team Admin No Permission').then(({channel}) => {
|
||||
// # Add the team admin user to the channel (as a regular member, not channel admin)
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
|
||||
// # Login as the team admin user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(true);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Channel Admin Tests', () => {
|
||||
it('MM-T3349-1 - Channel admin can convert public to private when permission is enabled', () => {
|
||||
// # Setup permissions - enable public to private conversion permission
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, 'channel-admin-pub', 'Channel Admin Public').then((channel) => {
|
||||
// # Make test user a channel admin
|
||||
makeUserChannelAdmin(channel.id, testUser.id);
|
||||
|
||||
// # Visit the channel again after role change
|
||||
visitChannel(testTeam.name, channel.name);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is enabled
|
||||
verifyConversionOptionEnabled(true);
|
||||
|
||||
// # Convert to private
|
||||
convertChannelToPrivate();
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
|
||||
// * Verify channel is now private
|
||||
verifyChannelIsPrivate(channel.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3349-2 - Channel admin cannot convert public to private when permission is removed', () => {
|
||||
// Generate a unique channel name with timestamp to avoid conflicts
|
||||
const timestamp = Date.now();
|
||||
const channelId = `channel-admin-no-perm-${timestamp}`;
|
||||
const displayName = `Channel Admin No Permission ${timestamp}`;
|
||||
|
||||
// # Setup permissions - remove public to private conversion permission
|
||||
setupPermissions({resetToDefault: true, publicToPrivate: false, removeFromTeamAdmin: true});
|
||||
|
||||
// # Create and visit a public channel
|
||||
createAndVisitPublicChannel(testTeam.name, channelId, displayName).then((channel) => {
|
||||
// # Make test user a channel admin
|
||||
makeUserChannelAdmin(channel.id, testUser.id);
|
||||
|
||||
// # Log back in as the test user after making channel admin
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Wait for login to complete
|
||||
cy.wait(500);
|
||||
|
||||
// # Visit the channel again after role change
|
||||
visitChannel(testTeam.name, channel.name);
|
||||
|
||||
// # Wait for channel to load
|
||||
cy.wait(500);
|
||||
|
||||
// # Open channel settings modal
|
||||
openChannelSettingsModal();
|
||||
|
||||
// * Verify conversion option is disabled
|
||||
verifyConversionOptionDisabled(true);
|
||||
|
||||
// # Close the modal
|
||||
closeChannelSettingsModal();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team, TeamMembership} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Group Message Conversion To Private Channel', () => {
|
||||
let testTeam1: Team;
|
||||
let testTeam2: Team;
|
||||
|
||||
let testUser1: UserProfile;
|
||||
let testUser2: UserProfile;
|
||||
let testUser3: UserProfile;
|
||||
|
||||
let gm: Channel;
|
||||
|
||||
before(() => {
|
||||
// we need two teams, and a set of users belonging to both teams
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam1 = team;
|
||||
testUser1 = user;
|
||||
|
||||
cy.apiCreateTeam('gmconversionteam2', 'GM Conversion Team 2').then(({team: team2}) => {
|
||||
testTeam2 = team2;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user: user3}) => {
|
||||
testUser3 = user3;
|
||||
|
||||
const teamMembers1 = [testUser1, testUser2, testUser3].map((u) => ({team_id: testTeam1.id, user_id: u.id}));
|
||||
cy.apiAddUsersToTeam(testTeam1.id, teamMembers1 as TeamMembership[]).then(() => {
|
||||
const teamMembers2 = [testUser1, testUser2, testUser3].map((u) => ({team_id: testTeam2.id, user_id: u.id}));
|
||||
cy.apiAddUsersToTeam(testTeam2.id, teamMembers2 as TeamMembership[]).then(() => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id]).then(({channel}) => {
|
||||
gm = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('GM conversion', () => {
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm.name}`);
|
||||
|
||||
// Open the GM conversion dialog
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
|
||||
// the dialog has a animation, so we're waiting for it to finish
|
||||
// by waiting for the (currently disabled) confirm button to appear
|
||||
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
||||
|
||||
// Open the team dropdown and select a team
|
||||
cy.findByText('Select Team').click({force: true});
|
||||
cy.findByText(testTeam2.display_name).click();
|
||||
|
||||
// Enter the new channel name and confirm
|
||||
cy.findByPlaceholderText('Channel name').type('Converted Channel');
|
||||
cy.get('.GenericModal__button.delete').click();
|
||||
|
||||
// verify the channel is in a team, as indicated by the presence
|
||||
// of `/channels/` in the URL us `/messages/` in case of GM
|
||||
cy.url().should('contain', `/${testTeam2.name}/channels/converted-channel`);
|
||||
|
||||
// verify the channel created is a private channel by verifying
|
||||
// the presence of the lock icon next to the channel name in LHS
|
||||
cy.get('.SidebarChannel:contains("Converted Channel")').get('.icon.icon-lock-outline').should('be.visible');
|
||||
});
|
||||
|
||||
it('When users belong to only one common team', () => {
|
||||
let testUser4: Cypress.UserProfile;
|
||||
let gm2;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user}) => {
|
||||
testUser4 = user;
|
||||
|
||||
const teamMembers = [testUser1, testUser2, testUser3, testUser4].map((u) => ({
|
||||
team_id: testTeam2.id,
|
||||
user_id: u.id,
|
||||
}));
|
||||
|
||||
cy.apiAddUsersToTeam(testTeam2.id, teamMembers as TeamMembership[]).then(() => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id, testUser4.id]).then(({channel}) => {
|
||||
gm2 = channel;
|
||||
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm2.name}`);
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
cy.get('.GenericModal__button.delete.disabled').wait(2000);
|
||||
|
||||
cy.contains('Select Team').should('not.exist');
|
||||
|
||||
cy.findByPlaceholderText('Channel name').type('Converted Channel 2');
|
||||
cy.get('.GenericModal__button.delete').click();
|
||||
|
||||
cy.url().should('contain', `/${testTeam2.name}/channels/converted-channel-2`);
|
||||
cy.get('.SidebarChannel:contains("Converted Channel 2")').get('.icon.icon-lock-outline').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('When users have no common team', () => {
|
||||
let testUser5: Cypress.UserProfile;
|
||||
let gm3;
|
||||
let testTeam3;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user}) => {
|
||||
testUser5 = user;
|
||||
|
||||
cy.apiCreateTeam('gmconversionteam3', 'GM Conversion Team 3').then(({team}) => {
|
||||
testTeam3 = team;
|
||||
|
||||
const teamMembers = [{
|
||||
team_id: testTeam3.id,
|
||||
user_id: testUser5.id,
|
||||
}];
|
||||
|
||||
cy.apiAddUsersToTeam(testTeam3.id, teamMembers as TeamMembership[]).then(() => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id, testUser5.id]).then(({channel}) => {
|
||||
gm3 = channel;
|
||||
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm3.name}`);
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByRole('menuitem', {name: 'Settings'}).trigger('mouseover');
|
||||
cy.findByText('Convert to Private Channel').click();
|
||||
cy.findByText('Unable to convert to a channel because group members are part of different teams').wait(2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('websocket event should update channel category correctly when in channel', () => {
|
||||
cy.apiCreateGroupChannel([testUser1.id, testUser2.id, testUser3.id]).then(({channel}) => {
|
||||
gm = channel;
|
||||
|
||||
// Open the GM
|
||||
cy.visit(`/${testTeam1.name}/messages/${gm.name}`);
|
||||
|
||||
// Wait until the channel is loaded
|
||||
cy.get('#channelHeaderDropdownButton').should('be.visible');
|
||||
|
||||
// convert via API call
|
||||
const timestamp = Date.now();
|
||||
cy.apiConvertGMToPrivateChannel(gm.id, testTeam2.id, `Channel ${timestamp}`, `c-${timestamp}`).then(() => {
|
||||
cy.url().should('contain', `${testTeam2.name}/channels/c-${timestamp}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Leave and Archive channel actions display as destructive', () => {
|
||||
let testUser: Cypress.UserProfile;
|
||||
let offTopicUrl: string;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit off-topic channel
|
||||
cy.apiInitSetup().then(({user, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
offTopicUrl = url;
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.reload();
|
||||
});
|
||||
|
||||
it('MM-T4943_1 Leave and Archive channel actions display as destructive in the channel dropdown menu', () => {
|
||||
// # click on channel drop-down menu
|
||||
cy.get('#channelHeaderTitle').should('be.visible').click();
|
||||
|
||||
// * View Info menu option should be visible
|
||||
cy.findByText('View Info').should('be.visible');
|
||||
|
||||
// * Move to... menu option should be visible
|
||||
cy.findByText('Move to...').should('be.visible').trigger('mouseover');
|
||||
|
||||
// * Favorites Sub-menu option should be visible
|
||||
cy.findByText('Favorites').should('be.visible');
|
||||
|
||||
// * New Category Sub-menu option should be visible
|
||||
cy.findByText('New Category').should('be.visible');
|
||||
|
||||
// * Move to... close menu option
|
||||
cy.findByText('Move to...').should('be.visible').trigger('mouseout');
|
||||
cy.findByRole('menuitem', {name: 'Move to...'}).should('have.attr', 'aria-expanded', 'false');
|
||||
|
||||
// * Notification Preferences menu option should be visible
|
||||
cy.get('#channelNotificationPreferences').should('be.visible');
|
||||
|
||||
// * Mute Channel menu option should be visible
|
||||
cy.get('#channelToggleMuteChannel').should('be.visible');
|
||||
|
||||
// * Manage Members menu option should be visible
|
||||
cy.get('#channelMembers').should('be.visible');
|
||||
|
||||
// * Channel Settings menu option should be visible
|
||||
cy.findByText('Channel Settings').should('be.visible');
|
||||
|
||||
// * Archive Channel menu option should be visible and have a background-color (destructive)
|
||||
cy.get('#channelArchiveChannel').should('be.visible').focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||
|
||||
// // * Leave Channel menu option should be visible and hav a background-color (destructive)
|
||||
cy.get('#channelLeaveChannel').should('be.visible').focus().should('have.css', 'background-color', 'rgb(210, 75, 78)');
|
||||
});
|
||||
|
||||
it('MM-T4943_2 Leave channel actions display as destructive in the Edit Channel Menu ', () => {
|
||||
// # Open Edit Channel Menu and verify menu optin
|
||||
cy.uiGetChannelSidebarMenu('Off-Topic').within(() => {
|
||||
// * Favorite menu option should be visible
|
||||
cy.findByText('Favorite').should('be.visible').should('not.have.css', 'color', 'rgb(210, 75, 78)');
|
||||
|
||||
// * Mute Channel menu option should be visible
|
||||
cy.findByText('Mute Channel').should('be.visible').should('not.have.css', 'color', 'rgb(210, 75, 78)');
|
||||
|
||||
// * Copy Link menu option should be visible
|
||||
cy.findByText('Copy Link').should('be.visible').should('not.have.css', 'color', 'rgb(210, 75, 78)');
|
||||
|
||||
// * Add Members menu option should be visible
|
||||
cy.findByText('Add Members').should('be.visible').should('not.have.css', 'color', 'rgb(210, 75, 78)');
|
||||
|
||||
// * Move to... menu option should be visible and is not destructive
|
||||
cy.findByText('Move to...').should('be.visible').should('not.have.css', 'color', 'rgb(210, 75, 78)');
|
||||
|
||||
// * Leave Channel menu option should be visible and have a color (destructive)
|
||||
cy.findByText('Leave Channel').should('be.visible').should('have.css', 'color', 'rgb(210, 75, 78)');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Leave channel', () => {
|
||||
let testTeam: Team;
|
||||
let testUser: UserProfile;
|
||||
let testChannel: Channel;
|
||||
|
||||
before(() => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
ThreadAutoFollow: true,
|
||||
CollapsedThreads: 'default_off',
|
||||
},
|
||||
});
|
||||
|
||||
// # Login as test user and visit created channel
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
|
||||
cy.apiSaveUserPreference([{
|
||||
user_id: user.id,
|
||||
category: 'crt_thread_pane_step',
|
||||
name: user.id,
|
||||
value: '0',
|
||||
}], user.id);
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
|
||||
it('MM-T4429_1 Leave a channel while RHS is open', () => {
|
||||
// # Post a message in the channel
|
||||
cy.postMessage('Test leave channel while RHS open');
|
||||
cy.getLastPostId().then((id) => {
|
||||
// # Open RHS
|
||||
cy.clickPostCommentIcon(id);
|
||||
|
||||
// # Post a reply message
|
||||
cy.postMessageAs({sender: testUser, message: 'another reply!', channelId: testChannel.id, rootId: id});
|
||||
|
||||
// * RHS should be visible
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
// * RHS text box should be visible
|
||||
cy.uiGetReplyTextBox();
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiLeaveChannel();
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * RHS should not be visible
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
|
||||
// * Assert that user is redirected to townsquare
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
cy.get('#channelHeaderTitle').should('be.visible').and('contain', 'Town Square');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T4429_2 Leave a channel while RHS is open and CRT on', () => {
|
||||
// * Post text box should be visible
|
||||
cy.uiGetPostTextBox();
|
||||
|
||||
// # Set CRT to on
|
||||
cy.uiChangeCRTDisplaySetting('ON');
|
||||
|
||||
// # Post a message in the channel
|
||||
cy.postMessage('Test leave channel while RHS open');
|
||||
cy.getLastPostId().then((id) => {
|
||||
// # Open RHS
|
||||
cy.clickPostCommentIcon(id);
|
||||
|
||||
// # Post a reply message
|
||||
cy.postMessageAs({sender: testUser, message: 'another reply!', channelId: testChannel.id, rootId: id});
|
||||
|
||||
// * RHS should be visible
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
// * RHS text box should be visible
|
||||
cy.uiGetReplyTextBox();
|
||||
|
||||
// # Archive the channel
|
||||
cy.uiLeaveChannel();
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * RHS should not be visible
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
|
||||
// * Assert that user is redirected to townsquare
|
||||
cy.url().should('include', '/channels/town-square');
|
||||
cy.get('#channelHeaderTitle').should('be.visible').and('contain', 'Town Square');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @channel @rhs
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import * as MESSAGES from '@/fixtures/messages';
|
||||
|
||||
describe('Channel RHS', () => {
|
||||
let testAdmin: Cypress.UserProfile;
|
||||
let testTeam: Cypress.Team;
|
||||
let testChannel: Cypress.Channel;
|
||||
|
||||
before(() => {
|
||||
cy.apiCreateCustomAdmin({loginAfter: true}).then(({sysadmin}) => {
|
||||
testAdmin = sysadmin;
|
||||
|
||||
cy.apiCreateTeam('team1', 'team1').then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, testAdmin.id);
|
||||
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel', 'O').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiAddUserToChannel(channel.id, testAdmin.id);
|
||||
cy.apiSaveCRTPreference(testAdmin.id, 'off');
|
||||
|
||||
cy.apiLogin(testAdmin);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Go to test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
|
||||
it('MM-44435 - should be able to open channel info, visit the system console and come back without issues -- KNOWN ISSUE: MM-47226', () => {
|
||||
// # Click on the channel info button
|
||||
cy.uiGetChannelInfoButton().click();
|
||||
|
||||
// * Verify that the channel info is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Info');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the channel info is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Info');
|
||||
});
|
||||
|
||||
it('MM-T5311 - should be able to open recent mentions, visit the system console and come back without issues', () => {
|
||||
// # Click on the recent mentions button
|
||||
cy.uiGetRecentMentionButton().click();
|
||||
|
||||
// * Verify that the recent mentions is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Recent Mentions');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the recent mentions is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Recent Mentions');
|
||||
});
|
||||
|
||||
it('MM-T5312 - should be able to open saved messages, visit the system console and come back without issues', () => {
|
||||
// # Click on the saved messages button
|
||||
cy.uiGetSavedPostButton().click();
|
||||
|
||||
// * Verify that the saved messages is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Saved messages');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the saved messages is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Saved messages');
|
||||
});
|
||||
|
||||
it('MM-T5313 - should be able to open Pinned messages, visit the system console and come back without issues', () => {
|
||||
// # Click on the Pinned messages button
|
||||
cy.uiGetChannelPinButton().click();
|
||||
|
||||
// * Verify that the Pinned messages is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Pinned messages');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the Pinned messages is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Pinned messages');
|
||||
});
|
||||
|
||||
it('MM-T5314 - should be able to open channel members, visit the system console and come back without issues', () => {
|
||||
// # Click on the channel members button
|
||||
cy.uiGetChannelMemberButton().click();
|
||||
|
||||
// * Verify that the channel members is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Members');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the channel members is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Members');
|
||||
});
|
||||
|
||||
it('MM-T5315 - should be able to open channel files, visit the system console and come back without issues', () => {
|
||||
// # Click on the channel files button
|
||||
cy.uiGetChannelFileButton().click();
|
||||
|
||||
// * Verify that the channel files is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Recent files');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the channel files is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Recent files');
|
||||
});
|
||||
|
||||
it('MM-T5316 - should be able to open search results, visit the system console and come back without issues', () => {
|
||||
// # Post a message
|
||||
cy.postMessage(MESSAGES.SMALL);
|
||||
|
||||
// # Enter the search terms and hit enter to start the search
|
||||
cy.uiGetSearchContainer().click();
|
||||
cy.uiGetSearchBox().clear().type(MESSAGES.TINY).type('{enter}');
|
||||
|
||||
// * Verify that the search results is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Search Results');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the search results is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Search Results');
|
||||
});
|
||||
|
||||
it('MM-T5317 - should be able to open thread reply, visit the system console and come back without issues', () => {
|
||||
// # Post a message
|
||||
cy.postMessage(MESSAGES.SMALL);
|
||||
|
||||
// # Click on the reply button
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
});
|
||||
|
||||
// * Verify that the thread reply is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Thread');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the thread reply is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Thread');
|
||||
});
|
||||
|
||||
it('MM-T5318 - should be able to open thread reply with CRT, visit the system console and come back without issues', () => {
|
||||
// # Enable CRT
|
||||
cy.apiSaveCRTPreference(testAdmin.id, 'on');
|
||||
|
||||
// # Refresh the page
|
||||
cy.reload();
|
||||
|
||||
// # Post a message
|
||||
cy.postMessage(MESSAGES.SMALL);
|
||||
|
||||
// # Click on the reply button
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
});
|
||||
|
||||
// * Verify that the thread reply is opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Thread');
|
||||
|
||||
// # visit the system console and leave it
|
||||
openSystemConsoleAndLeave();
|
||||
|
||||
// * Verify that the thread reply is still opened in RHS
|
||||
verifyRHSisOpenAndHasTitle('Thread');
|
||||
});
|
||||
});
|
||||
|
||||
function openSystemConsoleAndLeave() {
|
||||
// # visit the system console...
|
||||
cy.uiOpenProductMenu('System Console');
|
||||
|
||||
cy.wait(TIMEOUTS.THREE_SEC);
|
||||
|
||||
// # ...and leave it
|
||||
cy.get('.backstage-navbar__back').click();
|
||||
}
|
||||
|
||||
function verifyRHSisOpenAndHasTitle(title: string) {
|
||||
cy.get('#sidebar-right').should('exist').within(() => {
|
||||
cy.findByText(title).should('exist').and('be.visible');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('Restrict Direct Message Channels', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
let otherUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictDirectMessage: 'any',
|
||||
},
|
||||
});
|
||||
|
||||
// # Initialize setup
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create another user
|
||||
cy.apiCreateUser().then(({user}) => {
|
||||
otherUser = user;
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow direct messages between any users when RestrictDirectMessage is set to "any"', () => {
|
||||
// # Visit the town-square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open the direct messages modal
|
||||
cy.findByLabelText('DIRECT MESSAGES').parents('.SidebarChannelGroup').within(() => {
|
||||
cy.get('.SidebarChannelGroupHeader_addButton').click();
|
||||
});
|
||||
cy.get('#moreDmModal').should('exist');
|
||||
|
||||
// # Type the username of the other user in the search box
|
||||
cy.get('#selectItems input').should('be.enabled').typeWithForce(otherUser.username);
|
||||
|
||||
// * Verify that the otherUser is specifically listed in the direct messages modal
|
||||
cy.get('.more-modal__row').contains(otherUser.username).should('exist');
|
||||
});
|
||||
|
||||
it('should restrict direct messages to team members when RestrictDirectMessage is set to "team"', () => {
|
||||
// # set RestrictDirectMessage to 'team'
|
||||
cy.apiAdminLogin().then(() => {
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictDirectMessage: 'team',
|
||||
},
|
||||
}).then(() => {
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
// # Visit the town-square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open the direct messages modal
|
||||
cy.findByLabelText('DIRECT MESSAGES').parents('.SidebarChannelGroup').within(() => {
|
||||
cy.get('.SidebarChannelGroupHeader_addButton').click();
|
||||
});
|
||||
cy.get('#moreDmModal').should('exist');
|
||||
|
||||
// # Type the username of the other user in the search box
|
||||
cy.get('#selectItems input').should('be.enabled').typeWithForce(otherUser.username);
|
||||
|
||||
// * Verify that no users are listed in the direct messages modal
|
||||
cy.get('.more-modal__row').should('not.exist');
|
||||
|
||||
// # Add the other user back to the team
|
||||
cy.apiAdminLogin();
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
|
||||
// # Login as the test user again
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Visit the town-square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open the direct messages modal
|
||||
cy.findByLabelText('DIRECT MESSAGES').parents('.SidebarChannelGroup').within(() => {
|
||||
cy.get('.SidebarChannelGroupHeader_addButton').click();
|
||||
});
|
||||
cy.get('#moreDmModal').should('exist');
|
||||
|
||||
// # Type the username of the other user in the search box
|
||||
cy.get('#selectItems input').should('be.enabled').typeWithForce(otherUser.username);
|
||||
|
||||
// * Verify that the otherUser is specifically listed in the direct messages modal
|
||||
cy.get('.more-modal__row').contains(otherUser.username).should('exist');
|
||||
});
|
||||
|
||||
it('should not allow direct messages to users that no longer share a team', () => {
|
||||
// # Login as sysadmin and set RestrictDirectMessage to 'team'
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({
|
||||
TeamSettings: {
|
||||
RestrictDirectMessage: 'team',
|
||||
},
|
||||
});
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
|
||||
// # Login as the test user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Visit the town-square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open a direct message with another user
|
||||
cy.apiCreateDirectChannel([otherUser.id, testUser.id]).then((channel) => {
|
||||
// # Visit the direct message channel
|
||||
cy.visit(`/${testTeam.name}/messages/@${otherUser.username}`);
|
||||
|
||||
// # Verify that the channel is open and the user can send a message
|
||||
cy.get('.channel-header').should('exist');
|
||||
cy.get('#post_textbox').should('be.enabled');
|
||||
|
||||
// # Post a message in the direct message channel
|
||||
cy.get('#post_textbox').type('Hello, this is a test message{enter}');
|
||||
|
||||
// # Verify the message is posted
|
||||
cy.getLastPost().should('contain', 'Hello, this is a test message');
|
||||
|
||||
// # Remove the other user from the team
|
||||
cy.apiAdminLogin();
|
||||
cy.removeUserFromTeam(testTeam.id, otherUser.id);
|
||||
|
||||
// # Login as the test user again
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Check if the direct message channel is still in the sidebar
|
||||
cy.get(`#sidebarItem_${channel.channel.name}`).should('exist').click();
|
||||
|
||||
// # Verify that the channel is open and the user cannot send a message
|
||||
cy.get('#post_textbox').should('not.exist');
|
||||
cy.get('#noSharedTeamMessage').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel
|
||||
|
||||
describe('View Members modal', () => {
|
||||
it('MM-20164 - Going from a Member to an Admin should update the modal', () => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
cy.apiCreateUser().then(({user: user1}) => {
|
||||
cy.apiAddUserToTeam(team.id, user1.id);
|
||||
});
|
||||
|
||||
// # Promote user as a system admin
|
||||
// # Visit default channel and verify members modal
|
||||
cy.apiLogin(user);
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user system_admin');
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
verifyMemberDropdownAction(true);
|
||||
|
||||
// # Make user a regular member
|
||||
// # Reload and verify members modal
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user');
|
||||
cy.reload();
|
||||
verifyMemberDropdownAction(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifyMemberDropdownAction(hasActionItem: boolean) {
|
||||
// # Click member count to open member rhs
|
||||
cy.get('#member_rhs').click();
|
||||
|
||||
cy.uiGetRHS().should('be.visible').within(() => {
|
||||
// # Click "Manage"
|
||||
cy.findByText('Manage').click();
|
||||
});
|
||||
|
||||
// * Check to see any user has dropdown menu
|
||||
if (hasActionItem) {
|
||||
cy.uiGetRHS().findAllByText('Member').should('exist');
|
||||
} else {
|
||||
cy.uiGetRHS().findByText('Member').should('not.exist');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @channel
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
|
||||
const demoteToChannelMember = (user: Pick<UserProfile, 'id'>, channelId: string, admin: Pick<UserProfile, 'username' | 'password'>) => {
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'put',
|
||||
path: `channels/${channelId}/members/${user.id}/schemeRoles`,
|
||||
data: {
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const promoteToChannelAdmin = (user: Pick<UserProfile, 'id'>, channelId: string, admin: Pick<UserProfile, 'username' | 'password'>) => {
|
||||
cy.externalRequest({
|
||||
user: admin,
|
||||
method: 'put',
|
||||
path: `channels/${channelId}/members/${user.id}/schemeRoles`,
|
||||
data: {
|
||||
scheme_user: true,
|
||||
scheme_admin: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('Change Roles', () => {
|
||||
const admin = getAdminAccount();
|
||||
let testUser: UserProfile;
|
||||
let testChannelId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as test user and visit test channel
|
||||
cy.apiInitSetup().then(({team, user, channel}) => {
|
||||
testUser = user;
|
||||
testChannelId = channel.id;
|
||||
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
cy.apiAddUserToTeam(team.id, otherUser.id);
|
||||
});
|
||||
|
||||
// # Change permission so that regular users can't change channels or add members
|
||||
cy.apiGetRolesByNames(['channel_user']).then(({roles}) => {
|
||||
const role = roles[0];
|
||||
const permissions = role.permissions.filter((permission) => {
|
||||
return !(['manage_public_channel_members', 'manage_private_channel_members', 'manage_public_channel_properties', 'manage_private_channel_properties'].includes(permission));
|
||||
});
|
||||
|
||||
if (permissions.length !== role.permissions.length) {
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
}
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
|
||||
// # Make user a regular member for channel and system
|
||||
cy.externalUpdateUserRoles(user.id, 'system_user');
|
||||
demoteToChannelMember(testUser, testChannelId, admin);
|
||||
|
||||
// # Reload page to ensure no cache or saved information
|
||||
cy.reload(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T4174 User role to channel admin/member updates channel member modal immediately without refresh', () => {
|
||||
// # Go to member modal
|
||||
cy.uiGetChannelMemberButton().click();
|
||||
cy.uiGetRHS().findByText('Manage').should('not.exist');
|
||||
|
||||
// Promote user to a channel admin
|
||||
promoteToChannelAdmin(testUser, testChannelId, admin);
|
||||
|
||||
// * Check to see if a dropdown exists now
|
||||
cy.uiGetRHS().findByText('Manage').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,407 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings
|
||||
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Channel Settings', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let firstUser: Cypress.UserProfile;
|
||||
let addedUsersChannel: Cypress.Channel;
|
||||
let username: string;
|
||||
let groupId: string;
|
||||
const usernames: string[] = [];
|
||||
|
||||
const users: Cypress.UserProfile[] = [];
|
||||
|
||||
before(() => {
|
||||
// Require a license for groups
|
||||
cy.apiRequireLicense();
|
||||
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
firstUser = user;
|
||||
const teamId = testTeam.id;
|
||||
|
||||
// # Add 10 users
|
||||
for (let i = 0; i < 10; i++) {
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
users.push(newUser);
|
||||
cy.apiAddUserToTeam(teamId, newUser.id);
|
||||
});
|
||||
}
|
||||
cy.apiCreateChannel(teamId, 'channel-test', 'Channel').then(({channel}) => {
|
||||
addedUsersChannel = channel;
|
||||
});
|
||||
|
||||
// # Change permission so that regular users can't add team members
|
||||
cy.apiGetRolesByNames(['team_user']).then(({roles}) => {
|
||||
if (roles?.length) {
|
||||
const role = roles[0];
|
||||
const permissions = role.permissions.filter((permission: string) => {
|
||||
return !(['add_user_to_team'].includes(permission));
|
||||
});
|
||||
|
||||
if (permissions.length !== role.permissions.length) {
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cy.apiLogin(firstUser);
|
||||
}).then(() => {
|
||||
groupId = getRandomId();
|
||||
cy.apiCreateCustomUserGroup(`group${groupId}`, `group${groupId}`, [users[0].id, users[1].id]);
|
||||
});
|
||||
});
|
||||
|
||||
// it('MM-T859_1 Single User: Usernames are links, open profile popovers', () => {
|
||||
// // # Create and visit new channel
|
||||
// cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// // # Add users to channel
|
||||
// addNumberOfUsersToChannel(1, false);
|
||||
|
||||
// cy.getLastPostId().then((id) => {
|
||||
// // * The system message should contain 'added to the channel by you'
|
||||
// cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||
|
||||
// // # Verify username link
|
||||
// verifyMentionedUserAndProfilePopover(id);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('MM-T859_2 Combined Users: Usernames are links, open profile popovers', () => {
|
||||
// // # Create and visit new channel
|
||||
// cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel').then(({channel}) => {
|
||||
// cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// addNumberOfUsersToChannel(3, false);
|
||||
|
||||
// cy.getLastPostId().then((id) => {
|
||||
// cy.get(`#postMessageText_${id}`).should('contain', '2 others were added to the channel by you');
|
||||
|
||||
// // # Click "2 others" to expand more users
|
||||
// cy.get(`#post_${id}`).find('.markdown__paragraph-inline').siblings('a').first().click().then(() => {
|
||||
// // # Verify each username link
|
||||
// verifyMentionedUserAndProfilePopover(id);
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
it('MM-T856_1 Add existing users to public channel from drop-down > Add Members', () => {
|
||||
// # Visit the add users channel
|
||||
cy.visit(`/${testTeam.name}/channels/${addedUsersChannel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type into the input box to search for a user
|
||||
cy.get('#selectItems input').typeWithForce('user');
|
||||
|
||||
// # First add one user in order to see them disappearing from the list
|
||||
cy.get('#multiSelectList > div').not(':contains("Already in channel")').first().then((el) => {
|
||||
const childNodes = Array.from(el[0].childNodes);
|
||||
childNodes.map((child: ChildNode) => usernames.push((child as HTMLElement).innerText));
|
||||
|
||||
// # Get username from text for comparison
|
||||
username = usernames.toString().match(/\w+/g)![0];
|
||||
cy.get('#multiSelectList').should('contain', username);
|
||||
|
||||
// # Verify status wrapper is present within the modal list
|
||||
cy.get(el as unknown as string).children().first().should('have.class', 'status-wrapper');
|
||||
|
||||
// # Click to add the first user
|
||||
cy.wrap(el).click();
|
||||
|
||||
// # Verify users list is not visible
|
||||
cy.get('#multiSelectList').should('not.exist');
|
||||
|
||||
// # Click 'Add' button
|
||||
cy.uiGetButton('Add').click();
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
});
|
||||
|
||||
// # Verify that the last system post also contains the username
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).should('contain', `${username} added to the channel by you.`);
|
||||
});
|
||||
|
||||
// Add two more users
|
||||
addNumberOfUsersToChannel(2, false);
|
||||
|
||||
// Verify that the system post reflects the number of added users
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T856_2 Existing users cannot be added to public channel from drop-down > Add Members', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Visit the add users channel
|
||||
cy.visit(`/${testTeam.name}/channels/${addedUsersChannel.name}`);
|
||||
|
||||
// # Verify that the system message for adding users displays
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).should('contain', `added to the channel by @${firstUser.username}`);
|
||||
});
|
||||
|
||||
// Visit off topic where all users are added
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type into the input box to search for already added user
|
||||
cy.get('#selectItems input').typeWithForce(firstUser.username);
|
||||
|
||||
// * Verify user list exist
|
||||
cy.get('#multiSelectList').should('exist').within(() => {
|
||||
cy.findByText('Already in channel').should('be.visible');
|
||||
});
|
||||
cy.get('body').type('{esc}');
|
||||
});
|
||||
|
||||
it('Add group members to channel', () => {
|
||||
cy.apiLogin(firstUser);
|
||||
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-channel', 'New Channel').then(({channel}) => {
|
||||
// # Visit the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group'+ id created in beforeAll into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`group${groupId}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
|
||||
cy.getLastPostId().then((id) => {
|
||||
// * The system message should contain 'added to the channel by you'
|
||||
cy.get(`#postMessageText_${id}`).should('contain', 'added to the channel by you');
|
||||
|
||||
// # Verify username link
|
||||
verifyMentionedUserAndProfilePopover(id);
|
||||
});
|
||||
|
||||
// * Check that the number of channel members is 3
|
||||
cy.get('#channelMemberCountText').
|
||||
should('be.visible').
|
||||
and('have.text', '3');
|
||||
});
|
||||
});
|
||||
|
||||
it('Add group members that are not team members', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
const id = getRandomId();
|
||||
|
||||
// # Create a custom user group
|
||||
cy.apiCreateCustomUserGroup(`newgroup${id}`, `newgroup${id}`, [newUser.id]).then(() => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-group-channel', 'New Group Channel').then(({channel}) => {
|
||||
// # Visit a channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group' into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`newgroup${id}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// * Check you get a warning when adding a non team member
|
||||
cy.findByTestId('teamWarningBanner').should('contain', '1 user was not selected because they are not a part of this team');
|
||||
|
||||
// * Check the correct username is appearing in the team invite banner
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${newUser.username}`);
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Cancel').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Add group members and guests that are not team members', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
// # Create a guest user
|
||||
cy.apiCreateGuestUser({}).then(({guest}) => {
|
||||
const id = getRandomId();
|
||||
|
||||
// # Create a custom user group
|
||||
cy.apiCreateCustomUserGroup(`guestgroup${id}`, `guestgroup${id}`, [guest.id, newUser.id]).then(() => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'group-guest-channel', 'Channel').then(({channel}) => {
|
||||
// # Visit a channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group' into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`guestgroup${id}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// * Check you get a warning when adding a non team member
|
||||
cy.findByTestId('teamWarningBanner').should('contain', '2 users were not selected because they are not a part of this team');
|
||||
|
||||
// * Check the correct username is appearing in the invite to team portion
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${newUser.username}`);
|
||||
|
||||
// * Check the guest username is in the warning message and won't be added to the team
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${guest.username} is a guest user`);
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Cancel').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('User doesn\'t have permission to add user to team', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Create a new user
|
||||
cy.apiCreateUser().then(({user: newUser}) => {
|
||||
const id = getRandomId();
|
||||
|
||||
// # Create a custom user group
|
||||
cy.apiCreateCustomUserGroup(`newgroup${id}`, `newgroup${id}`, [newUser.id]).then(() => {
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, 'new-group-channel', 'Channel').then(({channel}) => {
|
||||
cy.apiLogin(firstUser);
|
||||
|
||||
// # Visit a channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// * Assert that modal appears
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// # Type 'group' into the input box
|
||||
cy.get('#selectItems input').typeWithForce(`newgroup${id}`);
|
||||
|
||||
// # Click the first row for a number of times
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
|
||||
// * Check you get a warning when adding a non team member
|
||||
cy.findByTestId('teamWarningBanner').should('contain', '1 user was not selected because they are not a part of this team');
|
||||
|
||||
// * Check the correct username is appearing in the team invite banner
|
||||
cy.findByTestId('teamWarningBanner').should('contain', `@${newUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifyMentionedUserAndProfilePopover(postId: string) {
|
||||
cy.get(`#post_${postId}`).find('.mention-link').each(($el) => {
|
||||
// # Get username from each mentioned link
|
||||
const userName = $el[0].innerHTML;
|
||||
|
||||
// # Click each username link
|
||||
cy.wrap($el).click();
|
||||
|
||||
// * Profile popover should be visible
|
||||
cy.get('div.user-profile-popover').should('be.visible');
|
||||
|
||||
// * The username in the popover the same as the username link for each user
|
||||
cy.get('div.user-profile-popover').should('contain', userName);
|
||||
cy.get('button.closeButtonRelativePosition').click();
|
||||
|
||||
// Click anywhere to close profile popover
|
||||
cy.get('#channelHeaderInfo').click();
|
||||
});
|
||||
}
|
||||
|
||||
function addNumberOfUsersToChannel(num = 1, allowExisting = false) {
|
||||
// # Open channel menu and click 'Add Members'
|
||||
cy.uiOpenChannelMenu('Members');
|
||||
cy.uiGetButton('Add').click();
|
||||
cy.get('#addUsersToChannelModal').should('be.visible');
|
||||
|
||||
// * Assert that modal appears
|
||||
// # Click the first row for a number of times
|
||||
Cypress._.times(num, () => {
|
||||
cy.get('#selectItems input').typeWithForce('user');
|
||||
|
||||
// cy.get('#multiSelectList').should('be.visible').first().click();
|
||||
if (allowExisting) {
|
||||
cy.get('#multiSelectList').should('exist').children().first().click();
|
||||
} else {
|
||||
cy.get('#multiSelectList').should('exist').children().not(':contains("Already in channel")').first().click();
|
||||
}
|
||||
});
|
||||
|
||||
// # Click the button "Add" to add user to a channel
|
||||
cy.uiGetButton('Add').click();
|
||||
|
||||
// # Wait for the modal to disappear
|
||||
cy.get('#addUsersToChannelModal').should('not.exist');
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_settings
|
||||
// node run_tests.js --group='@channel_settings'
|
||||
|
||||
import type {ChannelType} from '@mattermost/types/channels';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Channel Settings', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let user1: Cypress.UserProfile;
|
||||
let admin: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetMe().then(({user: adminUser}) => {
|
||||
admin = adminUser;
|
||||
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
user1 = user;
|
||||
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T1808 Hover effect exists to add a channel description / header (when not already present)', () => {
|
||||
// # Create a new public channel and then private channel
|
||||
['O', 'P'].forEach((channelType) => {
|
||||
cy.apiCreateChannel(testTeam.id, `chan${getRandomId()}`, 'chan', channelType as ChannelType).then(({channel}) => {
|
||||
// # Go to new channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Test hovering over the header with public and private channel
|
||||
hoverOnChannelDescriptionAndVerifyBehavior();
|
||||
});
|
||||
});
|
||||
|
||||
// # Create DM with admin and user 1
|
||||
cy.apiCreateDirectChannel([admin.id, user1.id]).then(() => {
|
||||
// # Go to DM
|
||||
cy.visit(`/${testTeam.name}/messages/@${user1.username}`);
|
||||
|
||||
// * Test hovering over the header with DM
|
||||
hoverOnChannelDescriptionAndVerifyBehavior();
|
||||
});
|
||||
|
||||
// # Create another user and add to the team
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, user2.id).then(() => {
|
||||
// # Create a GM with admin, user1 and user 2
|
||||
cy.apiCreateGroupChannel([user2.id, user1.id, admin.id]).then(({channel}) => {
|
||||
// # Visit the channel using the name using the channels route
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Test hovering over the header with GM
|
||||
hoverOnChannelDescriptionAndVerifyBehavior();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function hoverOnChannelDescriptionAndVerifyBehavior() {
|
||||
const channelDescriptionText = `test description ${getRandomId()}`;
|
||||
|
||||
// # Wait a little for channel to load
|
||||
cy.wait(TIMEOUTS.ONE_SEC);
|
||||
|
||||
// # Scan within channel header description area
|
||||
cy.get('#channelHeaderDescription').should('be.visible').find('span').invoke('show').click({multiple: true, force: true});
|
||||
|
||||
// # Scan inside the channel header modal
|
||||
cy.get('.a11y__modal.modal-dialog').should('be.visible').within(() => {
|
||||
// # Enter a channel description
|
||||
cy.findByTestId('edit_textbox').should('exist').clear().type(channelDescriptionText);
|
||||
|
||||
// # Click on save
|
||||
cy.findByText('Save').should('be.visible').click();
|
||||
});
|
||||
|
||||
cy.get('#channelHeaderDescription').should('be.visible').within(() => {
|
||||
// * Verify that new channel header is set and click it
|
||||
cy.findAllByText(channelDescriptionText).should('be.visible').click({multiple: true, force: true});
|
||||
});
|
||||
|
||||
// * Check clicking on it doesn't open the edit modal once again
|
||||
cy.get('.a11y__modal.modal-dialog').should('not.exist');
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel @channel_settings
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Channel routing', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as test user and go to town square
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T884_1 Renaming channel name validates against two user IDs being used in URL', () => {
|
||||
// # Create new test channel
|
||||
cy.uiCreateChannel({name: 'Test__Channel'});
|
||||
|
||||
// # Open channel settings modal from channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Assert if the channel settings modal is present
|
||||
cy.get('.ChannelSettingsModal').should('be.visible');
|
||||
|
||||
// # Click on the URL input button to edit the URL
|
||||
cy.get('.url-input-button').should('be.visible').click({force: true});
|
||||
|
||||
// # Type the two 26 character strings with 2 underscores between them
|
||||
cy.get('.url-input-container input').clear().type('uzsfmtmniifsjgesce4u7yznyh__uzsfmtmniifsjgesce5u7yznyh', {force: true}).wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Assert the error occurred with the appropriate message
|
||||
cy.get('.SaveChangesPanel').should('contain', 'There are errors in the form above');
|
||||
cy.get('.url-input-error').should('contain', 'User IDs are not allowed in channel URLs.');
|
||||
|
||||
// # Reset the changes so modal can be closed
|
||||
cy.get('[data-testid="SaveChangesPanel__cancel-btn"]').click();
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
});
|
||||
|
||||
it('MM-T884_2 Creating new channel validates against two user IDs being used as channel name', () => {
|
||||
// # click on create public channel
|
||||
cy.uiBrowseOrCreateChannel('Create new channel');
|
||||
|
||||
// * Verify that the new channel modal is visible
|
||||
cy.get('#new-channel-modal').should('be.visible').within(() => {
|
||||
// # Add the new channel name with invalid name and press Create Channel
|
||||
cy.get('#input_new-channel-modal-name').type('uzsfmtmniifsjgesce4u7yznyh__uzsfmtmniifsjgesce5u7yznyh', {force: true}).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.findByText('Create channel').should('be.visible').click();
|
||||
|
||||
// * Assert the error occurred with the appropriate message
|
||||
cy.get('.genericModalError').should('be.visible').within(() => {
|
||||
cy.findByText('Channel names can\'t be in a hexadecimal format. Please enter a different channel name.');
|
||||
});
|
||||
|
||||
// # Close the create channel modal
|
||||
cy.uiCancelButton().click();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T884_3 Creating a new channel validates against gm-like names being used as channel name', () => {
|
||||
// # click on create public channel
|
||||
cy.uiBrowseOrCreateChannel('Create new channel');
|
||||
|
||||
// * Verify that the new channel modal is visible
|
||||
cy.findByRole('dialog', {name: 'Create a new channel'}).within(() => {
|
||||
// # Add the new channel name with invalid name and press Create Channel
|
||||
cy.findByPlaceholderText('Enter a name for your new channel').type('71b03afcbb2d503d49f87f057549c43db4e19f92', {force: true}).wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetButton('Create channel').click();
|
||||
|
||||
// * Assert the error occurred with the appropriate message
|
||||
cy.get('.genericModalError').should('be.visible').within(() => {
|
||||
cy.findByText('Channel names can\'t be in a hexadecimal format. Please enter a different channel name.');
|
||||
});
|
||||
|
||||
// # Close the create channel modal
|
||||
cy.uiCancelButton().click();
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T883 Channel URL validation for spaces between characters', () => {
|
||||
const firstWord = getRandomId(26);
|
||||
const secondWord = getRandomId(26);
|
||||
const channelName = 'test-channel-' + getRandomId(8);
|
||||
|
||||
// # Create a new test channel and navigate to it
|
||||
cy.apiCreateChannel(testTeam.id, channelName, 'Test Channel for URL Validation').then(({channel}) => {
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
});
|
||||
|
||||
// # Open channel settings modal from channel header dropdown
|
||||
cy.get('#channelHeaderDropdownButton').click();
|
||||
cy.findByText('Channel Settings').click();
|
||||
|
||||
// # Change the channel name to {26 alphanumeric characters}[insert 2 spaces]{26 alphanumeric characters}
|
||||
// i.e. a total of 54 characters separated by 2 spaces
|
||||
cy.get('#input_channel-settings-name').clear().type(`${firstWord}${Cypress._.repeat(' ', 2)}${secondWord}`);
|
||||
|
||||
// # Since channel name no longer auto-updates URL, manually set the URL to test the space handling
|
||||
cy.get('.url-input-button').click();
|
||||
cy.get('.url-input-container input').clear().type(`${firstWord}${Cypress._.repeat(' ', 2)}${secondWord}`);
|
||||
cy.get('.url-input-container button.url-input-button').click();
|
||||
|
||||
// # Save changes
|
||||
cy.get('[data-testid="SaveChangesPanel__save-btn"]').click();
|
||||
|
||||
// * Verify changes are saved
|
||||
cy.get('.SaveChangesPanel').should('contain', 'Settings saved');
|
||||
|
||||
// # Close the modal
|
||||
cy.get('.GenericModal .modal-header button[aria-label="Close"]').click();
|
||||
|
||||
// * The channel name should be updated to the characters you typed with only 1 space between the characters (extra spaces are trimmed)
|
||||
cy.get('#channelHeaderTitle').contains(`${firstWord} ${secondWord}`);
|
||||
|
||||
// * The channel URL should be updated to the characters you typed, separated by 2 dashes
|
||||
cy.url().should('equal', `${Cypress.config('baseUrl')}/${testTeam.name}/channels/${firstWord}${Cypress._.repeat('-', 2)}${secondWord}`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_settings
|
||||
|
||||
describe('Channel settings', () => {
|
||||
let mainUser: Cypress.UserProfile;
|
||||
let otherUser: Cypress.UserProfile;
|
||||
let myTeam: Cypress.Team;
|
||||
|
||||
// # Ensure a list of channel names that will be alphabetically sorted
|
||||
const channelNames = new Array(20).fill(1).map((value, index) => `scroll${index}`);
|
||||
|
||||
before(() => {
|
||||
// # Create a user and a team (done by apiInitSetup)
|
||||
cy.apiInitSetup().then(({team, user: firstUser}) => {
|
||||
mainUser = firstUser;
|
||||
myTeam = team;
|
||||
|
||||
// # Create another user and add it to the same team
|
||||
cy.apiCreateUser().then(({user: secondUser}) => {
|
||||
otherUser = secondUser;
|
||||
cy.apiAddUserToTeam(team.id, secondUser.id);
|
||||
});
|
||||
|
||||
// # Create 20 channels (based on length of channelNames array) to ensure that the channels list is scrollable
|
||||
cy.wrap(channelNames).each((name) => {
|
||||
const displayName = `channel-${name}`;
|
||||
cy.apiCreateChannel(team.id, name.toString(), displayName, 'O', '', '', false).then(({channel}) => {
|
||||
// # Add our 2 created users to each channel so they can both post messages
|
||||
cy.apiAddUserToChannel(channel.id, mainUser.id);
|
||||
cy.apiAddUserToChannel(channel.id, otherUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T888 Channel sidebar: More unreads', () => {
|
||||
const firstChannelIndex = 0;
|
||||
const lastChannelIndex = channelNames.length - 1;
|
||||
|
||||
// # Navigate to off-topic channel
|
||||
cy.apiLogin(mainUser);
|
||||
cy.visit(`/${myTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Wait for channels to load
|
||||
cy.get(`#sidebarItem_${channelNames[firstChannelIndex]}`).should('be.visible');
|
||||
|
||||
// # Nudge the scrollbar slightly to make the "More Unreads" pills appear
|
||||
// @hmhealey - They seem to appear automatically on a regular browser, but not when running under Cypress
|
||||
cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo(0, 1);
|
||||
|
||||
// * The bottom "More Unreads" pill should be visible and the top one should not
|
||||
cy.get('#unreadIndicatorTop').should('not.be.visible');
|
||||
cy.get('#unreadIndicatorBottom').should('be.visible');
|
||||
|
||||
// # Click on the "More Unreads" pill to scroll down to the bottom
|
||||
cy.get('#unreadIndicatorBottom').click();
|
||||
|
||||
// * The list should have scrolled down to the bottom
|
||||
cy.get(`#sidebarItem_${channelNames[firstChannelIndex]}`).should('not.be.visible');
|
||||
cy.get(`#sidebarItem_${channelNames[lastChannelIndex]}`).should('be.visible');
|
||||
|
||||
// * The top "More Unreads" pill should now be visible and the bottom one should not
|
||||
cy.get('#unreadIndicatorTop').should('be.visible');
|
||||
cy.get('#unreadIndicatorBottom').should('not.be.visible');
|
||||
|
||||
// * The "More Unreads" pill should now be visible at the top of the channels list
|
||||
// # Click on the "More Unreads" pill to scroll up to the top
|
||||
cy.get('#unreadIndicatorTop').click();
|
||||
|
||||
// * The list should have scrolled up to the top
|
||||
cy.get(`#sidebarItem_${channelNames[firstChannelIndex]}`).should('be.visible');
|
||||
cy.get(`#sidebarItem_${channelNames[lastChannelIndex]}`).should('not.be.visible');
|
||||
|
||||
// # Scroll somewhere to the middle of the list
|
||||
cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo(0, 200);
|
||||
|
||||
// * Both "More Unreads" pills should now be visible
|
||||
cy.get('#unreadIndicatorTop').should('be.visible');
|
||||
cy.get('#unreadIndicatorBottom').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_sidebar
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import * as MESSAGES from '@/fixtures/messages';
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
|
||||
describe('Channel sidebar', () => {
|
||||
const sysadmin = getAdminAccount();
|
||||
|
||||
let testTeam: Cypress.Team;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Start with a new team
|
||||
cy.apiAdminLogin();
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, townSquareUrl}) => {
|
||||
testTeam = team;
|
||||
|
||||
cy.visit(townSquareUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display collapsed state when collapsed', () => {
|
||||
// # Check that the CHANNELS group header is visible
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible').as('channelsGroup');
|
||||
|
||||
// * Verify that the category doesn't appear collapsed currently
|
||||
cy.get('@channelsGroup').find('i').should('not.have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// # Click on CHANNELS
|
||||
cy.get('@channelsGroup').click();
|
||||
|
||||
// * Verify that the category now appears collapsed
|
||||
cy.get('@channelsGroup').find('i').should('have.class', 'icon-rotate-minus-90');
|
||||
});
|
||||
|
||||
it('should collapse channels that are not the currently viewed channel', () => {
|
||||
// # Check that the CHANNELS group header is visible
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible').as('channelsGroup');
|
||||
|
||||
// * Verify that both channels are visible when not collapsed
|
||||
cy.get('.SidebarChannelGroup').should('be.visible').as('sidebarChannelGroup');
|
||||
cy.get('@sidebarChannelGroup').findByText('Town Square').should('exist').and('be.visible');
|
||||
cy.get('@sidebarChannelGroup').findByText('Off-Topic').should('be.visible');
|
||||
|
||||
// # Click on CHANNELS
|
||||
cy.get('@channelsGroup').click();
|
||||
|
||||
// * Verify that both channels are visible when not collapsed
|
||||
cy.get('.SidebarChannelGroup').should('be.visible').as('sidebarChannelGroup');
|
||||
cy.get('@sidebarChannelGroup').findByText('Town Square').should('be.visible');
|
||||
cy.get('@sidebarChannelGroup').findByText('Off-Topic').should('not.exist');
|
||||
});
|
||||
|
||||
it('should collapse channels that are not unread channels', () => {
|
||||
const uniqueChannelName = MESSAGES.TINY;
|
||||
|
||||
// Create a new channel and post a message into it
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', uniqueChannelName, 'O', '', '', false).then(({channel}) => {
|
||||
cy.postMessageAs({sender: sysadmin, message: MESSAGES.SMALL, channelId: channel.id});
|
||||
|
||||
// Force a reload to ensure the unread message displays
|
||||
cy.reload();
|
||||
cy.uiGetPostTextBox();
|
||||
|
||||
// # Check that the CHANNELS group header is visible
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible').as('channelsGroup');
|
||||
|
||||
// * Verify that all channels are visible
|
||||
cy.get('.SidebarChannelGroup').should('be.visible').as('sidebarChannelGroup');
|
||||
cy.get('.SidebarChannelGroup').findByText('Off-Topic').should('exist').and('be.visible');
|
||||
cy.get('.SidebarChannelGroup').findByText(uniqueChannelName).should('exist').and('be.visible');
|
||||
|
||||
// # Click on CHANNELS
|
||||
cy.get('@channelsGroup').click();
|
||||
|
||||
// * Verify that Off-Topic is no longer visible but Channel Test still is
|
||||
cy.get('.SidebarChannelGroup').should('be.visible').as('sidebarChannelGroup');
|
||||
cy.get('@sidebarChannelGroup').findByText('Off-Topic').should('not.exist');
|
||||
cy.get('@sidebarChannelGroup').findByText(uniqueChannelName).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should save collapsed state and remember the state on refresh', () => {
|
||||
// # Check that the CHANNELS group header is visible
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible');
|
||||
|
||||
// * Verify that the category doesn't appear collapsed currently
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('be.visible').should('not.have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// # Click on CHANNELS
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible').click();
|
||||
|
||||
// * Verify that the category appears collapsed
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// Wait for state to settle
|
||||
// This is necessary since we have no observable way of finding out when the state actually settles so that it persists on reload
|
||||
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// # Reload the page and wait
|
||||
cy.reload();
|
||||
cy.uiGetPostTextBox();
|
||||
|
||||
// * Verify that the category still appears collapsed after refresh
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// # Click on CHANNELS
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').click();
|
||||
|
||||
// * Verify that the category appears not collapsed after refresh
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('not.have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// Wait for state to settle
|
||||
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// # Reload the page and wait
|
||||
cy.reload();
|
||||
cy.uiGetPostTextBox();
|
||||
|
||||
// * Verify that the category still appears not collapsed after refresh
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('not.have.class', 'icon-rotate-minus-90');
|
||||
});
|
||||
|
||||
it('should retain the collapsed state of categories when unread filter is enabled/disabled', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'channel-test', 'Channel Test').then(({channel}) => {
|
||||
cy.postMessageAs({sender: sysadmin, message: 'Test', channelId: channel.id});
|
||||
|
||||
// * Verify that CHANNELS starts expanded
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('not.have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// * Verify that all categories are visible
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible');
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('be.visible').should('not.have.class', 'icon-rotate-minus-90');
|
||||
cy.get('.SidebarChannelGroupHeader:contains(DIRECT MESSAGES)').should('be.visible');
|
||||
cy.get('.SidebarChannelGroupHeader:contains(DIRECT MESSAGES) i').should('be.visible').should('not.have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// # Collapse CHANNELS
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').click();
|
||||
|
||||
// * Verify that CHANNELS is collapsed
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('have.class', 'icon-rotate-minus-90');
|
||||
|
||||
// # Enable the unread filter
|
||||
cy.get('.SidebarFilters_filterButton').click();
|
||||
|
||||
// * Verify that the unread filter is enabled
|
||||
cy.get('.SidebarChannelGroupHeader:contains(UNREADS)').should('be.visible');
|
||||
|
||||
// # Disable the unread filter
|
||||
cy.get('.SidebarFilters_filterButton').click();
|
||||
|
||||
// * Verify that DIRECT MESSAGES is not collapsed but CHANNELS still is
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS) i').should('be.visible').should('have.class', 'icon-rotate-minus-90');
|
||||
cy.get('.SidebarChannelGroupHeader:contains(DIRECT MESSAGES) i').should('be.visible').should('not.have.class', 'icon-rotate-minus-90');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_sidebar
|
||||
|
||||
import {clickCategoryMenuItem} from './helpers';
|
||||
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
describe('Category muting', () => {
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({loginAfter: true}).then((({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.visit(`/${team.name}/channels/off-topic`);
|
||||
|
||||
// # Post any message
|
||||
cy.postMessage('hello');
|
||||
}));
|
||||
});
|
||||
|
||||
it('MM-T3488 category headers should be muted and unmuted correctly', () => {
|
||||
// * Verify that the Channels category and its channels start unmuted
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible').should('not.have.class', 'muted');
|
||||
cy.get('#sidebarItem_town-square').should('not.have.class', 'muted');
|
||||
cy.get('#sidebarItem_off-topic').should('not.have.class', 'muted');
|
||||
|
||||
// # Mute the category
|
||||
clickCategoryMenuItem({categoryDisplayName: 'CHANNELS', menuItemText: 'Mute Category', categoryMenuButtonName: 'Channels'});
|
||||
|
||||
// * Verify that the category has been muted
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('have.class', 'muted');
|
||||
cy.get('#sidebarItem_town-square').should('have.class', 'muted');
|
||||
cy.get('#sidebarItem_off-topic').should('have.class', 'muted');
|
||||
|
||||
// # Unmute the category
|
||||
clickCategoryMenuItem({categoryDisplayName: 'CHANNELS', menuItemText: 'Unmute Category', categoryMenuButtonName: 'Channels'});
|
||||
|
||||
// * Verify that the category is no longer muted
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('not.have.class', 'muted');
|
||||
cy.get('#sidebarItem_town-square').should('not.have.class', 'muted');
|
||||
cy.get('#sidebarItem_off-topic').should('not.have.class', 'muted');
|
||||
});
|
||||
|
||||
it('MM-T3489_1 moving a channel into a muted category should mute it', () => {
|
||||
// # Create a new category
|
||||
cy.uiCreateSidebarCategory().then((category) => {
|
||||
// # Mute the new category
|
||||
clickCategoryMenuItem({categoryDisplayName: category.displayName, menuItemText: 'Mute Category', categoryMenuButtonName: category.displayName.toLowerCase()});
|
||||
|
||||
// * Verify that Town Square starts unmuted
|
||||
cy.get('#sidebarItem_town-square').should('not.have.class', 'muted');
|
||||
|
||||
// * Verify that the new category is muted
|
||||
cy.get(`.SidebarChannelGroupHeader:contains(${category.displayName})`).should('have.class', 'muted');
|
||||
|
||||
// # Move Town Square into the custom category
|
||||
cy.uiMoveChannelToCategory('Town Square', category.displayName);
|
||||
|
||||
// * Verify that Town Square is now muted
|
||||
cy.get('#sidebarItem_town-square').should('have.class', 'muted');
|
||||
|
||||
// # Move Town Square back to Channels
|
||||
cy.uiMoveChannelToCategory('Town Square', 'Channels');
|
||||
|
||||
// * Verify that Town Square is now unmuted
|
||||
cy.get('#sidebarItem_town-square').should('not.have.class', 'muted');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3489_2 being added to a new channel should not mute it, even if the Channels category is muted', () => {
|
||||
// * Verify that the Channels category starts unmuted
|
||||
cy.get('.SidebarChannelGroupHeader:contains(CHANNELS)').should('be.visible').should('not.have.class', 'muted');
|
||||
|
||||
// # Mute Channels
|
||||
clickCategoryMenuItem({categoryDisplayName: 'CHANNELS', menuItemText: 'Mute Category', categoryMenuButtonName: 'Channels'});
|
||||
|
||||
cy.makeClient({user: getAdminAccount()}).then((client) => {
|
||||
// # Have another user create a channel
|
||||
const channelName = `channel${getRandomId()}`;
|
||||
cy.wrap(client.createChannel({
|
||||
display_name: channelName,
|
||||
name: channelName,
|
||||
team_id: testTeam.id,
|
||||
type: 'O',
|
||||
create_at: 0,
|
||||
creator_id: '',
|
||||
delete_at: 0,
|
||||
group_constrained: false,
|
||||
header: '',
|
||||
id: '',
|
||||
last_post_at: 0,
|
||||
purpose: '',
|
||||
scheme_id: '',
|
||||
update_at: 0,
|
||||
last_root_post_at: 0,
|
||||
})).then((channel: unknown) => {
|
||||
const ch = channel as Cypress.Channel;
|
||||
// # And then invite us to it
|
||||
cy.wrap(client.addToChannel(testUser.id, ch.id));
|
||||
|
||||
// * Verify that the test channel appears in the sidebar and is unmuted
|
||||
cy.get(`#sidebarItem_${ch.name}`).should('be.visible').should('not.have.class', 'muted');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_sidebar
|
||||
|
||||
import {clickSortCategoryMenuItem} from './helpers';
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
describe('Category sorting', () => {
|
||||
beforeEach(() => {
|
||||
// # Login as test user and visit town-square
|
||||
cy.apiAdminLogin();
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3834 Category sorting -- KNOWN ISSUE: MM-43576', () => {
|
||||
const channelNames = [];
|
||||
const categoryName = createCategoryFromSidebarMenu();
|
||||
|
||||
// # Create 5 channels and add them to a custom category
|
||||
for (let i = 0; i < 5; i++) {
|
||||
channelNames.push(createChannelAndAddToCategory(categoryName));
|
||||
cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo('bottom', {ensureScrollable: false});
|
||||
}
|
||||
|
||||
// # Sort alphabetically
|
||||
clickSortCategoryMenuItem(categoryName, 'Alphabetically');
|
||||
|
||||
// * Verify channels are sorted alphabetically
|
||||
verifyAlphabeticalSortingOrder(categoryName, channelNames.length);
|
||||
|
||||
// # Add another channel
|
||||
channelNames.push(createChannelAndAddToCategory(categoryName));
|
||||
cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo('bottom', {ensureScrollable: false});
|
||||
|
||||
// * Verify channels are still sorted alphabetically
|
||||
verifyAlphabeticalSortingOrder(categoryName, channelNames.length);
|
||||
|
||||
// # Sort by recency
|
||||
clickSortCategoryMenuItem(categoryName, 'Recent activity');
|
||||
|
||||
// # Sort channel names in reverse order that they were created (ie. most recent to least)
|
||||
let sortedByRecencyChannelNames = channelNames.concat().reverse();
|
||||
for (let i = 0; i < channelNames.length; i++) {
|
||||
// * Verify that the channels are in reverse order that they were created
|
||||
cy.get(`.SidebarChannelGroup:contains(${categoryName}) .NavGroupContent li:nth-child(${i + 1}) a[id^="sidebarItem_${sortedByRecencyChannelNames[i]}"]`).should('be.visible');
|
||||
}
|
||||
|
||||
// # Add another channel
|
||||
channelNames.push(createChannelAndAddToCategory(categoryName));
|
||||
cy.get('#SidebarContainer .simplebar-content').scrollTo('bottom', {ensureScrollable: false});
|
||||
|
||||
// # Sort channel names in reverse order that they were created (ie. most recent to least)
|
||||
sortedByRecencyChannelNames = channelNames.concat().reverse();
|
||||
for (let i = 0; i < channelNames.length; i++) {
|
||||
// * Verify that the channels are still in reverse order that they were created
|
||||
cy.get(`.SidebarChannelGroup:contains(${categoryName}) .NavGroupContent li:nth-child(${i + 1}) a[id^="sidebarItem_${sortedByRecencyChannelNames[i]}"]`).should('be.visible');
|
||||
}
|
||||
|
||||
// # Click on channel options of the oldest from the category
|
||||
cy.get(`.SidebarChannelGroup:contains(${categoryName}) .NavGroupContent a[id^="sidebarItem_${channelNames[0]}"]`).should('be.visible').within(() => {
|
||||
// # Open dropdown next to channel name
|
||||
cy.get('.SidebarMenu').invoke('show').get('.SidebarMenu_menuButton').should('be.visible').click({force: true});
|
||||
});
|
||||
|
||||
// # Open the channel menu, select Move to
|
||||
cy.findAllByRole('menu', {name: 'Edit channel menu'}).should('be.visible').within(() => {
|
||||
cy.findByText('Move to...').should('be.visible').trigger('mouseover');
|
||||
});
|
||||
|
||||
// # Click on move to favorites
|
||||
cy.findAllByRole('menu', {name: 'Move to submenu'}).should('be.visible').within(() => {
|
||||
cy.findByText('Favorites').should('be.visible').click({force: true});
|
||||
});
|
||||
|
||||
// * Verify the channel is now in Favourites
|
||||
cy.get(`.SidebarChannelGroup:contains(FAVORITES) .NavGroupContent a[id^="sidebarItem_${channelNames[0]}"]`).should('be.visible');
|
||||
channelNames.shift();
|
||||
|
||||
// # Sort manually
|
||||
clickSortCategoryMenuItem(categoryName, 'Manually');
|
||||
|
||||
// # Add another channel
|
||||
channelNames.push(createChannelAndAddToCategory(categoryName));
|
||||
cy.get('#SidebarContainer .simplebar-content').scrollTo('bottom', {ensureScrollable: false});
|
||||
|
||||
// * Verify that the channel has been placed at the bottom of the category
|
||||
cy.get(`.SidebarChannelGroup:contains(${categoryName}) .NavGroupContent li:nth-child(1) a[id^="sidebarItem_${channelNames[channelNames.length - 1]}"]`).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
function createChannelAndAddToCategory(categoryName: string) {
|
||||
const channelName = `channel-${getRandomId()}`;
|
||||
const userId = testUser.id;
|
||||
cy.apiCreateChannel(testTeam.id, channelName, 'New Test Channel').then(({channel}) => {
|
||||
// # Add the user to the channel
|
||||
cy.apiAddUserToChannel(channel.id, userId).then(() => {
|
||||
// # Move to a new category
|
||||
cy.uiMoveChannelToCategory(channel.display_name, categoryName);
|
||||
});
|
||||
});
|
||||
return channelName;
|
||||
}
|
||||
|
||||
function verifyAlphabeticalSortingOrder(categoryName: string, length: number) {
|
||||
// # Go through each channel to get its name
|
||||
const sortedAlphabeticalChannelNames: string[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
// Grab the elements in the order that they appear in the sidebar
|
||||
cy.get(`.SidebarChannelGroup:contains(${categoryName}) .NavGroupContent li:nth-child(${i + 1}) .SidebarChannelLinkLabel`).should('be.visible').invoke('text').then((text) => {
|
||||
sortedAlphabeticalChannelNames.push(text);
|
||||
|
||||
// # Sort the names manually
|
||||
const comparedSortedChannelNames = sortedAlphabeticalChannelNames.concat().sort((a, b) => a.localeCompare(b, 'en', {numeric: true}));
|
||||
|
||||
// * Verify that the sorted order matches the order they were already in
|
||||
assert.deepEqual(sortedAlphabeticalChannelNames, comparedSortedChannelNames);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createCategoryFromSidebarMenu() {
|
||||
// # Start with a new category
|
||||
const categoryName = `category-${getRandomId()}`;
|
||||
|
||||
// # Click on the sidebar menu dropdown and select Create Category
|
||||
cy.uiBrowseOrCreateChannel('Create new category');
|
||||
|
||||
// # Verify that Create Category modal has shown up.
|
||||
// # Wait for a while until the modal has fully loaded, especially during first-time access.
|
||||
cy.get('#editCategoryModal').should('be.visible').wait(TIMEOUTS.HALF_SEC).within(() => {
|
||||
cy.findByText('Create New Category').should('be.visible');
|
||||
|
||||
// # Enter category name and hit enter
|
||||
cy.findByPlaceholderText('Name your category').should('be.visible').type(categoryName).type('{enter}');
|
||||
});
|
||||
|
||||
return categoryName;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_sidebar
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
|
||||
describe('Category sorting', () => {
|
||||
beforeEach(() => {
|
||||
// # Login as test user and visit town-square
|
||||
cy.apiAdminLogin();
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team}) => {
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T3916 Create Category character limit', () => {
|
||||
// # Click on the sidebar menu dropdown and select Create Category
|
||||
cy.uiBrowseOrCreateChannel('Create new category');
|
||||
|
||||
// # Add a name 26 characters in length e.g `abcdefghijklmnopqrstuvwxyz`
|
||||
cy.get('#editCategoryModal').should('be.visible').wait(TIMEOUTS.HALF_SEC).within(() => {
|
||||
cy.findByText('Create New Category').should('be.visible');
|
||||
|
||||
// # Enter category name
|
||||
cy.findByPlaceholderText('Name your category').should('be.visible').type('abcdefghijklmnopqrstuvwxyz');
|
||||
});
|
||||
|
||||
// * Verify error state and negative character count at the end of the textbox based on the number of characters the user has exceeded
|
||||
cy.get('#editCategoryModal .MaxLengthInput.has-error').should('be.visible');
|
||||
cy.get('#editCategoryModal .MaxLengthInput__validation').should('be.visible').should('contain', '-4');
|
||||
|
||||
// * Verify Create button is disabled.
|
||||
cy.get('#editCategoryModal .GenericModal__button.confirm').should('be.visible').should('be.disabled');
|
||||
|
||||
// # Use backspace to remove 4 characters
|
||||
cy.get('#editCategoryModal .MaxLengthInput').should('be.visible').type('{backspace}{backspace}{backspace}{backspace}');
|
||||
|
||||
// * Verify error state and negative character count at the end of the textbox are no longer displaying
|
||||
cy.get('#editCategoryModal .MaxLengthInput.has-error').should('not.exist');
|
||||
cy.get('#editCategoryModal .MaxLengthInput__validation').should('not.exist');
|
||||
|
||||
// * Verify Create button is enabled
|
||||
cy.get('#editCategoryModal .GenericModal__button.confirm').should('be.visible').should('not.be.disabled');
|
||||
|
||||
// Click Create
|
||||
cy.get('#editCategoryModal .GenericModal__button.confirm').should('be.visible').click();
|
||||
|
||||
// Verify new category is created
|
||||
cy.findByLabelText('abcdefghijklmnopqrstuv').should('be.visible');
|
||||
});
|
||||
|
||||
// // Commented out since we've had to disable sticky headings because of issues with the menu components
|
||||
// it('MM-T3864 Sticky category headers', () => {
|
||||
// const categoryName = createCategoryFromSidebarMenu();
|
||||
|
||||
// // # Move test channel to Favourites
|
||||
// cy.get(`#sidebarItem_${testChannel.name}`).parent().then((element) => {
|
||||
// // # Get id of the channel
|
||||
// const id = element[0].getAttribute('data-rfd-draggable-id');
|
||||
// cy.get(`#sidebarItem_${testChannel.name}`).parent('li').within(() => {
|
||||
// // # Open dropdown next to channel name
|
||||
// cy.get('.SidebarMenu').invoke('show').get('.SidebarMenu_menuButton').should('be.visible').click({force: true});
|
||||
|
||||
// // # Favourite the channel
|
||||
// cy.get(`#favorite-${id} button`).should('be.visible').click({force: true});
|
||||
// });
|
||||
// });
|
||||
|
||||
// // # Create 15 channels and add them to a custom category
|
||||
// for (let i = 0; i < 15; i++) {
|
||||
// createChannelAndAddToCategory(categoryName);
|
||||
// cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo('bottom', {ensureScrollable: false});
|
||||
// }
|
||||
|
||||
// // # Create 10 channels and add them to Favourites
|
||||
// for (let i = 0; i < 10; i++) {
|
||||
// createChannelAndAddToFavourites();
|
||||
// cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo('bottom', {ensureScrollable: false});
|
||||
// }
|
||||
|
||||
// // # Scroll to the center of the channel list
|
||||
// cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo('center', {ensureScrollable: false});
|
||||
|
||||
// // * Verify that both the 'More Unreads' label and the category header are visible
|
||||
// cy.get('#unreadIndicatorTop').should('be.visible');
|
||||
// cy.get('#SidebarContainer .SidebarChannelGroupHeader:contains(FAVORITES)').should('be.visible');
|
||||
|
||||
// // # Scroll to the bottom of the list
|
||||
// cy.get('#SidebarContainer .simplebar-content-wrapper').scrollTo('bottom', {ensureScrollable: false});
|
||||
|
||||
// // * Verify that the 'More Unreads' label is still visible but the category is not
|
||||
// cy.get('#unreadIndicatorTop').should('be.visible');
|
||||
// cy.get('#SidebarContainer .SidebarChannelGroupHeader:contains(FAVORITES)').should('not.be.visible');
|
||||
// });
|
||||
});
|
||||
|
||||
// // Commented out since we've had to disable sticky headings because of issues with the menu components
|
||||
// function createChannelAndAddToFavourites() {
|
||||
// const userId = testUser.id;
|
||||
// cy.apiCreateChannel(testTeam.id, `channel-${getRandomId()}`, 'New Test Channel').then(({channel}) => {
|
||||
// // # Add the user to the channel
|
||||
// cy.apiAddUserToChannel(channel.id, userId).then(() => {
|
||||
// // # Move to a new category
|
||||
// cy.get(`#sidebarItem_${channel.name}`).parent().then((element) => {
|
||||
// // # Get id of the channel
|
||||
// const id = element[0].getAttribute('data-rfd-draggable-id');
|
||||
// cy.get(`#sidebarItem_${channel.name}`).parent('li').within(() => {
|
||||
// // # Open dropdown next to channel name
|
||||
// cy.get('.SidebarMenu').invoke('show').get('.SidebarMenu_menuButton').should('be.visible').click({force: true});
|
||||
|
||||
// // # Favourite the channel
|
||||
// cy.get(`#favorite-${id} button`).should('be.visible').click({force: true});
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// - Use element ID when selecting an element. Create one if none.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @channel_sidebar
|
||||
|
||||
import * as TIMEOUTS from '@/fixtures/timeouts';
|
||||
import {getAdminAccount} from '@/support/env';
|
||||
import {getRandomId} from '@/utils';
|
||||
|
||||
function verifyChannelSwitch(displayName: string, url: string) {
|
||||
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').should('contain', displayName);
|
||||
cy.url().should('include', url);
|
||||
}
|
||||
|
||||
describe('Channel sidebar', () => {
|
||||
const sysadmin = getAdminAccount();
|
||||
let testTeam: Cypress.Team;
|
||||
let testUser: Cypress.UserProfile;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit town-square
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should switch channels when clicking on a channel in the sidebar', () => {
|
||||
// # Start with a new team
|
||||
const teamName = `team-${getRandomId()}`;
|
||||
cy.createNewTeam(teamName, teamName);
|
||||
|
||||
// * Verify that we've switched to the new team
|
||||
cy.uiGetLHSHeader().findByText(teamName);
|
||||
|
||||
// # Click on Off Topic
|
||||
cy.get('.SidebarChannel:contains(Off-Topic)').should('be.visible').click();
|
||||
|
||||
// * Verify that the channel changed
|
||||
cy.url().should('include', `/${teamName}/channels/off-topic`);
|
||||
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').should('contain', 'Off-Topic');
|
||||
|
||||
// # Click on Town Square
|
||||
cy.get('.SidebarChannel:contains(Town Square)').should('be.visible').click();
|
||||
|
||||
// * Verify that the channel changed
|
||||
verifyChannelSwitch('Town Square', `/${teamName}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('should mark channel as read and unread in sidebar', () => {
|
||||
// # Start with a new team
|
||||
const teamName = `team-${getRandomId()}`;
|
||||
cy.createNewTeam(teamName, teamName);
|
||||
|
||||
// * Verify that we've switched to the new team
|
||||
cy.uiGetLHSHeader().findByText(teamName);
|
||||
|
||||
// * Verify that both Off Topic and Town Square are read
|
||||
cy.get('.SidebarChannel:not(.unread):contains(Off-Topic)').should('be.visible');
|
||||
cy.get('.SidebarChannel:not(.unread):contains(Town Square)').should('be.visible');
|
||||
|
||||
// # Have another user post in the Off Topic channel
|
||||
cy.apiGetChannelByName(teamName, 'off-topic').then(({channel}) => {
|
||||
cy.postMessageAs({sender: sysadmin, message: 'Test', channelId: channel.id});
|
||||
});
|
||||
|
||||
// * Verify that Off Topic is unread and Town Square is read
|
||||
cy.get('.SidebarChannel.unread:contains(Off-Topic)').should('be.visible');
|
||||
cy.get('.SidebarChannel:not(.unread):contains(Town Square)').should('be.visible');
|
||||
});
|
||||
|
||||
it('should remove channel from sidebar after leaving it', () => {
|
||||
// # Start with a new team
|
||||
const teamName = `team-${getRandomId()}`;
|
||||
cy.createNewTeam(teamName, teamName);
|
||||
|
||||
// * Verify that we've switched to the new team
|
||||
cy.uiGetLHSHeader().findByText(teamName);
|
||||
|
||||
// # Switch to Off Topic
|
||||
cy.visit(`/${teamName}/channels/off-topic`);
|
||||
|
||||
// # Wait for the channel to change
|
||||
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').should('contain', 'Off-Topic');
|
||||
|
||||
// # Click on the channel menu and select Leave Channel
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelLeaveChannel').should('be.visible').click();
|
||||
|
||||
// * Verify that we've switched to Town Square
|
||||
verifyChannelSwitch('Town Square', `/${teamName}/channels/town-square`);
|
||||
|
||||
// * Verify that Off Topic has disappeared from the sidebar
|
||||
cy.get('.SidebarChannel:contains(Off-Topic)').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T1684 should remove channel from sidebar after deleting it and navigate away', () => {
|
||||
// # Start with a new team
|
||||
const teamName = `team-${getRandomId()}`;
|
||||
cy.createNewTeam(teamName, teamName);
|
||||
|
||||
// * Verify that we've switched to the new team
|
||||
cy.uiGetLHSHeader().findByText(teamName);
|
||||
|
||||
// # Switch to Off Topic
|
||||
cy.visit(`/${teamName}/channels/off-topic`);
|
||||
|
||||
// # Wait for the channel to change
|
||||
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.HALF_MIN}).should('be.visible').should('contain', 'Off-Topic');
|
||||
|
||||
// # Click on the channel menu and select Archive Channel
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.get('#channelArchiveChannel').should('be.visible').click();
|
||||
cy.get('#deleteChannelModalDeleteButton').should('be.visible').click();
|
||||
|
||||
// * Verify we stay in the archived channel (no redirect)
|
||||
cy.url().should('include', `/${teamName}/channels/off-topic`);
|
||||
|
||||
// * Verify archived channel message is displayed
|
||||
cy.contains('You are viewing an archived channel').should('be.visible');
|
||||
|
||||
// * Verify channel is still visible in sidebar while viewing it
|
||||
cy.get('.SidebarChannel:contains(Off-Topic)').should('exist');
|
||||
|
||||
// # Navigate to a different channel
|
||||
cy.visit(`/${teamName}/channels/town-square`);
|
||||
|
||||
// * Verify archived channel disappears from sidebar after navigating away
|
||||
cy.get('.SidebarChannel:contains(Off-Topic)').should('not.exist');
|
||||
});
|
||||
|
||||
it('MM-T3351 Channels created from another instance should immediately appear in the sidebar', () => {
|
||||
// # Go to Town Square on the test team
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify that we've switched to the new team
|
||||
cy.uiGetLHSHeader().findByText(testTeam.display_name);
|
||||
|
||||
// # Create a new channel
|
||||
cy.apiCreateChannel(testTeam.id, `channel-${getRandomId()}`, 'New Test Channel').then(({channel}) => {
|
||||
// # Add the user to the channel
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id).then(() => {
|
||||
// * Verify that new channel appears in the sidebar;
|
||||
cy.get(`#sidebarItem_${channel.name}`).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user