snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts @mfa
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import * as TIMEOUTS from '@/fixtures/timeouts';
describe('Bot accounts ownership and API', () => {
let newTeam: Team;
let newUser: UserProfile;
let newChannel: Channel;
let botId: string;
let botName: string;
beforeEach(() => {
cy.apiAdminLogin();
// # Set ServiceSettings to expected values
const newSettings = {
ServiceSettings: {
EnforceMultifactorAuthentication: false,
},
};
cy.apiUpdateConfig(newSettings);
cy.apiInitSetup().then(({team, user, channel, townSquareUrl}) => {
newTeam = team;
newUser = user;
newChannel = channel;
cy.visit(townSquareUrl);
cy.postMessage('hello');
});
// # Create a test bot
cy.apiCreateBot().then(({bot}) => {
botId = bot.user_id;
botName = bot.display_name!;
cy.apiPatchUserRoles(bot.user_id, ['system_admin', 'system_user']);
});
});
it('MM-T1862 Only system admin are able to create bots', () => {
// # Open product switch menu and click "Integrations"
cy.uiOpenProductMenu('Integrations');
// * Confirm integrations are visible
cy.url().should('include', `/${newTeam.name}/integrations`);
cy.get('.backstage-header').findByText('Integrations').should('be.visible');
// # Login as a regular user
cy.apiLogin(newUser);
cy.visit(`/${newTeam.name}/channels/town-square`);
// # Click product switch button
cy.uiOpenProductMenu();
// * Confirm "Integrations" is not visible
cy.uiGetProductMenu().should('not.contain', 'Integrations');
});
it('MM-T1863 Only System Admin are able to create bots (API)', () => {
// # Login as a regular user
cy.apiLogin(newUser);
// # Try to create a new bot as a regular user
const botName3 = 'stay-enabled-bot-' + Date.now();
cy.request({
headers: {'X-Requested-With': 'XMLHttpRequest'},
url: '/api/v4/bots',
method: 'POST',
failOnStatusCode: false,
body: {
username: botName3,
display_name: 'some text',
description: 'some text',
},
}).then((response) => {
// * Validate that request was denied
expect(response.status).to.equal(403);
});
});
it('MM-T1864 Create bot (API)', () => {
// * This call will fail if bot was not created
cy.apiCreateBot();
});
it('MM-T1865 Create post as bot', () => {
// # Create token for the bot
cy.apiCreateToken(botId).then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
const msg1 = 'this is a bot message ' + botName;
cy.apiCreatePost(newChannel.id, msg1, '', {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token);
// # Re-login to validate post presence
cy.apiAdminLogin();
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
// * Validate post was created
cy.findByText(msg1).should('be.visible');
});
});
it.skip('MM-T1866 Create two posts in a row to the same channel', () => {
// # Create token for the bot
cy.apiCreateToken(botId).then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
const msg1 = 'this is a bot message ' + botName;
const msg2 = 'this is a bot message2 ' + botName;
cy.apiCreatePost(newChannel.id, msg1, '', {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token).then(({body: post1}) => {
cy.apiCreatePost(newChannel.id, msg2, '', {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token).then(({body: post2}) => {
// # Re-login to validate post presence
cy.apiAdminLogin();
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
// * Validate posts were created
cy.get(`#postMessageText_${post1.id}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
cy.get(`#postMessageText_${post2.id}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg2);
// * Validate first post has an image
cy.get(`#post_${post1.id}`).find('.Avatar').should('be.visible');
// * Validate that the second one doesn't
cy.get(`#post_${post2.id}`).should('have.class', 'same--user');
});
});
});
});
it('MM-T1867 Post as a bot and include an @ mention', () => {
// # Create token for the bot
cy.apiCreateToken(botId).then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
const msg1 = 'this is a bot message ' + botName;
cy.apiCreatePost(newChannel.id, msg1 + ' to @sysadmin', '', {}, token);
// # Re-login to validate post presence
cy.apiAdminLogin();
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
// * Assert that the last message posted contains highlighted mention
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).find('.mention--highlight').should('be.visible');
});
});
});
it('MM-T1868 BOT has a member role and is not in target channel and team', () => {
// # Create a test bot (member)
cy.apiCreateBot().then(({bot}) => {
// # Create token for the bot
cy.apiCreateToken(bot.user_id).then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
// # Try posting
cy.apiCreatePost(newChannel.id, 'this is a bot message ' + bot.username, '', {}, token, false).then((response) => {
// * Validate that posting was not allowed
expect(response.status).to.equal(403);
});
});
});
});
it('MM-T1869 BOT has a system admin role and is not in target channel and team', () => {
const botName3 = 'stay-enabled-bot-' + Date.now();
// # Create token for the bot
cy.apiCreateToken(botId).then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
// # Try posting
cy.apiCreatePost(newChannel.id, 'this is a bot message ' + botName3, '', {}, token).then((response) => {
// * Validate that posting was allowed
expect(response.status).to.equal(201);
});
});
});
});

View File

