Move /e2e -> /e2e-tests
Этот коммит содержится в:
@@ -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 @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
import {getRandomId} from '../../../../utils';
|
||||
|
||||
import {verifyEphemeralMessage} from './helper';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
let otherUser;
|
||||
let offTopicUrl;
|
||||
let channelUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then((out) => {
|
||||
testUser = out.user;
|
||||
offTopicUrl = out.offTopicUrl;
|
||||
channelUrl = out.channelUrl;
|
||||
|
||||
cy.apiCreateUser({prefix: 'other'}).then(({user}) => {
|
||||
otherUser = user;
|
||||
|
||||
cy.apiAddUserToTeam(out.team.id, otherUser.id).then(() => {
|
||||
cy.apiAddUserToChannel(out.channel.id, otherUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(channelUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
|
||||
it('MM-T573 / autocomplete list can scroll', () => {
|
||||
// # Clear post textbox
|
||||
cy.uiGetPostTextBox().clear().type('/');
|
||||
|
||||
// * Suggestion list should be visible
|
||||
// # Scroll to bottom and verify that the last command "/shrug" is visible
|
||||
cy.get('#suggestionList', {timeout: TIMEOUTS.FIVE_SEC}).should('be.visible').scrollTo('bottom').then((container) => {
|
||||
cy.contains('/away', {container}).should('not.be.visible');
|
||||
cy.contains('/shrug [message]', {container}).should('be.visible');
|
||||
});
|
||||
|
||||
// # Scroll to top and verify that the first command "/away" is visible
|
||||
cy.get('#suggestionList').scrollTo('top').then((container) => {
|
||||
cy.contains('/away', {container}).should('be.visible');
|
||||
cy.contains('/shrug [message]', {container}).should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T678 /code', () => {
|
||||
const message = '1. Not a list item, **not bolded**, http://notalink.com, ~off-topic is not a link to the channel.';
|
||||
|
||||
// # Use "/code"
|
||||
cy.postMessage(`/code ${message} `);
|
||||
|
||||
// * Verify that that markdown isn't rendered
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).find('.user-popover').should('have.text', testUser.username);
|
||||
cy.get(`#postMessageText_${postId}`).get('code').should('contain', message);
|
||||
});
|
||||
|
||||
// # Type "/code" with no text
|
||||
cy.postMessage('/code ');
|
||||
|
||||
// * Verify that an error message is shown
|
||||
verifyEphemeralMessage('A message must be provided with the /code command.');
|
||||
});
|
||||
|
||||
it('MM-T679 /echo', () => {
|
||||
const message = getRandomId();
|
||||
|
||||
// # Type "/echo message 3"
|
||||
cy.uiGetPostTextBox().clear().type(`/echo ${message} 3{enter}`);
|
||||
|
||||
// * Verify that post is not shown after 1 second
|
||||
cy.wait(TIMEOUTS.ONE_SEC);
|
||||
cy.getLastPost().within(() => {
|
||||
cy.findByText(message).should('not.exist');
|
||||
});
|
||||
|
||||
// * Verify that message is posted after 3 seconds
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
cy.getLastPost().within(() => {
|
||||
cy.findByText(testUser.username);
|
||||
cy.findByText(message);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T680 /help', () => {
|
||||
// # Type "/help"
|
||||
cy.postMessage('/help ');
|
||||
|
||||
// # get last posted message
|
||||
cy.wait(TIMEOUTS.HALF_SEC).getLastPostId().then((botLastPostId) => {
|
||||
cy.get(`#post_${botLastPostId}`).within(() => {
|
||||
// * Check if Bot message only visible to you
|
||||
cy.findByText('(Only visible to you)').should('exist');
|
||||
|
||||
// * Check if we got ephemeral message of our selection
|
||||
cy.contains('Mattermost is an open source platform for secure communication').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T681 /invite_people error message with no text or text that is not an email address', () => {
|
||||
// # Type "/invite_people 123"
|
||||
cy.postMessage('/invite_people 123');
|
||||
|
||||
// * Verify the message is shown saying "Please specify one or more valid email addresses"
|
||||
verifyEphemeralMessage('Please specify one or more valid email addresses');
|
||||
});
|
||||
|
||||
it('MM-T682 /leave', () => {
|
||||
// # Go to Off-Topic
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Type "/leave"
|
||||
cy.postMessage('/leave ');
|
||||
|
||||
// * Verity Off-Topic is not shown in LHS
|
||||
cy.get('#sidebar-left').should('be.visible').should('not.contain', 'Off-Topic');
|
||||
|
||||
// * Verify user is redirected to Town Square
|
||||
cy.uiGetLhsSection('CHANNELS').find('.active').should('contain', 'Town Square');
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', 'Town Square');
|
||||
});
|
||||
|
||||
it('MM-T574 /shrug test', () => {
|
||||
// # Login as otherUser and post a message
|
||||
cy.getCurrentChannelId().then((channelId) => {
|
||||
cy.postMessageAs({sender: otherUser, message: 'hello from otherUser', channelId});
|
||||
});
|
||||
|
||||
const message = getRandomId();
|
||||
|
||||
// # Post "/shrug test" as testUser
|
||||
cy.postMessage(`/shrug ${message} `);
|
||||
|
||||
// * Verify that it posted message as expected from testUser
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).find('.user-popover').should('have.text', testUser.username);
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', `${message} ¯\\_(ツ)_/¯`);
|
||||
});
|
||||
|
||||
// * Login as otherUser and verify that it read the same message as expected from testUser
|
||||
cy.apiLogin(otherUser);
|
||||
cy.visit(channelUrl);
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).find('.user-popover').should('have.text', testUser.username);
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', `${message} ¯\\_(ツ)_/¯`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T5100 /marketplace test', () => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
// # Go to town square
|
||||
cy.visit(`/${team.name}/channels/town-square`);
|
||||
|
||||
// # Post "/marketplace" as SystemAdmin
|
||||
cy.postMessage('/marketplace ');
|
||||
|
||||
cy.get('#modal_marketplace').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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 @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
import {getRandomId} from '../../../../utils';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
let testChannel;
|
||||
let otherChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({userPrefix: 'testUser'}).then(({team, user, channel}) => {
|
||||
testUser = user;
|
||||
testChannel = channel;
|
||||
|
||||
cy.apiCreateChannel(team.id, 'other-channel', 'Other Channel').then((out) => {
|
||||
otherChannel = out.channel;
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit('/');
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('#sidebarItem_off-topic').click();
|
||||
cy.uiGetPostTextBox();
|
||||
});
|
||||
|
||||
it('MM-T683 /join', () => {
|
||||
// # Type "/join ~new-channel"
|
||||
cy.postMessage(`/join ~${otherChannel.name} `);
|
||||
|
||||
// * Verify user is redirected to New Channel
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', otherChannel.display_name);
|
||||
});
|
||||
|
||||
it('MM-T684 /me', () => {
|
||||
// # Type "/me message"
|
||||
const message = getRandomId();
|
||||
cy.postMessage(`/me ${message}`);
|
||||
|
||||
// * Verify a message is posted
|
||||
cy.uiWaitUntilMessagePostedIncludes(message);
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).find('.user-popover').should('have.text', testUser.username);
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', message);
|
||||
|
||||
// * The message should match other system message formatting.
|
||||
cy.get(`#post_${postId}`).should('have.class', 'post--system');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T685 /me not case-sensitive', () => {
|
||||
// # Type "/Me message"
|
||||
const message = getRandomId();
|
||||
cy.postMessage(`/Me ${message}`);
|
||||
|
||||
// * Verify a message is posted
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).find('.user-popover').should('have.text', testUser.username);
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', message);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2345 /me on RHS', () => {
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).click();
|
||||
cy.get('#channelHeaderTitle').should('be.visible').should('contain', testChannel.display_name);
|
||||
|
||||
const rootMessage = 'root message';
|
||||
cy.postMessage(rootMessage);
|
||||
|
||||
// # Open RHS (reply thread)
|
||||
cy.clickPostCommentIcon();
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Verify the message, both in RHS and center, is from current user
|
||||
// and formatted with full opacity
|
||||
[`#rhsPost_${postId}`, `#post_${postId}`].forEach((selector) => {
|
||||
cy.get(selector).should('have.class', 'current--user').within(() => {
|
||||
cy.get('.post__header').findByText(testUser.username);
|
||||
cy.get('.post-message__text').findByText(rootMessage).should('have.css', 'color', 'rgb(63, 67, 80)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// # type /me message
|
||||
const message = 'reply';
|
||||
cy.postMessageReplyInRHS(`/me ${message} `);
|
||||
cy.uiWaitUntilMessagePostedIncludes(message);
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Verify the message reply, both in RHS and center, is from current user and formatted with lower opacity
|
||||
[`#rhsPost_${postId}`, `#post_${postId}`].forEach((selector) => {
|
||||
cy.get(selector).should('have.class', 'current--user').within(() => {
|
||||
cy.get('.profile-icon').should('not.be.visible');
|
||||
cy.get('.post-message__text').findByText(message).should('have.css', 'color', 'rgba(63, 67, 80, 0.6)');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T710 /mute error message', () => {
|
||||
const invalidChannel = `invalid-channel-${getRandomId()}`;
|
||||
|
||||
// # Type /mute with random characters
|
||||
cy.postMessage(`/mute ${invalidChannel} `);
|
||||
cy.uiWaitUntilMessagePostedIncludes('Please use the channel handle to identify channels');
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Should return an error message
|
||||
cy.get(`#postMessageText_${postId}`).
|
||||
should('have.text', `Could not find the channel ${invalidChannel}. Please use the channel handle to identify channels.`).
|
||||
|
||||
// * Channel handle links to: https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel
|
||||
contains('a', 'channel handle').then((link) => {
|
||||
const href = link.prop('href');
|
||||
cy.request(href).its('allRequestResponses').then((response) => {
|
||||
cy.wrap(response[0]['Request URL']).should('equal', 'https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2834 Slash command help stays visible for system slash command', () => {
|
||||
// # Type the rename slash command in textbox
|
||||
cy.uiGetPostTextBox().clear().type('/rename ');
|
||||
|
||||
// # Scan inside of suggestion list
|
||||
cy.get('#suggestionList').should('exist').and('be.visible').within(() => {
|
||||
// * Verify that renaming part of rename autosuggestion is still
|
||||
// visible in the autocomplete, since [text] is same as description and title, we will check if title exists
|
||||
cy.findAllByText('[text]').first().should('exist');
|
||||
});
|
||||
|
||||
// # Append Hello to /rename and hit enter
|
||||
cy.uiGetPostTextBox().type('Hello{enter}').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetPostTextBox().invoke('text').should('be.empty');
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Integrations', () => {
|
||||
before(() => {
|
||||
cy.apiInitSetup({loginAfter: true}).then(() => {
|
||||
cy.visit('/');
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
});
|
||||
|
||||
// This test was moved here since Cypress is behaving differently as compared to browser
|
||||
// and kept redirecting into the landing page even if the corresponding localstorage is already set.
|
||||
it('MM-T686 /logout', () => {
|
||||
// # Type "/logout"
|
||||
cy.uiGetPostTextBox().should('be.visible').clear().type('/logout {enter}').wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Ensure that the user was redirected to the login page
|
||||
cy.url().should('include', '/login');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
// 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 @integrations
|
||||
|
||||
import {getRandomId} from '../../../../utils';
|
||||
|
||||
import {loginAndVisitChannel} from './helper';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
const userGroup = [];
|
||||
let offTopicUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
offTopicUrl = url;
|
||||
|
||||
Cypress._.times(8, () => {
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
cy.apiAddUserToTeam(team.id, otherUser.id);
|
||||
userGroup.push(otherUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T664 /groupmsg initial tests', () => {
|
||||
function verifyPostedMessage(message, usernames) {
|
||||
// * Verify that the channel header contains each group member
|
||||
usernames.forEach((username) => {
|
||||
cy.contains('.channel-header__top', username).should('be.visible');
|
||||
});
|
||||
|
||||
// * Verify that the message is posted into the GM channel
|
||||
cy.uiWaitUntilMessagePostedIncludes(message);
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', message);
|
||||
});
|
||||
|
||||
// # Go back to off-topic channel
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
}
|
||||
|
||||
loginAndVisitChannel(testUser, offTopicUrl);
|
||||
|
||||
const usernames1 = Cypress._.map(userGroup, 'username').slice(0, 4);
|
||||
const usernames1Format = [
|
||||
`@${usernames1[0]},@${usernames1[1]},@${usernames1[2]},@${usernames1[3]}`, // Regular usernames format
|
||||
`${usernames1[0]}, @${usernames1[1]} , ${usernames1[2]} , @${usernames1[3]}`, // Irregular usernames format
|
||||
];
|
||||
|
||||
usernames1Format.forEach((users) => {
|
||||
const message = getRandomId();
|
||||
|
||||
// # Use /groupmsg command to send group message - "/groupmsg [usernames] [message]"
|
||||
cy.postMessage(`/groupmsg ${users} ${message}`);
|
||||
|
||||
// * Verify it redirects into the GM channel with new message posted.
|
||||
verifyPostedMessage(message, usernames1);
|
||||
|
||||
// # Use /groupmsg command to send message to existing GM - "group msg [usernames]" (note: no message)
|
||||
cy.postMessage(`/groupmsg ${users} `);
|
||||
|
||||
// * Verify it redirects into the GM channel without new message posted.
|
||||
verifyPostedMessage(message, usernames1);
|
||||
});
|
||||
|
||||
const usernames2 = Cypress._.map(userGroup, 'username').slice(1, 5);
|
||||
const usernames2Format = [
|
||||
`@${usernames2[0]}, @${usernames2[1]}, @${usernames2[2]}, @${usernames2[3]}`, // Regular usernames format
|
||||
`${usernames2[0]}, @${usernames2[1]} , ${usernames2[2]} , @${usernames2[3]}`, // Irregular usernames format
|
||||
];
|
||||
|
||||
usernames2Format.forEach((users) => {
|
||||
// # Use /groupmsg command to create GM - "group msg [usernames]" (note: no message)
|
||||
cy.postMessage(`/groupmsg ${users} `);
|
||||
|
||||
// * Verify that the channel header contains each group member
|
||||
usernames2.forEach((username) => {
|
||||
cy.contains('.channel-header__top', username).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T665 /groupmsg with users only and without message', () => {
|
||||
loginAndVisitChannel(testUser, offTopicUrl);
|
||||
|
||||
// # Use /groupmsg command to open group message - "/groupmsg [usernames]"
|
||||
const usernames = Cypress._.map(userGroup, 'username').slice(0, 3);
|
||||
const message = '/groupmsg @' + usernames.join(', @') + ' ';
|
||||
cy.postMessage(message);
|
||||
|
||||
// * Verify that the channel header contains each group member
|
||||
usernames.forEach((username) => {
|
||||
cy.contains('.channel-header__top', username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T666 /groupmsg error if messaging more than 7 users', () => {
|
||||
loginAndVisitChannel(testUser, offTopicUrl);
|
||||
|
||||
// # Include more than 7 valid users in the command
|
||||
const usernames = Cypress._.map(userGroup, 'username');
|
||||
const message = '/groupmsg @' + usernames.join(', @') + ' hello';
|
||||
cy.postMessage(message);
|
||||
|
||||
// * If adding more than 7 users (excluding current user), system message saying "Group messages are limited to a maximum of 7 users."
|
||||
cy.uiWaitUntilMessagePostedIncludes('Group messages are limited to a maximum of 7 users');
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', 'Group messages are limited to a maximum of 7 users.');
|
||||
});
|
||||
|
||||
// # Include one invalid user in the command
|
||||
const message2 = '/groupmsg @' + usernames.slice(0, 2).join(', @') + ', @hello again';
|
||||
cy.postMessage(message2);
|
||||
|
||||
// * If users cannot be found, returns error that user could not be found
|
||||
cy.uiWaitUntilMessagePostedIncludes('Unable to find the user: @hello');
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', 'Unable to find the user: @hello');
|
||||
});
|
||||
|
||||
// # Include more than one invalid user in the command
|
||||
const message3 = '/groupmsg @' + usernames.slice(0, 2).join(', @') + ', @hello, @world again';
|
||||
cy.postMessage(message3);
|
||||
|
||||
// * If users cannot be found, returns error that user could not be found
|
||||
cy.uiWaitUntilMessagePostedIncludes('Unable to find the users: @hello, @world');
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', 'Unable to find the users: @hello, @world');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
export function loginAndVisitChannel(user, channelUrl) {
|
||||
cy.apiLogin(user);
|
||||
cy.visit(channelUrl);
|
||||
|
||||
cy.get('#postListContent', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
cy.uiGetPostTextBox();
|
||||
}
|
||||
|
||||
export function verifyEphemeralMessage(message) {
|
||||
// # Checking if we got the ephemeral message with the selection we made
|
||||
cy.wait(TIMEOUTS.HALF_SEC).getLastPostId().then((botLastPostId) => {
|
||||
cy.get(`#post_${botLastPostId}`).within(() => {
|
||||
// * Check if Bot message only visible to you
|
||||
cy.findByText('(Only visible to you)').should('exist');
|
||||
|
||||
// * Check if we got ephemeral message of our selection
|
||||
cy.findByText(message).should('exist');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// 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 @integrations
|
||||
|
||||
import * as MESSAGES from '../../../../fixtures/messages';
|
||||
|
||||
describe('Invalid slash command', () => {
|
||||
const incorrectCommand1 = 'notacommand-1';
|
||||
const incorrectCommand2 = 'notacommand-2';
|
||||
const incorrectCommand3 = 'notacommand-3';
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
cy.postMessage('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T667 - Start message with slash and non-command', () => {
|
||||
// # Type a incorrect slash command and press enter
|
||||
cy.uiGetPostTextBox().type(`/${incorrectCommand1} {enter}`);
|
||||
|
||||
// * Check that error message of incorrect command is displayed
|
||||
verifyNonCommandErrorMessageIsDisplayed(incorrectCommand1);
|
||||
|
||||
// * Check that focus is still the center textbox
|
||||
cy.focused().should('have.id', 'post_textbox');
|
||||
|
||||
// # Backspace in the center textbox and verify error message disappeared
|
||||
cy.uiGetPostTextBox().type('{backspace}');
|
||||
verifyNonCommandErrorMessageIsNotDisplayed(incorrectCommand1);
|
||||
|
||||
// # Type another incorrect slash command
|
||||
cy.uiGetPostTextBox().clear().type(`/${incorrectCommand2} {enter}`);
|
||||
|
||||
// * Check that error message of incorrect command is displayed again
|
||||
verifyNonCommandErrorMessageIsDisplayed(incorrectCommand2);
|
||||
|
||||
// # Click on the link to post incorrect command as plain text
|
||||
cy.findByText('Click here to send as a message.').click({force: true});
|
||||
|
||||
// * Verify the incorrect command is posted as plain text when we pressed 'click here to send as message' link
|
||||
verifyLastPostedMessageContainsPlainTextOfCommand(incorrectCommand2);
|
||||
|
||||
// # Lets try to post incorrect message as plain text via twice enter press
|
||||
cy.uiGetPostTextBox().clear().type(`/${incorrectCommand3} {enter}`);
|
||||
|
||||
// * Check that error message of incorrect command is displayed again
|
||||
verifyNonCommandErrorMessageIsDisplayed(incorrectCommand3);
|
||||
|
||||
// # Lets press enter again in the textbox after error message is shown to submit command as plain text
|
||||
cy.uiGetPostTextBox().type('{enter}');
|
||||
|
||||
// * Verify incorrect command got posted as plain text message via twice enter press
|
||||
verifyLastPostedMessageContainsPlainTextOfCommand(incorrectCommand3);
|
||||
});
|
||||
|
||||
it('MM-T668 Start reply with slash and non-command', () => {
|
||||
// # Post a message in the center text plane
|
||||
cy.postMessage(MESSAGES.SMALL);
|
||||
|
||||
// # To the last message post a reply in RHS
|
||||
cy.getLastPostId().then((lastPostID) => {
|
||||
cy.clickPostCommentIcon(lastPostID);
|
||||
cy.postMessageReplyInRHS(MESSAGES.TINY);
|
||||
});
|
||||
|
||||
// # Type a incorrect slash command and press enter in RHS
|
||||
cy.uiGetReplyTextBox().type(`/${incorrectCommand1} {enter}`);
|
||||
|
||||
// # Move the text search for error inside the RHS container only, so we are certain it is rendered below RHS textbox
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// * Check that error message of incorrect command is displayed
|
||||
verifyNonCommandErrorMessageIsDisplayed(incorrectCommand1);
|
||||
});
|
||||
|
||||
// * Check that the focus is still the RHS textbox and not in the center textbox
|
||||
cy.focused().
|
||||
should('have.id', 'reply_textbox').
|
||||
and('not.have.id', 'post_textbox');
|
||||
|
||||
// * Verify hitting backspace in the textbox removes the error message
|
||||
cy.uiGetReplyTextBox().type('{backspace}');
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// * Verify error message is not displayed
|
||||
verifyNonCommandErrorMessageIsNotDisplayed(incorrectCommand1);
|
||||
});
|
||||
|
||||
// # Press enter once with incorrect to allow the error message to show
|
||||
cy.uiGetReplyTextBox().clear().type(`/${incorrectCommand2} {enter}`);
|
||||
|
||||
// * Check that error message of incorrect command is displayed
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
verifyNonCommandErrorMessageIsDisplayed(incorrectCommand2);
|
||||
});
|
||||
|
||||
// # Lets press enter again to submit it as plain text after error message is shown
|
||||
cy.uiGetReplyTextBox().type('{enter}');
|
||||
|
||||
// * Verify incorrect command got posted as plain text message via twice enter press
|
||||
verifyLastPostedMessageContainsPlainTextOfCommand(incorrectCommand2);
|
||||
|
||||
// # Lets add another incorrect command and press enter
|
||||
cy.uiGetReplyTextBox().clear().type(`/${incorrectCommand3} {enter}`);
|
||||
|
||||
// * Check that error message of incorrect command is displayed
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
verifyNonCommandErrorMessageIsDisplayed(incorrectCommand3);
|
||||
});
|
||||
|
||||
// # Click on the link to post incorrect command as plain text below textbox
|
||||
cy.findByText('Click here to send as a message.').should('exist').click({force: true});
|
||||
|
||||
// * Verify incorrect command got posted as plain text message via clicking link
|
||||
verifyLastPostedMessageContainsPlainTextOfCommand(incorrectCommand3);
|
||||
|
||||
// # Close RHS
|
||||
cy.uiCloseRHS();
|
||||
});
|
||||
});
|
||||
|
||||
function verifyNonCommandErrorMessageIsDisplayed(nonCommand) {
|
||||
cy.findByText(`Command with a trigger of '/${nonCommand}' not found.`);
|
||||
cy.findByText('Click here to send as a message.');
|
||||
}
|
||||
|
||||
function verifyNonCommandErrorMessageIsNotDisplayed(nonCommand) {
|
||||
cy.findByText(`Command with a trigger of '/${nonCommand}' not found.`).should('not.exist');
|
||||
cy.findByText('Click here to send as a message.').should('not.exist');
|
||||
}
|
||||
|
||||
function verifyLastPostedMessageContainsPlainTextOfCommand(nonCommand) {
|
||||
// # Get the last posted message
|
||||
cy.getLastPost().within(() => {
|
||||
// * Verify the incorrect command is posted as plain text
|
||||
cy.findByText(`/${nonCommand}`);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
import {loginAndVisitChannel} from './helper';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
let testTeam;
|
||||
const userGroup = [];
|
||||
let testChannel;
|
||||
let testChannelUrl;
|
||||
let offTopicUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user, offTopicUrl: url}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
offTopicUrl = url;
|
||||
|
||||
Cypress._.times(8, () => {
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
cy.apiAddUserToTeam(team.id, otherUser.id);
|
||||
userGroup.push(otherUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
testChannelUrl = `/${testTeam.name}/channels/${channel.name}`;
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T658 /invite - current channel', () => {
|
||||
cy.apiCreateUser().then(({user}) => {
|
||||
return cy.apiDeactivateUser(user.id).then(() => user);
|
||||
}).then((deactivatedUser) => {
|
||||
const userToInvite = userGroup[0];
|
||||
|
||||
loginAndVisitChannel(testUser, testChannelUrl);
|
||||
|
||||
// # Post `/invite @username` where username is a user who is not in the current channel
|
||||
cy.postMessage(`/invite @${userToInvite.username} `);
|
||||
|
||||
// * User who added them sees system message "username added to the channel by you"
|
||||
cy.uiWaitUntilMessagePostedIncludes(`@${userToInvite.username} added to the channel by you`);
|
||||
|
||||
// * Cannot invite deactivated users to a channel
|
||||
cy.postMessage(`/invite @${deactivatedUser.username} `);
|
||||
cy.uiWaitUntilMessagePostedIncludes(`We couldn't find the user ${deactivatedUser.username}. They may have been deactivated by the System Administrator.`);
|
||||
|
||||
cy.apiLogout();
|
||||
loginAndVisitChannel(userToInvite, offTopicUrl);
|
||||
|
||||
// * Added user sees channel added to LHS, mention badge
|
||||
cy.uiGetLhsSection('CHANNELS').
|
||||
findByLabelText(`${testChannel.display_name.toLowerCase()} public channel 1 mention`).
|
||||
should('be.visible').
|
||||
click();
|
||||
|
||||
// * Added user sees system message "username added to the channel by username."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`You were added to the channel by @${testUser.username}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T661 /invite extra white space before @ in DM or GM', () => {
|
||||
const [member1, member2, userToInviteGM, userToInviteDM] = userGroup;
|
||||
|
||||
loginAndVisitChannel(testUser, testChannelUrl);
|
||||
|
||||
// # In a GM use the /invite command to invite a user to a channel you have permission to add them to but place extra white space before the username
|
||||
cy.postMessage(`/groupmsg @${member1.username} @${member2.username} `);
|
||||
cy.postMessage(`/invite @${userToInviteGM.username} ~${testChannel.name} `);
|
||||
|
||||
// * User added to channel as expected
|
||||
cy.uiWaitUntilMessagePostedIncludes(`${userToInviteGM.username} added to ${testChannel.name} channel.`);
|
||||
|
||||
cy.uiAddDirectMessage().click();
|
||||
cy.get('#selectItems input').typeWithForce(userToInviteDM.username).wait(TIMEOUTS.ONE_SEC);
|
||||
cy.get('#multiSelectList').findByText(`@${userToInviteDM.username}`).click();
|
||||
cy.findByText('Go').click();
|
||||
cy.uiGetChannelHeaderButton().contains(userToInviteDM.username);
|
||||
|
||||
// # In a DM use the /invite command to invite a user to a channel you have permission to add them to but place extra white space before the username
|
||||
cy.postMessage(`/invite @${userToInviteDM.username} ~${testChannel.name} `);
|
||||
|
||||
// * User added to channel as expected
|
||||
cy.uiWaitUntilMessagePostedIncludes(`${userToInviteDM.username} added to ${testChannel.name} channel.`);
|
||||
});
|
||||
|
||||
it('MM-T659 /invite - other channel', () => {
|
||||
const userToInvite = userGroup[0];
|
||||
|
||||
loginAndVisitChannel(testUser, offTopicUrl);
|
||||
|
||||
// # Post `/invite @username ~channel` where channel name is a channel you have permission to add members to but not the current channel, and username is a user not in that other channel
|
||||
cy.postMessage(`/invite @${userToInvite.username} ~${testChannel.name} `);
|
||||
|
||||
// * User who added them sees system message "username added to channel."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`${userToInvite.username} added to ${testChannel.name} channel.`);
|
||||
|
||||
cy.apiLogout();
|
||||
loginAndVisitChannel(userToInvite, offTopicUrl);
|
||||
|
||||
// * Added user sees channel added to LHS, mention badge.
|
||||
cy.uiGetLhsSection('CHANNELS').
|
||||
findByLabelText(`${testChannel.display_name.toLowerCase()} public channel 1 mention`).
|
||||
should('be.visible').
|
||||
click();
|
||||
|
||||
// * Added user sees system message "username added to the channel by username."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`You were added to the channel by @${testUser.username}`);
|
||||
});
|
||||
|
||||
it('MM-T660_1 /invite tests when used in DMs and GMs', () => {
|
||||
const [member1, member2, userDM] = userGroup;
|
||||
|
||||
loginAndVisitChannel(testUser, testChannelUrl);
|
||||
|
||||
// # In a GM Use the /invite command to invite a channel to another channel (e.g., /invite @[channel name])
|
||||
cy.postMessage(`/groupmsg @${member1.username} @${member2.username} `);
|
||||
cy.postMessage(`/invite @${testChannel.name} `);
|
||||
|
||||
// * Error appears: "We couldn't find the user. They may have been deactivated by the System Administrator."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`We couldn't find the user ${testChannel.name}. They may have been deactivated by the System Administrator.`);
|
||||
|
||||
cy.uiAddDirectMessage().click();
|
||||
cy.get('#selectItems input').typeWithForce(userDM.username).wait(TIMEOUTS.ONE_SEC);
|
||||
cy.get('#multiSelectList').findByText(`@${userDM.username}`).click();
|
||||
cy.findByText('Go').click();
|
||||
cy.uiGetChannelHeaderButton().contains(userDM.username);
|
||||
|
||||
// # In a GM Use the /invite command to invite a channel to another channel (e.g., /invite @[channel name])
|
||||
cy.postMessage(`/invite @${testChannel.name} `);
|
||||
|
||||
// * Error appears: "We couldn't find the user. They may have been deactivated by the System Administrator."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`We couldn't find the user ${testChannel.name}. They may have been deactivated by the System Administrator.`);
|
||||
});
|
||||
|
||||
it('MM-T660_2 /invite tests when used in DMs and GMs', () => {
|
||||
const [member1, member2, userDM, userToInvite] = userGroup;
|
||||
|
||||
cy.apiAddUserToChannel(testChannel.id, userToInvite.id);
|
||||
loginAndVisitChannel(testUser, testChannelUrl);
|
||||
|
||||
// # In a GM use the /invite command to invite someone to a channel they're already a member of
|
||||
cy.postMessage(`/groupmsg @${member1.username} @${member2.username} `);
|
||||
cy.postMessage(`/invite @${userToInvite.username} ~${testChannel.name} `);
|
||||
|
||||
// * Error appears: "[username] is already in the channel"
|
||||
cy.uiWaitUntilMessagePostedIncludes(`${userToInvite.username} is already in the channel.`);
|
||||
|
||||
cy.uiAddDirectMessage().click();
|
||||
cy.get('#selectItems input').typeWithForce(userDM.username).wait(TIMEOUTS.ONE_SEC);
|
||||
cy.get('#multiSelectList').findByText(`@${userDM.username}`).click();
|
||||
cy.findByText('Go').click();
|
||||
cy.uiGetChannelHeaderButton().contains(userDM.username);
|
||||
|
||||
// # In a DM use the /invite command to invite someone to a channel they're already a member of
|
||||
cy.postMessage(`/invite @${userToInvite.username} ~${testChannel.name} `);
|
||||
|
||||
// * Error appears: "[username] is already in the channel"
|
||||
cy.uiWaitUntilMessagePostedIncludes(`${userToInvite.username} is already in the channel.`);
|
||||
});
|
||||
|
||||
it('MM-T660_3 /invite tests when used in DMs and GMs', () => {
|
||||
const [userA, userB, userC, userDM, member1, member2] = userGroup;
|
||||
|
||||
// # As UserA create a new public channel
|
||||
loginAndVisitChannel(testUser, offTopicUrl);
|
||||
cy.uiCreateChannel({name: `${userA.username}-channel`});
|
||||
cy.get('#postListContent').should('be.visible');
|
||||
|
||||
cy.apiLogout();
|
||||
loginAndVisitChannel(userB, offTopicUrl);
|
||||
|
||||
cy.uiAddDirectMessage().click();
|
||||
cy.get('#selectItems input').typeWithForce(userDM.username).wait(TIMEOUTS.ONE_SEC);
|
||||
cy.get('#multiSelectList').findByText(`@${userDM.username}`).click();
|
||||
cy.findByText('Go').click();
|
||||
cy.uiGetChannelHeaderButton().contains(userDM.username);
|
||||
|
||||
// # As UserB use the /invite command in a DM to invite UserC to the public channel that UserB is not a member of
|
||||
cy.postMessage(`/invite @${userC.username} ~${userA.username}-channel `);
|
||||
|
||||
// * Error appears: "You don't have enough permissions to add [username] in [public channel name]."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`You don't have enough permissions to add ${userC.username} in ${userA.username}-channel.`);
|
||||
|
||||
// # As UserB use the /invite command in a GM to invite UserC to the public channel that UserB is not a member of
|
||||
cy.postMessage(`/groupmsg @${member1.username} @${member2.username} `);
|
||||
cy.postMessage(`/invite @${userC.username} ~${userA.username}-channel `);
|
||||
|
||||
// * Error appears: "You don't have enough permissions to add [username] in [public channel name]."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`You don't have enough permissions to add ${userC.username} in ${userA.username}-channel.`);
|
||||
});
|
||||
|
||||
it('MM-T660_4 /invite tests when used in DMs and GMs', () => {
|
||||
const userToInvite = userGroup[0];
|
||||
|
||||
loginAndVisitChannel(testUser, offTopicUrl);
|
||||
|
||||
// # Use the /invite command to invite a user to a channel by typing the channel name out without the tilde (~).
|
||||
cy.postMessage(`/invite @${userToInvite.username} ${testChannel.display_name} `);
|
||||
|
||||
// * Error appears: "Could not find the channel [channel name]. Please use the channel handle to identify channels."
|
||||
cy.uiWaitUntilMessagePostedIncludes(`Could not find the channel ${testChannel.display_name.split(' ')[1]}. Please use the channel handle to identify channels.`);
|
||||
|
||||
// * "channel handle" is a live link to https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).
|
||||
contains('a', 'channel handle').should('have.attr', 'href', 'https://docs.mattermost.com/messaging/managing-channels.html#naming-a-channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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 @integrations
|
||||
|
||||
import {getJoinEmailTemplate, verifyEmailBody} from '../../../../utils';
|
||||
|
||||
import {loginAndVisitChannel} from './helper';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
let testTeam;
|
||||
const usersToInvite = [];
|
||||
let siteName;
|
||||
let testChannelUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
cy.apiInitSetup().then(({team, user, channelUrl}) => {
|
||||
testUser = user;
|
||||
testTeam = team;
|
||||
testChannelUrl = channelUrl;
|
||||
|
||||
Cypress._.times(2, () => {
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
usersToInvite.push(otherUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T575 /invite-people', () => {
|
||||
loginAndVisitChannel(testUser, testChannelUrl);
|
||||
|
||||
// # Post `/invite email1 email2` where emails are of users not added to the team yet
|
||||
cy.postMessage(`/invite_people ${usersToInvite.map((user) => user.email).join(' ')} `);
|
||||
|
||||
// * User who added them sees system message "Email invite(s) sent"
|
||||
cy.uiWaitUntilMessagePostedIncludes('Email invite(s) sent');
|
||||
|
||||
usersToInvite.forEach((invitedUser) => {
|
||||
cy.getRecentEmail({username: invitedUser.username, email: invitedUser.email}).then((data) => {
|
||||
const {body: actualEmailBody, subject} = data;
|
||||
|
||||
// * Verify the subject
|
||||
expect(subject).to.contain(`[${siteName}] ${testUser.username} invited you to join ${testTeam.display_name} Team`);
|
||||
|
||||
// * Verify email body
|
||||
const expectedEmailBody = getJoinEmailTemplate(testUser.username, invitedUser.email, testTeam);
|
||||
verifyEmailBody(expectedEmailBody, actualEmailBody);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
const testCases = [
|
||||
{command: '/away', className: 'icon-clock', message: 'You are now away'},
|
||||
{command: '/dnd', className: 'icon-minus-circle', message: 'Do Not Disturb is enabled. You will not receive desktop or mobile push notifications until Do Not Disturb is turned off.'},
|
||||
{command: '/offline', className: 'icon-circle-outline', message: 'You are now offline'},
|
||||
{command: '/online', className: 'icon-check-circle', message: 'You are now online'},
|
||||
];
|
||||
|
||||
let offTopicUrl;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl: url}) => {
|
||||
offTopicUrl = url;
|
||||
});
|
||||
});
|
||||
|
||||
it('I18456 Built-in slash commands: change user status via post', () => {
|
||||
cy.apiSaveMessageDisplayPreference('compact');
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
cy.postMessage(testCase.command + ' ');
|
||||
|
||||
verifyUserStatus(testCase, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('I18456 Built-in slash commands: change user status via suggestion list', () => {
|
||||
cy.apiSaveMessageDisplayPreference('clean');
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
// # Type "/" on textbox
|
||||
cy.uiGetPostTextBox().clear().type('/');
|
||||
|
||||
// # Verify that the suggestion list is visible
|
||||
cy.get('#suggestionList').should('be.visible').then((container) => {
|
||||
// # Find command and click
|
||||
cy.contains(new RegExp(testCase.command), {container}).click({force: true});
|
||||
});
|
||||
|
||||
// # Hit enter and verify user status
|
||||
cy.uiGetPostTextBox().type(' {enter}');
|
||||
verifyUserStatus(testCase, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifyUserStatus(testCase, isCompactMode) {
|
||||
// * Verify that the user status is as indicated
|
||||
cy.uiGetProfileHeader().
|
||||
find('i').
|
||||
should('be.visible').
|
||||
and('have.class', testCase.className);
|
||||
|
||||
cy.uiWaitUntilMessagePostedIncludes(testCase.message);
|
||||
|
||||
// * Verify that ephemeral message is posted as expected
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).find('.user-popover').should('have.text', 'System');
|
||||
|
||||
if (isCompactMode) {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', testCase.message + ' (Only visible to you)');
|
||||
} else {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', testCase.message);
|
||||
cy.get('.post__visibility').last().should('be.visible').and('have.text', '(Only visible to you)');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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 @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Integrations', () => {
|
||||
const away = {name: 'away', ariaLabel: 'Away Icon', message: 'You are now away', className: 'icon-clock'};
|
||||
const offline = {name: 'offline', ariaLabel: 'Offline Icon', message: 'You are now offline', className: 'icon-circle-outline'};
|
||||
const online = {name: 'online', ariaLabel: 'Online Icon', message: 'You are now online', className: 'icon-check', profileClassName: 'icon-check-circle'};
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and go to off-topic
|
||||
cy.apiInitSetup({loginAfter: true}).then(({offTopicUrl}) => {
|
||||
cy.visit(offTopicUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T670 /away', () => {
|
||||
// # Set online status and verify it's changed as the initial status
|
||||
setStatus(online.name, online.profileClassName);
|
||||
|
||||
verifyUserStatus(away);
|
||||
});
|
||||
|
||||
it('MM-T672 /offline', () => {
|
||||
// # Set online status and verify it's changed as the initial status
|
||||
setStatus(online.name, online.profileClassName);
|
||||
|
||||
verifyUserStatus(offline);
|
||||
|
||||
// # Switch to off-topic channel
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.findByLabelText('channel header region').findByText('Off-Topic').should('be.visible');
|
||||
|
||||
// # Then switch back to off-topic channel again
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.findByLabelText('channel header region').findByText('Off-Topic').should('be.visible');
|
||||
|
||||
// * Should not appear "New Messages" line
|
||||
cy.findByText('New Messages').should('not.exist');
|
||||
|
||||
// # Get the system message
|
||||
cy.uiGetNthPost(-2).within(() => {
|
||||
cy.findByText(offline.message);
|
||||
|
||||
// * Verify system message profile is visible and without status
|
||||
cy.findByLabelText('Mattermost Logo').should('be.visible');
|
||||
cy.get('.post__img').find('.status').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T674 /online', () => {
|
||||
// # Set offline status and verify it's changed as the initial status
|
||||
setStatus(offline.name, offline.className);
|
||||
|
||||
verifyUserStatus(online);
|
||||
});
|
||||
});
|
||||
|
||||
function setStatus(status, icon) {
|
||||
cy.apiUpdateUserStatus(status);
|
||||
cy.uiGetProfileHeader().
|
||||
find('i').
|
||||
and('have.class', icon);
|
||||
}
|
||||
|
||||
function verifyUserStatus(testCase) {
|
||||
// # Clear then type '/'
|
||||
cy.uiGetPostTextBox().clear().type('/');
|
||||
|
||||
// * Verify that the suggestion list is visible
|
||||
cy.get('#suggestionList').should('be.visible');
|
||||
|
||||
// # Post slash command to change user status
|
||||
cy.uiGetPostTextBox().type(`${testCase.name}{enter}`).wait(TIMEOUTS.ONE_HUNDRED_MILLIS).type('{enter}');
|
||||
|
||||
// * Get last post and verify system message
|
||||
cy.getLastPost().within(() => {
|
||||
cy.findByText(testCase.message);
|
||||
cy.findByText('(Only visible to you)');
|
||||
});
|
||||
|
||||
// * Verify status shown at user profile in LHS
|
||||
cy.uiGetProfileHeader().
|
||||
find('i').
|
||||
and('have.class', testCase.profileClassName || testCase.className);
|
||||
|
||||
// # Post a message
|
||||
cy.postMessage(testCase.name);
|
||||
|
||||
// Verify that the profile in the posted message shows correct status
|
||||
cy.get('.post__img').last().findByLabelText(testCase.ariaLabel);
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// 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 @integrations
|
||||
|
||||
/**
|
||||
* Note: This test requires webhook server running. Initiate `npm run start:webhook` to start.
|
||||
*/
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
import {
|
||||
enablePermission,
|
||||
goToSystemScheme,
|
||||
saveConfigForScheme,
|
||||
} from '../../enterprise/system_console/channel_moderation/helpers';
|
||||
|
||||
import {addNewCommand, runSlashCommand} from './helpers';
|
||||
|
||||
describe('Slash commands', () => {
|
||||
const trigger = 'my_trigger';
|
||||
let user1;
|
||||
let user2;
|
||||
let team1;
|
||||
let commandURL;
|
||||
const userIds = [];
|
||||
let groupChannel;
|
||||
let visitLink;
|
||||
|
||||
before(() => {
|
||||
cy.requireWebhookServer();
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
user1 = user;
|
||||
team1 = team;
|
||||
|
||||
cy.apiGetChannelByName(team.name, 'town-square').then(({channel}) => {
|
||||
commandURL = `${Cypress.env().webhookBaseUrl}/send_message_to_channel?channel_id=${channel.id}`;
|
||||
});
|
||||
|
||||
// # Create a GM with at least 3 users
|
||||
['charlie', 'diana', 'eddie'].forEach((name) => {
|
||||
cy.apiCreateUser({prefix: name, bypassTutorial: true}).then(({user: groupUser}) => {
|
||||
cy.apiAddUserToTeam(team1.id, groupUser.id);
|
||||
userIds.push(groupUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
// # Add test user to the list of group members
|
||||
userIds.push(user1.id);
|
||||
|
||||
cy.apiCreateGroupChannel(userIds).then(({channel}) => {
|
||||
groupChannel = channel;
|
||||
});
|
||||
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
user2 = otherUser;
|
||||
cy.apiAddUserToTeam(team.id, user2.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T669 Custom slash command in DM and GM', () => {
|
||||
const gmTrigger = 'gm_trigger';
|
||||
const dmTrigger = 'dm_trigger';
|
||||
|
||||
cy.apiAdminLogin(user1);
|
||||
cy.apiGetChannelByName(team1.name, groupChannel.name).then(({channel}) => {
|
||||
const customGMUrl = `${Cypress.env().webhookBaseUrl}/send_message_to_channel?channel_id=${channel.id}`;
|
||||
|
||||
// # Add a new command to send a GM
|
||||
addNewCommand(team1, gmTrigger, customGMUrl);
|
||||
|
||||
visitLink = `/${team1.name}/channels/${groupChannel.name}`;
|
||||
|
||||
// * Verify running custom command in GM
|
||||
runSlashCommand(visitLink, gmTrigger);
|
||||
|
||||
// # Cleanup command
|
||||
deleteCommand(team1, gmTrigger);
|
||||
});
|
||||
|
||||
// # Create a new DM channel
|
||||
cy.apiCreateDirectChannel([user1.id, user2.id]).then(() => {
|
||||
visitLink = `/${team1.name}/messages/@${user2.username}`;
|
||||
cy.visit(visitLink);
|
||||
});
|
||||
|
||||
// # Get channel id to create a custom slash command in DM
|
||||
cy.getCurrentChannelId().then((channelId) => {
|
||||
const message = `hello from ${user2.username}: ${Date.now()}`;
|
||||
cy.postMessageAs({sender: user2, message, channelId});
|
||||
|
||||
const customDMUrl = `${Cypress.env().webhookBaseUrl}/send_message_to_channel?channel_id=${channelId}`;
|
||||
|
||||
addNewCommand(team1, dmTrigger, customDMUrl);
|
||||
|
||||
// * Verify running custom command in DM
|
||||
runSlashCommand(visitLink, dmTrigger);
|
||||
|
||||
// # Cleanup command
|
||||
deleteCommand(team1, dmTrigger);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T696 Can\'t delete other user\'s slash command', () => {
|
||||
cy.apiAdminLogin(user1);
|
||||
|
||||
// # Create new Slash command
|
||||
addNewCommand(team1, trigger, 'http://dot.com');
|
||||
|
||||
goToSystemScheme();
|
||||
enablePermission('all_users-integrations-manage_slash_commands-checkbox');
|
||||
saveConfigForScheme();
|
||||
|
||||
// # Login as another user
|
||||
cy.apiLogin(user2);
|
||||
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team1.name}/integrations/commands/installed`);
|
||||
|
||||
// * Verify slash command exists
|
||||
cy.contains(`/${trigger}`);
|
||||
|
||||
// * Verify that Edit and Delete options do not show up
|
||||
cy.contains('Edit').should('not.exist');
|
||||
cy.contains('Delete').should('not.exist');
|
||||
|
||||
// # Cleanup command
|
||||
cy.apiAdminLogin(user1);
|
||||
deleteCommand(team1, trigger);
|
||||
});
|
||||
|
||||
it('MM-T697 Delete slash command', () => {
|
||||
// # Create new Slash command
|
||||
addNewCommand(team1, trigger, 'http://dot.com');
|
||||
|
||||
deleteCommand(team1, trigger);
|
||||
|
||||
// # Go back to home channel
|
||||
cy.visit(`/${team1.name}/channels/town-square`);
|
||||
|
||||
// # Run slash command
|
||||
cy.uiGetPostTextBox().clear().type(`/${trigger} {enter}`);
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify error
|
||||
cy.findByText(`Command with a trigger of '/${trigger}' not found.`).should('exist').and('be.visible');
|
||||
});
|
||||
|
||||
it('MM-T700 Slash command - Override username', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostUsernameOverride: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create new Slash command
|
||||
addNewCommand(team1, trigger, commandURL);
|
||||
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team1.name}/integrations/commands/installed`);
|
||||
|
||||
// # Update username
|
||||
// # click on last added command's(first child) edit action
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText('Edit').click();
|
||||
cy.get('#username').type('newname');
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// # Go back to home channel
|
||||
cy.visit(`/${team1.name}/channels/town-square`);
|
||||
|
||||
// # Run slash command
|
||||
cy.postMessage(`/${trigger} `);
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify that last post is by newname
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post__header').find('.user-popover').as('usernameForPopover').should('have.text', 'newname');
|
||||
});
|
||||
|
||||
// # Cleanup command
|
||||
deleteCommand(team1, trigger);
|
||||
});
|
||||
|
||||
it('MM-T701 Slash command - Override profile picture', () => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostIconOverride: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create new Slash command
|
||||
addNewCommand(team1, trigger, commandURL);
|
||||
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team1.name}/integrations/commands/installed`);
|
||||
|
||||
// # Update icon URL
|
||||
// # click on last added command's(first child) edit action
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText('Edit').click();
|
||||
const iconURL = 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png';
|
||||
cy.get('#iconUrl').type(iconURL);
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// # Go back to home channel
|
||||
cy.visit(`/${team1.name}/channels/town-square`);
|
||||
|
||||
// # Run slash command
|
||||
cy.postMessage(`/${trigger} `);
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify that last post has correct icon
|
||||
cy.getLastPost().within(() => {
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
const encodedIconUrl = encodeURIComponent(iconURL);
|
||||
cy.get('.profile-icon > img').as('profileIconForPopover').should('have.attr', 'src', `${baseUrl}/api/v4/image?url=${encodedIconUrl}`);
|
||||
});
|
||||
|
||||
// # Cleanup command
|
||||
deleteCommand(team1, trigger);
|
||||
});
|
||||
|
||||
it('MM-T703 Show custom slash command in autocomplete', () => {
|
||||
// # Create new Slash command
|
||||
addNewCommand(team1, trigger, commandURL);
|
||||
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team1.name}/integrations/commands/installed`);
|
||||
|
||||
// # Update autocomplete
|
||||
// # click on last added command's(first child) edit action
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText('Edit').click();
|
||||
cy.get('#autocomplete').click();
|
||||
const hint = '[test-hint]';
|
||||
cy.get('#autocompleteHint').type(hint);
|
||||
const desc = 'Auto description';
|
||||
|
||||
// since there are two selectors with the same id 'description' we pick one which is the 10-th child
|
||||
cy.get(':nth-child(10) > .col-md-5 > #description').type(desc);
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// # Go back to home channel
|
||||
cy.visit(`/${team1.name}/channels/town-square`);
|
||||
|
||||
// # Type slash
|
||||
cy.uiGetPostTextBox().clear().type('/');
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify that command is in the list
|
||||
cy.contains(trigger);
|
||||
|
||||
// # Type full command
|
||||
cy.uiGetPostTextBox().type(trigger);
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify that autocomplete info is correct
|
||||
cy.get('.slash-command__title').should('have.text', `${trigger} ${hint}`);
|
||||
cy.get('.slash-command__desc').should('have.text', `${desc}`);
|
||||
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team1.name}/integrations/commands/installed`);
|
||||
|
||||
// # Remove autocomplete
|
||||
// # click on last added command's(first child) edit action
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText('Edit').click();
|
||||
cy.get('#autocomplete').click();
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// # Go back to home channel
|
||||
cy.visit(`/${team1.name}/channels/town-square`);
|
||||
|
||||
// # Run slash command
|
||||
cy.uiGetPostTextBox().clear().type('/');
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify that command is not in the list
|
||||
cy.contains(trigger).should('not.exist');
|
||||
|
||||
// # Cleanup command
|
||||
deleteCommand(team1, trigger);
|
||||
});
|
||||
});
|
||||
|
||||
function deleteCommand(team, trigger) {
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team.name}/integrations/commands/installed`);
|
||||
|
||||
// # Delete slash command
|
||||
// * Verify that last added command's details contains `/trigger`
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText(`- /${trigger}`).should('be.visible');
|
||||
|
||||
// # Click on last added command's delete action
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText('Delete').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// * Verify slash command no longer displays in list
|
||||
cy.get('.backstage-list').find('.backstage-list__item').first().findByText(`- /${trigger}`).should('not.exist');
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
export function addNewCommand(team, trigger, url) {
|
||||
// # Open slash command page
|
||||
cy.visit(`/${team.name}/integrations/commands/installed`);
|
||||
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Type a trigger word, url and display name
|
||||
cy.get('#trigger').type(trigger);
|
||||
cy.get('#displayName').type('Test Message');
|
||||
cy.apiGetChannelByName(team.name, 'town-square').then(({channel}) => {
|
||||
let urlToType = url;
|
||||
if (url === '') {
|
||||
urlToType = `${Cypress.env('webhookBaseUrl')}/send_message_to_channel?channel_id=${channel.id}`;
|
||||
}
|
||||
cy.get('#url').type(urlToType);
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify we are at setup successful URL
|
||||
cy.url().should('include', '/integrations/commands/confirm');
|
||||
|
||||
// * Verify slash was successfully created
|
||||
cy.findByText('Setup Successful').should('exist').and('be.visible');
|
||||
|
||||
// * Verify token was created
|
||||
cy.findByText('Token').should('exist').and('be.visible');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} linkToVisit : Channel / Group message / DM link to visit
|
||||
* @param {*} trigger : Slash command trigger
|
||||
*/
|
||||
export function runSlashCommand(linkToVisit, trigger) {
|
||||
// # Go back to home channel
|
||||
cy.visit(linkToVisit);
|
||||
|
||||
// # Run slash command
|
||||
cy.uiGetPostTextBox().clear().type(`/${trigger}{enter}{enter}`);
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Get last post message text
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).get('.Tag').contains('BOT');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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 @integrations
|
||||
|
||||
/**
|
||||
* Note: This test requires webhook server running. Initiate `npm run start:webhook` to start.
|
||||
*/
|
||||
|
||||
import {addNewCommand, runSlashCommand} from './helpers';
|
||||
|
||||
describe('Slash commands page', () => {
|
||||
const trigger = 'test-message';
|
||||
let channelUrl;
|
||||
let testTeam;
|
||||
|
||||
before(() => {
|
||||
cy.requireWebhookServer();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
// # Go to integrations
|
||||
cy.visit(`/${team.name}/integrations`);
|
||||
|
||||
// * Validate that slash command section is enabled
|
||||
cy.get('#slashCommands').should('be.visible');
|
||||
|
||||
// # Open slash command page
|
||||
cy.get('#slashCommands').click();
|
||||
channelUrl = `${testTeam.name}/channels/town-square`;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T690 Add custom slash command: / error', () => {
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Type a trigger starting with slash
|
||||
cy.get('#trigger').type('//input');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify that we get the error message
|
||||
cy.findByText('A trigger word cannot begin with a /').should('exist').and('be.visible').scrollIntoView();
|
||||
});
|
||||
|
||||
it('MM-T691 Error: trigger word required', () => {
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Do not input trigger word
|
||||
cy.get('#url').type('http://example.com');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify that we get the error message
|
||||
cy.findByText('A trigger word is required').should('exist').and('be.visible').scrollIntoView();
|
||||
});
|
||||
|
||||
it('MM-T692 Error: no spaces in trigger word', () => {
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Type a trigger word with space in it
|
||||
cy.get('#trigger').type('trigger with space');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify that we get the error message
|
||||
cy.findByText('A trigger word must not contain spaces').should('exist').and('be.visible').scrollIntoView();
|
||||
});
|
||||
|
||||
it('MM-T693 Error: URL required', () => {
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Type a trigger word
|
||||
cy.get('#trigger').type('test');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify that we get the error message
|
||||
cy.findByText('A request URL is required').should('exist').and('be.visible').scrollIntoView();
|
||||
});
|
||||
|
||||
it('MM-T694 Error: trigger word in use', () => {
|
||||
const triggerWord = 'my_trigger_word';
|
||||
const url = 'http://test.com';
|
||||
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Type a trigger word and URL
|
||||
cy.get('#trigger').type(triggerWord);
|
||||
cy.get('#url').type(url);
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// # Go to integrations
|
||||
cy.visit(`/${testTeam.name}/integrations`);
|
||||
|
||||
// * Validate that slash command section is enabled
|
||||
cy.get('#slashCommands').should('be.visible');
|
||||
|
||||
// # Open slash command page
|
||||
cy.get('#slashCommands').click();
|
||||
|
||||
// # Add same command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Type same trigger word and URL
|
||||
cy.get('#trigger').type(triggerWord);
|
||||
cy.get('#url').type(url);
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify that we get the error message
|
||||
cy.findByText('This trigger word is already in use. Please choose another word.').should('exist').and('be.visible').scrollIntoView();
|
||||
});
|
||||
|
||||
it('MM-T695 Run custom slash command', () => {
|
||||
addNewCommand(testTeam, trigger, '');
|
||||
runSlashCommand(channelUrl, trigger);
|
||||
});
|
||||
|
||||
it('MM-T698 Cancel out of edit', () => {
|
||||
addNewCommand(testTeam, trigger, 'http://example.com');
|
||||
|
||||
// # Go to integrations
|
||||
cy.visit(`/${testTeam.name}/integrations`);
|
||||
|
||||
// # Open slash command page
|
||||
cy.get('#slashCommands').click();
|
||||
|
||||
// # Click on edit
|
||||
cy.get('a[href*="/edit"]').click();
|
||||
|
||||
// # Change url
|
||||
cy.get('#url').clear().type('http://mattermost.com');
|
||||
|
||||
// # Click on Cancel
|
||||
cy.get('a').contains('Cancel').click();
|
||||
|
||||
// # Click on edit again
|
||||
cy.get('a[href*="/edit"]').click();
|
||||
|
||||
// * Verify that url value is not changed
|
||||
cy.get('#url').should('have.value', 'http://example.com');
|
||||
});
|
||||
|
||||
it('MM-T699 Edit custom slash command', () => {
|
||||
addNewCommand(testTeam, trigger, '');
|
||||
|
||||
// # Go to integrations
|
||||
cy.visit(`/${testTeam.name}/integrations`);
|
||||
|
||||
// # Open slash command page
|
||||
cy.get('#slashCommands').click();
|
||||
|
||||
// # Click on edit
|
||||
cy.get('a[href*="/edit"]').click();
|
||||
|
||||
// # Update display name
|
||||
cy.get('#displayName').clear().type('Test Message - Edit');
|
||||
|
||||
// # Update
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Verify successful update
|
||||
cy.findByText('Test Message - Edit').should('exist').and('be.visible');
|
||||
|
||||
runSlashCommand(channelUrl, trigger);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
describe('Integrations/Incoming Webhook', () => {
|
||||
let incomingWebhook;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: true,
|
||||
description: 'Incoming webhook - attachment does not collapse',
|
||||
display_name: 'attachment-does-not-collapse',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T642 Attachment does not collapse', () => {
|
||||
// # Post the incoming webhook with a text attachment (lorem ipsum test text)
|
||||
const content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vel convallis arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur id convallis lectus. Quisque ut laoreet augue, et suscipit magna. Etiam ut interdum nunc. Nam euismod felis eu ipsum eleifend, eget rhoncus arcu fringilla. Nam id laoreet eros, a bibendum diam. Donec sed augue vel tortor porta pulvinar. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin interdum, nunc in tempor molestie, dui erat facilisis tellus, ut faucibus mauris est et felis. Suspendisse pulvinar mauris vel viverra pulvinar. Maecenas iaculis euismod mauris, id pharetra justo rutrum et.' +
|
||||
'Donec sit amet nulla varius, posuere enim sit amet, venenatis sapien. Morbi venenatis ornare urna id vestibulum. Curabitur efficitur efficitur arcu, vel rhoncus lorem varius sed. Ut venenatis interdum arcu, et rutrum est pretium eu. Nam laoreet tincidunt cursus. Pellentesque feugiat sit amet ipsum a porta. Phasellus nec laoreet nulla. Duis gravida dolor orci, vitae mollis orci consequat at. Sed tincidunt dolor nisi, at fermentum ligula tristique non. Duis pulvinar, eros quis ultrices aliquam, libero ipsum lobortis leo, quis ullamcorper sapien sem vel magna. Etiam sed ligula ut ipsum luctus venenatis. Sed mollis convallis dolor, eu dictum leo condimentum id. Praesent porttitor neque in volutpat iaculis.' +
|
||||
'Vestibulum fermentum, elit vel vestibulum vestibulum, lectus odio tincidunt leo, quis gravida erat tortor non arcu. Donec condimentum accumsan dolor eget tempus. Pellentesque convallis porta mattis. Aenean pulvinar felis tincidunt, finibus felis at, imperdiet massa. Duis sed pellentesque urna, finibus tristique risus. In quam magna, commodo nec commodo ut, consectetur non tortor. Cras accumsan faucibus arcu, quis suscipit purus posuere ac. Nunc at urna nec massa bibendum posuere. Pellentesque at rhoncus eros. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec a maximus ipsum. Phasellus sed venenatis lacus, a vestibulum massa. Nunc rutrum nunc et dui porta aliquam. In ac eros mattis, congue nisi ut, rhoncus lacus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent nec mauris at erat vehicula sollicitudin.' +
|
||||
'Aliquam ornare sed tortor ut placerat. Fusce posuere a odio nec aliquet. Cras nec maximus metus. In elementum tincidunt orci, at sagittis nisl. Pellentesque scelerisque lorem ultricies ipsum finibus, in iaculis purus tincidunt. Aliquam tempus nunc at elementum vehicula. Integer tempus pretium magna, sed gravida nisl porta at. Donec et imperdiet augue, eget cursus dolor. Sed non magna dui. Phasellus vel massa pulvinar, cursus diam sit amet, vestibulum neque. Vivamus accumsan, mi vitae ultrices pretium, arcu eros sodales enim, et pellentesque quam eros in ligula. In eu justo a quam iaculis consequat. Aenean ornare a velit ac aliquet. Nullam lobortis posuere neque a pretium.' +
|
||||
'Etiam dignissim sed ante commodo faucibus. Nunc vitae aliquet justo. Proin consequat leo vel libero porttitor, ac vulputate turpis bibendum. In vel libero sed odio euismod tincidunt id non dui. Quisque vitae est quis ante eleifend rutrum sed ut diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Integer rhoncus, leo nec iaculis sagittis, ligula mauris consequat felis, nec finibus odio sem tincidunt dolor. Pellentesque vel purus a sem rhoncus porta eget et erat. Sed interdum, justo ac dictum lacinia, nibh metus posuere arcu, vel euismod eros libero semper ligula. Proin elementum ligula quis ornare auctor. Integer vitae elementum augue, in congue lorem. Sed felis purus, consequat eu lacus in, fermentum accumsan diam. Duis lacus nunc, accumsan varius consequat nec, tincidunt et odio.';
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
attachments: [{fallback: 'testing attachment does not collapse', pretext: 'testing attachment does not collapse', text: content}],
|
||||
};
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload, waitFor: 'attachment-pretext'});
|
||||
|
||||
// * Check "show more" button is visible and click
|
||||
cy.getLastPostId().then((postId) => {
|
||||
const postMessageId = `#${postId}_message`;
|
||||
cy.get(postMessageId).within(() => {
|
||||
cy.get('#showMoreButton').scrollIntoView().should('be.visible').and('have.text', 'Show more').click();
|
||||
});
|
||||
});
|
||||
|
||||
// # Type /collapse and press Enter
|
||||
cy.uiGetPostTextBox().type('/collapse {enter}');
|
||||
|
||||
// * Check that the post from the webhook has NOT collapsed (verify expanded post)
|
||||
cy.getNthPostId(-2).then((postId) => {
|
||||
const postMessageId = `#${postId}_message`;
|
||||
cy.get(postMessageId).within(() => {
|
||||
// * Verify "show more" button says "Show less"
|
||||
cy.get('#showMoreButton').scrollIntoView().should('be.visible').and('have.text', 'Show less');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,128 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let testChannel;
|
||||
let otherUser;
|
||||
let incomingWebhook;
|
||||
|
||||
before(() => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostUsernameOverride: true,
|
||||
EnablePostIconOverride: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testChannel = channel;
|
||||
otherUser = user;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: true,
|
||||
description: 'Incoming webhook - basic formatting',
|
||||
display_name: 'basic-formatting',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
cy.postMessage('Test message');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T619 Webhook with @-mention, username and profile pic, and basic formatting', () => {
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
const payload = getPayload(testChannel, otherUser);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes('The following escaped characters should appear normally'));
|
||||
}));
|
||||
|
||||
cy.getLastPost().within((el) => {
|
||||
// * Verify that the username is overridden per webhook payload
|
||||
cy.get('.post__header').find('.user-popover').should('have.text', payload.username);
|
||||
|
||||
// * Verify that the user icon is overridden per webhook payload
|
||||
const encodedIconUrl = encodeURIComponent(payload.icon_url);
|
||||
cy.get('.profile-icon > img').should('have.attr', 'src', `${baseUrl}/api/v4/image?url=${encodedIconUrl}`);
|
||||
|
||||
// * Verify that the BOT label appears
|
||||
cy.get('.Tag').should('be.visible').and('have.text', 'BOT');
|
||||
|
||||
// * Verify that there's no status indicator
|
||||
cy.get('.status').should('not.exist');
|
||||
|
||||
// # Verify that the elements on posted message matched as expected
|
||||
cy.get('.post-message__text').within(() => {
|
||||
cy.wrap(el).should('contain', 'The following escaped characters should appear normally');
|
||||
cy.wrap(el).should('contain', '(ampersand, open angle, close angle): & < >');
|
||||
cy.wrap(el).should('contain', 'The following should appear as links:');
|
||||
cy.get('.markdown__link').eq(0).
|
||||
should('have.text', 'This is a link to about-dot-mattermost-dot-com').
|
||||
and('have.attr', 'href', 'https://mattermost.com/');
|
||||
cy.get('.markdown__link').eq(1).
|
||||
should('have.text', 'Markdown Link also to About page').
|
||||
and('have.attr', 'href', 'https://mattermost.com/');
|
||||
cy.wrap(el).should('contain', 'Normal Link:');
|
||||
cy.get('.markdown__link').eq(2).
|
||||
should('have.text', 'https://mattermost.com/').
|
||||
and('have.attr', 'href', 'https://mattermost.com/');
|
||||
cy.wrap(el).should('contain', 'Mail Link:');
|
||||
cy.get('.markdown__link').eq(3).
|
||||
should('have.text', 'Email').
|
||||
and('have.attr', 'href', 'mailto:mail@example.com');
|
||||
cy.wrap(el).should('contain', 'The following should be markdown formatted');
|
||||
cy.wrap(el).should('contain', '(mouse emoji, strawberry emoji, then formatting as indicated):');
|
||||
cy.get('.emoticon').eq(0).parent().
|
||||
should('have.html', `<span alt=":hamster:" class="emoticon" title=":hamster:" style="background-image: url("${baseUrl}/static/emoji/1f439.png");">:hamster:</span>`);
|
||||
cy.get('.emoticon').eq(1).parent().
|
||||
should('have.html', `<span alt=":strawberry:" class="emoticon" title=":strawberry:" style="background-image: url("${baseUrl}/static/emoji/1f353.png");">:strawberry:</span>`);
|
||||
cy.wrap(el).find('strong').should('have.text', 'bold');
|
||||
cy.wrap(el).find('em').should('have.text', 'italic');
|
||||
cy.wrap(el).find('strong').should('have.text', 'bold');
|
||||
cy.get('.codespan__pre-wrap').should('have.html', '<code>code</code>');
|
||||
cy.wrap(el).find('del').should('have.text', 'strike');
|
||||
cy.get('.mention-link').eq(0).should('have.text', '#hashtag');
|
||||
cy.get('.mention-link').eq(1).should('have.text', `@${otherUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getPayload(channel, user) {
|
||||
const text = `The following escaped characters should appear normally
|
||||
(ampersand, open angle, close angle): & < >
|
||||
The following should appear as links:
|
||||
<https://mattermost.com/|This is a link to about-dot-mattermost-dot-com>
|
||||
[Markdown Link also to About page](https://mattermost.com/)
|
||||
Normal Link: https://mattermost.com/
|
||||
Mail Link: <mailto:mail@example.com|Email>
|
||||
The following should be markdown formatted
|
||||
(mouse emoji, strawberry emoji, then formatting as indicated): 🐹 :strawberry: **bold** _italic_ \`code\` ~~strike~~ #hashtag
|
||||
The following should turn into a user mention and clicking it should open profile popover
|
||||
@${user.username}
|
||||
`;
|
||||
|
||||
return {
|
||||
channel: channel.name,
|
||||
username: 'new_username',
|
||||
text,
|
||||
icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png',
|
||||
};
|
||||
}
|
||||
@@ -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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let newIncomingHook;
|
||||
|
||||
before(() => {
|
||||
// # Create test team, channel, and webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: true,
|
||||
description: 'Test Webhook Description',
|
||||
display_name: 'Test Webhook Name',
|
||||
};
|
||||
|
||||
//# Create a new webhook
|
||||
cy.apiCreateWebhook(newIncomingHook);
|
||||
|
||||
// # Visit the webhook page
|
||||
cy.visit(`/${team.name}/integrations/incoming_webhooks`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T640 Cancel out of edit', () => {
|
||||
// # Make an edit to the webhook
|
||||
cy.findByText('Edit').click();
|
||||
cy.get('#displayName').type('name changed');
|
||||
cy.get('#description').type('description changed ');
|
||||
cy.get('#channelSelect').select('Town Square');
|
||||
cy.get('#channelLocked').uncheck();
|
||||
|
||||
//# Click cancel to cancel the edits
|
||||
cy.findByText('Cancel').click();
|
||||
|
||||
// # Assert the webhook's previous values are present
|
||||
cy.findAllByText(newIncomingHook.display_name).should('be.visible');
|
||||
cy.findAllByText(newIncomingHook.description).should('be.visible');
|
||||
cy.findByText('Delete').should('be.visible');
|
||||
cy.findByText('Edit').click();
|
||||
cy.get('#channelLocked').should('be.checked');
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
before(() => {
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnableIncomingWebhooks: true,
|
||||
},
|
||||
};
|
||||
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
// # Go to integrations
|
||||
cy.visit(`/${team.name}/integrations`);
|
||||
|
||||
// * Validate that incoming webhooks are enabled
|
||||
cy.get('#incomingWebhooks').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T637 Copy icon for Incoming Webhook URL', () => {
|
||||
const title = 'test-title';
|
||||
const description = 'test-description';
|
||||
const channel = 'Town Square';
|
||||
|
||||
cy.get('#incomingWebhooks').should('be.visible').click();
|
||||
|
||||
// # For this test purpose, fill in "Title", "Description" and select a "channel" with some test data
|
||||
cy.findByText('Add Incoming Webhook').should('be.visible').click();
|
||||
|
||||
cy.findByLabelText('Title').should('be.visible').type(title);
|
||||
|
||||
cy.findByLabelText('Description').should('be.visible').type(description);
|
||||
|
||||
cy.get('#channelSelect').should('be.visible').select(channel);
|
||||
|
||||
// # Scroll down and click "Save"
|
||||
cy.findByText('Save').should('be.visible').click();
|
||||
|
||||
cy.findByText('Setup Successful').should('be.visible');
|
||||
|
||||
// * You should see a "copy" icon to the right of the URL in the "Setup Successful" screen
|
||||
copyIconIsVisible('.backstage-form__confirmation');
|
||||
|
||||
// # Click "Done" in the "Setup Successful" screen
|
||||
cy.findByText('Done').should('be.visible').click();
|
||||
|
||||
// # You should see a "copy" icon to the right of the webhook's URL
|
||||
copyIconIsVisible('.item-details__url');
|
||||
});
|
||||
});
|
||||
|
||||
function copyIconIsVisible(element) {
|
||||
cy.get(element).within(() => {
|
||||
cy.get('.fa.fa-copy').
|
||||
should('be.visible').
|
||||
trigger('mouseover').
|
||||
should('have.attr', 'aria-describedby', 'copy');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
import {enableUsernameAndIconOverride} from './helpers';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let siteName;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T645 Delete Incoming Webhook', () => {
|
||||
// # Enable username and icon override at system console
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Go to test team/channel, open product menu and click "Integrations"
|
||||
cy.visit(`${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify that it redirects to integrations URL. Then, click "Incoming Webhooks"
|
||||
cy.url().should('include', `${testTeam.name}/integrations`);
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Incoming Webhooks').click();
|
||||
|
||||
// * Verify that it redirects to incoming webhooks URL. Then, click "Add Incoming Webhook"
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
cy.findByText('Add Incoming Webhook').click();
|
||||
|
||||
// * Verify that it redirects to where it can add incoming webhook
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks/add`);
|
||||
|
||||
// # Enter webhook details such as title, description and channel, then save
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
cy.get('#displayName').type('Webhook Title');
|
||||
cy.get('#description').type('Webhook Description');
|
||||
cy.get('#channelSelect').select(testChannel.display_name);
|
||||
cy.findByText('Save').scrollIntoView().click();
|
||||
});
|
||||
|
||||
// * Verify that it redirects to incoming webhook confirmation URL
|
||||
cy.url().should('include', `${testTeam.name}/integrations/confirm?type=incoming_webhooks&id=`).
|
||||
invoke('toString').then((hookConfirmationUrl) => {
|
||||
const hookId = hookConfirmationUrl.split('id=')[1];
|
||||
const hookUrl = `${Cypress.config('baseUrl')}/hooks/${hookId}`;
|
||||
|
||||
// * Verify that the hook ID in the URL matches with the one shown in a page
|
||||
// * Verify that the copy link is shown.
|
||||
cy.findByText(hookUrl).should('be.visible').
|
||||
parent().siblings('.fa-copy').should('be.visible');
|
||||
|
||||
// # Click "Done" and verify that it redirects to incoming webhooks URL
|
||||
cy.findByText('Done').click();
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
|
||||
// # Click back to site name and verify that it redirects to test team/channel
|
||||
cy.findByText(`Back to ${siteName}`).click();
|
||||
cy.url().should('include', `${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Post an incoming webhook and verify that it is posted in the channel
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
username: 'new-username',
|
||||
text: 'Setup for incoming webhook.',
|
||||
};
|
||||
cy.postIncomingWebhook({url: hookUrl, data: payload, waitFor: 'text'});
|
||||
|
||||
// * Verify the message is successfully posted as last post
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#${postId}_message`).should('exist').within(() => {
|
||||
// * Check if message text
|
||||
cy.findByText('Setup for incoming webhook.').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
// # Click 'Integrations' at product menu
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify that it redirects to integrations URL. Then, click "Incoming Webhooks"
|
||||
cy.url().should('include', `${testTeam.name}/integrations`);
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Incoming Webhooks').click();
|
||||
|
||||
// * Verify that it redirects to incoming webhooks URL. Then, click "Delete" and then confirm "Delete"
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
cy.findByText('Delete').click();
|
||||
cy.get('#confirmModalButton > span').click();
|
||||
|
||||
// # Click back to site name and verify that it redirects to test team/channel
|
||||
cy.findByText(`Back to ${siteName}`).click();
|
||||
cy.url().should('include', `${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Post an incoming webhook and verify that it is posted in the channel
|
||||
const payload1 = {
|
||||
channel: testChannel.name,
|
||||
username: 'new-username',
|
||||
text: 'after deleting incoming webhook.',
|
||||
};
|
||||
cy.task('postIncomingWebhook', {url: hookUrl, data: payload1}).then((res) => {
|
||||
// * Verify that it failed to post
|
||||
expect(res.status).equal(400);
|
||||
expect(res.data.message).equal('Invalid webhook.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
const maxDescription = '1234567890'.repeat(50);
|
||||
const overMaxDescription = `${maxDescription}123`;
|
||||
|
||||
let testTeam;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit the newly created test channel
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
// # Visit the Incoming Webhooks add page
|
||||
cy.visit(`/${team.name}/integrations/incoming_webhooks/add`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T636 Description field length check', () => {
|
||||
// * Check incoming description field only accepts 500 characters
|
||||
cy.get('#description').clear().type(maxDescription).should('have.value', maxDescription);
|
||||
cy.get('#description').clear().type(overMaxDescription).should('have.value', maxDescription);
|
||||
|
||||
// * Check outgoing description field only accepts 500 characters
|
||||
cy.visit(`/${testTeam.name}/integrations/outgoing_webhooks/add`);
|
||||
cy.get('#description').clear().type(maxDescription).should('have.value', maxDescription);
|
||||
cy.get('#description').clear().type(overMaxDescription).should('have.value', maxDescription);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
import {getRandomId} from '../../../../utils';
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
import {enableUsernameAndIconOverride} from './helpers';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let sysadmin;
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let testUser;
|
||||
let incomingWebhook;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetMe().then(({user}) => {
|
||||
sysadmin = user;
|
||||
});
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
testUser = user;
|
||||
|
||||
const newHook = {
|
||||
channel_id: testChannel.id,
|
||||
channel_locked: true,
|
||||
description: 'Incoming webhook - override',
|
||||
display_name: `incoming-override-${getRandomId()}`,
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T622 Disallow override of username and profile picture', () => {
|
||||
const iconUrl = 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png';
|
||||
|
||||
// # Enable username and icon override
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Login as test user, visit test channel and post any message
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', testChannel.display_name);
|
||||
cy.postMessage('a');
|
||||
|
||||
// # Post an incoming webhook
|
||||
const payload1 = getPayload(testChannel, iconUrl);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload1, waitFor: 'text'});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
// * Verify that the message is posted via incoming webhook
|
||||
cy.findByText(payload1.text).should('be.visible');
|
||||
|
||||
// * Verify that the username is overridden per webhook payload
|
||||
cy.get('.post__header').find('.user-popover').should('have.text', payload1.username);
|
||||
|
||||
// * Verify that the user icon is overridden per webhook payload
|
||||
const encodedIconUrl = encodeURIComponent(payload1.icon_url);
|
||||
cy.get('.profile-icon > img').should('have.attr', 'src', `${Cypress.config('baseUrl')}/api/v4/image?url=${encodedIconUrl}`);
|
||||
});
|
||||
|
||||
// # Disable username and icon override
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(false);
|
||||
|
||||
// # Login as test user, visit test channel and post any message
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.get('#channelHeaderTitle', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', testChannel.display_name);
|
||||
cy.postMessage('b');
|
||||
|
||||
// # Post another incoming webhook
|
||||
const payload2 = getPayload(testChannel, iconUrl);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload2, waitFor: 'text'});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
// * Verify that another message is posted via incoming webhook
|
||||
cy.findByText(payload2.text).should('be.visible');
|
||||
|
||||
// * Verify that the username shown is of the webhook creator and override is not allowed.
|
||||
cy.get('.post__header').find('.user-popover').should('have.text', sysadmin.username);
|
||||
|
||||
// * Verify that the user icon shown is of the webhook creator and override is not allowed.
|
||||
cy.get('.profile-icon > img').should('have.attr', 'src', `${Cypress.config('baseUrl')}/api/v4/users/${sysadmin.id}/image?_=0`);
|
||||
});
|
||||
|
||||
// * Verify previous webhook message if new setting is respected.
|
||||
cy.uiGetNthPost(-3).within(() => {
|
||||
// * Verify that the post is from previous webhook message.
|
||||
cy.findByText(payload1.text).should('be.visible');
|
||||
|
||||
// * Verify that the username shown is updated as webhook creator and override didn't take effect.
|
||||
cy.get('.post__header').find('.user-popover').should('have.text', sysadmin.username);
|
||||
|
||||
// * Verify that the user icon shown is updated as webhook creator and override didn't take effect.
|
||||
cy.get('.profile-icon > img').should('have.attr', 'src', `${Cypress.config('baseUrl')}/api/v4/users/${sysadmin.id}/image?_=0`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getPayload(channel, iconUrl) {
|
||||
return {
|
||||
channel: channel.name,
|
||||
username: 'user-overriden',
|
||||
icon_url: iconUrl,
|
||||
text: `${getRandomId()} - this is from incoming webhook.`,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let incomingWebhook;
|
||||
let testTeam;
|
||||
|
||||
before(() => {
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: true,
|
||||
description: 'Incoming webhook - Event: Editing Webhook',
|
||||
display_name: 'editing-webhook',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T641 Edit incoming webhook, webhook posts attachment', () => {
|
||||
cy.intercept('GET', '**api/v4/channels/**').as('channels');
|
||||
cy.intercept('GET', '**/api/v4/**').as('networkCalls');
|
||||
|
||||
// # Go to test team/channel, open product menu and click "Integrations"
|
||||
cy.visit(`${testTeam.name}/channels/town-square`);
|
||||
cy.wait('@channels');
|
||||
cy.wait('@networkCalls');
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify that it redirects to integrations URL. Then, click "Incoming Webhooks"
|
||||
cy.url().should('include', `${testTeam.name}/integrations`);
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Incoming Webhooks').click();
|
||||
|
||||
// * Verify that it redirects to incoming webhooks URL. Then, click "Add Incoming Webhook"
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
cy.findByText('Edit').click();
|
||||
|
||||
// # Change the channel from Off Topic to another channel that you have access to, then click "Update"
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
cy.get('#channelSelect').select('Town Square');
|
||||
cy.findByRole('button', {name: 'Update'}).scrollIntoView().click();
|
||||
});
|
||||
|
||||
// # Redirect to test team/channel
|
||||
cy.visit(`${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Post an incoming webhook and verify that it is posted in the channel
|
||||
const payload = {
|
||||
channel: 'town-square',
|
||||
username: 'new-username',
|
||||
attachments: [{fallback: 'fallback text', pretext: 'Optional text that appears above the attachment block', author_name: 'Authors Name', author_link: 'http://mattermost.org', author_icon: 'http://www.mattermost.org/wp-content/uploads/2016/04/icon.png', text: 'This is the text of the attachment. It should appear just above the image. \nIts very long, so it makes the text collapse behind a \\"Show More\\" button. If you click \\"Show More\\" the text should expand, and then if you click "Show Less" it should collapse again. The rest of the attachment should include one image of a graph and one thumbnail image of the Mattermost logo on the right hand side of the attachment. It should also include additional fields below the image that are formatted more like a table, in two columns. The left border of the attachment should be colored green. At the top of the attachment, there should be an author name followed by a bolded title. Both the author name and the title should be hyperlinks.', thumb_url: 'http://www.mattermost.org/wp-content/uploads/2016/04/icon.png', title: 'Testing Integration Attachments', title_link: 'https://www.google.com', color: '#00ff00', image_url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/ScientificGraphSpeedVsTime.svg/2000px-ScientificGraphSpeedVsTime.svg.png', fields: [{short: false, title: 'Area', value: 'Testing with a very long piece of text that will take up the whole width of the table. And then some more space even because it is really not a short field.'}, {short: true, title: 'Iteration', value: 'Testing'}, {short: true, title: 'State', value: 'New'}, {short: false, title: 'Reason', value: 'New defect reported'}]}],
|
||||
};
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.findByRole('link', {name: 'Testing Integration Attachments', hidden: true});
|
||||
cy.get('.attachment__image').should('be.visible');
|
||||
cy.get(':nth-child(2) > thead > tr > .attachment-field__caption').should('have.text', 'Area');
|
||||
cy.get(':nth-child(3) > thead > tr > :nth-child(1)').should('have.text', 'Iteration');
|
||||
cy.get('thead > tr > :nth-child(2)').should('have.text', 'State');
|
||||
cy.get(':nth-child(4) > thead > tr > .attachment-field__caption').should('have.text', 'Reason');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
// ***************************************************************
|
||||
|
||||
export function enableUsernameAndIconOverride(enable) {
|
||||
enableUsernameAndIconOverrideInt(enable, enable);
|
||||
}
|
||||
|
||||
export function enableUsernameAndIconOverrideInt(enableUsername, enableIcon) {
|
||||
// # Visit integration management at system console and change override values
|
||||
cy.visit('/admin_console/integrations/integration_management');
|
||||
cy.findByTestId('ServiceSettings.EnablePostUsernameOverride' + enableUsername).check({force: true});
|
||||
cy.findByTestId('ServiceSettings.EnablePostIconOverride' + enableIcon).check({force: true});
|
||||
|
||||
// # Save the settings
|
||||
cy.get('#saveSetting').should('be.enabled').click({force: true});
|
||||
cy.get('#saveSetting').should('be.disabled');
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
import {getRandomId} from '../../../../utils';
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
const inAppUsername = 'in-app';
|
||||
const inAppIconURL = 'https://pbs.twimg.com/profile_images/3303520670/4da3468b30495a5d73e6f31df068e5c9.jpeg';
|
||||
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let sysadmin;
|
||||
let incomingWebhook;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetMe().then(({user}) => {
|
||||
sysadmin = user;
|
||||
});
|
||||
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostUsernameOverride: true,
|
||||
EnablePostIconOverride: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: true,
|
||||
description: 'Incoming webhook - in-app override',
|
||||
display_name: 'in-app-override',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
|
||||
editIncomingWebhook(incomingWebhook.id, team.name, inAppUsername, inAppIconURL);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('MM-T620 Payload username and profile picture override in-app settings', () => {
|
||||
// # Post an incoming webhook with username and profile icon URL
|
||||
const payload = getPayload(testChannel, true);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
// # Click test channel on sidebar
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).should('be.visible').click({force: true});
|
||||
|
||||
// # Wait for the webhook message to get posted
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes(payload.text));
|
||||
}));
|
||||
|
||||
// * Verify that the username and profile icon are overridden per webhook payload
|
||||
verifyLastPost(sysadmin, payload.username, payload.icon_url);
|
||||
});
|
||||
|
||||
it('MM-T621 Override username and profile picture - remove overrides from payload', () => {
|
||||
// # Post an incoming webhook without username and profile icon URL
|
||||
const payload = getPayload(testChannel, false);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
// # Click test channel on sidebar
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).should('be.visible').click({force: true});
|
||||
|
||||
// # Wait for the webhook message to get posted
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes(payload.text));
|
||||
}));
|
||||
|
||||
// * Verify that the username and profile icon are based from webhook settings
|
||||
verifyLastPost(sysadmin, inAppUsername, inAppIconURL);
|
||||
});
|
||||
});
|
||||
|
||||
function editIncomingWebhook(incomingWebhookId, teamName, inAppUsername, inAppIconURL) {
|
||||
// # Edit incoming webhook
|
||||
cy.visit(`/${teamName}/integrations/incoming_webhooks/edit?id=${incomingWebhookId}`);
|
||||
cy.get('.backstage-header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').within(() => {
|
||||
cy.findByText('Incoming Webhooks').should('be.visible');
|
||||
cy.findByText('Edit').should('be.visible');
|
||||
});
|
||||
|
||||
// # Enter username and profile icon URL
|
||||
cy.findByLabelText('Username').should('exist').type(inAppUsername);
|
||||
cy.findByLabelText('Profile Picture').should('exist').type(inAppIconURL);
|
||||
|
||||
// # Click update and verify it redirects to incoming webhook page
|
||||
cy.findByText('Update').click();
|
||||
cy.url().should('include', `/${teamName}/integrations/incoming_webhooks`).wait(TIMEOUTS.ONE_SEC);
|
||||
}
|
||||
|
||||
function getPayload(channel, withUsernameAndProfileIcon) {
|
||||
const payload = {
|
||||
channel: channel.name,
|
||||
text: `${getRandomId()} - this is from incoming webhook`,
|
||||
};
|
||||
|
||||
if (!withUsernameAndProfileIcon) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return {
|
||||
...payload,
|
||||
username: 'payload_username',
|
||||
icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png',
|
||||
};
|
||||
}
|
||||
|
||||
function verifyLastPost(owner, username, iconUrl) {
|
||||
cy.getLastPost().within(() => {
|
||||
// * Verify username in post header
|
||||
cy.get('.post__header').find('.user-popover').as('usernameForPopover').should('have.text', username);
|
||||
|
||||
// * Verify profile icon in post header
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
const encodedIconUrl = encodeURIComponent(iconUrl);
|
||||
cy.get('.profile-icon > img').as('profileIconForPopover').should('have.attr', 'src', `${baseUrl}/api/v4/image?url=${encodedIconUrl}`);
|
||||
|
||||
// * Verify that the BOT label appears
|
||||
cy.get('.Tag').should('be.visible').and('have.text', 'BOT');
|
||||
|
||||
// * Verify that there's no status indicator
|
||||
cy.get('.status').should('not.exist');
|
||||
});
|
||||
|
||||
// # Click on username and verify profile popover
|
||||
cy.get('@usernameForPopover').click();
|
||||
verifyProfilePopover(owner, username, iconUrl);
|
||||
|
||||
// # Press escape key to close profile popover
|
||||
cy.get('body').typeWithForce('{esc}');
|
||||
|
||||
// # Click on profile icon and verify profile popover
|
||||
cy.get('@profileIconForPopover').click();
|
||||
verifyProfilePopover(owner, username, iconUrl);
|
||||
}
|
||||
|
||||
function verifyProfilePopover(owner, username, iconUrl) {
|
||||
// * Verify that the profile popover is shown
|
||||
cy.get('#user-profile-popover').should('be.visible').within(() => {
|
||||
// * Verify username from payload
|
||||
cy.get('.user-profile-popover__heading').should('be.visible').and('have.text', username);
|
||||
|
||||
// * Verify icon URL from payload
|
||||
cy.get('.Avatar').should('have.attr', 'src', iconUrl);
|
||||
|
||||
// * Verify that it matches with correct footer
|
||||
cy.get('.popover__row').should('be.visible').and('have.text', `This post was created by an integration from @${owner.username}`);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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
|
||||
// Stage: @dev
|
||||
// Group: @channels @incoming_webhook
|
||||
|
||||
import {enableUsernameAndIconOverride} from './helpers';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
const incomingWebhookText = 'This is a message to a newly created direct message channel';
|
||||
let incomingWebhookConfiguration;
|
||||
let incomingWebhook;
|
||||
let generatedTeam;
|
||||
let generatedChannel;
|
||||
let generatedUser;
|
||||
|
||||
before(() => {
|
||||
// # Enable username override
|
||||
enableUsernameAndIconOverride(true, false);
|
||||
|
||||
// # Create a new user, their team, and a channel to tie the webhook to
|
||||
cy.apiInitSetup({userPrefix: 'mm-t639-'}).then(({team, channel, user}) => {
|
||||
generatedTeam = team;
|
||||
generatedChannel = channel;
|
||||
generatedUser = user;
|
||||
incomingWebhookConfiguration = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: false,
|
||||
display_name: 'webhook',
|
||||
};
|
||||
cy.apiCreateWebhook(incomingWebhookConfiguration).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
}).then(() => {
|
||||
// # Send webhook notification
|
||||
const webhookPayload = {channel: `@${generatedUser.username}`, text: incomingWebhookText};
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: webhookPayload});
|
||||
}).then(() => {
|
||||
// # Open any page to get to the sidebar
|
||||
cy.visit(`/${generatedTeam.name}/channels/${generatedChannel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T639 🚀 incoming Webhook creates DM', () => {
|
||||
// # Verify that the channel was created correctly with an unread message, and open it
|
||||
cy.uiGetLHS().
|
||||
contains(generatedUser.username).
|
||||
should('have.class', 'unread-title').
|
||||
click();
|
||||
cy.getLastPost().within(($post) => {
|
||||
cy.wrap($post).contains(incomingWebhookConfiguration.display_name).should('be.visible');
|
||||
cy.wrap($post).contains('BOT').should('be.visible');
|
||||
cy.wrap($post).contains(incomingWebhookText).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 @incoming_webhook
|
||||
|
||||
import {enableUsernameAndIconOverride} from './helpers';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let testTeam;
|
||||
let siteName;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T625 Incoming webhook is image only', () => {
|
||||
// # Enable username and icon override at system console
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Go to test team/channel, open product menu and click "Integrations"
|
||||
cy.visit(`${testTeam.name}/channels/off-topic`);
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify that it redirects to integrations URL. Then, click "Incoming Webhooks"
|
||||
cy.url().should('include', `${testTeam.name}/integrations`);
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Incoming Webhooks').click();
|
||||
|
||||
// * Verify that it redirects to incoming webhooks URL. Then, click "Add Incoming Webhook"
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
cy.findByText('Add Incoming Webhook').click();
|
||||
|
||||
// * Verify that it redirects to where it can add incoming webhook
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks/add`);
|
||||
|
||||
// # Enter webhook details such as title, description and channel, then save
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
cy.get('#displayName').type('Webhook Title');
|
||||
cy.get('#description').type('Webhook Description');
|
||||
cy.get('#channelSelect').select('Off-Topic');
|
||||
cy.findByText('Save').scrollIntoView().click();
|
||||
});
|
||||
|
||||
// * Verify that it redirects to incoming webhook confirmation URL
|
||||
cy.url().should('include', `${testTeam.name}/integrations/confirm?type=incoming_webhooks&id=`).
|
||||
invoke('toString').then((hookConfirmationUrl) => {
|
||||
const hookId = hookConfirmationUrl.split('id=')[1];
|
||||
const hookUrl = `${Cypress.config('baseUrl')}/hooks/${hookId}`;
|
||||
|
||||
// * Verify that the hook ID in the URL matches with the one shown in a page
|
||||
// * Verify that the copy link is shown.
|
||||
cy.findByText(hookUrl).should('be.visible').
|
||||
parent().siblings('.fa-copy').should('be.visible');
|
||||
|
||||
// # Click "Done" and verify that it redirects to incoming webhooks URL
|
||||
cy.findByText('Done').click();
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
|
||||
// # Click back to site name and verify that it redirects to test team/channel
|
||||
cy.findByText(`Back to ${siteName}`).click();
|
||||
cy.url().should('include', `${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Post an incoming webhook and verify that it is posted in the channel
|
||||
const payload = {
|
||||
channel: 'off-topic',
|
||||
username: 'new-username',
|
||||
attachments: [{image_url: 'https://cdn.pixabay.com/photo/2017/10/10/22/24/wide-format-2839089_960_720.jpg'}],
|
||||
};
|
||||
cy.postIncomingWebhook({url: hookUrl, data: payload});
|
||||
cy.get('.attachment__image').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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let teamA;
|
||||
let teamB;
|
||||
let newIncomingHook;
|
||||
|
||||
before(() => {
|
||||
// # Login, create incoming webhook for Team A
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
teamA = team.name;
|
||||
newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
display_name: 'Team A Webhook',
|
||||
};
|
||||
|
||||
//# Create a new webhook for Team A
|
||||
cy.apiCreateWebhook(newIncomingHook);
|
||||
});
|
||||
|
||||
// # Login, create incoming webhook for Team B
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
teamB = team.name;
|
||||
newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
display_name: 'Team B Webhook',
|
||||
};
|
||||
|
||||
// # Create a new webhook for Team B
|
||||
cy.apiCreateWebhook(newIncomingHook);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T644 Integrations display on team where they were created', () => {
|
||||
// # Visit Test Team B Incoming Webhooks page
|
||||
cy.visit(`/${teamB}/integrations/incoming_webhooks`);
|
||||
|
||||
// * Assert the page contains only Team B Outgoing Webhook
|
||||
cy.findByText('Team B Webhook').and('does.not.contain', 'Team A Webhook');
|
||||
|
||||
// # Visit Team A Incoming Webhooks page
|
||||
cy.visit(`/${teamA}/integrations/incoming_webhooks`);
|
||||
|
||||
// * Assert the page contains only Team A Outgoing Webhook
|
||||
cy.findByText('Team A Webhook').and('does.not.contain', 'Team B Webhook');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
describe('Integrations/Incoming Webhook', () => {
|
||||
let incomingWebhook;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
// # Let the webhook have its own icon
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostIconOverride: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: true,
|
||||
description: 'Incoming webhook - Viewing attachments with invalid URL does not cause the application to crash',
|
||||
display_name: 'invalid_attachment_URL_webhook',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
});
|
||||
});
|
||||
it('MM-T624 Viewing attachments with invalid URL does not cause the application to crash', () => {
|
||||
// # Post the incoming webhook with bad image URL
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
text: 'The image below should be broken due to the invalid URL in the payload text you just sent.',
|
||||
attachments: [{
|
||||
fallback: 'Testing viewing attachments with invalid URL does not cause the application to crash.',
|
||||
pretext: 'Testing viewing attachments with invalid URL does not cause the application to crash.',
|
||||
image_url: 'https://example.com',
|
||||
}],
|
||||
icon_url: 'http://www.mattermost.org/wp-content/uploads/2016/04/icon_WS.png',
|
||||
};
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload, waitFor: 'attachment-pretext'});
|
||||
|
||||
// * Check that the message has sent and that the body is viewable
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes('The image below should be broken due to the invalid URL in the payload text you just sent.'));
|
||||
}));
|
||||
cy.getLastPostId().then((postId) => {
|
||||
const postMessageId = `#${postId}_message`;
|
||||
cy.get(postMessageId).within(() => {
|
||||
cy.get('.attachment__image').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let newIncomingHook;
|
||||
let incomingWebhook;
|
||||
|
||||
before(() => {
|
||||
// # Create new setup
|
||||
cy.apiInitSetup().then(({user}) => {
|
||||
testUser = user;
|
||||
|
||||
// # Login as the new user
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # Create a new team with the new user
|
||||
cy.apiCreateTeam('test-team', 'Team Testers').then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
// # Create a new test channel for the team
|
||||
cy.apiCreateChannel(testTeam.id, 'test-channel', 'Testers Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
// # Declare web-hook values
|
||||
newIncomingHook = {
|
||||
channel_id: testChannel.id,
|
||||
channel_locked: true,
|
||||
description: 'Test Webhook Description',
|
||||
display_name: 'Test Webhook Name',
|
||||
};
|
||||
|
||||
//# Create a new webhook
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
|
||||
// # Visit the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T643 Incoming webhook:Long URL for embedded image', () => {
|
||||
const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const queries = letters.split('').reduce((acc, letter) => {
|
||||
const newValue = acc + `&${letter}=${letters}`;
|
||||
return newValue;
|
||||
}, '');
|
||||
const url = `http://via.placeholder.com/300.png?expires=213134234234234234234234${queries}`;
|
||||
const payload = getPayload(testChannel, url);
|
||||
|
||||
// # Post the webhook message
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
// * Assert that the message was posted
|
||||
cy.uiWaitUntilMessagePostedIncludes('Hey attachments');
|
||||
cy.getLastPostId().then(() => {
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
const encodedUrl = `${baseUrl}/api/v4/image?url=${encodeURIComponent(url)}`;
|
||||
|
||||
// * Assert that file image is present
|
||||
cy.findByLabelText('file thumbnail').should('be.visible').and('have.attr', 'src', encodedUrl);
|
||||
|
||||
// * Assert that the Show More button is visible
|
||||
cy.findByText('Show more').should('be.visible').click();
|
||||
|
||||
// * Assert that the Show less button is visible
|
||||
cy.findByText('Show less').scrollIntoView().should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getPayload(channel, url) {
|
||||
const text = `Hey attachments .${'Lorem ipsum dolor '.repeat(240)}.`;
|
||||
|
||||
return {
|
||||
channel: channel.name,
|
||||
text,
|
||||
};
|
||||
}
|
||||
@@ -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.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @incoming_webhook
|
||||
|
||||
import {getRandomId} from '../../../../utils';
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let testTeam;
|
||||
let incomingWebhook;
|
||||
|
||||
before(() => {
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: false,
|
||||
description: 'Incoming webhook - setting',
|
||||
display_name: 'webhook-setting',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T623 Lock to this channel on webhook configuration works', () => {
|
||||
cy.apiCreateChannel(testTeam.id, 'other-channel', 'Other Channel').then(({channel}) => {
|
||||
// # Post the first incoming webhook
|
||||
const payload1 = getPayload(channel);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload1});
|
||||
|
||||
// # Switch to other channel and wait for the first webhook message to get posted successfully
|
||||
switchToChannel(testTeam.name, channel.name);
|
||||
waitUntilWebhookPosted(payload1.text);
|
||||
|
||||
// # Edit webhook to lock into the test channel
|
||||
editIncomingWebhook(incomingWebhook.id, testTeam.name, true);
|
||||
|
||||
const payload2 = getPayload(channel);
|
||||
|
||||
// # Try to post a second incoming webhook
|
||||
cy.task('postIncomingWebhook', {url: incomingWebhook.url, data: payload2}).then((res) => {
|
||||
// * Verify that it failed to post
|
||||
expect(res.status).equal(403);
|
||||
expect(res.data.message).equal('This webhook is not permitted to post to the requested channel.');
|
||||
});
|
||||
|
||||
// # Edit webhook to not lock into the test channel
|
||||
editIncomingWebhook(incomingWebhook.id, testTeam.name, false);
|
||||
|
||||
// # Retry posting the second incoming webhook
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload2});
|
||||
|
||||
// # Switch to other channel and wait for the second webhook message to get posted
|
||||
switchToChannel(testTeam.name, channel.name);
|
||||
waitUntilWebhookPosted(payload2.text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function switchToChannel(teamName, channelName) {
|
||||
cy.visit(`/${teamName}/channels/town-square`);
|
||||
cy.get(`#sidebarItem_${channelName}`, {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click({force: true});
|
||||
}
|
||||
|
||||
function editIncomingWebhook(incomingWebhookId, teamName, lockToChannel) {
|
||||
cy.visit(`/${teamName}/integrations/incoming_webhooks/edit?id=${incomingWebhookId}`);
|
||||
cy.get('.backstage-header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').within(() => {
|
||||
cy.findByText('Incoming Webhooks').should('be.visible');
|
||||
cy.findByText('Edit').should('be.visible');
|
||||
});
|
||||
|
||||
// # Check or uncheck "Lock to this channel"
|
||||
cy.findByLabelText('Lock to this channel').should('exist').as('lockChannel');
|
||||
if (lockToChannel) {
|
||||
cy.get('@lockChannel').check();
|
||||
} else {
|
||||
cy.get('@lockChannel').uncheck();
|
||||
}
|
||||
|
||||
// # Click update and verify it redirects to incoming webhook page
|
||||
cy.findByText('Update').click();
|
||||
cy.url().should('include', `/${teamName}/integrations/incoming_webhooks`).wait(TIMEOUTS.ONE_SEC);
|
||||
}
|
||||
|
||||
function getPayload(channel) {
|
||||
return {
|
||||
channel: channel.name,
|
||||
text: `${getRandomId()} - this is from incoming webhook`,
|
||||
};
|
||||
}
|
||||
|
||||
function waitUntilWebhookPosted(text) {
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes(text));
|
||||
}));
|
||||
}
|
||||
@@ -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 @incoming_webhook
|
||||
|
||||
import {enableUsernameAndIconOverride} from './helpers';
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let siteName;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2029 Setup for incoming webhook', () => {
|
||||
// # Enable username and icon override at system console
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Go to test team/channel, open product menu and click "Integrations"
|
||||
cy.visit(`${testTeam.name}/channels/${testChannel.name}`);
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify that it redirects to integrations URL. Then, click "Incoming Webhooks"
|
||||
cy.url().should('include', `${testTeam.name}/integrations`);
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Incoming Webhooks').click();
|
||||
|
||||
// * Verify that it redirects to incoming webhooks URL. Then, click "Add Incoming Webhook"
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
cy.findByText('Add Incoming Webhook').click();
|
||||
|
||||
// * Verify that it redirects to where it can add incoming webhook
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks/add`);
|
||||
|
||||
// # Enter webhook details such as title, description and channel, then save
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
cy.get('#displayName').type('Webhook Title');
|
||||
cy.get('#description').type('Webhook Description');
|
||||
cy.get('#channelSelect').select(testChannel.display_name);
|
||||
cy.findByText('Save').scrollIntoView().click();
|
||||
});
|
||||
|
||||
// * Verify that it redirects to incoming webhook confirmation URL
|
||||
cy.url().should('include', `${testTeam.name}/integrations/confirm?type=incoming_webhooks&id=`).
|
||||
invoke('toString').then((hookConfirmationUrl) => {
|
||||
const hookId = hookConfirmationUrl.split('id=')[1];
|
||||
const hookUrl = `${Cypress.config('baseUrl')}/hooks/${hookId}`;
|
||||
|
||||
// * Verify that the hook ID in the URL matches with the one shown in a page
|
||||
// * Verify that the copy link is shown.
|
||||
cy.findByText(hookUrl).should('be.visible').
|
||||
parent().siblings('.fa-copy').should('be.visible');
|
||||
|
||||
// # Click "Done" and verify that it redirects to incoming webhooks URL
|
||||
cy.findByText('Done').click();
|
||||
cy.url().should('include', `${testTeam.name}/integrations/incoming_webhooks`);
|
||||
|
||||
// # Click back to site name and verify that it redirects to test team/channel
|
||||
cy.findByText(`Back to ${siteName}`).click();
|
||||
cy.url().should('include', `${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Post an incoming webhook and verify that it is posted in the channel
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
username: 'new-username',
|
||||
text: 'Setup for incoming webhook.',
|
||||
};
|
||||
cy.postIncomingWebhook({url: hookUrl, data: payload, waitFor: 'text'});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,329 @@
|
||||
// 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 @incoming_webhook
|
||||
|
||||
describe('Incoming webhook', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let incomingWebhook;
|
||||
let offTopicLink;
|
||||
let sysadminUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostUsernameOverride: true,
|
||||
EnablePostIconOverride: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Create and visit new channel and create incoming webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
channel_locked: false,
|
||||
description: 'Incoming webhook - basic formatting',
|
||||
display_name: 'basic-formatting',
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
|
||||
offTopicLink = `/${team.name}/channels/off-topic`;
|
||||
});
|
||||
|
||||
cy.apiGetMe().then((me) => {
|
||||
sysadminUser = me.user;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
|
||||
describe('MM-T626 Incoming webhook is only image and fallback text', () => {
|
||||
const id = 'MM-T626';
|
||||
|
||||
const baseUrl = Cypress.config('baseUrl');
|
||||
const imageUrl = 'https://cdn.pixabay.com/photo/2017/10/10/22/24/wide-format-2839089_960_720.jpg';
|
||||
const imageSrc = `${baseUrl}/api/v4/image?url=${encodeURIComponent(imageUrl)}`;
|
||||
|
||||
it('first payload', () => {
|
||||
const search = id + '-1';
|
||||
const payload = {text: search, attachments: [{fallback: 'fallback text', image_url: imageUrl}]};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').should('have.text', search);
|
||||
cy.get('.attachment__image').should('have.attr', 'src', imageSrc);
|
||||
});
|
||||
});
|
||||
|
||||
it('second payload', () => {
|
||||
const search = id + '-2';
|
||||
const payload = {channel: 'off-topic', text: search, username: 'new_username', attachments: [{fallback: 'fallback text', image_url: imageUrl}], icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png'};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
cy.visit(offTopicLink);
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').should('have.text', search);
|
||||
cy.get('.attachment__image').should('have.attr', 'src', imageSrc);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MM-T627 Images with tall and wide aspect ratios appear correctly', () => {
|
||||
const id = 'MM-T627';
|
||||
|
||||
it('wide image', () => {
|
||||
const search = id + '-wide';
|
||||
const payload = {text: search, attachments: [{image_url: 'https://cdn.pixabay.com/photo/2017/10/10/22/24/wide-format-2839089_960_720.jpg'}]};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
// Original image is 960x246. 960/246 = ~3.9. Let's make sure image is rendered with 3.9 +/- 0.1
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').should('have.text', search);
|
||||
const originalWidth = 960;
|
||||
const originalHeight = 246;
|
||||
const aspectRatio = originalWidth / originalHeight;
|
||||
cy.get('img.attachment__image').should('be.visible').and((img) => {
|
||||
expect(img.width() / img.height()).to.be.closeTo(aspectRatio, 0.05);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('tall image', () => {
|
||||
const search = id + '-tall';
|
||||
const payload = {text: search, attachments: [{image_url: 'https://media.npr.org/programs/atc/features/2009/may/short/abetall3-0483922b5fb40887fc9fbe20a606e256cbbd10ee-s800-c85.jpg'}]};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').should('have.text', search);
|
||||
const originalWidth = 385;
|
||||
const originalHeight = 916;
|
||||
const aspectRatio = originalWidth / originalHeight;
|
||||
cy.get('img.attachment__image').should('be.visible').and((img) => {
|
||||
expect(img.width() / img.height()).to.be.closeTo(aspectRatio, 0.05);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T628 Incoming webhook supports Slack-style mentions', () => {
|
||||
const id = 'MM-T628';
|
||||
const text = `${id}: <!here> <!channel>`;
|
||||
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
username: 'new_username',
|
||||
text,
|
||||
icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png',
|
||||
};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.waitUntil(() => cy.getLastPost().then((el) => {
|
||||
const postedMessageEl = el.find('.post-message__text > p')[0];
|
||||
return Boolean(postedMessageEl && postedMessageEl.textContent.includes(id));
|
||||
}));
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').within(() => {
|
||||
cy.get('.mention--highlight').eq(0).should('have.text', '@here');
|
||||
cy.get('.mention--highlight').eq(1).should('have.text', '@channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T629 Incoming webhook with Slack attachment, mention in `pretext`', () => {
|
||||
const id = 'MM-T629';
|
||||
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
attachments: [{type: 'slack_attachment',
|
||||
color: '#7CD197',
|
||||
fields: [{short: false, title: 'Area', value: "Testing with a very long piece of text that will take up the whole width of the table (stopping short of the space where the thumbnail image is displayed). This is one more sentence to really make it a long field, and let's add a taco emoji :taco:."}, {short: true, title: 'Iteration', value: 'Testing'}, {short: true, title: 'State', value: 'New'}, {short: false, title: 'Reason', value: 'New defect reported'}, {short: false, title: 'Random field', value: 'This is a field which is not marked as short so it should be rendered on a separate row'}, {short: true, title: 'Short 1', value: 'Short field'}, {short: true, title: 'Short 2', value: 'Another one'}, {short: true, title: 'Field with link', value: '<http://example.com|Link>'}],
|
||||
mrkdwn_in: ['pretext'],
|
||||
pretext: `${id} <@${sysadminUser.id}> Some text here to look at (verify eyes emoji) :eyes:`,
|
||||
text: 'This is the text of the attachment. There should be a small Jenkins thumbnail off to the right.',
|
||||
thumb_url: 'https://slack.global.ssl.fastly.net/7bf4/img/services/jenkins-ci_128.png',
|
||||
title: 'A slack attachment',
|
||||
title_link: 'https://www.google.com'}],
|
||||
};
|
||||
|
||||
cy.visit(offTopicLink);
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).find('#unreadMentions').should('have.text', '1');
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).click({force: true});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.attachment__thumb-pretext').should('contain', id);
|
||||
cy.get('.attachment__thumb-pretext a.mention-link').should('have.text', '@sysadmin');
|
||||
cy.get('.attachment__thumb-pretext span[data-emoticon="eyes"]').should('exist');
|
||||
|
||||
cy.get('a.attachment__title-link[href="https://www.google.com"]').should('have.text', 'A slack attachment');
|
||||
cy.get('.attachment-field a.markdown__link[href="http://example.com"]').should('have.text', 'Link');
|
||||
cy.get('.attachment-field span[data-emoticon="taco"]').should('exist');
|
||||
|
||||
cy.get('.attachment__thumb-container .file-preview__button img').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T630 Incoming webhook with Slack attachment, mention in attachment `text`', () => {
|
||||
const id = 'MM-T630';
|
||||
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
attachments: [{type: 'slack_attachment',
|
||||
color: '#7CD197',
|
||||
fields: [{short: false, title: 'Area', value: "Testing with a very long piece of text that will take up the whole width of the table (stopping short of the space where the thumbnail image is displayed). This is one more sentence to really make it a long field, and let's add a taco emoji :taco:."}, {short: true, title: 'Iteration', value: 'Testing'}, {short: true, title: 'State', value: 'New'}, {short: false, title: 'Reason', value: 'New defect reported'}, {short: false, title: 'Random field', value: 'This is a field which is not marked as short so it should be rendered on a separate row'}, {short: true, title: 'Short 1', value: 'Short field'}, {short: true, title: 'Short 2', value: 'Another one'}, {short: true, title: 'Field with link', value: '<http://example.com|Link>'}],
|
||||
mrkdwn_in: ['pretext'],
|
||||
pretext: `${id} Some text here to look at (verify eyes emoji) :eyes:`,
|
||||
text: `This is the text of the attachment. <@${sysadminUser.id}>, There should be a small Jenkins thumbnail off to the right.`,
|
||||
thumb_url: 'https://slack.global.ssl.fastly.net/7bf4/img/services/jenkins-ci_128.png',
|
||||
title: 'A slack attachment',
|
||||
title_link: 'https://www.google.com'}],
|
||||
};
|
||||
|
||||
cy.visit(offTopicLink);
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).find('#unreadMentions').should('have.text', '1');
|
||||
cy.get(`#sidebarItem_${testChannel.name}`).click({force: true});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.attachment__thumb-pretext').should('contain', id);
|
||||
cy.get('.attachment__thumb-pretext span[data-emoticon="eyes"]').should('exist');
|
||||
|
||||
cy.get('.attachment__body .post-message__text-container a.mention-link').should('have.text', '@sysadmin');
|
||||
cy.get('a.attachment__title-link[href="https://www.google.com"]').should('have.text', 'A slack attachment');
|
||||
cy.get('.attachment-field a.markdown__link[href="http://example.com"]').should('have.text', 'Link');
|
||||
cy.get('.attachment-field span[data-emoticon="taco"]').should('exist');
|
||||
|
||||
cy.get('.attachment__thumb-container .file-preview__button img').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('MM-T631 Short field in payload can accept strings text in quotes for true and false', () => {
|
||||
const id = 'MM-T631';
|
||||
|
||||
const makePayloadFromShortValue = (short, currentID) => ({
|
||||
channel: testChannel.name,
|
||||
attachments: [{type: 'slack_attachment',
|
||||
color: '#7CD197',
|
||||
fields: [{short: false, title: 'Area', value: "Testing with a very long piece of text that will take up the whole width of the table (stopping short of the space where the thumbnail image is displayed). This is one more sentence to really make it a long field, and let's add a taco emoji :taco:."}, {short: true, title: 'Iteration', value: 'Testing'}, {short: true, title: 'State', value: 'New'}, {short: false, title: 'Reason', value: 'New defect reported'}, {short: false, title: 'Random field', value: 'This is a field which is not marked as short so it should be rendered on a separate row'}, {short: true, title: 'Short 1', value: 'Short field'},
|
||||
{short, title: 'Short 2', value: 'Another one'}, {short: true, title: 'Field with link', value: '<http://example.com|Link>'}],
|
||||
mrkdwn_in: ['pretext'],
|
||||
pretext: 'Some text here to look at (verify eyes emoji) :eyes:',
|
||||
text: `${currentID} ${short} This is the text of the attachment. <@${sysadminUser.id}>, there should be a small Jenkins thumbnail off to the right.`,
|
||||
thumb_url: 'https://slack.global.ssl.fastly.net/7bf4/img/services/jenkins-ci_128.png',
|
||||
title: 'A slack attachment',
|
||||
title_link: 'https://www.google.com',
|
||||
}]});
|
||||
|
||||
const testCases = [
|
||||
{short: true, shouldShowShort: true, desc: 'true boolean'},
|
||||
{short: false, shouldShowShort: false, desc: 'false boolean'},
|
||||
{short: 'true', shouldShowShort: true, desc: 'true string'},
|
||||
{short: 'false', shouldShowShort: false, desc: 'false string'},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase, i) => {
|
||||
it(`should show table elements based on short value ${testCase.desc}`, () => {
|
||||
const currentID = `${id} - ${i}`;
|
||||
const payload = makePayloadFromShortValue(testCase.short, currentID);
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.attachment__body .post-message__text-container p').should('contain', currentID);
|
||||
|
||||
if (testCase.shouldShowShort) {
|
||||
cy.get('table:nth-child(6) > thead > tr > th:nth-child(2)').should('have.text', 'Short 2');
|
||||
cy.get('table:nth-child(6) > tbody > tr > td:nth-child(2) > p').should('have.text', 'Another one');
|
||||
} else {
|
||||
cy.get('table:nth-child(7) > thead > tr > th:nth-child(1)').should('have.text', 'Short 2');
|
||||
cy.get('table:nth-child(7) > tbody > tr > td:nth-child(1) > p').should('have.text', 'Another one');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T632 Slack compatibility code shouldn\'t mess up characters', () => {
|
||||
const id = 'MM-T632';
|
||||
const text = `${id} <>|<>|`;
|
||||
const payload = {
|
||||
channel: testChannel.name,
|
||||
text,
|
||||
};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post__body p').should('have.text', text);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T634 Action buttons in Slack-style attachment post', () => {
|
||||
const id = 'MM-T634';
|
||||
|
||||
const payload = {text: id, attachments: [{pretext: 'This is the attachment pretext.', text: 'This is the attachment text.', actions: [{name: 'Select an option...', integration: {url: 'http://127.0.0.1:7357/action_options', context: {action: 'do_something'}}, type: 'select', data_source: 'channels'}, {name: 'Select an option...', integration: {url: 'http://127.0.0.1:7357/action_options', context: {action: 'do_something'}}, type: 'select', options: [{text: 'Option1', value: 'opt1'}, {text: 'Option2', value: 'opt2'}, {text: 'Option3', value: 'opt3'}]}, {name: 'Ephemeral Message', integration: {url: 'http://127.0.0.1:7357', context: {action: 'do_something_ephemeral'}}}, {name: 'Update', integration: {url: 'http://127.0.0.1:7357', context: {action: 'do_something_update'}}}]}]};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').should('have.text', id);
|
||||
|
||||
cy.get('.attachment-actions > :nth-child(1)').should('have.attr', 'data-testid', 'autoCompleteSelector');
|
||||
cy.get('.attachment-actions > :nth-child(1) input').should('have.attr', 'placeholder', 'Select an option...');
|
||||
cy.get('.attachment-actions > :nth-child(2)').should('have.attr', 'data-testid', 'autoCompleteSelector');
|
||||
cy.get('.attachment-actions > :nth-child(2) input').should('have.attr', 'placeholder', 'Select an option...');
|
||||
|
||||
cy.get('.attachment-actions > button:nth-child(3)').should('have.attr', 'data-action-id');
|
||||
cy.get('.attachment-actions > button:nth-child(3)').should('have.text', 'Ephemeral Message');
|
||||
cy.get('.attachment-actions > button:nth-child(4)').should('have.attr', 'data-action-id');
|
||||
cy.get('.attachment-actions > button:nth-child(4)').should('have.text', 'Update');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T635 Initial selection on post action dropdown', () => {
|
||||
const id = 'MM-T635';
|
||||
|
||||
const payload = {text: id, attachments: [{pretext: 'This is the attachment pretext.', text: 'This is the attachment text.', actions: [{name: 'Select an option...', integration: {url: 'http://127.0.0.1:7357/action_options', context: {action: 'do_something'}}, type: 'select', data_source: 'channels'}, {name: 'Select an option...', integration: {url: 'http://127.0.0.1:7357/action_options', context: {action: 'do_something'}}, type: 'select', options: [{text: 'Option1', value: 'opt1'}, {text: 'Option2', value: 'opt2'}, {text: 'Option3', value: 'opt3'}]}, {name: 'Ephemeral Message', integration: {url: 'http://127.0.0.1:7357', context: {action: 'do_something_ephemeral'}}}, {name: 'Update', integration: {url: 'http://127.0.0.1:7357', context: {action: 'do_something_update'}}}]}]};
|
||||
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post-message__text').should('have.text', id);
|
||||
|
||||
cy.get('.attachment-actions > :nth-child(1)').should('have.attr', 'data-testid', 'autoCompleteSelector');
|
||||
cy.get('.attachment-actions > :nth-child(1) input').should('have.attr', 'placeholder', 'Select an option...');
|
||||
cy.get('.attachment-actions > :nth-child(2)').should('have.attr', 'data-testid', 'autoCompleteSelector');
|
||||
cy.get('.attachment-actions > :nth-child(2) input').should('have.attr', 'placeholder', 'Select an option...');
|
||||
|
||||
cy.get('.attachment-actions > button:nth-child(3)').should('have.attr', 'data-action-id');
|
||||
cy.get('.attachment-actions > button:nth-child(3)').should('have.text', 'Ephemeral Message');
|
||||
cy.get('.attachment-actions > button:nth-child(4)').should('have.attr', 'data-action-id');
|
||||
cy.get('.attachment-actions > button:nth-child(4)').should('have.text', 'Update');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 @integrations
|
||||
|
||||
import {getRandomId} from '../../../../utils';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testUser;
|
||||
let secondUser;
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let incomingWebhook;
|
||||
|
||||
before(() => {
|
||||
// # Create new setup
|
||||
cy.apiInitSetup().then(({user}) => {
|
||||
testUser = user;
|
||||
|
||||
// # Create a second user
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
secondUser = user2;
|
||||
});
|
||||
|
||||
// # Login as the new user
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # Create a new team with the new user
|
||||
cy.apiCreateTeam('test-team', 'Team Testers').then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
// # Add second user to the test team
|
||||
cy.apiAddUserToTeam(testTeam.id, secondUser.id);
|
||||
|
||||
// # Create a new test channel for the team
|
||||
cy.apiCreateChannel(testTeam.id, 'test-channel', 'Testers Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
const newIncomingHook = {
|
||||
channel_id: testChannel.id,
|
||||
channel_locked: true,
|
||||
description: 'Test Webhook Description',
|
||||
display_name: 'Test Webhook Name',
|
||||
};
|
||||
|
||||
//# Create a new webhook
|
||||
cy.apiCreateWebhook(newIncomingHook).then((hook) => {
|
||||
incomingWebhook = hook;
|
||||
});
|
||||
|
||||
// # Add second user to the test channel
|
||||
cy.apiAddUserToChannel(testChannel.id, secondUser.id).then(() => {
|
||||
// # Remove the first user from the channel
|
||||
cy.apiDeleteUserFromTeam(testTeam.id, testUser.id).then(({data}) => {
|
||||
expect(data.status).to.equal('OK');
|
||||
});
|
||||
|
||||
// # Login as the second user
|
||||
cy.apiLogin(secondUser);
|
||||
|
||||
// # Visit the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T638 Webhook posts when webhook creator is not a member of the channel', () => {
|
||||
const payload = getPayload(testChannel);
|
||||
|
||||
// # Post the webhook message
|
||||
cy.postIncomingWebhook({url: incomingWebhook.url, data: payload});
|
||||
|
||||
// * Assert that the message was posted even though webhook author has been removed
|
||||
cy.uiWaitUntilMessagePostedIncludes(payload.text);
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('have.text', `${payload.text}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getPayload(testChannel) {
|
||||
return {
|
||||
channel: testChannel.name,
|
||||
text: `${getRandomId()} - this webhook was set up by a user that is no longer in this channel`,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let teamA;
|
||||
|
||||
before(() => {
|
||||
// # Setup with the new team
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
teamA = team.name;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T569 Integrations Page', () => {
|
||||
// # Visit the integrations page
|
||||
cy.visit(`/${teamA}/integrations`);
|
||||
|
||||
// # Shrink the page
|
||||
cy.viewport(500, 500);
|
||||
|
||||
// * Left side bar integrations link is visible and works
|
||||
cy.get('.backstage-sidebar__category > .category-title').should('be.visible').and('have.attr', 'href', `/${teamA}/integrations`).click();
|
||||
cy.url().should('include', '/integrations');
|
||||
|
||||
// * Left side bar incoming webhooks link is visible and works
|
||||
cy.get('#incomingWebhooks > .section-title').should('be.visible').and('have.attr', 'href', `/${teamA}/integrations/incoming_webhooks`).click();
|
||||
cy.url().should('include', '/incoming_webhooks');
|
||||
|
||||
// * Left side bar outgoing webhooks link is visible and works
|
||||
cy.get('#outgoingWebhooks > .section-title').should('be.visible').and('have.attr', 'href', `/${teamA}/integrations/outgoing_webhooks`).click();
|
||||
cy.url().should('include', '/outgoing_webhooks');
|
||||
|
||||
// * Left side bar slash commands link is visible and works
|
||||
cy.get('#slashCommands > .section-title').should('be.visible').and('have.attr', 'href', `/${teamA}/integrations/commands`).click();
|
||||
cy.url().should('include', 'commands');
|
||||
|
||||
// * Left side bar bot accounts link is visible and works
|
||||
cy.get('#botAccounts > .section-title').should('be.visible').and('have.attr', 'href', `/${teamA}/integrations/bots`).click();
|
||||
cy.url().should('include', '/bots');
|
||||
|
||||
// # Return to integrations home
|
||||
cy.visit(`/${teamA}/integrations`);
|
||||
|
||||
// # Isolate icon links
|
||||
cy.get('.integrations-list.d-flex.flex-wrap').within(() => {
|
||||
// * Incoming Webhooks link is visible and works
|
||||
cy.findByText('Incoming Webhooks').scrollIntoView().should('be.visible').click();
|
||||
cy.url().should('include', '/incoming_webhooks');
|
||||
});
|
||||
|
||||
// # Return to integrations home
|
||||
cy.visit(`/${teamA}/integrations`);
|
||||
|
||||
// * Outgoing Webhooks link is visible and works
|
||||
cy.get('.integrations-list.d-flex.flex-wrap').within(() => {
|
||||
cy.findByText('Outgoing Webhooks').scrollIntoView().should('be.visible').click();
|
||||
cy.url().should('include', '/outgoing_webhooks');
|
||||
});
|
||||
|
||||
// # Return to integrations home
|
||||
cy.visit(`/${teamA}/integrations`);
|
||||
|
||||
// * Slash Commands link is visible and works
|
||||
cy.get('.integrations-list.d-flex.flex-wrap').within(() => {
|
||||
cy.findByText('Slash Commands').scrollIntoView().should('be.visible').click();
|
||||
cy.url().should('include', '/commands');
|
||||
});
|
||||
|
||||
// # Return to integrations home
|
||||
cy.visit(`/${teamA}/integrations`);
|
||||
|
||||
// * Bot Accounts link is visible and works
|
||||
cy.get('.integrations-list.d-flex.flex-wrap').within(() => {
|
||||
cy.findByText('Bot Accounts').scrollIntoView().should('be.visible').click();
|
||||
cy.url().should('include', '/bots');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @channels @integrations
|
||||
|
||||
import {getRandomId} from '../../../utils';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let teamName;
|
||||
|
||||
before(() => {
|
||||
// # Setup with the new team and channel
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
teamName = team.name;
|
||||
|
||||
// # Setup 2 incoming webhooks
|
||||
Cypress._.times(2, (i) => {
|
||||
const newIncomingHook = {
|
||||
channel_id: channel.id,
|
||||
description: `Incoming webhook Test Description ${i}`,
|
||||
display_name: `Test ${i}`,
|
||||
};
|
||||
cy.apiCreateWebhook(newIncomingHook);
|
||||
});
|
||||
|
||||
// # Setup 2 outgoing webhooks
|
||||
Cypress._.times(2, (i) => {
|
||||
const newOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: `Test ${i} `,
|
||||
trigger_words: [`test-trigger-${i}`],
|
||||
callback_urls: ['https://mattermost.com'],
|
||||
};
|
||||
cy.apiCreateWebhook(newOutgoingHook, false);
|
||||
});
|
||||
|
||||
// # Setup 2 Slash Commands
|
||||
Cypress._.times(2, (i) => {
|
||||
const slashCommand1 = {
|
||||
description: `Test Slash Command ${i}`,
|
||||
display_name: `Test ${i}`,
|
||||
method: 'P',
|
||||
team_id: team.id,
|
||||
trigger: `trigger${i}`,
|
||||
url: 'https://google.com',
|
||||
};
|
||||
cy.apiCreateCommand(slashCommand1);
|
||||
});
|
||||
|
||||
// # Setup 2 bot accounts
|
||||
Cypress._.times(2, () => {
|
||||
cy.apiCreateBot();
|
||||
});
|
||||
|
||||
// # Visit the integrations page
|
||||
cy.visit(`/${teamName}/integrations`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T571 Integration search gives feed back when there are no results', () => {
|
||||
// # Shrink the page, set up constants
|
||||
cy.viewport('ipad-2');
|
||||
const results = 'Test';
|
||||
const noResults = `${getRandomId(6)}`;
|
||||
|
||||
// * Check incoming webhooks for no match message
|
||||
cy.get('#incomingWebhooks').click();
|
||||
cy.get('#searchInput').type(results).then(() => {
|
||||
cy.get('#emptySearchResultsMessage').should('not.exist');
|
||||
});
|
||||
cy.get('#searchInput').clear().type(noResults);
|
||||
cy.get('#emptySearchResultsMessage').contains(`No incoming webhooks match ${noResults}`);
|
||||
|
||||
// * Check outgoing webhooks for no match message
|
||||
cy.get('#outgoingWebhooks').click();
|
||||
cy.get('#searchInput').type(results).then(() => {
|
||||
cy.get('#emptySearchResultsMessage').should('not.exist');
|
||||
});
|
||||
cy.get('#searchInput').clear().type(noResults);
|
||||
cy.get('#emptySearchResultsMessage').contains(`No outgoing webhooks match ${noResults}`);
|
||||
|
||||
// * Check slash commands for no match message
|
||||
cy.get('#slashCommands').click();
|
||||
cy.get('#searchInput').type(results).then(() => {
|
||||
cy.get('#emptySearchResultsMessage').should('not.exist');
|
||||
});
|
||||
cy.get('#searchInput').clear().type(noResults);
|
||||
cy.get('#emptySearchResultsMessage').contains(`No commands match ${noResults}`);
|
||||
|
||||
// * Check bot accounts for no match message
|
||||
cy.get('#botAccounts').click();
|
||||
cy.get('#searchInput').type(results).then(() => {
|
||||
cy.get('#emptySearchResultsMessage').should('not.exist');
|
||||
});
|
||||
cy.get('#searchInput').clear().type(noResults);
|
||||
cy.get('#emptySearchResultsMessage').contains(`No bot accounts match ${noResults}`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,449 @@
|
||||
// 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 @integrations
|
||||
|
||||
import {getRandomId} from '../../../utils';
|
||||
import * as MESSAGES from '../../../fixtures/messages';
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
|
||||
describe('Integrations page', () => {
|
||||
let testTeam;
|
||||
|
||||
before(() => {
|
||||
// # Set ServiceSettings to expected values
|
||||
const newSettings = {
|
||||
ServiceSettings: {
|
||||
EnableOAuthServiceProvider: true,
|
||||
EnableIncomingWebhooks: true,
|
||||
EnableOutgoingWebhooks: true,
|
||||
EnableCommands: true,
|
||||
},
|
||||
};
|
||||
cy.apiUpdateConfig(newSettings);
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
// # Go to integrations
|
||||
cy.visit(`/${team.name}/integrations`);
|
||||
|
||||
// * Validate that all sections are enabled
|
||||
cy.get('#incomingWebhooks').should('be.visible');
|
||||
cy.get('#outgoingWebhooks').should('be.visible');
|
||||
cy.get('#slashCommands').should('be.visible');
|
||||
cy.get('#botAccounts').should('be.visible');
|
||||
cy.get('#oauthApps').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('should display correct message when incoming webhook not found', () => {
|
||||
// # Open incoming web hooks page
|
||||
cy.get('#incomingWebhooks').click();
|
||||
|
||||
// # Add web 'include', '/newPage'hook
|
||||
cy.get('#addIncomingWebhook').click();
|
||||
|
||||
// # Pick the channel
|
||||
cy.get('#channelSelect').select('Town Square');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveWebhook').click();
|
||||
|
||||
// * Validate that save succeeded
|
||||
cy.get('#formTitle').should('have.text', 'Setup Successful');
|
||||
|
||||
// # Close the Add dialog
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// # Type random stuff into the search box
|
||||
const searchString = `some random stuff ${Date.now()}`;
|
||||
cy.get('#searchInput').type(`${searchString}{enter}`);
|
||||
|
||||
// * Validate that the correct empty message is shown
|
||||
cy.get('#emptySearchResultsMessage').should('be.visible').and('have.text', `No incoming webhooks match ${searchString}`);
|
||||
});
|
||||
|
||||
it('should display correct message when outgoing webhook not found', () => {
|
||||
// # Open outgoing web hooks page
|
||||
cy.get('#outgoingWebhooks').click();
|
||||
|
||||
// # Add web hook
|
||||
cy.get('#addOutgoingWebhook').click();
|
||||
|
||||
// # Pick the channel and dummy callback
|
||||
cy.get('#channelSelect').select('Town Square');
|
||||
cy.get('#callbackUrls').type('https://dummy');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveWebhook').click();
|
||||
|
||||
// * Validate that save succeeded
|
||||
cy.get('#formTitle').should('have.text', 'Setup Successful');
|
||||
|
||||
// # Close the Add dialog
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// # Type random stuff into the search box
|
||||
const searchString = `some random stuff ${Date.now()}`;
|
||||
cy.get('#searchInput').type(`${searchString}{enter}`);
|
||||
|
||||
// * Validate that the correct empty message is shown
|
||||
cy.get('#emptySearchResultsMessage').should('be.visible').and('have.text', `No outgoing webhooks match ${searchString}`);
|
||||
});
|
||||
|
||||
it('should display correct message when slash command not found', () => {
|
||||
// # Open slash command page
|
||||
cy.get('#slashCommands').click();
|
||||
|
||||
// # Add new command
|
||||
cy.get('#addSlashCommand').click();
|
||||
|
||||
// # Pick a dummy trigger and callback
|
||||
cy.get('#trigger').type(`test-trigger${Date.now()}`);
|
||||
cy.get('#url').type('https://dummy');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// * Validate that save succeeded
|
||||
cy.get('#formTitle').should('have.text', 'Setup Successful');
|
||||
|
||||
// # Close the Add dialog
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// # Type random stuff into the search box
|
||||
const searchString = `some random stuff ${Date.now()}`;
|
||||
cy.get('#searchInput').type(`${searchString}{enter}`);
|
||||
|
||||
// * Validate that the correct empty message is shown
|
||||
cy.get('#emptySearchResultsMessage').should('be.visible').and('have.text', `No commands match ${searchString}`);
|
||||
});
|
||||
|
||||
it('should display correct message when OAuth app not found', () => {
|
||||
// # Open OAuth apps page
|
||||
cy.get('#oauthApps').click();
|
||||
|
||||
// # Add new command
|
||||
cy.get('#addOauthApp').click();
|
||||
|
||||
// # Fill in dummy details
|
||||
cy.get('#name').type(`test-name${getRandomId()}`);
|
||||
cy.get('#description').type(`test-descr${getRandomId()}`);
|
||||
cy.get('#homepage').type(`https://dummy${getRandomId()}`);
|
||||
cy.get('#callbackUrls').type('https://dummy');
|
||||
|
||||
// # Save
|
||||
cy.get('#saveOauthApp').click();
|
||||
|
||||
// * Validate that save succeeded
|
||||
cy.get('#formTitle').should('have.text', 'Setup Successful');
|
||||
|
||||
// # Close the Add dialog
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// # Type random stuff into the search box
|
||||
const searchString = `some random stuff ${Date.now()}`;
|
||||
cy.get('#searchInput').type(`${searchString}{enter}`);
|
||||
|
||||
// * Validate that the correct empty message is shown
|
||||
cy.get('#emptySearchResultsMessage').should('be.visible').and('have.text', `No OAuth 2.0 Applications match ${searchString}`);
|
||||
});
|
||||
|
||||
it('should display correct message when bot account not found', () => {
|
||||
// # Open bot account page
|
||||
cy.get('#botAccounts').click();
|
||||
|
||||
// # Add new bot
|
||||
cy.get('#addBotAccount').click();
|
||||
|
||||
// # Fill in dummy details
|
||||
cy.get('#username').type(`test-bot${getRandomId()}`);
|
||||
|
||||
// # Save
|
||||
cy.get('#saveBot').click();
|
||||
|
||||
// # Click done button
|
||||
cy.get('#doneButton').click();
|
||||
|
||||
// * Make sure we are done saving
|
||||
cy.url().should('contain', '/integrations/bots');
|
||||
|
||||
// # Type random stuff into the search box
|
||||
const searchString = `some random stuff ${Date.now()}`;
|
||||
cy.get('#searchInput').type(`${searchString}{enter}`);
|
||||
|
||||
// * Validate that the correct empty message is shown
|
||||
cy.get('#emptySearchResultsMessage').should('be.visible').and('have.text', `No bot accounts match ${searchString}`);
|
||||
});
|
||||
|
||||
it('MM-T570 Integration Page titles are bolded', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open product menu and click 'Integrations'
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
cy.get('.integration-option__title').contains('Incoming Webhooks').click();
|
||||
|
||||
integrationPageTitleIsBold('Incoming Webhooks');
|
||||
integrationPageTitleIsBold('Outgoing Webhooks');
|
||||
integrationPageTitleIsBold('Slash Commands');
|
||||
integrationPageTitleIsBold('OAuth 2.0 Applications');
|
||||
integrationPageTitleIsBold('Bot Accounts');
|
||||
});
|
||||
|
||||
it('MM-T572 Copy icon for Slash Command', () => {
|
||||
// # Visit home channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Click 'Integrations' at product menu
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify we are at integrations page URL
|
||||
cy.url().should('include', '/integrations');
|
||||
|
||||
// # Scan the area of integrations list
|
||||
cy.get('.integrations-list').should('exist').within(() => {
|
||||
// # Open Slash commands directory
|
||||
cy.findByText('Slash Commands').should('exist').and('be.visible').click({force: true});
|
||||
});
|
||||
|
||||
// * Verify we are at slash commands URL
|
||||
cy.url().should('include', '/integrations/commands');
|
||||
|
||||
// # Hit create slash command button
|
||||
cy.findByText('Add Slash Command').should('exist').and('be.visible').click();
|
||||
|
||||
// * Verify we are at slash commands add URL
|
||||
cy.url().should('include', '/integrations/commands/add');
|
||||
|
||||
const customSlashName = MESSAGES.SMALL;
|
||||
|
||||
// # Enter a title for custom slash command
|
||||
cy.findByLabelText('Title').should('exist').scrollIntoView().type(customSlashName);
|
||||
|
||||
// # Enter a trigger word for custom slash command
|
||||
cy.findByLabelText('Command Trigger Word').should('exist').scrollIntoView().type('example');
|
||||
|
||||
// # Enter a request url for custom slash command
|
||||
cy.findByLabelText('Request URL').should('exist').scrollIntoView().type('https://example.com');
|
||||
|
||||
// # Hit save to save the custom slash command
|
||||
cy.findByText('Save').should('exist').scrollIntoView().click();
|
||||
|
||||
// * Verify we are at setup successful URL
|
||||
cy.url().should('include', '/integrations/commands/confirm');
|
||||
|
||||
// * Verify slash was successfully created
|
||||
cy.findByText('Setup Successful').should('exist').and('be.visible');
|
||||
|
||||
// * Verify token was created
|
||||
cy.findByText('Token').should('exist').and('be.visible');
|
||||
|
||||
// * Verify copy icon is shown
|
||||
cy.get('.fa.fa-copy').should('exist').and('be.visible').
|
||||
trigger('mouseover').and('have.attr', 'aria-describedby', 'copy');
|
||||
|
||||
// # Hit done to move from confirm screen
|
||||
cy.findByText('Done').should('exist').and('be.visible').click();
|
||||
|
||||
// * Verify we are back to installed slash commands screen
|
||||
cy.url().should('include', '/integrations/commands/installed');
|
||||
|
||||
// * Verify our created command is in the list
|
||||
cy.findByText(customSlashName).should('exist').and('be.visible').scrollIntoView();
|
||||
|
||||
// # Loop over all custom slash commands
|
||||
cy.get('.backstage-list').children().each((el) => {
|
||||
// # For each custom slash command was created
|
||||
cy.wrap(el).within(() => {
|
||||
// Verify copy icon for token is present
|
||||
cy.get('.fa.fa-copy').should('exist').and('be.visible').
|
||||
trigger('mouseover').and('have.attr', 'aria-describedby', 'copy');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T702 Edit to invalid URL', () => {
|
||||
// # Visit home channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Click 'Integrations' at product menu
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify we are at integrations page URL
|
||||
cy.url().should('include', '/integrations');
|
||||
|
||||
// # Scan the area of integrations list
|
||||
cy.get('.integrations-list').should('exist').within(() => {
|
||||
// # Open Slash commands directory
|
||||
cy.findByText('Slash Commands').should('exist').and('be.visible').click({force: true});
|
||||
});
|
||||
|
||||
// * Verify we are at slash commands URL
|
||||
cy.url().should('include', '/integrations/commands');
|
||||
|
||||
// # Hit create slash command button
|
||||
cy.findByText('Add Slash Command').should('exist').and('be.visible').click();
|
||||
|
||||
// * Verify we are at slash commands add URL
|
||||
cy.url().should('include', '/integrations/commands/add');
|
||||
|
||||
const customSlashName = `customSlash-${Date.now()}`;
|
||||
|
||||
// # Enter a title for custom slash command
|
||||
cy.findByLabelText('Title').should('exist').scrollIntoView().type(customSlashName);
|
||||
|
||||
// # Enter a trigger word for custom slash command
|
||||
cy.findByLabelText('Command Trigger Word').should('exist').scrollIntoView().type(customSlashName);
|
||||
|
||||
// # Enter a request url for custom slash command
|
||||
cy.findByLabelText('Request URL').should('exist').scrollIntoView().type('https://example.com');
|
||||
|
||||
// # Hit save to save the custom slash command
|
||||
cy.findByText('Save').should('exist').scrollIntoView().click();
|
||||
|
||||
// * Verify we are at setup successful URL
|
||||
cy.url().should('include', '/integrations/commands/confirm');
|
||||
|
||||
// * Verify slash was successfully created
|
||||
cy.findByText('Setup Successful').should('exist').and('be.visible');
|
||||
|
||||
// * Verify token was created
|
||||
cy.findByText('Token').should('exist').and('be.visible');
|
||||
|
||||
// # Hit done to move from confirm screen
|
||||
cy.findByText('Done').should('exist').and('be.visible').click();
|
||||
|
||||
// * Verify we are back to installed slash commands screen
|
||||
cy.url().should('include', '/integrations/commands/installed');
|
||||
|
||||
// * Verify our created command is in the list
|
||||
cy.findByText(customSlashName).should('exist').and('be.visible').scrollIntoView().
|
||||
parents('.backstage-list__item').within(() => {
|
||||
// # Click on the edit of slash command
|
||||
cy.findByText('Edit').should('exist').and('be.visible').click();
|
||||
});
|
||||
|
||||
// * Verify that we are on edit slash command page
|
||||
cy.url().should('include', '/integrations/commands/edit');
|
||||
|
||||
// # Edit the request url field
|
||||
cy.findByLabelText('Request URL').should('exist').and('be.visible').scrollIntoView().
|
||||
clear().type('mattermost.com');
|
||||
|
||||
// # Hit save to save edited custom slash command
|
||||
cy.findByText('Update').should('exist').scrollIntoView().click();
|
||||
|
||||
// * Verify that confirm modal is displayed to save the changes
|
||||
cy.get('#confirmModal').should('exist').and('be.visible').within(() => {
|
||||
// * Confirm that caution text is visible
|
||||
cy.findByText('Your changes may break the existing slash command. Are you sure you would like to update it?').
|
||||
should('exist').and('be.visible');
|
||||
|
||||
// # Press update button to confirm
|
||||
cy.findByText('Update').should('exist').and('be.visible').click();
|
||||
});
|
||||
|
||||
// * Verify that we get the error message
|
||||
cy.findByText('Invalid URL. Must be a valid URL and start with http:// or https://.').
|
||||
should('exist').and('be.visible').scrollIntoView();
|
||||
|
||||
// # Go back to home channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('MM-T580 Custom slash command auto-complete displays trigger word and not command name', () => {
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Click 'Integrations' at product menu
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify we are at integrations page URL
|
||||
cy.url().should('include', '/integrations');
|
||||
|
||||
// # Scan the area of integrations list
|
||||
cy.get('.integrations-list').should('exist').within(() => {
|
||||
// # Open Slash commands directory
|
||||
cy.findByText('Slash Commands').should('exist').and('be.visible').click({force: true});
|
||||
});
|
||||
|
||||
// * Verify we are at slash commands directory URL
|
||||
cy.url().should('include', '/integrations/commands');
|
||||
|
||||
// # Hit create slash command button
|
||||
cy.findByText('Add Slash Command').should('exist').and('be.visible').click();
|
||||
|
||||
// * Verify we are at slash commands add URL
|
||||
cy.url().should('include', '/integrations/commands/add');
|
||||
|
||||
const commandTitle = `abc-${Date.now()}`;
|
||||
const commandTrigger = `xyz-${Date.now()}`;
|
||||
|
||||
// # Enter a title for custom slash command
|
||||
cy.findByLabelText('Title').should('exist').scrollIntoView().type(commandTitle);
|
||||
|
||||
// # Enter a trigger word for custom slash command different from slash title
|
||||
cy.findByLabelText('Command Trigger Word').should('exist').scrollIntoView().type(commandTrigger);
|
||||
|
||||
// # Enter a request url for custom slash command
|
||||
cy.findByLabelText('Request URL').should('exist').scrollIntoView().type('https://example.com');
|
||||
|
||||
// # Check the option of autocomplete
|
||||
cy.findByLabelText('Autocomplete').should('exist').scrollIntoView().click();
|
||||
|
||||
// # Hit save to save the custom slash command
|
||||
cy.findByText('Save').should('exist').scrollIntoView().click();
|
||||
|
||||
// * Verify we are at setup successful URL
|
||||
cy.url().should('include', '/integrations/commands/confirm');
|
||||
|
||||
// * Verify slash was successfully created
|
||||
cy.findByText('Setup Successful').should('exist').and('be.visible');
|
||||
|
||||
// * Verify token was created
|
||||
cy.findByText('Token').should('exist').and('be.visible');
|
||||
|
||||
// # Hit done to move from confirm screen
|
||||
cy.findByText('Done').should('exist').and('be.visible').click();
|
||||
|
||||
// * Verify we are back to installed slash commands screen
|
||||
cy.url().should('include', '/integrations/commands/installed');
|
||||
|
||||
// * Verify our created command is in the list
|
||||
cy.findByText(commandTitle).should('exist').and('be.visible').scrollIntoView();
|
||||
|
||||
// # Return to channels
|
||||
cy.visit(`${testTeam.name}/channels/town-square`);
|
||||
|
||||
const first2LettersOfCommandTrigger = commandTrigger.slice(0, 2);
|
||||
|
||||
// # Type first 2 letters of the command trigger word
|
||||
cy.uiGetPostTextBox().should('be.visible').clear().type(`/${first2LettersOfCommandTrigger}`);
|
||||
|
||||
// # Scan inside of suggestion list
|
||||
cy.get('#suggestionList').should('exist').and('be.visible').within(() => {
|
||||
// * Verify that commands trigger is suggested
|
||||
cy.findByText(commandTrigger).should('exist').and('be.visible');
|
||||
|
||||
// * Verify that commands title is not suggested
|
||||
cy.findByText(commandTitle).should('not.exist');
|
||||
});
|
||||
|
||||
// # Append Hello to custom slash command and hit enter
|
||||
cy.uiGetPostTextBox().type('{enter}').wait(TIMEOUTS.HALF_SEC).type('Hello{enter}').wait(TIMEOUTS.HALF_SEC);
|
||||
cy.uiGetPostTextBox().invoke('text').should('be.empty');
|
||||
});
|
||||
});
|
||||
|
||||
function integrationPageTitleIsBold(title) {
|
||||
cy.get('.section-title__text').contains(title).click();
|
||||
cy.get('.item-details__name').should('be.visible').and('have.css', 'font-weight', '600');
|
||||
}
|
||||
@@ -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 @integrations
|
||||
|
||||
/**
|
||||
* Note: This test requires webhook server running. Initiate `npm run start:webhook` to start.
|
||||
*/
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let offTopicChannel;
|
||||
|
||||
before(() => {
|
||||
cy.requireWebhookServer();
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team;
|
||||
|
||||
cy.apiGetChannelByName(team.name, 'off-topic').then(({channel}) => {
|
||||
offTopicChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Visit town-square
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('MM-T706 Error Handling for Slash Commands', () => {
|
||||
const command = {
|
||||
auto_complete: false,
|
||||
description: 'Test for Slash Command',
|
||||
display_name: 'Send message to different channel via slash command',
|
||||
icon_url: '',
|
||||
method: 'P',
|
||||
team_id: testTeam.id,
|
||||
trigger: 'error_handling',
|
||||
url: `${Cypress.env().webhookBaseUrl}/send_message_to_channel?type=system_message&channel_id=${offTopicChannel.id}`,
|
||||
username: '',
|
||||
};
|
||||
|
||||
// # Create a slash command
|
||||
cy.apiCreateCommand(command).then(({data: slashCommand}) => {
|
||||
// * Verify that off-topic channel is read
|
||||
cy.findByLabelText('off-topic public channel').should('exist');
|
||||
|
||||
// # Post a slash command that sends message to off-topic channel
|
||||
cy.uiGetPostTextBox().
|
||||
clear().
|
||||
type(`/${slashCommand.trigger} {enter}`);
|
||||
|
||||
// * Verify slash command error
|
||||
cy.findByText(`Command '${slashCommand.trigger}' failed to post response. Please contact your System Administrator.`).should('be.visible');
|
||||
|
||||
// * Verify that off-topic channel is unread and then click
|
||||
cy.findByLabelText('off-topic public channel unread').
|
||||
should('exist').
|
||||
click();
|
||||
|
||||
// * Verify that only "Hello World" is posted in off-topic channel
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('be.visible').and('have.text', 'Hello World');
|
||||
});
|
||||
cy.getNthPostId(-2).then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('be.visible').and('not.have.text', 'Extra response 2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T707 Send a message to a different channel than where the slash command was issued from', () => {
|
||||
const command = {
|
||||
auto_complete: false,
|
||||
description: 'Test for Slash Command',
|
||||
display_name: 'Send message to different channel via slash command',
|
||||
icon_url: '',
|
||||
method: 'P',
|
||||
team_id: testTeam.id,
|
||||
trigger: 'send_message_from_different_channel',
|
||||
url: `${Cypress.env().webhookBaseUrl}/send_message_to_channel?channel_id=${offTopicChannel.id}`,
|
||||
username: '',
|
||||
};
|
||||
|
||||
// # Create a slash command
|
||||
cy.apiCreateCommand(command).then(({data: slashCommand}) => {
|
||||
// * Verify that off-topic channel is read
|
||||
cy.findByLabelText('off-topic public channel').should('exist');
|
||||
|
||||
// # Post a slash command that sends message to off-topic channel
|
||||
cy.postMessage(`/${slashCommand.trigger} `);
|
||||
|
||||
// * Verify that off-topic channel is unread and then click
|
||||
cy.findByLabelText('off-topic public channel unread').
|
||||
should('exist').
|
||||
click();
|
||||
|
||||
// * Verify that both messages are posted in off-topic channel
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('be.visible').and('have.text', 'Hello World');
|
||||
});
|
||||
cy.getNthPostId(-2).then((postId) => {
|
||||
cy.get(`#postMessageText_${postId}`).should('be.visible').and('have.text', 'Extra response 2');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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 @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let testUser;
|
||||
let outgoingWebhook;
|
||||
|
||||
before(() => {
|
||||
const callbackUrl = `${Cypress.env().webhookBaseUrl}/post_outgoing_webhook`;
|
||||
|
||||
cy.requireWebhookServer();
|
||||
|
||||
// # Create test team, channel, and webhook
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testTeam = team.name;
|
||||
testChannel = channel.name;
|
||||
testUser = user;
|
||||
|
||||
const newOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: 'New Outgoing Webhook',
|
||||
trigger_words: ['testing'],
|
||||
callback_urls: [callbackUrl],
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newOutgoingHook, false).then((hook) => {
|
||||
outgoingWebhook = hook;
|
||||
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({webhook, status}) => {
|
||||
expect(status).equal(200);
|
||||
expect(webhook.id).equal(outgoingWebhook.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiLogin(user);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T617 Delete outgoing webhook', () => {
|
||||
// # Confirm outgoing webhook is working
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
cy.postMessage('testing');
|
||||
cy.uiWaitUntilMessagePostedIncludes('Outgoing Webhook Payload');
|
||||
|
||||
// # Login as sysadmin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// * Assert from API that outgoing webhook is active
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({status}) => {
|
||||
expect(status).equal(200);
|
||||
});
|
||||
|
||||
// # Delete outgoing webhook
|
||||
cy.visit(`/${testTeam}/integrations/outgoing_webhooks`);
|
||||
cy.findAllByText('Delete', {timeout: TIMEOUTS.ONE_MIN}).click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// * Assert the webhook has been deleted
|
||||
cy.findByText('No outgoing webhooks found').should('exist');
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({status}) => {
|
||||
expect(status).equal(404);
|
||||
});
|
||||
|
||||
// * Return to app and assert trigger word no longer works
|
||||
cy.apiLogin(testUser);
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
cy.postMessage('testing');
|
||||
|
||||
// * Assert bot message does not arrive
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#${lastPostId}_message`).should('not.contain', 'Outgoing Webhook Payload');
|
||||
});
|
||||
|
||||
// * Verify from API that outgoing webhook has been deleted
|
||||
cy.apiAdminLogin();
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({status}) => {
|
||||
expect(status).equal(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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 @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let testUser;
|
||||
let outgoingWebhook;
|
||||
|
||||
before(() => {
|
||||
const callbackUrl = `${Cypress.env().webhookBaseUrl}/post_outgoing_webhook`;
|
||||
|
||||
cy.requireWebhookServer();
|
||||
|
||||
// # Create test team, channel, and webhook
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testTeam = team.name;
|
||||
testChannel = channel.name;
|
||||
testUser = user;
|
||||
|
||||
const newOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: 'New Outgoing Webhook',
|
||||
trigger_words: ['testing'],
|
||||
callback_urls: [callbackUrl],
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newOutgoingHook, false).then((hook) => {
|
||||
outgoingWebhook = hook;
|
||||
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({webhook, status}) => {
|
||||
expect(status).equal(200);
|
||||
expect(webhook.id).equal(outgoingWebhook.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T613 Disable outgoing webhooks in System Console', () => {
|
||||
// # Confirm outgoing webhook is working with trigger word
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
cy.postMessage('testing');
|
||||
cy.uiWaitUntilMessagePostedIncludes('Outgoing Webhook Payload');
|
||||
|
||||
// # Login as sysadmin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// * Assert from API that outgoing webhook is active
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({status}) => {
|
||||
expect(status).equal(200);
|
||||
});
|
||||
|
||||
// # Disable outgoing webhooks from console
|
||||
cy.visit('/admin_console/integrations/integration_management');
|
||||
cy.findByTestId('ServiceSettings.EnableOutgoingWebhooksfalse').click().should('be.checked');
|
||||
cy.get('#saveSetting').click();
|
||||
cy.get('#saveSetting').should('be.disabled');
|
||||
|
||||
// * Assert from API that outgoing webhook has been disabled
|
||||
cy.apiAdminLogin();
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({status}) => {
|
||||
expect(status).equal(501);
|
||||
});
|
||||
|
||||
// # Login as regular user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// * Assert that trigger word no longer triggers webhook
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
cy.postMessage('testing');
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#${lastPostId}_message`).should('not.contain', 'Outgoing Webhook Payload');
|
||||
});
|
||||
|
||||
// # Login as sysadmin
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Re-enable outgoing webhooks from console
|
||||
cy.visit('/admin_console/integrations/integration_management');
|
||||
cy.findByTestId('ServiceSettings.EnableOutgoingWebhookstrue').click().should('be.checked');
|
||||
cy.get('#saveSetting').click();
|
||||
|
||||
// * Assert from API that outgoing webhook is active
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({status}) => {
|
||||
expect(status).equal(200);
|
||||
});
|
||||
|
||||
// # Login as regular user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// * Assert outgoing webhook is working with trigger word
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
cy.postMessage('testing');
|
||||
cy.uiWaitUntilMessagePostedIncludes('Outgoing Webhook Payload');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,398 @@
|
||||
// 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 @outgoing_webhook
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
import {
|
||||
enableUsernameAndIconOverrideInt,
|
||||
enableUsernameAndIconOverride,
|
||||
} from '../incoming_webhook/helpers';
|
||||
|
||||
describe('Outgoing webhook', () => {
|
||||
const triggerWord = 'text';
|
||||
const messageWithTriggerWord = 'text with some more text';
|
||||
const callbackUrl = `${Cypress.env().webhookBaseUrl}/post_outgoing_webhook`;
|
||||
const noChannelSelectionOption = '--- Select a channel ---';
|
||||
const overrideIconUrl = 'http://via.placeholder.com/150/00F/888';
|
||||
const defaultUsername = 'webhook';
|
||||
const overriddenUsername = 'user-overridden';
|
||||
const defaultIcon = 'webhook_icon.jpg';
|
||||
const overriddenIcon = 'webhook_override_icon.png';
|
||||
|
||||
let sysadmin;
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let testUser;
|
||||
let otherUser;
|
||||
let siteName;
|
||||
let offTopicUrl;
|
||||
let testChannelUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiGetConfig().then(({config}) => {
|
||||
siteName = config.TeamSettings.SiteName;
|
||||
});
|
||||
cy.apiGetMe().then(({user}) => {
|
||||
sysadmin = user;
|
||||
});
|
||||
cy.requireWebhookServer();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnablePostUsernameOverride: false,
|
||||
EnablePostIconOverride: false,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiInitSetup().then((out) => {
|
||||
testTeam = out.team;
|
||||
testChannel = out.channel;
|
||||
testUser = out.user;
|
||||
offTopicUrl = out.offTopicUrl;
|
||||
testChannelUrl = out.channelUrl;
|
||||
});
|
||||
|
||||
cy.apiCreateUser().then(({user: user1}) => {
|
||||
otherUser = user1;
|
||||
cy.apiAddUserToTeam(testTeam.id, otherUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T584 default username and profile pic Trigger = posting anything in the specified channel', () => {
|
||||
// # Enable user name and icon overrides
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Visit test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl, channelSelect: testChannel.display_name});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Post any message in a channel as testUser
|
||||
postMessageInChannel(testUser, testChannelUrl, Date.now());
|
||||
|
||||
// * Verify default profile name and icon of posted webhook message
|
||||
verifyProfileNameAndIcon({username: defaultUsername, userIcon: defaultIcon});
|
||||
|
||||
// # Post any message in a channel as otherUser
|
||||
postMessageInChannel(testUser, testChannelUrl, Date.now());
|
||||
|
||||
// * Verify default profile name and icon of posted webhook message
|
||||
verifyProfileNameAndIcon({username: defaultUsername, userIcon: defaultIcon});
|
||||
});
|
||||
|
||||
it('MM-T2035 default username and overridden profile pic (using command) Trigger = posting a trigger word in any channel', () => {
|
||||
// # Visit test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl, triggerWord, channelSelect: testChannel.display_name});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Enable user icon override only
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverrideInt(false, true);
|
||||
|
||||
// # Visit test channel
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
// # Edit outgoing webhook
|
||||
editOutgoingWebhook(testTeam.name, siteName, {iconUrl: overrideIconUrl, channelSelect: noChannelSelectionOption});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Post a message in test channel as testUser
|
||||
postMessageInChannel(testUser, testChannelUrl, messageWithTriggerWord);
|
||||
|
||||
// * Verify default profile name and overridden icon of posted webhook message
|
||||
verifyProfileNameAndIcon({username: sysadmin.username, userIcon: overriddenIcon});
|
||||
|
||||
// # Visit off-topic channel
|
||||
cy.visit(offTopicUrl);
|
||||
|
||||
// # Post a message in off-topic channel as testUser
|
||||
postMessageInChannel(testUser, offTopicUrl, messageWithTriggerWord);
|
||||
|
||||
// * Verify default profile name and overridden icon of posted webhook message
|
||||
verifyProfileNameAndIcon({username: sysadmin.username, userIcon: overriddenIcon});
|
||||
});
|
||||
|
||||
it('MM-T2036 overridden username and profile pic (using Mattermost UI)', () => {
|
||||
// # Go to test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl, triggerWord});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Enable user name and icon overrides
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Visit test channel
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
// # Edit outgoing webhook
|
||||
editOutgoingWebhook(testTeam.name, siteName, {username: overriddenUsername, iconUrl: overrideIconUrl});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Post a message in off-topic channel as testUser
|
||||
postMessageInChannel(testUser, offTopicUrl, messageWithTriggerWord);
|
||||
|
||||
// * Verify default profile name and overridden icon of posted webhook message
|
||||
verifyProfileNameAndIcon({username: overriddenUsername, userIcon: overriddenIcon});
|
||||
});
|
||||
|
||||
it('MM-T2037 Outgoing Webhooks - overridden username and profile pic from webhook', () => {
|
||||
const usernameFromWebhook = 'user_from_webhook';
|
||||
const newCallbackUrl = callbackUrl + '?override_username=' + usernameFromWebhook + '&override_icon_url=' + overrideIconUrl;
|
||||
|
||||
// # Visit test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl: newCallbackUrl, triggerWord});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Enable user name and icon overrides
|
||||
cy.apiAdminLogin();
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Visit test channel
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
// # Edit outgoing webhook
|
||||
editOutgoingWebhook(testTeam.name, siteName, {callbackUrl: newCallbackUrl, withConfirmation: true});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Post a message in off-topic as testUser
|
||||
postMessageInChannel(testUser, offTopicUrl, messageWithTriggerWord);
|
||||
|
||||
// # Verify overridden profile name and icon from posted webhook message
|
||||
verifyProfileNameAndIcon({username: usernameFromWebhook, userIcon: overriddenIcon});
|
||||
|
||||
// # Post a message in test channel as otherUser
|
||||
postMessageInChannel(otherUser, testChannelUrl, messageWithTriggerWord);
|
||||
|
||||
// # Verify overridden profile name and icon from posted webhook message
|
||||
verifyProfileNameAndIcon({username: usernameFromWebhook, userIcon: overriddenIcon});
|
||||
});
|
||||
|
||||
it('MM-T2038 Bot posts as a comment/reply', () => {
|
||||
const newCallbackUrl = callbackUrl + '?response_type=comment';
|
||||
|
||||
// # Visit test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl: newCallbackUrl, triggerWord});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Edit outgoing webhook
|
||||
editOutgoingWebhook(testTeam.name, siteName, {callbackUrl: newCallbackUrl, withConfirmation: true});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Post a message in off-topic as testUser
|
||||
postMessageInChannel(testUser, offTopicUrl, messageWithTriggerWord);
|
||||
|
||||
cy.getLastPost().should('contain', 'comment');
|
||||
});
|
||||
|
||||
it('MM-T2039 Outgoing Webhooks - Reply to bot post', () => {
|
||||
const secondMessage = 'some text';
|
||||
|
||||
// # Visit test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl, triggerWord});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
// # Post a message in off-topic as testUser
|
||||
postMessageInChannel(testUser, offTopicUrl, messageWithTriggerWord);
|
||||
cy.postMessage(secondMessage);
|
||||
|
||||
// # Post a reply on RHS to the webhook post
|
||||
cy.getNthPostId(-2).then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
cy.uiGetRHS();
|
||||
cy.postMessageReplyInRHS('A reply to the webhook post');
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
});
|
||||
|
||||
cy.uiGetPostHeader().contains('Commented on ' + sysadmin.username + '\'s message: #### Outgoing Webhook Payload');
|
||||
});
|
||||
|
||||
it('MM-T2040 Disable overriding username and profile pic in System Console', () => {
|
||||
// # Visit test channel and post a message
|
||||
cy.visit(testChannelUrl);
|
||||
cy.postMessage('hello');
|
||||
|
||||
// # Set outgoing webhook
|
||||
setOutgoingWebhook(testTeam.name, siteName, {callbackUrl, triggerWord});
|
||||
|
||||
// * Verify it redirects to test channel
|
||||
cy.url().should('include', testChannelUrl);
|
||||
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Enable user name and icon overrides
|
||||
enableUsernameAndIconOverride(true);
|
||||
|
||||
// # Disable user name and icon overrides
|
||||
enableUsernameAndIconOverride(false);
|
||||
|
||||
// # Post a message in off-topic as testUser
|
||||
postMessageInChannel(testUser, offTopicUrl, messageWithTriggerWord);
|
||||
|
||||
// # Verify creator's profile name and icon from posted webhook message
|
||||
verifyProfileNameAndIcon({username: sysadmin.username, userId: sysadmin.id});
|
||||
});
|
||||
});
|
||||
|
||||
function postMessageInChannel(user, channelUrl, message) {
|
||||
cy.apiLogin(user);
|
||||
cy.visit(channelUrl);
|
||||
cy.postMessage(message);
|
||||
cy.uiWaitUntilMessagePostedIncludes('#### Outgoing Webhook Payload');
|
||||
}
|
||||
|
||||
function setOutgoingWebhook(teamName, siteName, {callbackUrl, channelSelect, triggerWord}) {
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * Verify that it redirects to integrations URL. Then, click "Outgoing Webhooks"
|
||||
cy.url().should('include', `${teamName}/integrations`);
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Outgoing Webhooks').click();
|
||||
|
||||
// * Verify that it redirects to outgoing webhooks URL. Then, click "Add Outgoing Webhook"
|
||||
cy.url().should('include', `${teamName}/integrations/outgoing_webhooks`);
|
||||
cy.findByText('Add Outgoing Webhook').click();
|
||||
|
||||
// * Verify that it redirects to where it can add outgoing webhook
|
||||
cy.url().should('include', `${teamName}/integrations/outgoing_webhooks/add`);
|
||||
|
||||
// # Enter webhook details such as title, description and channel, then save
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
cy.get('#displayName').type('Webhook Title');
|
||||
cy.get('#description').type('Webhook Description');
|
||||
|
||||
if (triggerWord) {
|
||||
cy.get('#triggerWords').type(triggerWord);
|
||||
}
|
||||
if (channelSelect) {
|
||||
cy.get('#channelSelect').select(channelSelect);
|
||||
}
|
||||
cy.get('#callbackUrls').type(callbackUrl);
|
||||
cy.findByText('Save').scrollIntoView().should('be.visible').click();
|
||||
});
|
||||
|
||||
// # Click "Done" and verify that it redirects to incoming webhooks URL
|
||||
cy.findByText('Done').click();
|
||||
cy.url().should('include', `${teamName}/integrations/outgoing_webhooks`);
|
||||
|
||||
// # Click back to site name and verify that it redirects to test team/channel
|
||||
cy.findByText(`Back to ${siteName}`).click();
|
||||
}
|
||||
|
||||
function editOutgoingWebhook(teamName, siteName, {username, iconUrl, callbackUrl, channelSelect, withConfirmation}) {
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
|
||||
// * click "Outgoing Webhooks"
|
||||
cy.get('.backstage-sidebar').should('be.visible').findByText('Outgoing Webhooks').click();
|
||||
|
||||
// * click "Edit"
|
||||
cy.get('.item-actions > a > span').click();
|
||||
|
||||
// * Verify that it redirects to where it can add outgoing webhook
|
||||
cy.url().should('include', `${teamName}/integrations/outgoing_webhooks/edit`);
|
||||
|
||||
// # Change the profile pic for the outgoing webhook
|
||||
cy.get('.backstage-form').should('be.visible').within(() => {
|
||||
if (username) {
|
||||
cy.get('#username').type(username);
|
||||
}
|
||||
if (iconUrl) {
|
||||
cy.get('#iconURL').scrollIntoView().type(iconUrl);
|
||||
}
|
||||
if (channelSelect) {
|
||||
cy.get('#channelSelect').select(channelSelect);
|
||||
}
|
||||
if (callbackUrl) {
|
||||
cy.get('#callbackUrls').type(callbackUrl);
|
||||
}
|
||||
|
||||
cy.get('#saveWebhook').click().wait(TIMEOUTS.ONE_SEC);
|
||||
});
|
||||
|
||||
if (withConfirmation) {
|
||||
cy.get('#confirmModalButton').should('be.visible').click();
|
||||
}
|
||||
|
||||
// # Click back to site name and verify that it redirects to test team/channel
|
||||
cy.findByText(`Back to ${siteName}`).click();
|
||||
}
|
||||
|
||||
function verifyProfileNameAndIcon({username, userIcon, userId}) {
|
||||
// * Verify the username
|
||||
cy.uiGetPostHeader().findByText(username);
|
||||
|
||||
// * Verify the overridden user profile icon
|
||||
if (userIcon) {
|
||||
cy.uiGetPostProfileImage().
|
||||
find('img').
|
||||
invoke('attr', 'src').
|
||||
then((url) => cy.request({url, encoding: 'base64'})).
|
||||
then(({status, body}) => {
|
||||
cy.fixture(userIcon).then((imageData) => {
|
||||
expect(status).to.equal(200);
|
||||
expect(body).to.eq(imageData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// * Verify the user profile icon
|
||||
if (userId) {
|
||||
cy.uiGetPostProfileImage().
|
||||
find('img').
|
||||
should('have.attr', 'src').
|
||||
and('include', `/api/v4/users/${userId}/image?_=`);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('Prompting set status', () => {
|
||||
let user1;
|
||||
let user2;
|
||||
let testChannelUrl;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({user, team}) => {
|
||||
user1 = user;
|
||||
testChannelUrl = `/${team.name}/channels/town-square`;
|
||||
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
user2 = otherUser;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, user2.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(testChannelUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T673 Prompting to set status to online', () => {
|
||||
// # Set user status to offline
|
||||
cy.uiOpenUserMenu('Offline');
|
||||
|
||||
// * Your status stays offline in your view
|
||||
cy.uiGetSetStatusButton().find('.icon-circle-outline');
|
||||
|
||||
// # Log out
|
||||
cy.apiLogout();
|
||||
|
||||
cy.apiLogin(user2);
|
||||
cy.visit(testChannelUrl);
|
||||
openDM(user1.username);
|
||||
|
||||
// * Your status stays offline in other users' views.
|
||||
cy.get('#channelHeaderInfo').within(() => {
|
||||
cy.get('.offline--icon').should('be.visible');
|
||||
cy.get('.online--icon').should('not.exist');
|
||||
cy.findByText('Offline').should('be.visible');
|
||||
});
|
||||
cy.apiGetUserStatus(user1.id).then((result) => {
|
||||
cy.wrap(result.status.status).should('be.equal', 'offline');
|
||||
});
|
||||
|
||||
// # Log back in
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
// # On modal that asks if you want to be set Online, select No.
|
||||
cy.get('.modal-content').within(() => {
|
||||
cy.findByText('Your Status is Set to "Offline"').should('be.visible');
|
||||
cy.get('#cancelModalButton').click();
|
||||
});
|
||||
|
||||
// * Your status stays offline in your view
|
||||
cy.uiGetSetStatusButton().find('.icon-circle-outline');
|
||||
|
||||
// * Your status stays offline in other user's view.
|
||||
cy.apiLogin(user2);
|
||||
cy.visit(testChannelUrl);
|
||||
openDM(user1.username);
|
||||
|
||||
// * Your status stays offline in other user's view.
|
||||
cy.get('#channelHeaderInfo').within(() => {
|
||||
cy.get('.offline--icon').should('be.visible');
|
||||
cy.get('.online--icon').should('not.exist');
|
||||
cy.findByText('Offline').should('be.visible');
|
||||
});
|
||||
cy.apiGetUserStatus(user1.id).then((result) => {
|
||||
cy.wrap(result.status.status).should('be.equal', 'offline');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const openDM = (username) => {
|
||||
// # Click '+' to open DM and wait for some time to get the DM modal fully loaded
|
||||
cy.uiAddDirectMessage().click().wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Type username and wait for some time to load users list
|
||||
cy.get('#selectItems input').typeWithForce(username).wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Find the user in the list and click
|
||||
cy.get('#multiSelectList').findByText(`@${username}`).click();
|
||||
|
||||
// * Verify that the user is selected
|
||||
cy.get('#selectItems').findByText(username).should('be.visible');
|
||||
|
||||
// # Click go to open DM with the user
|
||||
cy.findByText('Go').click();
|
||||
};
|
||||
@@ -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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
const callbackUrl = `${Cypress.env().webhookBaseUrl}/post_outgoing_webhook`;
|
||||
|
||||
cy.requireWebhookServer();
|
||||
|
||||
// # Create test team, channel, and webhook
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team.name;
|
||||
testChannel = channel.name;
|
||||
|
||||
const newOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: 'New Outgoing Webhook',
|
||||
trigger_words: ['testing'],
|
||||
callback_urls: [callbackUrl],
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(newOutgoingHook, false);
|
||||
cy.visit(`/${testTeam}/integrations/outgoing_webhooks`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T612 Regenerate token', () => {
|
||||
// # Grab the generated token
|
||||
let generatedToken;
|
||||
cy.get('.item-details__token').then((number1) => {
|
||||
generatedToken = number1.text().split(' ').pop();
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
|
||||
// * Post message and assert token is present in test message
|
||||
cy.postMessage('testing');
|
||||
cy.uiWaitUntilMessagePostedIncludes(generatedToken);
|
||||
|
||||
// # Regenerate the token
|
||||
cy.visit(`/${testTeam}/integrations/outgoing_webhooks`);
|
||||
cy.findAllByText('Regenerate Token').click();
|
||||
|
||||
// # Wait until the old token is replaced by a new one
|
||||
cy.waitUntil(() => cy.get('.item-details__token').then((el) => {
|
||||
return !el[0].innerText.includes(generatedToken);
|
||||
}));
|
||||
|
||||
// # Grab the regenerated token
|
||||
let regeneratedToken;
|
||||
cy.get('.item-details__token').then((number2) => {
|
||||
regeneratedToken = number2.text().split(' ').pop();
|
||||
|
||||
// * Post a message and confirm regenerated token appears only
|
||||
cy.visit(`/${testTeam}/channels/${testChannel}`);
|
||||
cy.postMessage('testing');
|
||||
cy.uiWaitUntilMessagePostedIncludes(regeneratedToken).then(() => {
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#${lastPostId}_message`).should('not.contain', generatedToken);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let outgoingWebhook;
|
||||
const missing = 'missing';
|
||||
const Alpha = 'Alpha Common';
|
||||
const Bravo = 'Bravo & $ @';
|
||||
const Charlie = 'Charlie Common';
|
||||
const triggerA = 'apple';
|
||||
const triggerB = 'banana';
|
||||
const triggerC = 'carrot';
|
||||
|
||||
before(() => {
|
||||
const callbackUrl = `${Cypress.env().webhookBaseUrl}/post_outgoing_webhook`;
|
||||
|
||||
cy.requireWebhookServer();
|
||||
|
||||
// # Create test team and 3 outgoing web-hooks
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
testTeam = team.name;
|
||||
|
||||
const firstOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: Alpha,
|
||||
trigger_words: [triggerA],
|
||||
callback_urls: [callbackUrl],
|
||||
};
|
||||
|
||||
const secondOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: Bravo,
|
||||
trigger_words: [triggerB],
|
||||
callback_urls: [callbackUrl],
|
||||
};
|
||||
|
||||
const thirdOutgoingHook = {
|
||||
team_id: team.id,
|
||||
display_name: Charlie,
|
||||
trigger_words: [triggerC],
|
||||
callback_urls: [callbackUrl],
|
||||
};
|
||||
|
||||
cy.apiCreateWebhook(firstOutgoingHook, false).then((hook) => {
|
||||
outgoingWebhook = hook;
|
||||
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({webhook, status}) => {
|
||||
expect(status).equal(200);
|
||||
expect(webhook.id).equal(outgoingWebhook.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiCreateWebhook(secondOutgoingHook, false).then((hook) => {
|
||||
outgoingWebhook = hook;
|
||||
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({webhook, status}) => {
|
||||
expect(status).equal(200);
|
||||
expect(webhook.id).equal(outgoingWebhook.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiCreateWebhook(thirdOutgoingHook, false).then((hook) => {
|
||||
outgoingWebhook = hook;
|
||||
|
||||
cy.apiGetOutgoingWebhook(outgoingWebhook.id).then(({webhook, status}) => {
|
||||
expect(status).equal(200);
|
||||
expect(webhook.id).equal(outgoingWebhook.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T614 Search on Outgoing Webhooks page', () => {
|
||||
// * Assert that search for Alpha (lower-case) returns only Alpha webhook
|
||||
cy.visit(`/${testTeam}/integrations/outgoing_webhooks`);
|
||||
cy.get('#searchInput').type('alpha');
|
||||
verifyWebhooksList([Alpha], [Bravo, Charlie]);
|
||||
|
||||
// * Assert that search for Bravo (upper-case) returns only Bravo webhook
|
||||
cy.get('#searchInput').clear().type('BRAVO');
|
||||
verifyWebhooksList([Bravo], [Alpha, Charlie]);
|
||||
|
||||
// * Assert that search for Charlie (mixed-case, partial) returns only Charlie webhook
|
||||
cy.get('#searchInput').clear().type('cHaRl');
|
||||
verifyWebhooksList([Charlie], [Alpha, Bravo]);
|
||||
|
||||
// * Assert that search for random text returns no results
|
||||
cy.get('#searchInput').clear().type(missing);
|
||||
cy.get('.backstage-list').contains(`No outgoing webhooks match ${missing}`);
|
||||
|
||||
// * Assert that search for special character text returns only Bravo webhook
|
||||
cy.get('#searchInput').clear().type('$');
|
||||
verifyWebhooksList([Bravo], [Alpha, Charlie]);
|
||||
|
||||
// * Assert that a common search term surfaces correct webhooks
|
||||
cy.get('#searchInput').clear().type('common');
|
||||
verifyWebhooksList([Alpha, Charlie], [Bravo]);
|
||||
});
|
||||
});
|
||||
|
||||
function verifyWebhooksList(contain = [], notContain = []) {
|
||||
contain.forEach((name) => {
|
||||
cy.get('.backstage-list').findByText(name).should('be.visible');
|
||||
});
|
||||
|
||||
notContain.forEach((name) => {
|
||||
cy.get('.backstage-list').findByText(name).should('not.exist');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 @integrations
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
// # Create test team and channel
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team.name;
|
||||
testChannel = channel.display_name;
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T616 Copy icon for Outgoing Webhook token', () => {
|
||||
// Visit the integrations > add page
|
||||
cy.visit(`/${testTeam}/integrations/outgoing_webhooks/add`);
|
||||
|
||||
// * Assert that we are on the add page
|
||||
cy.url().should('include', '/outgoing_webhooks/add');
|
||||
|
||||
// # Manually set up an outgoing web-hook
|
||||
cy.get('#displayName').type('test');
|
||||
cy.get('#channelSelect').select(testChannel);
|
||||
cy.get('#triggerWords').type('trigger');
|
||||
cy.get('#callbackUrls').type('https://mattermost.com');
|
||||
cy.findByText('Save').click();
|
||||
|
||||
// Assert that webhook was set up
|
||||
cy.findByText('Setup Successful').should('be.visible');
|
||||
|
||||
// * Assert that token copy icon is present
|
||||
cy.findByTestId('copyText').should('be.visible');
|
||||
|
||||
// # Close the add outgoing webhooks page
|
||||
cy.findByText('Done').click();
|
||||
|
||||
// * Assert that we are back on the integrations > outgoing webhooks page
|
||||
cy.get('#addOutgoingWebhook').should('exist');
|
||||
|
||||
// * Assert that the copy icon is present
|
||||
cy.findByTestId('copyText').should('be.visible');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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 @integrations @plugin @not_cloud
|
||||
|
||||
import {agendaPlugin} from '../../../utils/plugins';
|
||||
|
||||
describe('Integrations', () => {
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.shouldHavePluginUploadEnabled();
|
||||
|
||||
// # Login as test user and visit the newly created test channel
|
||||
cy.apiInitSetup().then(({team, user, channel}) => {
|
||||
// # Upload and enable Agenda plugin required for test
|
||||
cy.apiUploadAndEnablePlugin(agendaPlugin);
|
||||
|
||||
// # Login as regular user and visit test channel
|
||||
cy.apiLogin(user);
|
||||
cy.visit(`/${team.name}/channels/${channel.name}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T2835 Slash command help stays visible for plugin', () => {
|
||||
// * Suggestion list is not visible
|
||||
cy.get('#suggestionList').should('not.exist').then(() => {
|
||||
// * Suggestion list is visible after typing "/agenda " with space character
|
||||
cy.findByTestId('post_textbox').type('/agenda ');
|
||||
cy.get('#suggestionList').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
// 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 @plugin @not_cloud
|
||||
|
||||
import * as MESSAGES from '../../../fixtures/messages';
|
||||
import {matterpollPlugin} from '../../../utils/plugins';
|
||||
|
||||
describe('/poll', () => {
|
||||
let user1;
|
||||
let user2;
|
||||
let testChannelUrl;
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.shouldHavePluginUploadEnabled();
|
||||
|
||||
cy.apiInitSetup().then(({team, user, offTopicUrl}) => {
|
||||
user1 = user;
|
||||
testChannelUrl = offTopicUrl;
|
||||
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
user2 = otherUser;
|
||||
|
||||
cy.apiAddUserToTeam(team.id, user2.id);
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiUpdateConfig({
|
||||
PluginSettings: {
|
||||
Enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
// # Upload and enable "matterpoll" plugin
|
||||
cy.apiUploadAndEnablePlugin(matterpollPlugin);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(testChannelUrl);
|
||||
});
|
||||
|
||||
it('MM-T576_1 /poll', () => {
|
||||
// # In center post the following: /poll "Do you like https://mattermost.com?"
|
||||
cy.postMessage('/poll "Do you like https://mattermost.com?"');
|
||||
|
||||
cy.uiGetPostBody().within(() => {
|
||||
// * Poll displays as expected in center
|
||||
cy.findByLabelText('matterpoll').should('be.visible');
|
||||
|
||||
// * Mattermost URL renders as a live link
|
||||
cy.contains('a', 'https://mattermost.com').
|
||||
should('have.attr', 'href', 'https://mattermost.com');
|
||||
|
||||
// # Click "Yes" or "No"
|
||||
cy.findByText('Yes').click();
|
||||
});
|
||||
|
||||
// * After clicking Yes or No, ephemeral message displays "Your vote has been counted"
|
||||
cy.uiWaitUntilMessagePostedIncludes('Your vote has been counted.');
|
||||
|
||||
// * If you go back and change your vote to another answer, ephemeral message displays "Your vote has been updated."
|
||||
cy.uiGetNthPost(-2).within(() => {
|
||||
cy.findByText('No').click();
|
||||
});
|
||||
cy.uiWaitUntilMessagePostedIncludes('Your vote has been updated');
|
||||
|
||||
// # Click to reply on any message to open the RHS
|
||||
cy.postMessage(MESSAGES.SMALL);
|
||||
cy.clickPostCommentIcon();
|
||||
|
||||
cy.uiGetRHS().within(() => {
|
||||
// # In RHS, post `/poll reply`
|
||||
cy.uiGetReplyTextBox().type('/poll reply');
|
||||
cy.findByTestId('SendMessageButton').click();
|
||||
|
||||
// * Poll displays as expected in RHS.
|
||||
cy.findByLabelText('matterpoll').should('be.visible');
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(user2);
|
||||
cy.visit(testChannelUrl);
|
||||
|
||||
// # Another user clicks Yes or No
|
||||
cy.uiGetNthPost(-3).within(() => {
|
||||
cy.findByText('No').click();
|
||||
});
|
||||
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(testChannelUrl);
|
||||
cy.uiGetNthPost(-3).within(() => {
|
||||
cy.findByText('End Poll').click();
|
||||
});
|
||||
|
||||
cy.findByText('End').click();
|
||||
|
||||
// * Username displays the same on the original poll post and on the "This poll has ended" post
|
||||
cy.uiWaitUntilMessagePostedIncludes('The poll Do you like https://mattermost.com? has ended');
|
||||
cy.uiGetNthPost(-4).within(() => {
|
||||
cy.contains('This poll has ended').scrollIntoView().should('be.visible');
|
||||
cy.contains(user1.nickname);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T576_2 /poll', () => {
|
||||
// # Type and enter: `/poll "Q" "A1" "A2"`
|
||||
cy.postMessage('/poll "Q" "A1" "A2"');
|
||||
|
||||
// # Click an answer option
|
||||
cy.uiGetPostBody().within(() => {
|
||||
cy.contains('Total votes: 0').should('be.visible');
|
||||
cy.findByText('A1').click();
|
||||
|
||||
// * The vote count to go up
|
||||
cy.contains('Total votes: 1').should('be.visible');
|
||||
});
|
||||
|
||||
//* User who voted sees a message that their vote was counted
|
||||
cy.uiWaitUntilMessagePostedIncludes('Your vote has been counted.');
|
||||
});
|
||||
|
||||
it('MM-T576_3 /poll', () => {
|
||||
cy.postMessage('/poll "Do you like https://mattermost.com?"');
|
||||
cy.uiGetPostBody().within(() => {
|
||||
cy.findByText('Yes').click();
|
||||
});
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(user2);
|
||||
cy.visit(testChannelUrl);
|
||||
cy.uiGetPostBody().within(() => {
|
||||
cy.findByText('Yes').click();
|
||||
});
|
||||
cy.apiLogout();
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(testChannelUrl);
|
||||
cy.uiGetPostBody().within(() => {
|
||||
// # Click "End Poll"
|
||||
cy.findByText('End Poll').click();
|
||||
});
|
||||
cy.findByText('End').click();
|
||||
|
||||
// * There is a message in the channel that the Poll has ended with a "here" link to view the responses
|
||||
cy.uiWaitUntilMessagePostedIncludes('The poll Do you like https://mattermost.com? has ended and the original post has been updated. You can jump to it by pressing here.');
|
||||
cy.uiGetPostBody().within(() => {
|
||||
cy.contains('a', 'here').click();
|
||||
});
|
||||
|
||||
// * Clicking the link highlight the poll post in the center channel
|
||||
cy.uiGetNthPost(-2).scrollIntoView().
|
||||
should('have.class', 'post--highlight').
|
||||
within(() => {
|
||||
// * Users who voted are listed below the responses
|
||||
cy.findByText(`@${user1.username}`).should('be.visible');
|
||||
cy.findByText(`@${user2.username}`).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T576_4 /poll', () => {
|
||||
// # Type and enter `/poll ":pizza:" ":thumbsup:" ":thumbsdown:"`
|
||||
cy.postMessage('/poll ":pizza:" ":thumbsup:" ":thumbsdown:"');
|
||||
|
||||
cy.uiGetPostBody().within(() => {
|
||||
// * Poll displays showing a slice of pizza emoji in place of the word "pizza"
|
||||
cy.get('h1 > span[data-emoticon="pizza"]').should('be.visible');
|
||||
cy.findByText('pizza').should('not.exist');
|
||||
|
||||
// * Emoji for "thumbsup" and "thumbsdown" are shown in place of the words "yes" and "no"
|
||||
cy.get('button > span[data-emoticon="thumbsup"]').should('be.visible');
|
||||
cy.get('button > span[data-emoticon="thumbsdown"]').should('be.visible');
|
||||
cy.findByText('thumbsup').should('not.exist');
|
||||
cy.findByText('thumbsdown').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
// ***************************************************************
|
||||
|
||||
// Group: @channels @integrations
|
||||
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
// # Login as test user and visit the newly created test channel
|
||||
cy.apiInitSetup().then(({team, channel}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
cy.visit(`/${team.name}/integrations/commands/add`);
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T581 Regen token', () => {
|
||||
// # Setup slash command
|
||||
cy.get('#displayName', {timeout: TIMEOUTS.ONE_MIN}).type('Token Regen Test');
|
||||
cy.get('#description').type('test of token regeneration');
|
||||
cy.get('#trigger').type('regen');
|
||||
cy.get('#url').type('http://hidden-peak-21733.herokuapp.com/test_inchannel');
|
||||
cy.get('#autocomplete').check();
|
||||
cy.get('#saveCommand').click();
|
||||
|
||||
// # Grab token 1
|
||||
let generatedToken;
|
||||
cy.get('p.word-break--all').then((number1) => {
|
||||
generatedToken = number1.text().split(' ').pop();
|
||||
});
|
||||
|
||||
// # Return to channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// * Post first message and assert token1 is present in the message
|
||||
cy.postMessage('/regen testing');
|
||||
cy.uiWaitUntilMessagePostedIncludes(testChannel.id);
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#${lastPostId}_message`).contains(generatedToken);
|
||||
});
|
||||
|
||||
// # Return to slash command setup and regenerate the token
|
||||
cy.visit(`/${testTeam.name}/integrations/commands/installed`);
|
||||
cy.findByText('Regenerate Token').click();
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// # Grab token 2
|
||||
let regeneratedToken;
|
||||
cy.get('.item-details__token > span').then((number2) => {
|
||||
regeneratedToken = number2.text().split(' ').pop();
|
||||
});
|
||||
|
||||
// Return to channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// * Post second message and assert token2 is present in the message
|
||||
cy.postMessage('/regen testing 2nd message');
|
||||
cy.uiWaitUntilMessagePostedIncludes(testChannel.id);
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#${lastPostId}_message`).contains(regeneratedToken).should('not.contain', generatedToken);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,269 @@
|
||||
// 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 @integrations
|
||||
|
||||
/**
|
||||
* Note: This test requires webhook server running. Initiate `npm run start:webhook` to start.
|
||||
*/
|
||||
|
||||
import {getRandomId} from '../../../utils';
|
||||
|
||||
describe('Integrations', () => {
|
||||
let user1;
|
||||
let user2;
|
||||
let team1;
|
||||
let team2;
|
||||
let offTopicUrl1;
|
||||
let offTopicUrl2;
|
||||
let commandURL;
|
||||
|
||||
const commandTrigger = 'test-ephemeral';
|
||||
const timestamp = Date.now();
|
||||
|
||||
before(() => {
|
||||
cy.requireWebhookServer();
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableLinkPreviews: true,
|
||||
},
|
||||
});
|
||||
|
||||
cy.apiInitSetup().then(({team, user, offTopicUrl}) => {
|
||||
user1 = user;
|
||||
team1 = team;
|
||||
offTopicUrl1 = offTopicUrl;
|
||||
cy.apiGetChannelByName(team1.name, 'off-topic').then(({channel}) => {
|
||||
commandURL = `${Cypress.env().webhookBaseUrl}/send_message_to_channel?channel_id=${channel.id}`;
|
||||
});
|
||||
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
user2 = otherUser;
|
||||
cy.apiAddUserToTeam(team.id, user2.id);
|
||||
});
|
||||
|
||||
cy.apiCreateTeam(`test-team-${timestamp}`, `test-team-${timestamp}`).then(({team: anotherTeam}) => {
|
||||
team2 = anotherTeam;
|
||||
offTopicUrl2 = `/${team2.name}/channels/off-topic`;
|
||||
cy.apiAddUserToTeam(team2.id, user1.id);
|
||||
cy.apiAddUserToTeam(team2.id, user2.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T662 /join command for private channels', () => {
|
||||
const privateChannelName = `private-channel-${getRandomId()}`;
|
||||
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # User 1 Create a private channel, with ${channelName}
|
||||
cy.uiCreateChannel({name: privateChannelName, isPrivate: true});
|
||||
|
||||
// # User, who is a member of the channel, try /join command without tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/join ${privateChannelName} `);
|
||||
|
||||
// * Private channel should be active
|
||||
cy.uiGetLhsSection('CHANNELS').get('.active').should('contain', privateChannelName);
|
||||
|
||||
// # User, who is a member of the channel, try /join command with tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/join ~${privateChannelName} `);
|
||||
|
||||
// * private channel should be active
|
||||
cy.uiGetLhsSection('CHANNELS').find('.active').should('contain', privateChannelName);
|
||||
|
||||
// # Login with user without privilege
|
||||
cy.apiLogin(user2);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # User, who is *not* a member of the channel, try /join command without tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/join ${privateChannelName} `);
|
||||
|
||||
// * Error message should be presented.
|
||||
cy.getLastPost().should('contain', 'An error occurred while joining the channel.').and('contain', 'System');
|
||||
|
||||
// # User, who is *not* a member of the channel, try /join command with tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/join ~${privateChannelName} `);
|
||||
|
||||
// * Error message should be presented.
|
||||
cy.getLastPost().should('contain', 'An error occurred while joining the channel.').and('contain', 'System');
|
||||
});
|
||||
|
||||
it('MM-T663 /open command for private channels', () => {
|
||||
const privateChannelName = `private-channel-${getRandomId()}`;
|
||||
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # User 1 Create a private channel, with ${channelName}
|
||||
cy.uiCreateChannel({name: privateChannelName, isPrivate: true});
|
||||
|
||||
// # User, who is a member of the channel, try /open command without tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/open ${privateChannelName} `);
|
||||
|
||||
// * Private channel should be active
|
||||
cy.uiGetLhsSection('CHANNELS').find('.active').should('contain', privateChannelName);
|
||||
|
||||
// # User, who is a member of the channel, try /open command with tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/open ~${privateChannelName} `);
|
||||
|
||||
// * Private channel should be active
|
||||
cy.uiGetLhsSection('CHANNELS').find('.active').should('contain', privateChannelName);
|
||||
|
||||
// # Login with user without privilege
|
||||
cy.apiLogin(user2);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # User, who is *not* a member of the channel, try /open command without tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/open ${privateChannelName} `);
|
||||
|
||||
// * Error message should be presented.
|
||||
cy.getLastPost().should('contain', 'An error occurred while joining the channel.').and('contain', 'System');
|
||||
|
||||
// # User, who is *not* a member of the channel, try /open command with tilde
|
||||
cy.uiGetLhsSection('CHANNELS').findByText('Off-Topic').click();
|
||||
cy.uiPostMessageQuickly(`/open ~${privateChannelName} `);
|
||||
|
||||
// * Error message should be presented.
|
||||
cy.getLastPost().should('contain', 'An error occurred while joining the channel.').and('contain', 'System');
|
||||
});
|
||||
|
||||
it('MM-T687 /msg', () => {
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # Post message
|
||||
const firstMessage = 'First message';
|
||||
cy.uiPostMessageQuickly(`/msg @${user2.username} ${firstMessage} `);
|
||||
cy.uiWaitUntilMessagePostedIncludes(firstMessage);
|
||||
|
||||
// * The user stays in the same team
|
||||
cy.get(`#${team1.name}TeamButton`).parent().should('have.class', 'active');
|
||||
|
||||
// * The user is in the DM channel with user2
|
||||
cy.get(`#sidebarItem_${Cypress._.sortBy([user1.id, user2.id]).join('__')}`).parent().should('be.visible').and('have.class', 'active');
|
||||
|
||||
// * The last message is written by user1 and contains the correct text.
|
||||
cy.getLastPost().should('contain', firstMessage).and('contain', user1.username);
|
||||
|
||||
cy.visit(offTopicUrl2);
|
||||
|
||||
// # Post message
|
||||
const secondMessage = 'Second message';
|
||||
cy.uiPostMessageQuickly(`/msg @${user2.username} ${secondMessage} `);
|
||||
cy.uiWaitUntilMessagePostedIncludes(secondMessage);
|
||||
|
||||
// * The user stays in the same team
|
||||
cy.get(`#${team2.name}TeamButton`).parent().should('have.class', 'active');
|
||||
|
||||
// * The user is in the DM channel with user2
|
||||
cy.get(`#sidebarItem_${Cypress._.sortBy([user1.id, user2.id]).join('__')}`).parent().should('be.visible').and('have.class', 'active');
|
||||
|
||||
// * The last message is written by user1 and contains the correct text.
|
||||
cy.getLastPost().should('contain', secondMessage).and('contain', user1.username);
|
||||
});
|
||||
|
||||
it('MM-T688 /expand', () => {
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # Post command
|
||||
cy.uiPostMessageQuickly('/expand ');
|
||||
|
||||
// * System post received confirming the new setting
|
||||
cy.getLastPost().should('contain', 'Image links now expand by default').and('contain', 'System');
|
||||
|
||||
// # Post message
|
||||
cy.postMessage('https://raw.githubusercontent.com/furqanmlk/furqanmlk.github.io/main/images/png-image-file.png');
|
||||
cy.getLastPostId().as('postID');
|
||||
|
||||
cy.get('@postID').then((postID) => {
|
||||
cy.get(`#post_${postID}`).should('be.visible').within(() => {
|
||||
// * Preview should be expanded
|
||||
cy.findByLabelText('Toggle Embed Visibility').
|
||||
should('be.visible').and('have.attr', 'data-expanded', 'true');
|
||||
|
||||
// * Preview should be visible
|
||||
cy.findByLabelText('file thumbnail').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('MM-T689 capital letter autocomplete, /collapse', () => {
|
||||
cy.apiLogin(user1);
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # Post message
|
||||
cy.postMessage('https://raw.githubusercontent.com/furqanmlk/furqanmlk.github.io/main/images/png-image-file.png');
|
||||
cy.getLastPostId().as('postID');
|
||||
|
||||
// # Open RHS
|
||||
cy.clickPostCommentIcon();
|
||||
|
||||
// # Type uppercase letter
|
||||
cy.uiGetReplyTextBox().type('/C');
|
||||
|
||||
// # Scan inside of suggestion list
|
||||
cy.get('#suggestionList').should('exist').and('be.visible').within(() => {
|
||||
// * Verify the collapse option exist in autocomplete and select it
|
||||
cy.findAllByText('collapse').first().should('exist').click();
|
||||
});
|
||||
|
||||
// # Hit enter to send the message
|
||||
cy.uiGetReplyTextBox().type('{enter}');
|
||||
|
||||
cy.get('@postID').then((postID) => {
|
||||
cy.get(`#rhsPost_${postID}`).should('be.visible').within(() => {
|
||||
// * Preview should not be expanded
|
||||
cy.findByLabelText('Toggle Embed Visibility').
|
||||
should('be.visible').and('have.attr', 'data-expanded', 'false');
|
||||
|
||||
// * Preview should not be visible
|
||||
cy.findByLabelText('file thumbnail').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
// * System post received confirming the new setting
|
||||
cy.getLastPost().should('contain', 'Image links now collapse by default').and('contain', 'System');
|
||||
});
|
||||
|
||||
it('MM-T705 Ephemeral message', () => {
|
||||
cy.apiAdminLogin();
|
||||
cy.visit(offTopicUrl1);
|
||||
|
||||
// # Navigate to slash commands and create the slash command
|
||||
cy.uiOpenProductMenu('Integrations');
|
||||
cy.get('#slashCommands').click();
|
||||
cy.get('#addSlashCommand').click();
|
||||
cy.get('#displayName').type(`Test${timestamp}`);
|
||||
cy.get('#trigger').type(commandTrigger);
|
||||
cy.get('#url').type(commandURL);
|
||||
cy.get('#saveCommand').click();
|
||||
cy.get('#doneButton').click();
|
||||
cy.findByText('Back to Mattermost').click();
|
||||
|
||||
// # Post slash command
|
||||
cy.uiPostMessageQuickly(`/${commandTrigger} `);
|
||||
cy.getLastPost().within(() => {
|
||||
// * Should come from the webhook bot
|
||||
cy.get('.BotTag').should('exist');
|
||||
|
||||
// * Should contain the "Hello World" text
|
||||
cy.findByText('Hello World').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
Ссылка в новой задаче
Block a user