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

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

@@ -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(&quot;${baseUrl}/static/emoji/1f439.png&quot;);">:hamster:</span>`);
cy.get('.emoticon').eq(1).parent().
should('have.html', `<span alt=":strawberry:" class="emoticon" title=":strawberry:" style="background-image: url(&quot;${baseUrl}/static/emoji/1f353.png&quot;);">: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): &amp; &lt; &gt;
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 ![graph](${url}).${'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');
});
});
});