@@ -0,0 +1,316 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts @mfa
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import * as TIMEOUTS from '@/fixtures/timeouts';
describe('Bot accounts ownership and API', () => {
let newTeam: Team;
let newUser: UserProfile;
let newChannel: Channel;
let botId: string;
let botUsername: string;
let botName: string;
let adminUser: UserProfile;
beforeEach(() => {
cy.apiAdminLogin().then(({user}) => {
adminUser = user;
});
// # Set ServiceSettings to expected values
const newSettings = {
ServiceSettings: {
EnforceMultifactorAuthentication: false,
},
};
cy.apiUpdateConfig(newSettings);
cy.apiInitSetup().then(({team, user, channel, townSquareUrl}) => {
newTeam = team;
newUser = user;
newChannel = channel;
cy.visit(townSquareUrl);
cy.postMessage('hello');
});
// # Create a test bot
cy.apiCreateBot().then(({bot}) => {
botId = bot.user_id;
botUsername = bot.username;
botName = bot.display_name!;
cy.apiPatchUserRoles(bot.user_id, ['system_admin', 'system_user']);
});
});
it('MM-T1870 BOT has a system admin role and can also post to private channels they do not belong to', () => {
const channelName = 'channel' + Date.now();
// # Create private channel that bot doesn't belong to
cy.apiCreateChannel(newTeam.id, channelName, channelName, 'P').then(({channel}) => {
// # Create token for the bot
cy.apiCreateToken(botId).then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
const msg1 = 'this is a bot message ' + botName;
// # Create a post
cy.apiCreatePost(channel.id, msg1 + ' to @sysadmin', '', {}, token!);
// # Re-login to validate post presence
cy.apiAdminLogin();
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
});
});
});
});
it('MM-T1872 Bot can post to DM channel', () => {
// # Create DM channel that bot doesn't belong to
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
// # Create token for the bot
cy.apiAccessToken(botId, 'some text').then(({token}) => {
const msg1 = 'this is a bot message ' + botName;
// # Post test message
cy.postBotMessage({message: msg1, token: token!, channelId: channel.id});
// # Validate post presence
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
});
});
});
});
it('MM-T1874 Bots can post when MFA is enforced', () => {
// # Create token for the bot
cy.apiAccessToken(botId, 'some text').then(({token}) => {
const msg1 = 'this is a bot message ' + botName;
cy.postBotMessage({channelId: newChannel.id, message: msg1, props: {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token: token!});
// # Visit test channel
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
// * Validate post was created
cy.findByText(msg1).should('be.visible');
const newSettings = {
ServiceSettings: {
EnforceMultifactorAuthentication: true,
},
};
cy.apiUpdateConfig(newSettings);
const msg2 = 'this is a bot message2 ' + botName;
cy.postBotMessage({channelId: newChannel.id, message: msg2, props: {attachments: [{pretext: 'Look some text', text: 'This is text'}]}, token: token!});
cy.visit(`/${newTeam.name}/channels/` + newChannel.name);
// * Validate post was created
cy.findByText(msg2).should('be.visible');
});
});
it('MM-T1875 A bot cannot create another bot', () => {
// # Create token for the bot
cy.apiAccessToken(botId, 'some text').then(({token}) => {
// # Logout to allow posting as bot
cy.apiLogout();
// # Try to create a new bot
cy.request({
headers: {'X-Requested-With': 'XMLHttpRequest', Authorization: `Bearer ${token}`},
url: '/api/v4/bots',
method: 'POST',
failOnStatusCode: false,
body: {
username: botName + '333',
display_name: 'some text',
description: 'some text',
},
}).then((response) => {
// * Validate that request was denied
expect(response.status).to.equal(403);
});
});
});
it('MM-T1877 Reactivate a deactivated bot', () => {
// # Create private channel that bot doesn't belong to
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
// # Create token for the bot
cy.apiAccessToken(botId, 'some text').then(({token}) => {
const msg1 = 'this is a bot message ' + botName;
// # Create a post
cy.postBotMessage({channelId: channel.id, message: msg1, token: token!});
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
});
// # Disable the bot
cy.visit(`/${newTeam.name}/integrations/bots`);
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().parent().findByText('Disable').click();
// # Try to post again
const msg2 = 'this is a bot message2 ' + botName;
// # Logout to allow posting as bot
cy.apiLogout();
// # Create a post
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!, failOnStatus: false}).then(({status}) => {
// * Validate that posting failed
expect(status).to.equal(401);
});
cy.apiAdminLogin();
// # Enable the bot again
cy.visit(`/${newTeam.name}/integrations/bots`);
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().parent().findByText('Enable').click();
// # Try to post again
// * Validate that posting works
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!});
// * Validate post presence
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg2);
});
});
});
});
it('MM-T1878 Disable token can not be used to post', () => {
// # Create DM channel that bot doesn't belong to
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
// # Create token for the bot
cy.apiAccessToken(botId, 'some text').then(({token, id}) => {
const msg1 = 'this is a bot message ' + botName;
// # Create a post
cy.postBotMessage({channelId: channel.id, message: msg1, token: token!});
// # Validate post presence
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
});
// # Disable the bot token
cy.visit(`/${newTeam.name}/integrations/bots`);
cy.findByText(`${botName} (@${botUsername})`).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
cy.get(`#${id}_deactivate`).click();
});
// # Try to post again
const msg2 = 'this is a bot message2 ' + botName;
// # Create a post
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!, failOnStatus: false}).then(({status}) => {
// * Validate that posting failed
expect(status).to.equal(401);
});
// # Enable the bot token again
cy.visit(`/${newTeam.name}/integrations/bots`);
cy.findAllByText('Bot Accounts');
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
cy.get(`#${id}_activate`).click().wait(TIMEOUTS.ONE_SEC);
// # Try to post again
// * Validate that posting works
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!});
// # Validate post presence
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg2);
});
});
});
});
});
it('MM-T1880 Deleted token can not be used to post', () => {
// # Create private channel that bot doesn't belong to
cy.apiCreateDirectChannel([newUser.id, adminUser.id]).then(({channel}) => {
// # Create token for the bot
cy.apiAccessToken(botId, 'some text').then(({token, id}) => {
const msg1 = 'this is a bot message ' + botName;
// # Create a post
cy.postBotMessage({channelId: channel.id, message: msg1, token: token!});
// # Validate post presence
cy.visit(`/${newTeam.name}/channels/` + channel.name);
cy.getLastPostId().then((postId) => {
// * Validate post was created
cy.get(`#postMessageText_${postId}`, {timeout: TIMEOUTS.ONE_MIN}).should('contain', msg1);
});
// # Disable the bot token
cy.visit(`/${newTeam.name}/integrations/bots`);
cy.findByText(`${botName} (@${botUsername})`).scrollIntoView().then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Delete token
cy.get(`#${id}_delete`).click();
cy.get('#confirmModalButton').click();
// # Try to post again
const msg2 = 'this is a bot message2 ' + botName;
// # Create a post
cy.postBotMessage({channelId: channel.id, message: msg2, token: token!, failOnStatus: false}).then(({status}) => {
// * Validate that posting failed
expect(status).to.equal(401);
});
});
});
});
});
});

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @not_cloud @bot_accounts
import type {Team} from '@mattermost/types/teams';
import * as TIMEOUTS from '@/fixtures/timeouts';
describe('Bot accounts ownership and API', () => {
let newTeam: Team;
before(() => {
cy.shouldNotRunOnCloudEdition();
cy.apiInitSetup({
promoteNewUserAsAdmin: true,
loginAfter: true,
}).then(({team}) => {
newTeam = team;
});
// # Set ServiceSettings to expected values
const newSettings = {
ServiceSettings: {
DisableBotsWhenOwnerIsDeactivated: true,
},
};
cy.apiUpdateConfig(newSettings);
});
it('MM-T1861 Bots do not re-enable if the owner is re-activated', () => {
// # Create another admin account
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
// # Login as the new admin
cy.apiLogin(sysadmin);
// # Create a new bot as the new admin
cy.apiCreateBot({prefix: 'stay-enabled-bot'}).then(({bot}) => {
// # Login again as main admin
cy.apiAdminLogin();
// # Deactivate the newly created admin
cy.apiDeactivateUser(sysadmin.id);
// # Get bot list
cy.visit(`/${newTeam.name}/integrations/bots`);
// # Search for the other bot
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
// * Validate that the plugin is disabled since its owner is deactivated
cy.get('.bot-list__disabled').scrollIntoView().should('be.visible');
// # Re-activate the newly created admin
cy.apiActivateUser(sysadmin.id);
// # Repeat the test to confirm it stays disabled
// # Get bot list
cy.visit(`/${newTeam.name}/integrations/bots`);
// # Search for the other bot
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
// * Validate that the plugin is disabled even though its owner is activated
cy.get('.bot-list__disabled').scrollIntoView().should('be.visible');
});
});
});
});

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Bot} from '@mattermost/types/bots';
import type {Team} from '@mattermost/types/teams';
import {createBotPatch} from '@/support/api/bots';
describe('Bot channel intro and avatar', () => {
let team: Team;
let bot: Bot;
before(() => {
cy.apiInitSetup().then((out) => {
team = out.team;
});
cy.makeClient().then(async (client) => {
// # Create bot
bot = await client.createBot(createBotPatch());
await client.addToTeam(team.id, bot.user_id);
});
});
it('MM-T1839 Bots have default profile image visible', () => {
// # Open bot DM channel
cy.visit(`/${team.name}/messages/@${bot.username}`);
// # Get channel intro and bot-post Avatars
cy.get<HTMLImageElement[]>(`#channelIntro .profile-icon > img.Avatar, img.Avatar[alt="${bot.username} profile image"]`).
should(($imgs) => {
// * Verify imgs downloaded
expect($imgs[0].naturalWidth).to.be.greaterThan(0);
expect($imgs[1].naturalWidth).to.be.greaterThan(0);
}).
each(($img) => {
// * Verify img visible and has src
cy.wrap($img).
should('be.visible').
and('have.attr', 'src').
then((url) => cy.request({url, encoding: 'binary'} as unknown as Partial<Cypress.RequestOptions>)).
then(({body}) => {
// * Verify matches expected default bot avatar
cy.fixture('bot-default-avatar.png', 'binary').should('deep.equal', body);
});
});
});
});

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import {getRandomId} from '@/utils';
describe('Create bot', () => {
it('MM-T1810 Create a Bot via the UI', () => {
cy.apiUpdateConfig({
ServiceSettings: {
EnableUserAccessTokens: true,
},
});
createBot();
});
it('MM-T1811 Create a Bot when personal access tokens are set to False', () => {
cy.apiUpdateConfig({
ServiceSettings: {
EnableUserAccessTokens: false,
},
});
createBot();
});
});
function createBot() {
cy.apiInitSetup().then(({team}) => {
// # Go to town-square channel
cy.visit(`/${team.name}/channels/town-square`);
cy.postMessage('hello');
// # Go to bot integrations page
cy.uiOpenProductMenu('Integrations');
cy.get('a.integration-option[href$="/bots"]').click();
cy.get('#addBotAccount').click();
// # Fill and submit form
cy.get('#username').type(`bot-${getRandomId()}`);
cy.get('#displayName').type('Test Bot');
cy.get('#saveBot').click();
// * Verify confirmation page
cy.url().
should('include', `/${team.name}/integrations/confirm`).
should('match', /token=[a-zA-Z0-9]{26}/);
// * Verify confirmation form/token
cy.get('div.backstage-form').
should('include.text', 'Setup Successful').
should((confirmation) => {
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
});
cy.get('#doneButton').click();
});
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @not_cloud @bot_accounts
import type {Team} from '@mattermost/types/teams';
import {createBotInteractive} from './helpers';
import {getRandomId} from '@/utils';
describe('Bot accounts - CRUD Testing', () => {
let newTeam: Team;
before(() => {
cy.shouldNotRunOnCloudEdition();
cy.shouldHavePluginUploadEnabled();
// # Set ServiceSettings to expected values
const newSettings = {
EmailSettings: {
SMTPServer: '',
},
PluginSettings: {
Enable: true,
},
};
cy.apiUpdateConfig(newSettings);
// # Create a test bot
cy.apiCreateBot();
// # Create and visit new channel
cy.apiInitSetup().then(({team}) => {
newTeam = team;
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
});
});
it('MM-T1849 Create a Personal Access Token when email config is invalid', () => {
// # Create a test bot and validate that token is created
const botUsername = `bot-${getRandomId()}`;
createBotInteractive(newTeam, botUsername);
cy.get('#doneButton').click();
// # Add a new token to the bot
// * Check that the previously created bot is listed
cy.findByText(`Test Bot (@${botUsername})`).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Click the 'Create token' button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Create New Token').should('be.visible').click();
// # Add description
cy.wrap(el[0].parentElement!.parentElement!).find('input').click().type('description!');
// # Save
cy.findByTestId('saveSetting').click();
// # Click Close button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Close').should('be.visible').click();
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// * Check that token is visible
cy.wrap(el[0].parentElement!.parentElement!).findAllByText(/Token ID:/).should('have.length', 2);
});
});
});

View File

@@ -0,0 +1,201 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Bot} from '@mattermost/types/bots';
import type {Team} from '@mattermost/types/teams';
import {createBotInteractive} from './helpers';
import {getRandomId} from '@/utils';
describe('Bot accounts - CRUD Testing', () => {
let newTeam: Team;
let testBot: Bot & {fullDisplayName: string};
before(() => {
// # Create and visit new channel
cy.apiInitSetup().then(({team}) => {
newTeam = team;
});
cy.apiAdminLogin();
});
beforeEach(() => {
// # Create a test bot
cy.apiCreateBot().then(({bot}) => {
testBot = bot;
});
});
it('MM-T1841 Long description text', () => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Check that the previously created bot is listed
cy.findByText(testBot.fullDisplayName).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Click the edit button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Edit').should('be.visible').click();
// * Validate redirect to edit screen
cy.url().should('include', `/${newTeam.name}/integrations/bots/edit`);
// # type long string
const longDescription = 'A'.repeat(1020); // 1024 is the limit
cy.get('#description').clear().type(longDescription);
// * Validate that it's fully typed
cy.get('#description').should('have.value', longDescription);
// # type some more characters
cy.get('#description').type('{end}12345');
// * Validate that it's partially updated
cy.get('#description').should('have.value', longDescription + '1234');
// # Update the bot
cy.get('#saveBot').click();
// * Validate that bot saved correctly
cy.url().should('include', `/${newTeam.name}/integrations/bots`);
// * Validate that the description exists
cy.findAllByText(longDescription + '1234').should('exist');
});
});
it('MM-T1842 Change BOT role', () => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Check that the previously created bot is listed
cy.findByText(testBot.fullDisplayName).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Click the edit button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Edit').should('be.visible').click();
// * Validate redirect to edit screen
cy.url().should('include', `/${newTeam.name}/integrations/bots/edit`);
// # Select sysadmin
cy.get('select').select('System Admin');
// * Validate that permissions are set and read only
cy.get('#postChannels').should('be.checked').should('be.disabled');
cy.get('#postAll').should('be.checked').should('be.disabled');
// # Update the bot
cy.get('#saveBot').click();
// * Validate that bot saved correctly
cy.url().should('include', `/${newTeam.name}/integrations/bots`);
});
});
it('MM-T1843 ID along with actual token is created', () => {
// # Create the bot and validate token is visible
createBotInteractive(newTeam);
cy.get('#doneButton').click();
});
it('MM-T1844 Token is hidden when you return to the page but ID is still visible', () => {
// # Create the bot and validate token is visible
const botUsername = `bot-${getRandomId()}`;
createBotInteractive(newTeam, botUsername).then((text) => {
// # Close the Add dialog
cy.get('#doneButton').click();
// * Check that the previously created bot is listed
cy.findByText(`Test Bot (@${botUsername})`).then((el) => {
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// * Validate that token is NOT visible on the next page
const token = text.substr(text.indexOf('Token: ') + 7, 26);
cy.findByText(new RegExp(token)).should('not.exist');
});
});
});
it('MM-T1845 Create a new token via the UI', () => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Check that the previously created bot is listed
cy.findByText(testBot.fullDisplayName).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Click the 'Create token' button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Create New Token').should('be.visible').click();
// # Try saving without description
cy.findByTestId('saveSetting').click();
cy.wrap(el[0].parentElement!.parentElement!).find('input').scrollIntoView();
// * Check for error message
cy.get('#clientError').should('be.visible');
// # Add description
cy.wrap(el[0].parentElement!.parentElement!).find('input').click().type(testBot.username + 'description!');
// # Save and check that no error is visible
cy.findByTestId('saveSetting').click();
cy.get('#clientError').should('not.exist');
cy.findAllByText(testBot.username + 'description!').should('exist');
});
});
it('MM-T1848 Delete Token', () => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Check that the previously created bot is listed
cy.findByText(testBot.fullDisplayName).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Click the 'Create token' button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Create New Token').should('be.visible').click();
// # Add description
cy.wrap(el[0].parentElement!.parentElement!).find('input').click().type('description!');
// # Save
cy.findByTestId('saveSetting').click();
// # Click Close button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Close').should('be.visible').click();
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// * Check that token is visible
cy.wrap(el[0].parentElement!.parentElement!).findByText(/Token ID:/).should('be.visible');
// # Click Delete button
cy.wrap(el[0].parentElement!.parentElement!).findByText('Delete').should('be.visible').click();
// * Validate that confirmation dialog is visible and click the delete button
cy.get('#confirmModalButton').should('be.visible').click();
// * Check that token is not visible
cy.wrap(el[0].parentElement!.parentElement!).findByText(/Token ID:/).should('not.exist');
});
});
});

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @bot_accounts
import type {Channel} from '@mattermost/types/channels';
import type {UserProfile} from '@mattermost/types/users';
describe('Bot display name', () => {
let offTopicChannel: Channel;
let otherSysadmin: UserProfile;
before(() => {
cy.intercept('**/api/v4/**').as('resources');
// # Set ServiceSettings to expected values
const newSettings = {
ServiceSettings: {
EnableUserAccessTokens: false,
},
};
cy.apiUpdateConfig(newSettings);
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
otherSysadmin = sysadmin;
cy.apiLogin(otherSysadmin);
cy.apiInitSetup().then(({team}) => {
cy.apiGetChannelByName(team.name, 'off-topic').then(({channel}) => {
offTopicChannel = channel;
});
cy.visit(`/${team.name}/channels/off-topic`);
cy.wait('@resources');
// # Wait for the page to fully load before continuing
cy.get('#sidebar-header-container').should('be.visible').and('have.text', team.display_name);
});
});
});
it('MM-T1813 Display name for bots stays current', () => {
cy.makeClient({user: otherSysadmin}).then((client) => {
// # Create a bot and get bot user id
cy.apiCreateBot().then(({bot}) => {
const botUserId = bot.user_id;
const firstMessage = 'This is the first message from a bot that will change its name';
const secondMessage = 'This is the second message from a bot that has changed its name';
// # Get token from bot's id
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
//# Add bot to team
cy.apiAddUserToTeam(offTopicChannel.team_id, botUserId);
// # Post message as bot through api with auth token
const props = {attachments: [{pretext: 'Some Pretext', text: 'Some Text'}]};
cy.postBotMessage({token: token!, message: firstMessage, props, channelId: offTopicChannel.id}).
its('id').
should('exist').
as('botPost');
cy.uiWaitUntilMessagePostedIncludes(firstMessage);
// # Go to the channel
cy.get('#sidebarItem_off-topic').click({force: true});
// * Verify bot display name
cy.get('@botPost').then((postIdA) => {
cy.get(`#post_${postIdA} button.user-popover`).click();
cy.get('div.user-profile-popover').
should('be.visible');
cy.findByTestId(`popover-fullname-${bot.username}`).
should('have.text', bot.display_name);
}).then(() => {
// # Change display name after prior verification
cy.wrap(client.patchBot(bot.user_id, {display_name: `NEW ${bot.display_name}`})).then(() => {
cy.postBotMessage({token: token!, message: secondMessage, props, channelId: offTopicChannel.id}).
its('id').
should('exist').
as('newBotPost');
cy.uiWaitUntilMessagePostedIncludes(secondMessage);
// * Verify changed display name
cy.get('@newBotPost').then(() => {
cy.get('div.user-profile-popover').
should('be.visible');
cy.findByTestId(`popover-fullname-${bot.username}`).
should('have.text', `NEW ${bot.display_name}`);
});
});
});
});
});
});
});
});

View File

@@ -0,0 +1,95 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Team} from '@mattermost/types/teams';
import * as MESSAGES from '@/fixtures/messages';
import {getRandomId} from '@/utils';
describe('Edit bot', () => {
let testTeam: Team;
before(() => {
cy.apiInitSetup().then(({team, townSquareUrl}) => {
testTeam = team;
cy.visit(townSquareUrl);
cy.postMessage('hello');
});
});
it('MM-T1840 Description allows for special character', () => {
const userName = `bot-${getRandomId()}`;
const description = MESSAGES.LARGE.concat('!@#$%&*');
// # Create bot
createBot(userName, testTeam.name);
// * Set alias for bot entry in bot list, this also checks that the bot entry exists
cy.get('.backstage-list__item').contains('.backstage-list__item', userName).as('botEntry');
cy.get('@botEntry').then((el) => {
// # Find the edit link for the bot
const editLink = el.find('.item-actions>a');
if (editLink.text() === 'Edit') {
// # Click the edit link for the bot
cy.wrap(editLink).click();
// * Check that user name is as expected
cy.get('#username').should('have.value', userName);
// * Check that details are empty
cy.get('#displayName').should('have.value', '');
cy.get('#description').should('have.value', '');
// # Set long description
cy.get('#description').clear().type(description);
// # Click update button
cy.get('#saveBot').click();
}
});
// * Get bot entry in bot list by username
cy.get('@botEntry').then((el) => {
cy.wrap(el).scrollIntoView();
// * Confirm long description is as expected
cy.wrap(el.find('.bot-details__description')).should('have.text', description);
});
});
function createBot(userName: string, teamName: string) {
// # Go to bot integrations page
cy.uiOpenProductMenu('Integrations');
cy.get('a.integration-option[href$="/bots"]').click();
cy.get('#addBotAccount').click();
// # Fill and submit form
cy.get('#username').type(userName);
cy.get('#saveBot').click();
// * Verify confirmation page
cy.url().
should('include', `/${teamName}/integrations/confirm`).
should('match', /token=[a-zA-Z0-9]{26}/);
// * Verify confirmation form/token
cy.get('div.backstage-form').
should('include.text', 'Setup Successful').
should((confirmation) => {
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
});
cy.get('#doneButton').click();
}
});

View File

@@ -0,0 +1,139 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Team} from '@mattermost/types/teams';
import * as TIMEOUTS from '@/fixtures/timeouts';
import {getRandomId} from '@/utils';
describe('Edit bot username', () => {
let team: Team;
before(() => {
cy.apiInitSetup().then((out) => {
team = out.team;
});
});
it('MM-T2923 Edit bot username.', () => {
// # Visit bot config
cy.visit('/admin_console/integrations/bot_accounts');
// # Verify that the setting is enabled
cy.findByTestId('ServiceSettings.EnableBotAccountCreationtrue', {timeout: TIMEOUTS.ONE_MIN}).should('be.checked');
// # Visit the integrations
goToCreateBot();
const initialBotName = `bot-${getRandomId()}`;
// # Fill and submit form
cy.get('#username').clear().type(initialBotName);
cy.get('#displayName').clear().type('Test Bot');
cy.get('#saveBot').click();
cy.get('#doneButton').click();
// * Set alias for bot entry in bot list, this also checks that the bot entry exists
cy.get('.backstage-list__item').contains('.backstage-list__item', initialBotName).as('botEntry');
cy.get('@botEntry').then((el) => {
// # Find the edit link for the bot
const editLink = el.find('.item-actions>a');
if (editLink.text() === 'Edit') {
// # Click the edit link for the bot
cy.wrap(editLink).click();
// * Check that user name is as expected
cy.get('#username').should('have.value', initialBotName);
// * Check that the display name is correct
cy.get('#displayName').should('have.value', 'Test Bot');
// * Check that description is empty
cy.get('#description').should('have.value', '');
const newBotName = `bot-${getRandomId()}`;
// * Enter the new user name
cy.get('#username').clear().type(newBotName);
// # Click update button
cy.get('#saveBot').click();
return cy.wrap(newBotName);
}
return cy.wrap(null);
}).then((newBotName) => {
if (!newBotName) {
throw new Error('Expected edited bot username to be returned');
}
// * Set alias for bot entry in bot list, this also checks that the bot entry exists
cy.get('.backstage-list__item').contains('.backstage-list__item', newBotName).as('newbotEntry');
// * Get bot entry in bot list by username
cy.get('@newbotEntry').then((el) => {
cy.wrap(el).scrollIntoView();
});
});
});
it('MM-T1838 Bot naming convention is enforced', () => {
goToCreateBot();
// # Attempt invalid bot usernames
tryUsername('be', NAMING_WARNING_STANDARD);
tryUsername('@be', NAMING_WARNING_STANDARD);
tryUsername('abe.', NAMING_WARNING_ENDING_PERIOD);
// # Attempt valid bot username
const validBotName = `abe-the-bot-${getRandomId()}`;
tryUsername(validBotName);
});
const NAMING_WARNING_STANDARD = 'Usernames have to begin with a lowercase letter and be 3-22 characters long. You can use lowercase letters, numbers, periods, dashes, and underscores.';
const NAMING_WARNING_ENDING_PERIOD = 'Bot usernames cannot have a period as the last character';
function tryUsername(name: string, warningMessage?: string) {
cy.get('#username').clear().type(name);
cy.get('#saveBot').click();
if (warningMessage) {
// * Verify expected warning
cy.get('.backstage-form__footer .has-error').should('have.text', warningMessage);
} else {
// * Verify confirmation page
cy.url().
should('include', `/${team.name}/integrations/confirm`).
should('match', /token=[a-zA-Z0-9]{26}/);
// * Verify confirmation form/token
cy.get('div.backstage-form').
should('include.text', 'Setup Successful').
and('include.text', name).
and((confirmation) => {
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
});
// # back to start
goToCreateBot();
}
}
function goToCreateBot() {
cy.visit(`/${team.name}/integrations/bots`);
// * Assert that adding bots possible
cy.get('#addBotAccount', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').click();
}
});

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Team} from '@mattermost/types/teams';
import * as TIMEOUTS from '@/fixtures/timeouts';
import {getRandomId} from '@/utils';
export function createBotInteractive(team: Team, username = `bot-${getRandomId()}`) {
// # Visit the Integrations > Bot Accounts page
cy.visit(`/${team.name}/integrations/bots`);
// # Click add bot
cy.get('#addBotAccount').click();
// # Fill and submit form
cy.get('#username').type(username);
cy.get('#displayName').type('Test Bot');
cy.get('#saveBot').click();
// * Verify confirmation page
cy.url({timeout: TIMEOUTS.ONE_MIN}).
should('include', `/${team.name}/integrations/confirm`).
should('match', /token=[a-zA-Z0-9]{26}/);
// * Verify confirmation form/token
cy.get('div.backstage-form').
should('include.text', 'Setup Successful').
should((confirmation) => {
expect(confirmation.text()).to.match(/Token: [a-zA-Z0-9]{26}/);
});
return cy.get('div.backstage-form').invoke('text');
}

View File

@@ -0,0 +1,77 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Bot} from '@mattermost/types/bots';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {createBotPatch} from '@/support/api/bots';
import {generateRandomUser} from '@/support/api/user';
import * as TIMEOUTS from '@/fixtures/timeouts';
describe('Bots in lists', () => {
let team: Team;
let channel: Channel;
let bots: Bot[];
let createdUsers: UserProfile[];
before(() => {
cy.apiInitSetup().then((out) => {
team = out.team;
channel = out.channel;
});
cy.makeClient().then(async (client) => {
// # Create bots
bots = await Promise.all([
client.createBot(createBotPatch()),
client.createBot(createBotPatch()),
client.createBot(createBotPatch()),
]);
// # Create users
createdUsers = await Promise.all([
client.createUser(generateRandomUser() as UserProfile, '', ''),
client.createUser(generateRandomUser() as UserProfile, '', ''),
]);
await Promise.all([
...bots,
...createdUsers,
].map(async (user) => {
// * Verify username exists
cy.wrap(user).its('username');
// # Add to team and channel
await client.addToTeam(team.id, (user as Bot).user_id ?? (user as UserProfile).id);
await client.addToChannel((user as Bot).user_id ?? (user as UserProfile).id, channel.id);
}));
});
});
it('MM-T1834 Bots are not listed on “Users” list in System Console > Users', () => {
// # Go to system console > users
cy.visit('/admin_console/user_management/users');
bots.forEach(({username}) => {
// # Search for bot
cy.get('#input_searchTerm').clear().type(`${username}`).wait(TIMEOUTS.ONE_SEC);
// * Verify bot not in list
cy.get('.noRows').should('have.text', 'No data');
// * Verify pseudo checksum total of non bot users
cy.get('.adminConsoleListTabletOptionalHead > span').should('have.text', '0 users').should('be.visible');
});
});
});

View File

@@ -0,0 +1,110 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Group: @channels @bot_accounts
import type {Bot} from '@mattermost/types/bots';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {createBotPatch} from '@/support/api/bots';
import {generateRandomUser} from '@/support/api/user';
describe('Bots in lists', () => {
let team: Team;
let channel: Channel;
let testUser: UserProfile;
const STATUS_PRIORITY: Record<string, number> = {
online: 0,
away: 1,
dnd: 2,
offline: 3,
ooo: 3,
};
before(() => {
cy.apiInitSetup().then((out) => {
team = out.team;
channel = out.channel;
testUser = out.user;
});
cy.makeClient().then(async (client) => {
// # Create bots
const bots = await Promise.all([
client.createBot(createBotPatch() as Partial<Bot>),
client.createBot(createBotPatch() as Partial<Bot>),
client.createBot(createBotPatch() as Partial<Bot>),
]);
// # Create users
const createdUsers = await Promise.all([
client.createUser(generateRandomUser() as UserProfile, '', ''),
client.createUser(generateRandomUser() as UserProfile, '', ''),
]);
await Promise.all([
...bots,
...createdUsers,
].map(async (user) => {
// * Verify username exists
cy.wrap(user).its('username');
// # Add to team and channel
await client.addToTeam(team.id, (user as Bot).user_id ?? (user as UserProfile).id);
await client.addToChannel((user as Bot).user_id ?? (user as UserProfile).id, channel.id);
}));
});
});
it('MM-T1835 Channel Members list for BOTs', () => {
cy.makeClient({user: testUser}).then((client) => {
// # Login as regular user and visit a channel
cy.apiLogin(testUser);
cy.visit(`/${team.name}/channels/${channel.name}`);
// # Open channel members
cy.get('.channel-header__trigger').click();
cy.findByText('Manage members').click();
cy.get('.more-modal__row .more-modal__name').then(async ($query) => {
// # Extract usernames from jQuery collection
const usernames = $query.toArray().map(({innerText}) => innerText.split('\n')[0]);
// # Get users
const profiles = await client.getProfilesByUsernames(usernames);
const statuses = await client.getStatusesByIds(profiles.map((user) => user.id));
const users = Cypress._.zip(profiles, statuses).map(([profile, status]) => ({...profile, ...status}));
// # Sort 'em
const sortedUsers = Cypress._.sortBy(users, [
({is_bot: isBot}) => (isBot ? 1 : 0), // users first
({status}) => STATUS_PRIORITY[status ?? 'offline'],
({username}) => username,
]);
// * Verify order of member-dropdown users against API-sourced/data-sorted version
cy.wrap(usernames).should('deep.equal', sortedUsers.map(({username}) => username));
});
// * Verify no statuses on bots
cy.get('.more-modal__row--bot .status-wrapper .status').should('not.exist');
// * Verify bot badges
cy.get('.more-modal__row--bot .Tag').then(($tags) => {
$tags.toArray().forEach((tagEl) => {
cy.wrap(tagEl).then(() => tagEl.scrollIntoView());
cy.wrap(tagEl).should('be.visible').and('have.text', 'BOT');
});
});
});
});
});

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Team} from '@mattermost/types/teams';
import type {Channel} from '@mattermost/types/channels';
import {createBotPatch} from '@/support/api/bots';
import {createChannelPatch} from '@/support/api/channel';
describe('Managing bots in Teams and Channels', () => {
let team: Team;
before(() => {
cy.apiUpdateConfig({
TeamSettings: {
RestrictCreationToDomains: 'sample.mattermost.com',
},
});
cy.apiInitSetup({loginAfter: true}).then((out) => {
team = out.team;
});
});
it('MM-T1814 Add a BOT to a team', () => {
cy.makeClient().then(async (client) => {
// # Go to channel
const channel = await client.getChannelByName(team.id, 'town-square');
cy.visit(`/${team.name}/channels/${channel.name}`);
// # Invite bot to team (asserts the modal success message)
const bot = await client.createBot(createBotPatch());
cy.uiInviteMemberToCurrentTeam(bot.username);
// * Verify system message in-channel
cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the team by you.`);
});
});
it('MM-T1815 Add a BOT to a team that has email restricted', () => {
cy.makeClient().then(async (client) => {
// # Go to channel
const channel = await client.getChannelByName(team.id, 'town-square');
cy.visit(`/${team.name}/channels/${channel.name}`);
// # Invite bot to team
const bot = await client.createBot(createBotPatch());
cy.uiInviteMemberToCurrentTeam(bot.username);
// * Verify system message in-channel
cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the team by you.`);
});
});
it('MM-T1816 Add a BOT to a channel', () => {
cy.makeClient().then(async (client) => {
// # Go to channel
const channel = await client.createChannel(createChannelPatch(team.id, 'a-chan', 'A Channel') as Channel);
cy.visit(`/${team.name}/channels/${channel.name}`);
// # Add bot to team
const bot = await client.createBot(createBotPatch());
await client.addToTeam(team.id, bot.user_id);
// # Add bot to channel in team
cy.uiInviteUsersToCurrentChannel([bot.username]);
// * Verify system message in-channel
cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the channel by you.`);
});
});
it('MM-T1817 Add a BOT to a channel that is not on the Team', () => {
cy.makeClient().then(async (client) => {
// # Go to channel
const channel = await client.createChannel(createChannelPatch(team.id, 'a-chan', 'A Channel') as Channel);
cy.visit(`/${team.name}/channels/${channel.name}`);
// # Invite bot to team
const bot = await client.createBot(createBotPatch());
cy.postMessage(`/invite @${bot.username} `);
// * Verify system message in-channel
cy.uiWaitUntilMessagePostedIncludes(`You can add @${bot.username} to this channel once they are members of`);
});
});
it('MM-T1818 No ephemeral post about Adding a bot to a channel When Bot is mentioned', () => {
cy.makeClient().then(async (client) => {
// # Go to channel
const channel = await client.createChannel(createChannelPatch(team.id, 'a-chan', 'A Channel') as Channel);
cy.visit(`/${team.name}/channels/${channel.name}`);
// # And bot to team
const bot = await client.createBot(createBotPatch());
cy.apiAddUserToTeam(team.id, bot.user_id);
// # Mention bot
const message = `hey @${bot.username}, tell me a rhyme..`;
cy.postMessage(message);
// * Verify no ephemeral post is shown asking if you want to invite the bot to the server
cy.uiGetNthPost(-1).should('contain.text', message);
});
});
});

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts @plugin @not_cloud
import type {Team} from '@mattermost/types/teams';
import * as TIMEOUTS from '@/fixtures/timeouts';
import {matterpollPlugin} from '@/utils/plugins';
describe('Managing bot accounts', () => {
let newTeam: Team;
before(() => {
cy.shouldNotRunOnCloudEdition();
cy.shouldHavePluginUploadEnabled();
// # Create and visit new channel
cy.apiInitSetup().then(({team}) => {
newTeam = team;
});
});
beforeEach(() => {
cy.apiAdminLogin();
});
it('MM-T1859 Bot is kept active when owner is disabled', () => {
// # Visit bot config
cy.visit('/admin_console/integrations/bot_accounts');
// # Click 'false' to disable
cy.findByTestId('ServiceSettings.DisableBotsWhenOwnerIsDeactivatedfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
// # Save
cy.findByTestId('saveSetting').should('be.enabled').click();
// # Create another admin account
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
// # Login as the new admin
cy.apiLogin(sysadmin);
// # Create a new bot as the new admin
cy.apiCreateBot({prefix: 'stay-enabled-bot'}).then(({bot}) => {
// # Login again as main admin
cy.apiAdminLogin();
// # Deactivate the newly created admin
cy.apiDeactivateUser(sysadmin.id);
// # Get bot list
cy.visit(`/${newTeam.name}/integrations/bots`);
// # Search for the other bot
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.display_name!);
// * Validate that the plugin is still active, even though its owner is disabled
cy.get('.bot-list__disabled').should('not.exist');
cy.findByText(bot.fullDisplayName).scrollIntoView().should('be.visible');
cy.visit(`/${newTeam.name}/messages/@sysadmin`);
// # Get last post message text
cy.getLastPostId().then((postId) => {
cy.get(`#postMessageText_${postId}`).as('postMessageText');
});
// * Verify entire message
cy.get('@postMessageText').
should('be.visible').
and('contain.text', `${sysadmin.username} was deactivated. They managed the following bot accounts`).
and('contain.text', bot.username);
});
});
});
it.skip('MM-T1853 Bots managed plugins can be created when Enable Bot Account Creation is set to false', () => {
// # Upload and enable "matterpoll" plugin
cy.apiUploadAndEnablePlugin(matterpollPlugin);
// # Visit bot config
cy.visit('/admin_console/integrations/bot_accounts');
// # Click 'false' to disable
cy.findByTestId('ServiceSettings.EnableBotAccountCreationfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
// # Save
cy.findByTestId('saveSetting').should('be.enabled').click();
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Validate that plugin installed ok
cy.contains('Matterpoll (@matterpoll)', {timeout: TIMEOUTS.ONE_MIN});
});
});

View File

@@ -0,0 +1,208 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Team} from '@mattermost/types/teams';
import * as TIMEOUTS from '@/fixtures/timeouts';
import {getRandomId} from '@/utils';
describe('Managing bot accounts', () => {
let newTeam: Team;
before(() => {
// # Create and visit new channel
cy.apiInitSetup().then(({team}) => {
newTeam = team;
});
});
beforeEach(() => {
cy.apiAdminLogin();
const newSettings = {
ServiceSettings: {
EnableBotAccountCreation: true,
},
};
cy.apiUpdateConfig(newSettings);
});
it('MM-T1851 No option to create BOT accounts when Enable Bot Account Creation is set to False.', () => {
// # Visit bot config
cy.visit('/admin_console/integrations/bot_accounts');
// # Click 'false' to disable
cy.findByTestId('ServiceSettings.EnableBotAccountCreationfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
// # Save
cy.findByTestId('saveSetting').should('be.enabled').click();
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Assert that adding bots is not possible
cy.get('#addBotAccount', {timeout: TIMEOUTS.ONE_MIN}).should('not.exist');
});
it('MM-T1852 Bot creation via API is not permitted when Enable Bot Account Creation is set to False', () => {
// # Visit bot config
cy.visit('/admin_console/integrations/bot_accounts');
// # Click 'false' to disable
cy.findByTestId('ServiceSettings.EnableBotAccountCreationfalse', {timeout: TIMEOUTS.ONE_MIN}).click();
// # Save
cy.findByTestId('saveSetting').should('be.enabled').click().wait(TIMEOUTS.HALF_SEC);
// * Validate that creating bot fails
cy.request({
headers: {'X-Requested-With': 'XMLHttpRequest'},
url: '/api/v4/bots',
method: 'POST',
failOnStatusCode: false,
body: {
username: `bot-${getRandomId()}`,
display_name: 'test bot',
description: 'test bot',
},
}).then((response) => {
expect(response.status).to.equal(403);
expect(response.body.message).to.equal('Bot creation has been disabled.');
return cy.wrap(response);
});
});
it('MM-T1854 Bots can be create when Enable Bot Account Creation is set to True.', () => {
// # Visit bot config
cy.visit('/admin_console/integrations/bot_accounts');
// * Check that creation is enabled
cy.findByTestId('ServiceSettings.EnableBotAccountCreationtrue', {timeout: TIMEOUTS.ONE_MIN}).should('be.checked');
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Assert that adding bots is possible
cy.get('#addBotAccount', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
});
it('MM-T1856 Disable Bot', () => {
cy.apiCreateBot({prefix: 'test-bot'}).then(({bot}) => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// # Filter bot
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
// * Check that the previously created bot is listed
cy.findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().then((el) => {
// # Click the disable button
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(3)').should('be.visible').click();
});
// * Check that the bot is in the 'disabled' section
cy.get('.bot-list__disabled').scrollIntoView().findByText(bot.fullDisplayName).should('be.visible');
});
});
it('MM-T1857 Enable Bot', () => {
cy.apiCreateBot({prefix: 'test-bot'}).then(({bot}) => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Check that the previously created bot is listed
cy.findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().then((el) => {
// # Click the disable button
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(3)').should('be.visible').click();
});
// # Filter bot
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.username);
// # Re-enable the bot
cy.get('.bot-list__disabled').scrollIntoView().findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).scrollIntoView().then((el) => {
// # Click the enable button
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(1)').should('be.visible').click();
});
// * Check that the bot is in the 'enabled' section
cy.findByText(bot.fullDisplayName).scrollIntoView().should('be.visible');
cy.get('.bot-list__disabled').should('not.exist');
});
});
it('MM-T1858 Search active and disabled Bot accounts', () => {
cy.apiCreateBot({prefix: 'hello-bot'}).then(({bot}) => {
// # Visit the integrations
cy.visit(`/${newTeam.name}/integrations/bots`);
// * Check that the previously created bot is listed
cy.findByText(bot.fullDisplayName, {timeout: TIMEOUTS.ONE_MIN}).then((el) => {
// # Make sure it's on the screen
cy.wrap(el[0].parentElement!.parentElement!).scrollIntoView();
// # Click the disable button
cy.wrap(el[0].parentElement!.parentElement!).find('button:nth-child(3)').should('be.visible').click();
});
// * Validate that disabled section appears
cy.get('.bot-list__disabled').scrollIntoView().should('be.visible');
// # Search for the other bot
cy.apiCreateBot({prefix: 'other-bot'}).then(({bot: otherBot}) => {
cy.get('#searchInput').type(otherBot.username);
// * Validate that disabled section disappears
cy.get('.bot-list__disabled').should('not.exist');
});
});
});
it('MM-T1860 Bot is disabled when owner is deactivated', () => {
// # Create another admin account
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
// # Login as the new admin
cy.apiLogin(sysadmin);
// # Create a new bot as the new admin
cy.apiCreateBot({prefix: 'stay-enabled-bot'}).then(({bot}) => {
// # Login again as main admin
cy.apiAdminLogin();
// # Deactivate the newly created admin
cy.apiDeactivateUser(sysadmin.id);
// # Get bot list
cy.visit(`/${newTeam.name}/integrations/bots`);
// # Search for the other bot
cy.get('#searchInput', {timeout: TIMEOUTS.ONE_MIN}).type(bot.display_name!);
// * Validate that the plugin is disabled since it's owner is deactivate
cy.get('.bot-list__disabled').scrollIntoView().findByText(bot.fullDisplayName).scrollIntoView().should('be.visible');
cy.visit(`/${newTeam.name}/messages/@sysadmin`);
// # Get last post message text
cy.getLastPostId().then((postId) => {
cy.get(`#postMessageText_${postId}`).as('postMessageText');
});
// * Verify entire message
cy.get('@postMessageText').
should('be.visible').
and('contain.text', `${sysadmin.username} was deactivated. They managed the following bot accounts`).
and('contain.text', bot.username);
});
});
});
});

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Channel} from '@mattermost/types/channels';
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
describe('Bot post message', () => {
let offTopicChannel: Channel;
before(() => {
cy.apiInitSetup().then(({team}) => {
cy.apiGetChannelByName(team.name, 'off-topic').then(({channel}) => {
offTopicChannel = channel;
});
cy.visit(`/${team.name}/channels/off-topic`);
});
});
it('MM-T1812 Post as a bot when personal access tokens are false', () => {
// # Create a bot and get bot user id
cy.apiCreateBot().then(({bot}) => {
const botUserId = bot.user_id;
const message = 'This is a message from a bot.';
// # Get token from bot's id
cy.apiAccessToken(botUserId, 'Create token').then(({token}) => {
//# Add bot to team
cy.apiAddUserToTeam(offTopicChannel.team_id, botUserId);
// # Post message as bot through api with auth token
const props = {attachments: [{pretext: 'Some Pretext', text: 'Some Text'}]};
cy.postBotMessage({token: token!, message, props, channelId: offTopicChannel.id}).
its('id').
should('exist').
as('botPost');
// * Verify bot message
cy.uiWaitUntilMessagePostedIncludes(message);
cy.get('@botPost').then((postId) => {
cy.get(`#postMessageText_${postId}`).
should('be.visible').
and('have.text', message);
});
});
});
});
});

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Team} from '@mattermost/types/teams';
import {createBotPatch} from '@/support/api/bots';
describe('Managing bots in Teams and Channels', () => {
let team: Team;
before(() => {
cy.apiUpdateConfig({
TeamSettings: {
RestrictCreationToDomains: 'sample.mattermost.com',
},
});
cy.apiInitSetup({loginAfter: true}).then((out) => {
team = out.team;
});
});
it('MM-T1819 Promote a BOT to team admin', () => {
cy.makeClient().then(async (client) => {
// # Go to channel
const channel = await client.getChannelByName(team.id, 'off-topic');
cy.visit(`/${team.name}/channels/${channel.name}`);
// # Add bot to team
const bot = await client.createBot(createBotPatch());
await client.addToTeam(team.id, bot.user_id);
// # Open team menu and click 'Manage Members'
cy.uiOpenTeamMenu('Manage members');
// # Find bot
cy.get('.more-modal__list').find('.more-modal__row').its('length').should('be.gt', 0);
cy.get('#searchUsersInput').type(bot.username);
// # Wait for loading screen
cy.get('#teamMembersModal .loading-screen').should('be.visible');
// # Find bot member dropdown
cy.get(`#teamMembersDropdown_${bot.username}`).as('memberDropdown').should('contain.text', 'Member').click();
// # Promote bot to team admin
cy.get('li').contains('Make Team Admin').click();
// * Verify bot was promoted
cy.get('@memberDropdown').should('contain.text', 'Team Admin');
});
});
});

View File

@@ -0,0 +1,99 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts @not_cloud
import type {Bot} from '@mattermost/types/bots';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {createBotPatch} from '@/support/api/bots';
import {generateRandomUser} from '@/support/api/user';
describe('Bot accounts', () => {
let team: Team;
let channel: Channel;
let testUser: UserProfile;
let bots: Bot[];
let createdUsers: UserProfile[];
before(() => {
cy.shouldNotRunOnCloudEdition();
cy.apiInitSetup().then((out) => {
team = out.team;
channel = out.channel;
testUser = out.user;
});
cy.makeClient().then(async (client) => {
// # Create bots
bots = await Promise.all([
client.createBot(createBotPatch()),
client.createBot(createBotPatch()),
client.createBot(createBotPatch()),
]);
// # Create users
createdUsers = await Promise.all([
client.createUser(generateRandomUser() as UserProfile, '', ''),
client.createUser(generateRandomUser() as UserProfile, '', ''),
]);
await Promise.all([
...bots,
...createdUsers,
].map(async (user) => {
// * Verify username exists
cy.wrap(user).its('username');
// # Add to team and channel
await client.addToTeam(team.id, (user as Bot).user_id ?? (user as UserProfile).id);
await client.addToChannel((user as Bot).user_id ?? (user as UserProfile).id, channel.id);
}));
});
});
beforeEach(() => {
cy.apiAdminLogin();
});
it('MM-T1836 Bot accounts display', () => {
// # Login as regular user and visit a channel
cy.apiLogin(testUser);
cy.visit(`/${team.name}/messages/@${bots[0].username}`);
cy.get('.SidebarChannelGroup:contains(DIRECT MESSAGES) .SidebarChannel.active > .SidebarLink').then(($link) => {
// * Verify DM label
cy.wrap($link).find('.SidebarChannelLinkLabel').should('have.text', bots[0].username);
// * Verify bot icon exists
cy.wrap($link).find('.Avatar').should('exist').
invoke('attr', 'src').
then((url) => cy.request({url, encoding: 'binary'})).
then(({body}) => {
// * Verify it matches default bot avatar
cy.fixture('bot-default-avatar.png', 'binary').should('deep.equal', body);
});
});
cy.postMessage('Bump bot chat recency');
// # Open a new DM
cy.visit(`/${team.name}/messages/@${createdUsers[0].username}`);
cy.postMessage('Hello, regular user');
// * Verify Bots and Regular users as siblings in DMs
cy.get('.SidebarChannelGroup:contains(DIRECT MESSAGES) .SidebarChannel.active').siblings('.SidebarChannel').then(($siblings) => {
cy.wrap($siblings).contains('.SidebarChannelLinkLabel', bots[0].username);
});
});
});

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @bot_accounts
import type {Bot} from '@mattermost/types/bots';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {createBotPatch} from '@/support/api/bots';
import * as TIMEOUTS from '@/fixtures/timeouts';
describe('Bot tags', () => {
let me: UserProfile;
let team: Team;
let channel: Channel;
let postId: string;
before(() => {
cy.apiInitSetup().then((out) => {
team = out.team;
channel = out.channel;
});
let meId: string;
cy.getCurrentUserId().then((id) => {
meId = id;
});
cy.makeClient().then(async (client) => {
// # Setup state
me = await client.getUser(meId);
const bot = await client.createBot(createBotPatch() as Partial<Bot>);
await client.addToTeam(team.id, bot.user_id);
await client.addToChannel(bot.user_id, channel.id);
const {token} = await client.createUserAccessToken(bot.user_id, 'Create token');
const message = `Message for @${me.username}. Signed, @${bot.username}.`;
// # Post message as bot through api with auth token
const props = {attachments: [{pretext: 'Some Pretext', text: 'Some Text'}]};
cy.postBotMessage({token: token!, message, props, channelId: channel.id}).then(async ({id}) => {
postId = id;
await client.pinPost(postId);
cy.visit(`/${team.name}/channels/${channel.name}`);
cy.get(`#post_${postId}`).trigger('mouseover', {force: true});
cy.wait(TIMEOUTS.HALF_SEC).get(`#CENTER_flagIcon_${postId}`).click();
});
});
});
it('MM-T1831 BOT tag is visible in search results', () => {
// # Open search
cy.uiSearchPosts(`Message for @${me.username}`);
// * Verify bot badge
cy.get('.sidebar--right__title').should('contain.text', 'Search Results');
rhsPostHasBotBadge(postId);
});
it('MM-T1832 BOT tag is visible in Recent Mentions', () => {
// # Open mentions
cy.uiGetRecentMentionButton().click();
// * Verify bot badge
cy.get('.sidebar--right__title').should('contain.text', 'Recent Mentions');
rhsPostHasBotBadge(postId);
});
it('MM-T1833 BOT tag is visible in Pinned Messages', () => {
// # Open pinned posts
cy.uiGetChannelPinButton().click();
// * Verify bot badge
cy.get('.sidebar--right__title').should('contain.text', 'Pinned messages');
rhsPostHasBotBadge(postId);
});
it('MM-T3659 BOT tag is visible in Saved Messages', () => {
// # Open saved posts
cy.uiGetSavedPostButton().click();
// * Verify bot badge
cy.get('.sidebar--right__title').should('contain.text', 'Saved messages');
rhsPostHasBotBadge(postId);
});
});
function rhsPostHasBotBadge(postId: string) {
cy.get(`.post#searchResult_${postId} .Tag`).should('be.visible').and('have.text', 'BOT');
}