Move /e2e -> /e2e-tests
Этот коммит содержится в:
@@ -0,0 +1,107 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('admin console', () => {
|
||||
let testUser;
|
||||
let testTeam;
|
||||
let testPlaybook;
|
||||
let testSysadmin;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
testSysadmin = sysadmin;
|
||||
});
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testSysddmin
|
||||
cy.apiLogin(testSysadmin);
|
||||
});
|
||||
|
||||
describe('site statistics', () => {
|
||||
it('playbooks and runs counters are visible', () => {
|
||||
// # Go to admin console > site statistics
|
||||
cy.visit('/admin_console/reporting/system_analytics');
|
||||
|
||||
// * Check that the playbook and run counters are visible
|
||||
cy.findByTestId('playbooks.playbook_count').should('exist');
|
||||
cy.findByTestId('playbooks.playbook_run_count').should('exist');
|
||||
});
|
||||
|
||||
it('playbook counter increases after creating a playbook', () => {
|
||||
let counter;
|
||||
|
||||
// # Go to admin console > site statistics
|
||||
cy.visit('/admin_console/reporting/system_analytics');
|
||||
|
||||
// # Capture current value of playbook counter
|
||||
cy.findByTestId('playbooks.playbook_count').invoke('prop', 'innerText').then((pbCount) => {
|
||||
counter = parseInt(pbCount, 10);
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
}).then(() => {
|
||||
cy.apiLogin(testSysadmin);
|
||||
|
||||
// # Go to admin console > site statistics
|
||||
cy.visit('/admin_console/reporting/system_analytics');
|
||||
|
||||
// * Verify that the Playbook Counter has been increased by 1
|
||||
cy.findByTestId('playbooks.playbook_count').contains(String(counter + 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('run counter increases after creating a run', () => {
|
||||
let counter;
|
||||
|
||||
// # Go to admin console > site statistics
|
||||
cy.visit('/admin_console/reporting/system_analytics');
|
||||
|
||||
// # Capture current value of run counter
|
||||
cy.findByTestId('playbooks.playbook_run_count').invoke('prop', 'innerText').then((runCount) => {
|
||||
counter = parseInt(runCount, 10);
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'My run for test',
|
||||
ownerUserId: testUser.id,
|
||||
}).then(() => {
|
||||
cy.apiLogin(testSysadmin);
|
||||
|
||||
// # Go to admin console > site statistics
|
||||
cy.visit('/admin_console/reporting/system_analytics');
|
||||
|
||||
// * Verify that the Run Counter has been increased by 1
|
||||
cy.findByTestId('playbooks.playbook_run_count').contains(String(counter + 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
189
e2e-tests/cypress/tests/integration/playbooks/api/runs_spec.js
Обычный файл
189
e2e-tests/cypress/tests/integration/playbooks/api/runs_spec.js
Обычный файл
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('api > runs', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('creating a run', () => {
|
||||
describe('in an existing, public channel', () => {
|
||||
it('with no team_id specified', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel').then(({channel}) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
}, {expectedStatusCode: 201}).then((body) => {
|
||||
expect(body).to.have.property('owner_user_id', testUser.id);
|
||||
expect(body).to.have.property('reporter_user_id', testUser.id);
|
||||
expect(body).to.have.property('team_id', testTeam.id);
|
||||
expect(body).to.have.property('channel_id', channel.id);
|
||||
expect(body).to.have.property('playbook_id', testPlaybook.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with correct team_id specified', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel').then(({channel}) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
teamId: testTeam.id,
|
||||
}, {expectedStatusCode: 201}).then((body) => {
|
||||
expect(body).to.have.property('owner_user_id', testUser.id);
|
||||
expect(body).to.have.property('reporter_user_id', testUser.id);
|
||||
expect(body).to.have.property('team_id', testTeam.id);
|
||||
expect(body).to.have.property('channel_id', channel.id);
|
||||
expect(body).to.have.property('playbook_id', testPlaybook.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with wrong team_id specified', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel').then(({channel}) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
teamId: 'other_team_id',
|
||||
}, {expectedStatusCode: 400}).then((body) => {
|
||||
expect(body).to.have.property('error', 'unable to create playbook run');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in an existing, private channel', () => {
|
||||
it('with no team_id specified', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel', 'P').then(({channel}) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
}, {expectedStatusCode: 201}).then((body) => {
|
||||
expect(body).to.have.property('owner_user_id', testUser.id);
|
||||
expect(body).to.have.property('reporter_user_id', testUser.id);
|
||||
expect(body).to.have.property('team_id', testTeam.id);
|
||||
expect(body).to.have.property('channel_id', channel.id);
|
||||
expect(body).to.have.property('playbook_id', testPlaybook.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with correct team_id specified', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel', 'P').then(({channel}) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
teamId: testTeam.id,
|
||||
}, {expectedStatusCode: 201}).then((body) => {
|
||||
expect(body).to.have.property('owner_user_id', testUser.id);
|
||||
expect(body).to.have.property('reporter_user_id', testUser.id);
|
||||
expect(body).to.have.property('team_id', testTeam.id);
|
||||
expect(body).to.have.property('channel_id', channel.id);
|
||||
expect(body).to.have.property('playbook_id', testPlaybook.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with wrong team_id specified', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel', 'P').then(({channel}) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
teamId: 'other_team_id',
|
||||
}, {expectedStatusCode: 400}).then((body) => {
|
||||
expect(body).to.have.property('error', 'unable to create playbook run');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('in an existing, private channel, of which the user is not a member', () => {
|
||||
// # Create a test channel without a playbook run
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel', 'P').then(({channel}) => {
|
||||
// # Leave the channel
|
||||
cy.apiRemoveUserFromChannel(channel.id, testUser.id);
|
||||
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
ownerUserId: testUser.id,
|
||||
channelId: channel.id,
|
||||
playbookId: testPlaybook.id,
|
||||
teamId: testTeam.id,
|
||||
}, {expectedStatusCode: 403}).then((body) => {
|
||||
expect(body).to.have.property('error', 'unable to create playbook run');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('in a channel with an existing playbook run', () => {
|
||||
// # Run the playbook, creating a channel.
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'Playbook',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # Run the testPlaybook in the previously created channel
|
||||
cy.apiRunPlaybook({
|
||||
owner_user_id: testUser.id,
|
||||
channel_id: playbookRun.channel_id,
|
||||
playbook_id: testPlaybook.id,
|
||||
}, {expectedStatusCode: 400}).then((body) => {
|
||||
expect(body).to.have.property('error', 'unable to create playbook run');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import {onlyOn} from '@cypress/skip-test';
|
||||
|
||||
describe('channels > App Bar', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let appBarEnabled;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
// # Start a playbook run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'Playbook Run',
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiGetConfig(true).then(({config}) => {
|
||||
appBarEnabled = config.EnableAppBar === 'true';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('App Bar disabled', () => {
|
||||
it('should not show the Playbook App Bar icon', () => {
|
||||
onlyOn(!appBarEnabled);
|
||||
|
||||
// # Navigate directly to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify App Bar icon is not showing
|
||||
cy.get('#channel_view').within(() => {
|
||||
cy.getPlaybooksAppBarIcon().should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('App Bar enabled', () => {
|
||||
it('should show the Playbook App Bar icon', () => {
|
||||
onlyOn(appBarEnabled);
|
||||
|
||||
// # Navigate directly to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify App Bar icon is showing
|
||||
cy.getPlaybooksAppBarIcon().should('exist');
|
||||
});
|
||||
|
||||
it('should show "Playbooks" tooltip for Playbook App Bar icon', () => {
|
||||
onlyOn(appBarEnabled);
|
||||
|
||||
// # Navigate directly to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Hover over the channel header icon
|
||||
cy.getPlaybooksAppBarIcon().trigger('mouseover');
|
||||
|
||||
// * Verify tooltip text
|
||||
cy.findByRole('tooltip', {name: 'Playbooks'}).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,390 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > broadcast', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testAdmin;
|
||||
let testPublicChannel1;
|
||||
let testPublicChannel2;
|
||||
let testPrivateChannel1;
|
||||
let testPrivateChannel2;
|
||||
let publicBroadcastPlaybook;
|
||||
let privateBroadcastPlaybook;
|
||||
let allBroadcastPlaybook;
|
||||
let rootDeletePlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin: adminUser}) => {
|
||||
testAdmin = adminUser;
|
||||
cy.apiAddUserToTeam(testTeam.id, adminUser.id);
|
||||
cy.apiSaveJoinLeaveMessagesPreference(adminUser.id, false);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'public-channel',
|
||||
'Public Channel 1',
|
||||
'O',
|
||||
).then(({channel: publicChannel1}) => {
|
||||
testPublicChannel1 = publicChannel1;
|
||||
|
||||
// # Create a public channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'public-channel',
|
||||
'Public Channel 2',
|
||||
'O',
|
||||
).then(({channel: publicChannel2}) => {
|
||||
testPublicChannel2 = publicChannel2;
|
||||
|
||||
// # Create a private channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'private-channel',
|
||||
'Private Channel 1',
|
||||
'P',
|
||||
).then(({channel: privateChannel1}) => {
|
||||
testPrivateChannel1 = privateChannel1;
|
||||
|
||||
// # Create a private channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'private-channel',
|
||||
'Private Channel 2',
|
||||
'P',
|
||||
).then(({channel: privateChannel2}) => {
|
||||
testPrivateChannel2 = privateChannel2;
|
||||
|
||||
// # Create a playbook that will broadcast to public channel1
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook - public broadcast',
|
||||
userId: testUser.id,
|
||||
broadcastChannelIds: [testPublicChannel1.id],
|
||||
broadcastEnabled: true,
|
||||
}).then((playbook) => {
|
||||
publicBroadcastPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a playbook that will broadcast to private channel1
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook - private broadcast',
|
||||
userId: testUser.id,
|
||||
broadcastChannelIds: [testPrivateChannel1.id],
|
||||
broadcastEnabled: true,
|
||||
}).then((playbook) => {
|
||||
privateBroadcastPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a playbook that will broadcast to all 4 channels
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook - public and private broadcast',
|
||||
userId: testUser.id,
|
||||
broadcastChannelIds: [testPublicChannel1.id, testPublicChannel2.id, testPrivateChannel1.id, testPrivateChannel2.id],
|
||||
broadcastEnabled: true,
|
||||
}).then((playbook) => {
|
||||
allBroadcastPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a playbook for testing deleting root posts
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook - test deleting root posts',
|
||||
userId: testUser.id,
|
||||
broadcastChannelIds: [testPublicChannel1.id, testPrivateChannel1.id],
|
||||
broadcastEnabled: true,
|
||||
otherMembers: [testAdmin.id],
|
||||
invitedUserIds: [testAdmin.id],
|
||||
}).then((playbook) => {
|
||||
rootDeletePlaybook = playbook;
|
||||
});
|
||||
|
||||
// # invite testAdmin to the channel they will need to be in to delete the post
|
||||
cy.apiAddUserToChannel(testPublicChannel1.id, testAdmin.id);
|
||||
cy.apiAddUserToChannel(testPrivateChannel1.id, testAdmin.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Go to Town Square
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('to public channels', () => {
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Playbook Run (${now})`;
|
||||
const playbookRunChannelName = `playbook-run-${now}`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: publicBroadcastPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Update the playbook run's status
|
||||
const updateMessage = 'Update - ' + now;
|
||||
cy.updateStatus(updateMessage);
|
||||
|
||||
// * Verify the posts
|
||||
const initialMessage = playbookRunName;
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPublicChannel1.name, playbookRunName, initialMessage, updateMessage);
|
||||
});
|
||||
|
||||
it('does not broadcast when broadcast is disabled, even if broadcastChannelIds contain data', () => {
|
||||
// # Create a brand new channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'public-channel-do-not-broadcast',
|
||||
'Public Channel 1 - do not broadcast',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
// # Create a playbook with broadcast disabled, but with broadcastChannelIds containing channel1
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook - disabled public broadcast',
|
||||
userId: testUser.id,
|
||||
broadcastChannelIds: [channel.id],
|
||||
broadcastEnabled: false,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Playbook Run (${now})`;
|
||||
const playbookRunChannelName = `playbook-run-${now}`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Update the playbook run's status
|
||||
const updateMessage = 'Update - ' + now;
|
||||
cy.updateStatus(updateMessage);
|
||||
|
||||
// # Navigate to the broadcast channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Verify that the last post is the system post containing the join message,
|
||||
// so no announcement nor update was posted
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#postMessageText_${lastPostId}`).contains('You joined the channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('to private channels', () => {
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: privateBroadcastPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Update the playbook run's status
|
||||
const updateMessage = 'Update - ' + now;
|
||||
cy.updateStatus(updateMessage);
|
||||
|
||||
// * Verify the posts
|
||||
const initialMessage = playbookRunName;
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPrivateChannel1.name, playbookRunName, initialMessage, updateMessage);
|
||||
});
|
||||
|
||||
it('to 4 public and private channels', () => {
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: allBroadcastPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Update the playbook run's status
|
||||
const updateMessage = 'Update - ' + now;
|
||||
cy.updateStatus(updateMessage, 0);
|
||||
|
||||
// * Verify the posts
|
||||
const initialMessage = playbookRunName;
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPublicChannel1.name, playbookRunName, initialMessage, updateMessage);
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPrivateChannel1.name, playbookRunName, initialMessage, updateMessage);
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPublicChannel2.name, playbookRunName, initialMessage, updateMessage);
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPrivateChannel2.name, playbookRunName, initialMessage, updateMessage);
|
||||
});
|
||||
|
||||
it('to 2 channels, delete the root post, update again', () => {
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: rootDeletePlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Update the playbook run's status
|
||||
const updateMessage = 'Update - ' + now;
|
||||
cy.updateStatus(updateMessage, 0);
|
||||
|
||||
// * Verify the posts
|
||||
const initialMessage = playbookRunName;
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPublicChannel1.name, playbookRunName, initialMessage, updateMessage);
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPrivateChannel1.name, playbookRunName, initialMessage, updateMessage);
|
||||
|
||||
// # need to be admin to delete the bot's posts
|
||||
cy.apiLogin(testAdmin);
|
||||
|
||||
// # Delete both root posts
|
||||
deleteLatestPostRoot(testTeam, testPublicChannel1.name);
|
||||
deleteLatestPostRoot(testTeam, testPrivateChannel1.name);
|
||||
|
||||
// # Log back in as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Make two more updates
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Update the playbook run's status twice
|
||||
const updateMessage2 = updateMessage + ' - 2';
|
||||
cy.updateStatus(updateMessage2, 0);
|
||||
const updateMessage3 = updateMessage + ' - 3';
|
||||
cy.updateStatus(updateMessage3, 0);
|
||||
|
||||
// * Verify the posts
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPublicChannel1.name, playbookRunName, updateMessage2, updateMessage3);
|
||||
verifyInitialAndStatusPostInBroadcast(testTeam, testPrivateChannel1.name, playbookRunName, updateMessage2, updateMessage3);
|
||||
});
|
||||
});
|
||||
|
||||
const verifyInitialAndStatusPostInBroadcast = (testTeam, channelName, runName, initialMessage, updateMessage) => {
|
||||
cy.log(`Verifying initial and status post in broadcast (channel ${channelName}, run ${runName})`);
|
||||
|
||||
// # Navigate to the broadcast channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channelName}`);
|
||||
|
||||
// * Verify that the last post contains the expected header and the update message verbatim
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
// # Open RHS comment menu
|
||||
cy.clickPostCommentIcon(lastPostId);
|
||||
|
||||
cy.get('#rhsContainer').
|
||||
should('exist').
|
||||
within(() => {
|
||||
// * Thread should have two posts
|
||||
cy.findAllByRole('listitem').should('have.length', 2);
|
||||
|
||||
// * The first should be announcement
|
||||
cy.findAllByRole('listitem').eq(0).contains(initialMessage);
|
||||
|
||||
// * Latest post should be update
|
||||
cy.get(`#rhsPost_${lastPostId}`).contains(
|
||||
`posted an update for ${runName}`,
|
||||
);
|
||||
cy.get(`#rhsPost_${lastPostId}`).contains('tasks checked');
|
||||
cy.get(`#rhsPost_${lastPostId}`).contains('participant');
|
||||
cy.get(`#rhsPost_${lastPostId}`).contains(updateMessage);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const deleteLatestPostRoot = (testTeam, channelName) => {
|
||||
cy.log(`Deleting latest root post (channel ${channelName})`);
|
||||
|
||||
// # Navigate to the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channelName}`);
|
||||
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
// # Open RHS comment menu
|
||||
cy.clickPostCommentIcon(lastPostId);
|
||||
|
||||
cy.get('#rhsContainer').
|
||||
should('exist').
|
||||
within(() => {
|
||||
cy.findAllByRole('listitem').eq(0).then((root) => {
|
||||
const rootId = root.attr('id').slice(8);
|
||||
|
||||
// # Click root's post dot menu.
|
||||
cy.clickPostDotMenu(rootId, 'RHS_ROOT');
|
||||
|
||||
// # Click delete button.
|
||||
const id = `#delete_post_${rootId}`;
|
||||
cy.wrap(id).as('deleteId');
|
||||
});
|
||||
});
|
||||
|
||||
// * Post extra options is visible
|
||||
cy.findByLabelText('Post extra options').should('exist');
|
||||
|
||||
// # Click delete button.
|
||||
cy.get('@deleteId').then((deleteId) => {
|
||||
cy.get(deleteId).should('be.visible').click();
|
||||
});
|
||||
|
||||
// * Check that confirmation dialog is open.
|
||||
cy.get('#deletePostModal').should('be.visible');
|
||||
|
||||
// * Check that confirmation dialog contains correct text
|
||||
cy.get('#deletePostModal').
|
||||
should('contain', 'Are you sure you want to delete this Post?');
|
||||
|
||||
// * Check that confirmation dialog shows that the post has one comment on it
|
||||
cy.get('#deletePostModal').should('contain', 'This post has 1 comment on it.');
|
||||
|
||||
// # Confirm deletion.
|
||||
cy.get('#deletePostModalButton').click();
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import {onlyOn} from '@cypress/skip-test';
|
||||
|
||||
describe('channels > channel header', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let testPlaybookRun;
|
||||
let appBarEnabled;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
// # Start a playbook run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'Playbook Run',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
testPlaybookRun = run;
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiGetConfig(true).then(({config}) => {
|
||||
appBarEnabled = config.EnableAppBar === 'true';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('App Bar enabled', () => {
|
||||
it('webapp should hide the Playbook channel header button', () => {
|
||||
onlyOn(appBarEnabled);
|
||||
|
||||
// # Navigate directly to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify channel header button is not showing
|
||||
cy.get('#channel-header').within(() => {
|
||||
cy.get('#incidentIcon').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('App Bar disabled', () => {
|
||||
it('webapp should show the Playbook channel header button', () => {
|
||||
onlyOn(!appBarEnabled);
|
||||
|
||||
// # Navigate directly to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify channel header button is showing
|
||||
cy.get('#channel-header').within(() => {
|
||||
cy.get('#incidentIcon').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('tooltip text should show "Playbooks" for Playbook channel header button', () => {
|
||||
onlyOn(!appBarEnabled);
|
||||
|
||||
// # Navigate directly to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Hover over the channel header icon
|
||||
cy.get('#channel-header').within(() => {
|
||||
cy.get('#incidentIcon').trigger('mouseover');
|
||||
});
|
||||
|
||||
// * Verify tooltip text
|
||||
cy.get('#pluginTooltip').contains('Playbooks');
|
||||
});
|
||||
});
|
||||
|
||||
describe('description text', () => {
|
||||
it('should contain a link to the playbook', () => {
|
||||
// # Navigate directly to a playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// * Verify link to playbook
|
||||
cy.get('.header-description__text').findByText('Playbook').should('have.attr', 'href').then((href) => {
|
||||
expect(href).to.equals(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
});
|
||||
});
|
||||
it('should contain a link to the overview page', () => {
|
||||
// # Navigate directly to a playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// * Verify link to overview page
|
||||
cy.get('.header-description__text').findByText('the overview page').should('have.attr', 'href').then((href) => {
|
||||
expect(href).to.equals(`/playbooks/runs/${testPlaybookRun.id}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,367 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
|
||||
describe('channels > general actions', () => {
|
||||
let testTeam;
|
||||
let testSysadmin;
|
||||
let testUser;
|
||||
let testChannel;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testSysadmin = user;
|
||||
|
||||
cy.apiCreateUser().then((resp) => {
|
||||
testUser = resp.user;
|
||||
cy.apiAddUserToTeam(team.id, resp.user.id);
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// TODO: Make this work with CRT enabled.
|
||||
cy.apiSaveCRTPreference(testUser.id, 'off');
|
||||
});
|
||||
|
||||
cy.apiLogin(testSysadmin);
|
||||
|
||||
// TODO: Make this work with CRT enabled.
|
||||
cy.apiSaveCRTPreference(testSysadmin.id, 'off');
|
||||
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'action-channel',
|
||||
'Action Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on join trigger', () => {
|
||||
it('channel categorization can be enabled and works', () => {
|
||||
// # Go to the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// # Enable the categorization action and set the name
|
||||
cy.contains('sidebar category').click();
|
||||
cy.contains('Enter category name').click().type('example category{enter}');
|
||||
|
||||
cy.get('#channel-actions-modal').within(() => {
|
||||
// # Save action
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
});
|
||||
|
||||
// # Switch to another user and reload
|
||||
// # This drops them into the same channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.reload();
|
||||
cy.wait(TIMEOUTS.TEN_SEC);
|
||||
|
||||
// * Verify the channel category + channel exists
|
||||
cy.contains('.SidebarChannelGroup', 'example category', {matchCase: false}).
|
||||
should('exist').
|
||||
within(() => {
|
||||
cy.contains(testChannel.display_name).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('welcome message can be enabled and is shown to a joining user', () => {
|
||||
// # Go to the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// # Toggle on and set the welcome message
|
||||
cy.contains('temporary welcome message').click();
|
||||
cy.findByTestId('channel-actions-modal_welcome-msg').
|
||||
type('test ephemeral welcome message');
|
||||
|
||||
cy.get('#channel-actions-modal').within(() => {
|
||||
// # Save action
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
});
|
||||
|
||||
// # Switch to another user and reload
|
||||
// # This drops them into the same channel
|
||||
cy.apiLogin(testUser);
|
||||
cy.reload();
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// * Verify the welcome message is shown
|
||||
cy.verifyEphemeralMessage('test ephemeral welcome message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyword trigger', () => {
|
||||
it('prompt to run playbook can be enabled and works', () => {
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
|
||||
// # Login as the non-sysadmin user first
|
||||
// # to do the channel & action creation.
|
||||
// # In the 'Select a playbook' dropdown later in this test,
|
||||
// # sysadmin users could potentially see many other playbooks
|
||||
// # besides the one created directly above. `testUser` will not.
|
||||
cy.apiLogin(testUser);
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'action-channel',
|
||||
'Action Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
// # Go to the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// # Set a keyword, enable the playbook trigger,
|
||||
// # and select the Playbook to run
|
||||
cy.contains('Type a keyword or phrase, then press Enter on your keyboard').click().type('red alert{enter}');
|
||||
cy.contains('Prompt to run a playbook').click();
|
||||
cy.contains('Select a playbook').click();
|
||||
cy.findByText('Public Playbook').click();
|
||||
|
||||
cy.get('#channel-actions-modal').within(() => {
|
||||
// # Save action
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
});
|
||||
|
||||
// # Post the trigger phrase
|
||||
cy.uiPostMessageQuickly('error detected red alert!');
|
||||
|
||||
// * Verify that the bot posts the expected prompt
|
||||
// # Open the playbook run modal
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.contains('trigger for the Public Playbook').should('exist');
|
||||
cy.contains('Yes, run playbook').should('exist').click();
|
||||
});
|
||||
});
|
||||
|
||||
// # Enter a name and start the run
|
||||
cy.findByTestId('playbookRunNameinput').type('run from trigger');
|
||||
cy.findByRole('button', {name: /start run/i}).click();
|
||||
|
||||
// * Verify text from the run channel description
|
||||
cy.contains('start of the run').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes the post and ignores the thread when clicking on No, ignore thread', () => {
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
|
||||
// # Login as the non-sysadmin user first
|
||||
// # to do the channel & action creation.
|
||||
// # In the 'Select a playbook' dropdown later in this test,
|
||||
// # sysadmin users could potentially see many other playbooks
|
||||
// # besides the one created directly above. `testUser` will not.
|
||||
cy.apiLogin(testUser);
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'action-channel',
|
||||
'Action Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
// # Go to the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// # Set a keyword, enable the playbook trigger,
|
||||
// # and select the Playbook to run
|
||||
cy.contains('Type a keyword or phrase, then press Enter on your keyboard').click().type('red alert{enter}');
|
||||
cy.contains('Prompt to run a playbook').click();
|
||||
cy.contains('Select a playbook').click();
|
||||
cy.findByText('Public Playbook').click();
|
||||
|
||||
cy.get('#channel-actions-modal').within(() => {
|
||||
// # Save action
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
});
|
||||
|
||||
// # Post the trigger phrase
|
||||
cy.uiPostMessageQuickly('error detected red alert!');
|
||||
|
||||
// * Verify that the bot posts the expected prompt
|
||||
// # Click on No, ignore thread
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.contains('trigger for the Public Playbook').should('exist');
|
||||
cy.contains('No, ignore thread').should('exist').click();
|
||||
});
|
||||
});
|
||||
|
||||
// # Reload the channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// * Verify that the prompt post is no longer there
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.contains('No, ignore thread').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
// # Reply to the last thread with the trigger phrase
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.clickPostCommentIcon(postId);
|
||||
cy.postMessageReplyInRHS('error detected red alert!');
|
||||
});
|
||||
|
||||
// * Verify that the bot did not post the prompt
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.contains('trigger for the Public Playbook').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('disabled triggers do not run even with a keyword set', () => {
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
|
||||
// # Login as the non-sysadmin user first
|
||||
// # to do the channel & action creation.
|
||||
// # In the 'Select a playbook' dropdown later in this test,
|
||||
// # sysadmin users could potentially see many other playbooks
|
||||
// # besides the one created directly above. `testUser` will not.
|
||||
cy.apiLogin(testUser);
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'action-channel',
|
||||
'Action Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
// # Go to the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// # Set a keyword, enable the playbook trigger,
|
||||
// # and select the playbook to run. Turn the
|
||||
// # trigger back off but leave the keyword set.
|
||||
cy.contains('Type a keyword or phrase, then press Enter on your keyboard').click().type('red alert{enter}');
|
||||
cy.contains('Prompt to run a playbook').click();
|
||||
cy.contains('Select a playbook').click();
|
||||
cy.findByText('Public Playbook').click();
|
||||
cy.contains('Prompt to run a playbook').click();
|
||||
|
||||
cy.get('#channel-actions-modal').within(() => {
|
||||
// # Save action
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
});
|
||||
|
||||
// # Post the trigger phrase
|
||||
cy.uiPostMessageQuickly('error detected red alert!');
|
||||
|
||||
// * Verify that the bot _has not_ posted the expected prompt
|
||||
cy.getLastPostId().then((postId) => {
|
||||
cy.get(`#post_${postId}`).within(() => {
|
||||
cy.contains('trigger for the Public Playbook').should('not.exist');
|
||||
cy.contains('Yes, run playbook').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('action settings are disabled for non-channel admin', () => {
|
||||
// # Login as non-channel admin
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Go to the test channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// * Verify the toggles are disabled
|
||||
cy.findByRole('dialog', {name: /channel actions/i}).within(() => {
|
||||
cy.get('input').should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
||||
it('action settings are reset to the default when switching to a channel with no actions configured', () => {
|
||||
// # Create an additional channel
|
||||
const name = 'New channel ' + Date.now();
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'new-channel',
|
||||
name,
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
// # Visit the first channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// # Enable the categorization action and set the name
|
||||
const categoryName = 'example category ' + Date.now();
|
||||
cy.contains('sidebar category').click();
|
||||
cy.contains('Enter category name').click().type(categoryName + '{enter}');
|
||||
|
||||
cy.get('#channel-actions-modal').within(() => {
|
||||
// # Save action
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
});
|
||||
|
||||
// # wait to avoid MM-45969
|
||||
cy.wait(5000);
|
||||
|
||||
// # Switch to the additional channel
|
||||
cy.get('#sidebarItem_' + channel.name).click();
|
||||
|
||||
// # Open Channel Header and the Channel Actions modal
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
cy.findByText('Channel Actions').click();
|
||||
|
||||
// * Verify that the categorization action is disabled
|
||||
cy.findByText('Add the channel to a sidebar category for the user').parent().within(() => {
|
||||
cy.get('input').should('not.be.checked');
|
||||
});
|
||||
|
||||
// * Verify that the category name is not there
|
||||
cy.findByText(categoryName).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,644 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > actions', () => {
|
||||
let testTeam;
|
||||
let testSysadmin;
|
||||
let testUser;
|
||||
let testPublicChannel;
|
||||
const testUsers = [];
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup({userPrefix: 'u'}).then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
testSysadmin = sysadmin;
|
||||
});
|
||||
|
||||
// # Create extra test users in this team
|
||||
cy.apiCreateUser({prefix: 'u'}).then((payload) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, payload.user.id);
|
||||
testUsers.push(payload.user);
|
||||
});
|
||||
|
||||
cy.apiCreateUser({prefix: 'u'}).then((payload) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, payload.user.id);
|
||||
testUsers.push(payload.user);
|
||||
});
|
||||
|
||||
// # Create a public channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'public-channel',
|
||||
'Public Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
testPublicChannel = channel;
|
||||
cy.apiAddUserToChannel(channel.id, testUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Go to Town Square
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
describe(('when a playbook run starts'), () => {
|
||||
describe('invite members setting', () => {
|
||||
it('with no invited users and setting disabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
let playbookId;
|
||||
|
||||
// # Create a playbook with the invite users disabled and no invited users
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
invitedUserIds: [],
|
||||
inviteUsersEnabled: false,
|
||||
}).then((playbook) => {
|
||||
playbookId = playbook.id;
|
||||
});
|
||||
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that no users were invited
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).
|
||||
contains('You were added to the channel by @playbooks.').
|
||||
should('not.contain', 'joined the channel');
|
||||
});
|
||||
});
|
||||
|
||||
it('with invited users and setting enabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with a couple of invited users and the setting enabled, and a playbook run with it
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
invitedUserIds: [testUsers[0].id, testUsers[1].id],
|
||||
inviteUsersEnabled: true,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the users were invited
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.findByText('2 others').click();
|
||||
});
|
||||
|
||||
cy.get(`#postMessageText_${id}`).contains(`@${testUsers[0].username}`);
|
||||
cy.get(`#postMessageText_${id}`).contains(`@${testUsers[1].username}`);
|
||||
cy.get(`#postMessageText_${id}`).contains('added to the channel by @playbooks.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with invited users and setting disabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with a couple of invited users and the setting enabled, and a playbook run with it
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
invitedUserIds: [testUsers[0].id, testUsers[1].id],
|
||||
inviteUsersEnabled: false,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that no users were invited
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).
|
||||
contains('You were added to the channel by @playbooks.').
|
||||
should('not.contain', 'joined the channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with non-existent users', () => {
|
||||
let userToRemove;
|
||||
let playbook;
|
||||
|
||||
// # Create a playbook with a user that is later removed from the team
|
||||
cy.apiLogin(testSysadmin).then(() => {
|
||||
cy.apiCreateUser().then((result) => {
|
||||
userToRemove = result.user;
|
||||
cy.apiAddUserToTeam(testTeam.id, userToRemove.id);
|
||||
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with the user that will be removed from the team.
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id, testSysadmin.id],
|
||||
invitedUserIds: [userToRemove.id],
|
||||
inviteUsersEnabled: true,
|
||||
}).then((res) => {
|
||||
playbook = res;
|
||||
});
|
||||
|
||||
// # Remove user from the team
|
||||
cy.apiDeleteUserFromTeam(testTeam.id, userToRemove.id);
|
||||
});
|
||||
}).then(() => {
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a new playbook run with the playbook.
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that there is an error message from the bot
|
||||
cy.getNthPostId(1).then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).
|
||||
contains(`Failed to invite the following users: @${userToRemove.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('default owner setting', () => {
|
||||
it('defaults to the creator when no owner is specified', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
let playbookId;
|
||||
|
||||
// # Create a playbook with the default owner setting set to false
|
||||
// and no owner specified
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
defaultOwnerId: '',
|
||||
defaultOwnerEnabled: false,
|
||||
}).then((playbook) => {
|
||||
playbookId = playbook.id;
|
||||
});
|
||||
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the RHS shows the owner being the creator
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText('Owner').parent().within(() => {
|
||||
cy.findByText(`@${testUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults to the creator when no owner is specified, even if the setting is enabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
let playbookId;
|
||||
|
||||
// # Create a playbook with the default owner setting set to false
|
||||
// and no owner specified
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
defaultOwnerId: '',
|
||||
defaultOwnerEnabled: true,
|
||||
}).then((playbook) => {
|
||||
playbookId = playbook.id;
|
||||
});
|
||||
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the RHS shows the owner being the creator
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText('Owner').parent().within(() => {
|
||||
cy.findByText(`@${testUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('assigns the owner when they are part of the invited members list', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with the owner being part of the invited users
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
invitedUserIds: [testUsers[0].id],
|
||||
inviteUsersEnabled: true,
|
||||
defaultOwnerId: testUsers[0].id,
|
||||
defaultOwnerEnabled: true,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the RHS shows the owner being the invited user
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText('Owner').parent().within(() => {
|
||||
cy.findByText(`@${testUsers[0].username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('assigns the owner even if they are not invited', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with the owner being part of the invited users
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
invitedUserIds: [],
|
||||
inviteUsersEnabled: false,
|
||||
defaultOwnerId: testUsers[0].id,
|
||||
defaultOwnerEnabled: true,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the RHS shows the owner being the invited user
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText('Owner').parent().within(() => {
|
||||
cy.findByText(`@${testUsers[0].username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('assigns the owner when they and the creator are the same', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
let playbookId;
|
||||
|
||||
// # Create a playbook with the default owner setting set to false
|
||||
// and no owner specified
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
defaultOwnerId: testUser.id,
|
||||
defaultOwnerEnabled: true,
|
||||
}).then((playbook) => {
|
||||
playbookId = playbook.id;
|
||||
});
|
||||
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the RHS shows the owner being the creator
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText('Owner').parent().within(() => {
|
||||
cy.findByText(`@${testUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('broadcast channel setting', () => {
|
||||
it('with channel configured and setting enabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with a couple of invited users and the setting enabled, and a playbook run with it
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
broadcastChannelIds: [testPublicChannel.id],
|
||||
broadcastEnabled: true,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel.
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the channel is created and that the first post exists.
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).
|
||||
contains('You were added to the channel by @playbooks.').
|
||||
should('not.contain', 'joined the channel');
|
||||
});
|
||||
|
||||
// # Navigate to the broadcast channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testPublicChannel.name}`);
|
||||
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#postMessageText_${lastPostId}`).contains(`${playbookRunName}`);
|
||||
cy.get(`#postMessageText_${lastPostId}`).contains(`@${testUser.username} ran the ${playbookName} playbook.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with channel configured and setting disabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with a couple of invited users and the setting enabled, and a playbook run with it
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
broadcastChannelIds: [testPublicChannel.id],
|
||||
broadcastEnabled: false,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the channel is created and that the first post exists.
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).
|
||||
contains('You were added to the channel by @playbooks.').
|
||||
should('not.contain', 'joined the channel');
|
||||
});
|
||||
|
||||
// # Navigate to the broadcast channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testPublicChannel.name}`);
|
||||
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#postMessageText_${lastPostId}`).should('not.contain', `New Run: ~${playbookRunName}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with non-existent channel', () => {
|
||||
let playbookId;
|
||||
|
||||
// # Create a playbook with a channel that is later deleted
|
||||
cy.apiLogin(testSysadmin).then(() => {
|
||||
const channelDisplayName = String('Channel to delete ' + Date.now());
|
||||
const channelName = channelDisplayName.replace(/ /g, '-').toLowerCase();
|
||||
cy.apiCreateChannel(testTeam.id, channelName, channelDisplayName).then(({channel}) => {
|
||||
// # Create a playbook with the channel to be deleted as the announcement channel
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id, testSysadmin.id],
|
||||
broadcastChannelIds: [channel.id],
|
||||
broadcastEnabled: true,
|
||||
}).then((playbook) => {
|
||||
playbookId = playbook.id;
|
||||
});
|
||||
|
||||
// # Delete channel
|
||||
cy.apiDeleteChannel(channel.id);
|
||||
});
|
||||
}).then(() => {
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a new playbook run with the playbook.
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that there is an error message from the bot
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).
|
||||
contains('Failed to broadcast run creation to the configured channel.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('creation webhook setting', () => {
|
||||
it('with webhook correctly configured and setting enabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a playbook with a correct webhook and the setting enabled
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
webhookOnCreationURLs: ['https://httpbin.org/post'],
|
||||
webhookOnCreationEnabled: true,
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
description: 'Playbook run description.',
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the bot has not posted a message informing of the failure to send the webhook
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
cy.get(`#postMessageText_${lastPostId}`).
|
||||
should('not.contain', 'Playbook run creation announcement through the outgoing webhook failed. Contact your System Admin for more information.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a playbook run is finished', () => {
|
||||
it('retrospective is disabled', () => {
|
||||
const playbookName = 'Playbook (' + Date.now() + ')';
|
||||
|
||||
// # Create a new playbook run with that playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
const playbookRunChannelName = `run-${now}`;
|
||||
|
||||
// # Create a playbook with the disabled retrospective functionality
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookName,
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [testUser.id],
|
||||
retrospectiveEnabled: false,
|
||||
}).then((playbook) => {
|
||||
// # Run playbook
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
}).then((playbookRun) => {
|
||||
// # End the playbook run
|
||||
cy.apiFinishRun(playbookRun.id);
|
||||
});
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that playbook run finished message was posted
|
||||
cy.findAllByTestId('postView').contains(`marked ${playbookName} as finished`);
|
||||
|
||||
// * Verify that retrospective dialog was not posted
|
||||
cy.findAllByTestId('retrospective-reminder').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > post type components', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testChannel;
|
||||
let testPlaybookRun;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin();
|
||||
|
||||
cy.apiInitSetup({loginAfter: true}).then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((playbook) => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: 'Test Run',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'other-channel',
|
||||
'Other Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update post (custom_run_update)', () => {
|
||||
it('displays in run channel', () => {
|
||||
// # Go to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/test-run`);
|
||||
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Post a status update
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testPlaybookRun.id,
|
||||
message: 'status update',
|
||||
reminder: 60,
|
||||
});
|
||||
|
||||
// Grab the post id
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_status_update',
|
||||
type: 'page',
|
||||
properties: {
|
||||
post_id: postId,
|
||||
playbook_run_id: testPlaybookRun.id,
|
||||
channel_type: 'O',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays when permalinked in a different channel', () => {
|
||||
// # Go to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/test-run`);
|
||||
|
||||
// # Post a status update
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testPlaybookRun.id,
|
||||
message: 'status update',
|
||||
reminder: 60,
|
||||
});
|
||||
|
||||
// Grab the post id
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Go to the other channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Post a permalink to the status update
|
||||
cy.uiPostMessageQuickly(`${Cypress.config('baseUrl')}/${testTeam.name}/pl/${postId}`);
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_status_update',
|
||||
type: 'page',
|
||||
properties: {
|
||||
post_id: postId,
|
||||
playbook_run_id: testPlaybookRun.id,
|
||||
channel_type: 'O',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
cy.getLastPost().then((element) => {
|
||||
// # Verify the expected message text
|
||||
cy.get(element).contains(`${testUser.username} posted an update for ${testPlaybookRun.name}`);
|
||||
cy.get(element).contains('status update');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('displays when permalinked in a different channel, even if not a member of the original channel', () => {
|
||||
// # Go to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/test-run`);
|
||||
|
||||
// # Post a status update
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testPlaybookRun.id,
|
||||
message: 'status update',
|
||||
reminder: 60,
|
||||
});
|
||||
|
||||
cy.getLastPostId().then((postId) => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Leave the playbook run channel
|
||||
cy.uiLeaveChannel();
|
||||
|
||||
// # Go to the other channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Post a permalink to the status update
|
||||
cy.uiPostMessageQuickly(`${Cypress.config('baseUrl')}/${testTeam.name}/pl/${postId}`);
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_status_update',
|
||||
type: 'page',
|
||||
properties: {
|
||||
post_id: postId,
|
||||
playbook_run_id: testPlaybookRun.id,
|
||||
channel_type: '',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
cy.getLastPost().then((element) => {
|
||||
// # Verify the expected message text
|
||||
cy.get(element).contains(`${testUser.username} posted an update for ${testPlaybookRun.name}`);
|
||||
cy.get(element).contains('status update');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > retrospective', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybookWithMetrics;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create playbook with metrics
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook with metrics',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
metrics: [
|
||||
{
|
||||
title: 'Time to acknowledge',
|
||||
description: 'some description text',
|
||||
type: 'metric_duration',
|
||||
target: 7200000,
|
||||
},
|
||||
{
|
||||
title: 'Cost',
|
||||
description: 'Cost of some events',
|
||||
type: 'metric_currency',
|
||||
target: 400,
|
||||
},
|
||||
{
|
||||
title: 'Number of customers',
|
||||
description: 'Number of customers who had issues',
|
||||
type: 'metric_integer',
|
||||
target: 30,
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
description: 'Duration of incident',
|
||||
type: 'metric_duration',
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPlaybookWithMetrics = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
describe('runs with metrics', () => {
|
||||
let runId;
|
||||
let runName;
|
||||
let playbookRunChannelName;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
runName = `Run (${now})`;
|
||||
playbookRunChannelName = `run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybookWithMetrics.id,
|
||||
playbookRunName: runName,
|
||||
ownerUserId: testUser.id,
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((run) => {
|
||||
runId = run.id;
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish retrospective', () => {
|
||||
it('retrospective with 4 key metrics', () => {
|
||||
// # Navigate directly to the retro tab
|
||||
cy.visit(`/playbooks/runs/${runId}/retrospective`);
|
||||
|
||||
// * Verify metrics number
|
||||
cy.getStyledComponent('InputContainer').should('have.length', 4);
|
||||
|
||||
// # Enter metrics values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('00:11:10').
|
||||
tab().type('560').
|
||||
tab().type('12').
|
||||
tab().type('14:00:59');
|
||||
|
||||
// # Publish retrospective
|
||||
publishRetro();
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify channel retro post content
|
||||
cy.findAllByTestId('postView').last().contains(`Retrospective for ${runName} has been published by`);
|
||||
cy.getStyledComponent('MetricInfo').should('have.length', 4);
|
||||
cy.getStyledComponent('MetricInfo').eq(0).contains('11 hours, 10 minutes');
|
||||
cy.getStyledComponent('MetricInfo').eq(1).contains('560');
|
||||
cy.getStyledComponent('MetricInfo').eq(2).contains('12');
|
||||
cy.getStyledComponent('MetricInfo').eq(3).contains('14 days, 59 minutes');
|
||||
});
|
||||
|
||||
it('retrospective with 3 key metrics', () => {
|
||||
// # Remove first metric, leave only 3
|
||||
testPlaybookWithMetrics.metrics.splice(0, 1);
|
||||
cy.apiUpdatePlaybook(testPlaybookWithMetrics).then(() => {
|
||||
// # Navigate directly to the retro tab
|
||||
cy.visit(`/playbooks/runs/${runId}/retrospective`);
|
||||
|
||||
// * Verify metrics number
|
||||
cy.getStyledComponent('InputContainer').should('have.length', 3);
|
||||
|
||||
// # Enter metrics values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('43').
|
||||
tab().type('121').
|
||||
tab().type('11:00:02');
|
||||
|
||||
// # Publish retrospective
|
||||
publishRetro();
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify channel retro post content
|
||||
cy.findAllByTestId('postView').last().contains(`Retrospective for ${runName} has been published by`);
|
||||
cy.getStyledComponent('MetricInfo').should('have.length', 3);
|
||||
cy.getStyledComponent('MetricInfo').eq(0).contains('43');
|
||||
cy.getStyledComponent('MetricInfo').eq(1).contains('121');
|
||||
cy.getStyledComponent('MetricInfo').eq(2).contains('11 days, 2 minutes');
|
||||
});
|
||||
});
|
||||
|
||||
it('retrospective with 2 key metrics', () => {
|
||||
// # Remove first two metrics, leave only 2
|
||||
testPlaybookWithMetrics.metrics.splice(0, 2);
|
||||
cy.apiUpdatePlaybook(testPlaybookWithMetrics).then(() => {
|
||||
// # Navigate directly to the retro tab
|
||||
cy.visit(`/playbooks/runs/${runId}/retrospective`);
|
||||
|
||||
// * Verify metrics number
|
||||
cy.getStyledComponent('InputContainer').should('have.length', 2);
|
||||
|
||||
// # Enter metrics values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('0').
|
||||
tab().type('00:04:02');
|
||||
|
||||
// # Publish retrospective
|
||||
publishRetro();
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify channel retro post content
|
||||
cy.findAllByTestId('postView').last().contains(`Retrospective for ${runName} has been published by`);
|
||||
cy.getStyledComponent('MetricInfo').should('have.length', 2);
|
||||
cy.getStyledComponent('MetricInfo').eq(0).contains('0');
|
||||
cy.getStyledComponent('MetricInfo').eq(1).contains('4 hours, 2 minutes');
|
||||
});
|
||||
});
|
||||
|
||||
it('retrospective with 1 key metrics', () => {
|
||||
// # Remove first 3 metrics, leave only 1
|
||||
testPlaybookWithMetrics.metrics.splice(0, 3);
|
||||
cy.apiUpdatePlaybook(testPlaybookWithMetrics).then(() => {
|
||||
// # Navigate directly to the retro tab
|
||||
cy.visit(`/playbooks/runs/${runId}/retrospective`);
|
||||
|
||||
// * Verify metrics number
|
||||
cy.getStyledComponent('InputContainer').should('have.length', 1);
|
||||
|
||||
// # Enter metrics values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('00:00:00');
|
||||
|
||||
// # Publish retrospective
|
||||
publishRetro();
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify channel retro post content
|
||||
cy.findAllByTestId('postView').last().contains(`Retrospective for ${runName} has been published by`);
|
||||
cy.getStyledComponent('MetricInfo').should('have.length', 1);
|
||||
cy.getStyledComponent('MetricInfo').eq(0).contains('0 seconds');
|
||||
});
|
||||
});
|
||||
|
||||
it('retrospective with no metrics', () => {
|
||||
// # Remove all metrics
|
||||
testPlaybookWithMetrics.metrics.splice(0, 4);
|
||||
cy.apiUpdatePlaybook(testPlaybookWithMetrics).then(() => {
|
||||
// # Navigate directly to the retro tab
|
||||
cy.visit(`/playbooks/runs/${runId}/retrospective`);
|
||||
|
||||
// * Verify there are no metrics inputs
|
||||
cy.getStyledComponent('InputContainer').should('not.exist');
|
||||
|
||||
// # Publish retrospective
|
||||
publishRetro();
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify channel retro post content
|
||||
cy.findAllByTestId('postView').last().contains(`Retrospective for ${runName} has been published by`);
|
||||
cy.getStyledComponent('MetricInfo').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const publishRetro = () => {
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
// * Verify we're showing the publish retro confirmation modal
|
||||
cy.findByText('Are you sure you want to publish?');
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > rhs > header', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let testPlaybookRun;
|
||||
let playbookRunChannelName;
|
||||
let playbookRunName;
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
testPlaybookRun = run;
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
});
|
||||
|
||||
describe('shows name', () => {
|
||||
it('of active playbook run', () => {
|
||||
// * Verify the title is displayed
|
||||
cy.get('#rhsContainer').contains(playbookRunName);
|
||||
});
|
||||
|
||||
it('of renamed playbook run', () => {
|
||||
// * Verify the existing title is displayed
|
||||
cy.get('#rhsContainer').contains(playbookRunName);
|
||||
|
||||
// # Rename the channel
|
||||
cy.apiPatchChannel(testPlaybookRun.channel_id, {
|
||||
id: testPlaybookRun.channel_id,
|
||||
display_name: 'Updated',
|
||||
});
|
||||
|
||||
// * Verify the updated title is displayed
|
||||
cy.get('#rhsContainer').contains(playbookRunName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit run name', () => {
|
||||
it('by clicking on name', () => {
|
||||
cy.get('#rhsContainer').findByTestId('rendered-run-name').should('be.visible').click();
|
||||
|
||||
// # type text in textarea
|
||||
cy.get('#rhsContainer').findByTestId('textarea-run-name').should('be.visible').clear().type('new run name{ctrl+enter}');
|
||||
|
||||
// * make sure the updated name is here
|
||||
cy.get('#rhsContainer').findByTestId('rendered-run-name').should('be.visible').contains('new run name');
|
||||
|
||||
// * make sure the channel name remains unchanged
|
||||
cy.get('#channelHeaderInfo').findByRole('heading').contains(playbookRunName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit summary', () => {
|
||||
it('by clicking on placeholder', () => {
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').click();
|
||||
|
||||
// # type text in textarea
|
||||
cy.get('#rhsContainer').findByTestId('textarea-description').should('be.visible').type('new summary{ctrl+enter}');
|
||||
|
||||
// * make sure the updated summary is here
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').contains('new summary');
|
||||
});
|
||||
|
||||
it('by clicking on dot menu item', () => {
|
||||
// # click on the field
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByTestId('buttons-row').invoke('show').within(() => {
|
||||
cy.findAllByRole('button').eq(1).click();
|
||||
});
|
||||
});
|
||||
|
||||
cy.findByText('Edit run summary').click({force: true});
|
||||
|
||||
// # type text in textarea
|
||||
cy.focused().should('be.visible').type('new summary{ctrl+enter}');
|
||||
|
||||
// * make sure the updated summary is here
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').contains('new summary');
|
||||
});
|
||||
});
|
||||
|
||||
describe('participate', () => {
|
||||
it('icon is not visible if I am a participant', () => {
|
||||
// * assert icon is not visible if I'm participant
|
||||
cy.get('#rhsContainer').findByTestId('rhs-participate-icon').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,489 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import {HALF_SEC, ONE_SEC} from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('channels > rhs > checklist', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: team.id,
|
||||
title: 'Playbook',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1', command: '/invalid'},
|
||||
{title: 'Step 2', command: '/echo VALID'},
|
||||
{title: 'Step 3', command: '/playbook check 0 0'},
|
||||
{title: 'Step 4'},
|
||||
{title: 'Step 5'},
|
||||
{title: 'Step 6'},
|
||||
{title: 'Step 7'},
|
||||
{title: 'Step 8'},
|
||||
{title: 'Step 9'},
|
||||
{title: 'Step 10'},
|
||||
{title: 'Step 11'},
|
||||
{title: 'Step 12'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1', command: '/invalid'},
|
||||
{title: 'Step 2', command: '/echo VALID'},
|
||||
{title: 'Step 3'},
|
||||
{title: 'Step 4'},
|
||||
{title: 'Step 5'},
|
||||
{title: 'Step 6'},
|
||||
{title: 'Step 7'},
|
||||
{title: 'Step 8'},
|
||||
{title: 'Step 9'},
|
||||
{title: 'Step 10'},
|
||||
{title: 'Step 11'},
|
||||
{title: 'Step 12'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 3',
|
||||
items: [
|
||||
{title: 'Step 1', command: '/invalid'},
|
||||
{title: 'Step 2', command: '/echo VALID'},
|
||||
{title: 'Step 3'},
|
||||
{title: 'Step 4'},
|
||||
{title: 'Step 5'},
|
||||
{title: 'Step 6'},
|
||||
{title: 'Step 7'},
|
||||
{title: 'Step 8'},
|
||||
{title: 'Step 9'},
|
||||
{title: 'Step 10'},
|
||||
{title: 'Step 11'},
|
||||
{title: 'Step 12'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 3',
|
||||
items: [
|
||||
{title: 'Step 1', command: '/invalid'},
|
||||
{title: 'Step 2', command: '/echo VALID'},
|
||||
{title: 'Step 3'},
|
||||
{title: 'Step 4'},
|
||||
{title: 'Step 5'},
|
||||
{title: 'Step 6'},
|
||||
{title: 'Step 7'},
|
||||
{title: 'Step 8'},
|
||||
{title: 'Step 9'},
|
||||
{title: 'Step 10'},
|
||||
{title: 'Step 11'},
|
||||
{title: 'Step 12'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [
|
||||
user.id,
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// // # Switch to clean display mode
|
||||
// cy.apiSaveMessageDisplayPreference('clean');
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to task list without scrolling issues
|
||||
cy.viewport('macbook-13');
|
||||
});
|
||||
|
||||
describe('rhs stuff', () => {
|
||||
let playbookRunName;
|
||||
let playbookRunChannelName;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify the playbook run RHS is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText(playbookRunName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('header', () => {
|
||||
it('has title', () => {
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText('Tasks').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows an ephemeral error when running an invalid slash command', () => {
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify the command has not yet been run.
|
||||
cy.findAllByTestId('run').eq(0).should('have.text', 'Run');
|
||||
|
||||
// * Run the /invalid slash command
|
||||
cy.findAllByTestId('run').eq(0).click();
|
||||
|
||||
// * Verify the command still has not yet been run.
|
||||
cy.findAllByTestId('run').eq(0).should('have.text', 'Run');
|
||||
});
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Failed to execute slash command /invalid');
|
||||
});
|
||||
|
||||
it('successfully runs a valid slash command', () => {
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify the command has not yet been run.
|
||||
cy.findAllByTestId('run').eq(1).should('have.text', 'Run');
|
||||
|
||||
// * Run the /invalid slash command
|
||||
cy.findAllByTestId('run').eq(1).click();
|
||||
|
||||
// * Verify the command has now been run.
|
||||
cy.findAllByTestId('run').eq(1).should('have.text', 'Rerun');
|
||||
});
|
||||
|
||||
// # Verify the expected output.
|
||||
cy.verifyPostedMessage('VALID');
|
||||
});
|
||||
|
||||
it('still shows slash commands as having been run after reload', () => {
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify the command has not yet been run.
|
||||
cy.findAllByTestId('run').eq(1).should('have.text', 'Run');
|
||||
|
||||
// * Run the /invalid slash command
|
||||
cy.findAllByTestId('run').eq(1).click();
|
||||
|
||||
// * Verify the command has now been run.
|
||||
cy.findAllByTestId('run').eq(1).should('have.text', 'Rerun');
|
||||
});
|
||||
|
||||
// # Verify the expected output.
|
||||
cy.verifyPostedMessage('VALID');
|
||||
|
||||
// # Reload the page
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify the invalid command still has not yet been run.
|
||||
cy.findAllByTestId('run').eq(0).should('have.text', 'Run');
|
||||
|
||||
// * Verify the valid command has been run.
|
||||
cy.findAllByTestId('run').eq(1).should('have.text', 'Rerun');
|
||||
});
|
||||
});
|
||||
|
||||
it('runs /playbook slash commands', () => {
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify the `/playbook check 0 0` command has not yet been run.
|
||||
cy.findAllByTestId('run').eq(2).should('have.text', 'Run');
|
||||
|
||||
// * Run the slash command
|
||||
cy.findAllByTestId('run').eq(2).click();
|
||||
|
||||
// * Verify the command has now been run.
|
||||
cy.findAllByTestId('run').eq(2).should('have.text', 'Rerun');
|
||||
|
||||
// * Verify the first checklist item is checked
|
||||
cy.findAllByTestId('checkbox-item-container').eq(0).within(() => {
|
||||
// # Check the overdue task
|
||||
cy.get('input').should('be.checked');
|
||||
});
|
||||
});
|
||||
|
||||
// # Reload the page
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify the command has still been run.
|
||||
cy.findAllByTestId('run').eq(2).should('have.text', 'Rerun');
|
||||
|
||||
// * Verify the first checklist item is still checked
|
||||
cy.findAllByTestId('checkbox-item-container').eq(0).within(() => {
|
||||
// # Check the overdue task
|
||||
cy.get('input').should('be.checked');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can skip and restore task', () => {
|
||||
// # Skip task and verify
|
||||
skipTask(0);
|
||||
|
||||
// # Hover over the checklist item
|
||||
cy.findAllByTestId('checkbox-item-container').eq(0).trigger('mouseover');
|
||||
|
||||
// # Click dot menu
|
||||
cy.findAllByTestId('checkbox-item-container').eq(0).within(() => {
|
||||
cy.findByTitle('More').click();
|
||||
});
|
||||
|
||||
// # Click the restore button
|
||||
cy.findByRole('button', {name: 'Restore task'}).click();
|
||||
|
||||
// * Verify the item has been restored
|
||||
cy.findAllByTestId('checkbox-item-container').eq(0).within(() => {
|
||||
cy.get('[data-cy=skipped]').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('add new task', () => {
|
||||
const newTasktext = 'This is my new task' + Date.now();
|
||||
|
||||
cy.addNewTaskFromRHS(newTasktext);
|
||||
|
||||
// Check that it was created
|
||||
cy.findByText(newTasktext).should('exist');
|
||||
});
|
||||
|
||||
it('add new task slash command', () => {
|
||||
const newTasktext = 'Task from slash command' + Date.now();
|
||||
|
||||
cy.uiPostMessageQuickly(`/playbook checkadd 0 ${newTasktext}`);
|
||||
|
||||
// Check that it was created
|
||||
cy.findByText(newTasktext).should('exist');
|
||||
});
|
||||
|
||||
it('creates a new checklist', () => {
|
||||
// # Click on the button to add a checklist
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByTestId('add-a-checklist-button').click();
|
||||
});
|
||||
|
||||
// # Type a title and click on the Add button
|
||||
const title = 'Checklist - ' + Date.now();
|
||||
cy.findByTestId('checklist-title-input').type(title);
|
||||
cy.findByTestId('checklist-item-save-button').click();
|
||||
|
||||
// # Click on the button to add a checklist
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText(title).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('renames a checklist', () => {
|
||||
const oldTitle = 'Stage 1';
|
||||
const newTitle = 'New title - ' + Date.now();
|
||||
|
||||
// # Open the dot menu and click on the rename button
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText(oldTitle).trigger('mouseover');
|
||||
cy.findAllByTestId('checklistHeader').eq(0).within(() => {
|
||||
cy.findByTitle('More').click();
|
||||
});
|
||||
});
|
||||
cy.findByTestId('dropdownmenu').findByText('Rename checklist').click();
|
||||
|
||||
// # Type the new title and click the confirm button
|
||||
cy.findByTestId('checklist-title-input').type(newTitle);
|
||||
cy.findByTestId('checklist-item-save-button').click();
|
||||
|
||||
// * Verify that the checklist changed its name
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByText(oldTitle).should('not.exist');
|
||||
cy.findByText(oldTitle + newTitle).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('can set due date, from hover menu', () => {
|
||||
// # Set due date and verify
|
||||
setTaskDueDate(6, 'in 10 minutes');
|
||||
});
|
||||
|
||||
it('can set due date, from edit mode', () => {
|
||||
// # Hover over the checklist item
|
||||
cy.findAllByTestId('checkbox-item-container').eq(6).trigger('mouseover');
|
||||
|
||||
// # Click the edit button
|
||||
cy.findAllByTestId('hover-menu-edit-button').eq(0).click();
|
||||
|
||||
cy.findAllByTestId('due-date-info-button').eq(0).click();
|
||||
|
||||
// # Enter due date in 3 days
|
||||
cy.get('.playbook-react-select__value-container').type('in 3 days').
|
||||
wait(HALF_SEC).
|
||||
trigger('keydown', {
|
||||
key: 'Enter',
|
||||
});
|
||||
|
||||
// * Verify if Due in 3 days info is added
|
||||
cy.findAllByTestId('due-date-info-button').eq(0).should('exist').within(() => {
|
||||
cy.findByText('in 3 days').should('exist');
|
||||
cy.findByText('Due').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('filter overdue tasks', {retries: {runMode: 3}}, () => {
|
||||
// # Set overdue date for several items
|
||||
setTaskDueDate(2, '1 hour ago');
|
||||
|
||||
setTaskDueDate(3, '7 hours ago', 1);
|
||||
setTaskDueDate(5, '3 hours ago', 2);
|
||||
setTaskDueDate(6, '6 hours ago', 3);
|
||||
|
||||
// # Skip task
|
||||
skipTask(3);
|
||||
|
||||
// # Mark a task as completed
|
||||
cy.findAllByTestId('checkbox-item-container').eq(5).within(() => {
|
||||
// # Check the overdue task
|
||||
cy.get('input').click();
|
||||
});
|
||||
|
||||
// * Verify if overdue tasks info was added. Should not include skipped / completed tasks.
|
||||
cy.findAllByTestId('overdue-tasks-filter').eq(0).should('exist').within(() => {
|
||||
cy.findByText('2 tasks overdue').should('exist');
|
||||
});
|
||||
|
||||
// # Filter overdue tasks
|
||||
cy.findAllByTestId('overdue-tasks-filter').eq(0).click();
|
||||
|
||||
// * Verify if filter works. Should not include skipped / completed tasks.
|
||||
cy.findAllByTestId('checkbox-item-container').should('have.length', 2);
|
||||
|
||||
// # Cancel filter overdue tasks
|
||||
cy.findAllByTestId('overdue-tasks-filter').eq(0).click();
|
||||
|
||||
// * Verify if filter was canceled
|
||||
cy.findAllByTestId('checkbox-item-container').should('have.length', 48);
|
||||
});
|
||||
|
||||
it('filter overdue automatically disappear if we check all overdue items', () => {
|
||||
// # Set due date
|
||||
setTaskDueDate(2, '1 minute ago');
|
||||
|
||||
// * Verify if overdue tasks info was added
|
||||
cy.findAllByTestId('overdue-tasks-filter').eq(0).should('exist').within(() => {
|
||||
cy.findByText('1 task overdue').should('exist');
|
||||
});
|
||||
|
||||
// # Filter overdue tasks
|
||||
cy.findAllByTestId('overdue-tasks-filter').eq(0).click();
|
||||
|
||||
// * Verify if filter works
|
||||
cy.findAllByTestId('checkbox-item-container').should('have.length', 1);
|
||||
|
||||
// # Mark a task as completed
|
||||
cy.findAllByTestId('checkbox-item-container').within(() => {
|
||||
// # Check the overdue task
|
||||
cy.get('input').click();
|
||||
});
|
||||
|
||||
// * Verify there is no filter
|
||||
cy.findAllByTestId('overdue-tasks-filter').should('not.exist');
|
||||
|
||||
// * Verify if filter was canceled
|
||||
cy.findAllByTestId('checkbox-item-container').should('have.length', 48);
|
||||
});
|
||||
|
||||
it('switching between runs with the same checklist', () => {
|
||||
// # Create another run using the same playbook
|
||||
const playbookRunName2 = 'RunWithSameChecklist';
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: playbookRunName2,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Set due date for the first channel's task
|
||||
setTaskDueDate(2, 'in 2 hours');
|
||||
|
||||
// # Switch to the second run channel
|
||||
cy.get('#sidebarItem_runwithsamechecklist').click();
|
||||
|
||||
// * Verify that tasks do not have due dates
|
||||
cy.findAllByTestId('checkbox-item-container').eq(2).within(() => {
|
||||
cy.findAllByTestId('due-date-info-button').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('scroll 2-3 pages and open due date selector- unexpected scroll issue', () => {
|
||||
// # Hover over the checklist item that is ~3 pages down
|
||||
cy.findAllByTestId('checkbox-item-container').eq(26).trigger('mouseover').within(() => {
|
||||
// # Click the set due date button
|
||||
cy.get('.icon-calendar-outline').click();
|
||||
});
|
||||
|
||||
// * Verify if date selector is visible
|
||||
cy.get('.playbook-react-select').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const setTaskDueDate = (taskIndex, dateQuery, offset = 0) => {
|
||||
// # Hover over the checklist item
|
||||
cy.findAllByTestId('checkbox-item-container').eq(taskIndex).trigger('mouseover').within(() => {
|
||||
// # Click the set due date button
|
||||
cy.get('.icon-calendar-outline').click();
|
||||
});
|
||||
|
||||
// # Wait for react select to finish rendering.
|
||||
cy.wait(ONE_SEC);
|
||||
|
||||
// # Enter due date query
|
||||
cy.get('.playbook-react-select').within(() => {
|
||||
cy.get('input').type(dateQuery, {force: true}).
|
||||
wait(HALF_SEC).
|
||||
trigger('keydown', {
|
||||
key: 'Enter',
|
||||
});
|
||||
});
|
||||
|
||||
// * Verify if Due date info is added
|
||||
cy.findAllByTestId('due-date-info-button').eq(offset).should('exist').within(() => {
|
||||
cy.findByText(dateQuery).should('exist');
|
||||
cy.findByText('Due').should('exist');
|
||||
});
|
||||
};
|
||||
|
||||
const skipTask = (taskIndex) => {
|
||||
// # Hover over the checklist item
|
||||
cy.findAllByTestId('checkbox-item-container').eq(taskIndex).trigger('mouseover');
|
||||
|
||||
// # Click dot menu
|
||||
cy.findAllByTestId('checkbox-item-container').eq(taskIndex).within(() => {
|
||||
cy.findByTitle('More').click();
|
||||
});
|
||||
|
||||
// # Click the skip button
|
||||
cy.findByRole('button', {name: 'Skip task'}).click();
|
||||
};
|
||||
@@ -0,0 +1,225 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > rhs > header', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
});
|
||||
|
||||
describe('shows name', () => {
|
||||
it('of active playbook run', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify the title is displayed
|
||||
cy.get('#rhsContainer').contains(playbookRunName);
|
||||
});
|
||||
|
||||
it('of renamed playbook run', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify the existing title is displayed
|
||||
cy.get('#rhsContainer').contains(playbookRunName);
|
||||
|
||||
// # Rename the channel
|
||||
cy.apiPatchChannel(playbookRun.channel_id, {
|
||||
id: playbookRun.channel_id,
|
||||
display_name: 'Updated',
|
||||
});
|
||||
|
||||
// * Verify the updated title is displayed
|
||||
cy.get('#rhsContainer').contains(playbookRunName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit summary', () => {
|
||||
it('by clicking on placeholder', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # click on the field
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').click();
|
||||
|
||||
// # type text in textarea
|
||||
cy.get('#rhsContainer').findByTestId('textarea-description').should('be.visible').type('new summary{ctrl+enter}');
|
||||
|
||||
// * make sure the updated summary is here
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').contains('new summary');
|
||||
|
||||
// * reload the page
|
||||
cy.reload();
|
||||
|
||||
// * make sure the updated summary is still there
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').contains('new summary');
|
||||
});
|
||||
|
||||
it('by clicking on dot menu item', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # click on the field
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByTestId('buttons-row').invoke('show').within(() => {
|
||||
cy.findAllByRole('button').eq(1).click();
|
||||
});
|
||||
});
|
||||
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.get('span').should('have.length', 3);
|
||||
cy.findByText('Edit run summary').click();
|
||||
});
|
||||
|
||||
// # type text in textarea
|
||||
cy.focused().should('be.visible').type('new summary{ctrl+enter}');
|
||||
|
||||
// * make sure the updated summary is here
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').contains('new summary');
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit summary of finished run', () => {
|
||||
let playbookRunChannelName;
|
||||
let finishedPlaybookRun;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
finishedPlaybookRun = playbookRun;
|
||||
});
|
||||
});
|
||||
|
||||
it('by clicking on placeholder', () => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Wait for the RHS to open
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
// # Mark the run as finished
|
||||
cy.apiFinishRun(finishedPlaybookRun.id);
|
||||
|
||||
// # click on the field
|
||||
cy.get('#rhsContainer').findByTestId('rendered-description').should('be.visible').click();
|
||||
|
||||
// * Verify textarea does not appear
|
||||
cy.get('#rhsContainer').findByTestId('textarea-description').should('not.exist');
|
||||
|
||||
// * Verify no prompt to join appears (timeout ensures it fails right away before toast disappears)
|
||||
cy.findByText('Become a participant to interact with this run', {timeout: 500}).should('not.exist');
|
||||
});
|
||||
|
||||
it('by clicking on dot menu item', () => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Wait for the RHS to open
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
// # Mark the run as finished
|
||||
cy.apiFinishRun(finishedPlaybookRun.id);
|
||||
|
||||
// # click on the field
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
cy.findByTestId('buttons-row').invoke('show').within(() => {
|
||||
cy.findAllByRole('button').eq(1).click();
|
||||
});
|
||||
});
|
||||
|
||||
// * Verify the menu items
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.get('span').should('have.length', 2);
|
||||
cy.findByText('Edit run summary').should('not.exist');
|
||||
});
|
||||
|
||||
// * Verify no prompt to join appears (timeout ensures it fails right away before toast disappears)
|
||||
cy.findByText('Become a participant to interact with this run', {timeout: 500}).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,159 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > rhs > home', () => {
|
||||
let testSysadmin;
|
||||
let testTeam;
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
testSysadmin = sysadmin;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('default permission settings', () => {
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate to the application, starting in a non-run channel.
|
||||
cy.visit(`/${testTeam.name}/`);
|
||||
|
||||
// * Check time bar in the channel section
|
||||
// * as an indicator of page stability / end of rendering
|
||||
cy.findByText('Today').should('be.visible');
|
||||
});
|
||||
|
||||
describe('telemetry', () => {
|
||||
it('track page view', () => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{name: 'channels_rhs_home', type: 'page'}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shows available', () => {
|
||||
it('starter templates', () => {
|
||||
// templates are defined in webapp/src/components/templates/template_data.tsx
|
||||
const templates = [
|
||||
{name: 'Blank', checklists: '1 checklist', actions: '1 action'},
|
||||
{name: 'Product Release', checklists: '4 checklists', actions: '3 actions'},
|
||||
{name: 'Incident Resolution', checklists: '4 checklists', actions: '4 actions'},
|
||||
{name: 'Customer Onboarding', checklists: '4 checklists', actions: '3 actions'},
|
||||
{name: 'Employee Onboarding', checklists: '5 checklists', actions: '2 actions'},
|
||||
{name: 'Feature Lifecycle', checklists: '5 checklists', actions: '3 actions'},
|
||||
{name: 'Bug Bash', checklists: '5 checklists', actions: '3 actions'},
|
||||
{name: 'Learn how to use playbooks', checklists: '2 checklists', actions: '2 actions'},
|
||||
];
|
||||
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// * Verify the templates are shown
|
||||
cy.findByText('Playbook Templates').
|
||||
parent().
|
||||
next().
|
||||
within(() => {
|
||||
cy.findAllByTestId('template-details').each(($templateElement, index) => {
|
||||
cy.wrap($templateElement).within(() => {
|
||||
cy.findByText(templates[index].name).should('exist');
|
||||
cy.findByText(templates[index].checklists).should('exist');
|
||||
cy.findByText(templates[index].actions).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('show zero case if there are playbooks', () => {
|
||||
beforeEach(() => {
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Team Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
});
|
||||
|
||||
it('without pre-populated channel name template', () => {
|
||||
// * Verify the templates are not shown
|
||||
cy.findAllByTestId('template-details').should('not.exist');
|
||||
|
||||
// * Verify the zero case is shown
|
||||
cy.get('#sidebar-right').findByText('There are no runs in progress linked to this channel').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let restrictedTestTeam;
|
||||
let restrictedTestUser;
|
||||
|
||||
describe('user is lacking permissions to create playbooks', () => {
|
||||
before(() => {
|
||||
cy.apiLogin(testSysadmin);
|
||||
|
||||
cy.apiCreateUser().then(({user}) => {
|
||||
restrictedTestUser = user;
|
||||
});
|
||||
|
||||
cy.apiCreateTeam('restricted-team', 'Restricted Team').then(({team}) => {
|
||||
restrictedTestTeam = team;
|
||||
cy.apiAddUserToTeam(restrictedTestTeam.id, restrictedTestUser.id);
|
||||
});
|
||||
|
||||
cy.apiCreateScheme('Restricted Team Scheme', 'team').then(({scheme}) => {
|
||||
cy.apiSetTeamScheme(restrictedTestTeam.id, scheme.id);
|
||||
cy.apiGetRolesByNames([scheme.default_team_user_role]).then(({roles}) => {
|
||||
const role = roles[0];
|
||||
|
||||
// Remove permissions to create playbooks
|
||||
const permissions = role.permissions.filter((perm) => !(/playbook_(private|public)_create/).test(perm));
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as user with restricted permissions
|
||||
cy.apiLogin(restrictedTestUser);
|
||||
|
||||
// # Navigate to the application, starting in a non-run channel.
|
||||
cy.visit(`/${restrictedTestTeam.name}/`);
|
||||
});
|
||||
|
||||
it('permission notice should be shown and no create button should exist', () => {
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
cy.get('#sidebar-right').within(() => {
|
||||
// * Verify notice about missing permissions exists
|
||||
cy.findByText('You don\'t have permission to create playbooks in this workspace.').should('be.visible');
|
||||
|
||||
// * Verify create playbook button does not exist
|
||||
cy.findByText('Create playbook').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,263 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > rhs > runlist', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let testChannel;
|
||||
const numActiveRuns = 10;
|
||||
const numFinishedRuns = 4;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'The playbook name',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
// # Create a test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
// # Run the playbook a few times in the existing channel
|
||||
for (let i = 0; i < numActiveRuns; i++) {
|
||||
const runName = 'playbook-run-' + i;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testChannel.id,
|
||||
playbookRunName: runName,
|
||||
});
|
||||
}
|
||||
|
||||
// # Do it again but finished
|
||||
for (let i = 0; i < numFinishedRuns; i++) {
|
||||
const runName = 'playbook-run-' + i;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testChannel.id,
|
||||
playbookRunName: runName,
|
||||
}).then((run) => {
|
||||
cy.apiFinishRun(run.id);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// # Wait the RHS to load
|
||||
cy.findByText('Runs in progress').should('be.visible');
|
||||
});
|
||||
|
||||
it('track page view', () => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{name: 'channels_rhs_runlist', type: 'page'}]);
|
||||
});
|
||||
|
||||
it('can filter', () => {
|
||||
// # Click the filter menu
|
||||
cy.findByTestId('rhs-runs-filter-menu').click();
|
||||
|
||||
// * Verify displayed options
|
||||
cy.get('[data-testid="dropdownmenu"] > :nth-child(1) > div').should('have.text', numActiveRuns);
|
||||
cy.get('[data-testid="dropdownmenu"] > :nth-child(2) > div').should('have.text', numFinishedRuns);
|
||||
|
||||
// # Click the filter
|
||||
cy.get('[data-testid="dropdownmenu"] > :nth-child(2)').click();
|
||||
|
||||
// * Verify displayed options
|
||||
cy.get('[data-testid="rhs-runs-list"]').children().should('have.length', numFinishedRuns);
|
||||
});
|
||||
|
||||
it('can show more (pagination)', () => {
|
||||
// * Verify we have the first page
|
||||
cy.get('[data-testid="rhs-runs-list"] > div').should('have.length', 8);
|
||||
|
||||
// # CLick in the show-more button
|
||||
cy.get('[data-testid="rhs-runs-list"] > button').click();
|
||||
|
||||
// * Verify we have loaded the second page
|
||||
cy.get('[data-testid="rhs-runs-list"] > div').should('have.length', 10);
|
||||
});
|
||||
|
||||
it('card has the basic info', () => {
|
||||
// # Click the first run
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1)').within(() => {
|
||||
cy.findByText('playbook-run-9').should('be.visible');
|
||||
cy.findByText('The playbook name').should('be.visible');
|
||||
cy.findByText(testUser.username).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('can click though', () => {
|
||||
// # Click the first run
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1)').click();
|
||||
|
||||
// * Verify we made it to the run details at Channels RHS
|
||||
cy.get('#rhsContainer').contains('playbook-run-9');
|
||||
cy.get('#rhsContainer').contains('Tasks');
|
||||
});
|
||||
|
||||
it('can see give feedback button', () => {
|
||||
// * Verify give feedback button exists and has the right URL
|
||||
cy.get('#rhsContainer').findByText('Give feedback').
|
||||
should('exist').
|
||||
and('have.attr', 'href').
|
||||
and('include', 'https://mattermost.com/pl/playbooks-feedback');
|
||||
});
|
||||
|
||||
describe('dotmenu', () => {
|
||||
it('can navigate to RDP', () => {
|
||||
// # Click the first run's dotmenu
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1)').findByRole('button').click();
|
||||
|
||||
// # Click on go to run
|
||||
cy.findByText('Go to run overview').click();
|
||||
|
||||
// * Assert we are in the run details page
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=channel_rhs_dotmenu');
|
||||
});
|
||||
|
||||
it('can navigate to PBE', () => {
|
||||
// # Click the first run's dotmenu
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1)').findByRole('button').click();
|
||||
|
||||
// # Click on go to polaybook
|
||||
cy.findByText('Go to playbook').click();
|
||||
|
||||
// * Assert we are in the PBE page
|
||||
cy.url().should('include', `/playbooks/${testPlaybook.id}`);
|
||||
});
|
||||
|
||||
it('can change run name', () => {
|
||||
// # Click on the kebab menu
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1) .icon-dots-vertical').click();
|
||||
|
||||
// # Click on the rename run option
|
||||
cy.findByText('Rename run').click();
|
||||
|
||||
// # type new name
|
||||
cy.findByTestId('run-name-input').clear().type('My cool new run name');
|
||||
|
||||
// # click save
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify the name has changed
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1)').contains('My cool new run name');
|
||||
});
|
||||
|
||||
it('can change linked channel', () => {
|
||||
// # Click on the kebab menu
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1) .icon-dots-vertical').click();
|
||||
|
||||
// # Click on the rename run option
|
||||
cy.findByText('Link run to a different channel').click();
|
||||
|
||||
// # type new name
|
||||
cy.get('.modal-body').within(() => {
|
||||
// # select town square
|
||||
cy.findByText(testChannel.display_name).click().type('Town Square{enter}');
|
||||
});
|
||||
|
||||
// # click save
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Let the listing refresh
|
||||
cy.wait(1000);
|
||||
|
||||
// * Verify we have the first page
|
||||
cy.get('[data-testid="rhs-runs-list"] > div').should('have.length', 8);
|
||||
|
||||
// # CLick in the show-more button
|
||||
cy.get('[data-testid="rhs-runs-list"] > button').click();
|
||||
|
||||
// * Verify the channel has changed, now one run less
|
||||
cy.get('[data-testid="rhs-runs-list"] > div').should('have.length', 9);
|
||||
});
|
||||
|
||||
describe('navigation', () => {
|
||||
let testChannelWith2Runs;
|
||||
before(() => {
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a test channel
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel').then(({channel}) => {
|
||||
testChannelWith2Runs = channel;
|
||||
|
||||
// # Run the playbook a few times in the existing channel
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const runName = 'playbook-run-' + i;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testChannelWith2Runs.id,
|
||||
playbookRunName: runName,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('stays at list even if one only linked run after moving run', () => {
|
||||
// # Visit channel with 2 runs
|
||||
cy.visit(`/${testTeam.name}/channels/${testChannelWith2Runs.name}`);
|
||||
|
||||
// # Click on the kebab menu
|
||||
cy.get('[data-testid="rhs-runs-list"] > :nth-child(1) .icon-dots-vertical').click();
|
||||
|
||||
// # Click on the rename run option
|
||||
cy.findByText('Link run to a different channel').click();
|
||||
|
||||
// # type new name
|
||||
cy.get('.modal-body').within(() => {
|
||||
// # select town square
|
||||
cy.findByText(testChannelWith2Runs.display_name).click().type('Town Square{enter}');
|
||||
});
|
||||
|
||||
// # click save
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify the run is not there, but we are still in the list (not rhs details)
|
||||
cy.get('[data-testid="rhs-runs-list"] > div').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,590 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels rhs > start a run', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiCreateChannel(testTeam.id, 'existing-channel', 'Existing Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
|
||||
const createPlaybook = ({channelNameTemplate, runSummaryTemplate, channelId, channelMode, title}) => {
|
||||
const runSummaryTemplateEnabled = Boolean(runSummaryTemplate);
|
||||
|
||||
// # Create a public playbook
|
||||
return cy.apiCreatePlaybook({
|
||||
title: title || 'Public Playbook',
|
||||
channelNameTemplate,
|
||||
runSummaryTemplate,
|
||||
runSummaryTemplateEnabled,
|
||||
channelMode,
|
||||
channelId,
|
||||
teamId: testTeam.id,
|
||||
makePublic: true,
|
||||
memberIDs: [testUser.id],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((playbook) => {
|
||||
cy.wrap(playbook);
|
||||
});
|
||||
};
|
||||
|
||||
describe('From RHS run list > ', () => {
|
||||
beforeEach(() => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
});
|
||||
|
||||
describe('playbook configured as create new channel', () => {
|
||||
it('defaults', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'create_new_channel',
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is filled
|
||||
cy.findByTestId('run-name-input').should('have.value', 'Channel template');
|
||||
|
||||
// * Assert summary template is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'channels_rhs_runlist',
|
||||
playbookId: playbook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
public: true,
|
||||
hasPlaybookChanged: true,
|
||||
hasNameChanged: false,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
}},
|
||||
], {waitForCalls: 2});
|
||||
|
||||
// * Verify we are on the channel just created
|
||||
cy.url().should('include', `/${testTeam.name}/channels/channel-template`);
|
||||
|
||||
// * Verify channel name
|
||||
cy.get('h2').contains('Beginning of Channel template');
|
||||
|
||||
// * Verify run RHS
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.contains('Channel template');
|
||||
cy.contains('run summary template');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('change title/summary', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'create_new_channel',
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template are filled (and force wait to them)
|
||||
cy.findByTestId('run-name-input').should('have.value', 'Channel template');
|
||||
|
||||
// * Assert summary template is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Fill run summary
|
||||
cy.findByTestId('run-summary-input').clear().type('Test Run Summary');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'channels_rhs_runlist',
|
||||
playbookId: playbook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
public: true,
|
||||
hasPlaybookChanged: true,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: true,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
}},
|
||||
]);
|
||||
|
||||
// * Verify we are on the channel just created
|
||||
cy.url().should('include', `/${testTeam.name}/channels/test-run-name`);
|
||||
|
||||
// * Verify channel name
|
||||
cy.get('h2').contains('Beginning of Test Run Name');
|
||||
|
||||
// * Verify run RHS
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.contains('Test Run Name');
|
||||
cy.contains('Test Run Summary');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('change to link to existing channel defaults to current channel', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'create_new_channel',
|
||||
}).then((playbook) => {
|
||||
// # Visit the town square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// # Change to link to existing channel
|
||||
cy.findByTestId('link-existing-channel-radio').click();
|
||||
|
||||
// * Assert current channel is selected
|
||||
cy.findByText('Town Square').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('change to link to existing channel with already selected channel', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'create_new_channel',
|
||||
channelId: testChannel.id,
|
||||
}).then((playbook) => {
|
||||
// # Visit the town square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// # Change to link to existing channel
|
||||
cy.findByTestId('link-existing-channel-radio').click();
|
||||
|
||||
// * Assert selected channel is unchanged
|
||||
cy.findByText(testChannel.display_name).should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('change to link to existing channel', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'create_new_channel',
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// # Change to link to existing channel
|
||||
cy.findByTestId('link-existing-channel-radio').click();
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Select test channel instead of current channel
|
||||
cy.findByText('Town Square').click().type(`${testChannel.display_name}{enter}`);
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'channels_rhs_runlist',
|
||||
playbookId: playbook.id,
|
||||
channelMode: 'link_existing_channel',
|
||||
hasPlaybookChanged: true,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: true,
|
||||
hasChannelIdChanged: true,
|
||||
hasPublicChanged: false,
|
||||
}},
|
||||
]);
|
||||
|
||||
// * Verify we are on the existing channel
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// * Verify channel name
|
||||
cy.get('h2').contains(`Beginning of ${testChannel.display_name}`);
|
||||
|
||||
// * Verify run RHS
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.contains('Test Run Name');
|
||||
cy.contains('run summary template');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('playbook configured as linked to existing channel', () => {
|
||||
it('defaults', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'link_existing_channel',
|
||||
channelId: testChannel.id,
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is empty
|
||||
cy.findByTestId('run-name-input').should('be.empty');
|
||||
|
||||
// * Assert template summary is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'channels_rhs_runlist',
|
||||
playbookId: playbook.id,
|
||||
channelMode: 'link_existing_channel',
|
||||
hasPlaybookChanged: true,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
}},
|
||||
]);
|
||||
|
||||
// * Verify we are on the existing channel
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// * Verify channel name
|
||||
cy.get('h2').contains(`Beginning of ${testChannel.display_name}`);
|
||||
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify run RHS
|
||||
cy.contains('Test Run Name');
|
||||
cy.contains('run summary template');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fill initially empty channel', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'link_existing_channel',
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is empty
|
||||
cy.findByTestId('run-name-input').should('be.empty');
|
||||
|
||||
// * Assert template summary is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Fill Town square as the channel to be linked
|
||||
cy.findByText('Select a channel').click().type(`${testChannel.display_name}{enter}`);
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'channels_rhs_runlist',
|
||||
playbookId: playbook.id,
|
||||
channelMode: 'link_existing_channel',
|
||||
hasPlaybookChanged: true,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: true,
|
||||
hasPublicChanged: false,
|
||||
}},
|
||||
]);
|
||||
|
||||
// * Verify we are on the existing channel
|
||||
cy.url().should('include', `/${testTeam.name}/channels/${testChannel.name}`);
|
||||
|
||||
// * Verify channel name
|
||||
cy.get('h2').contains(`Beginning of ${testChannel.display_name}`);
|
||||
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify run RHS
|
||||
cy.contains('Test Run Name');
|
||||
cy.contains('run summary template');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('change to create new channel', () => {
|
||||
// # Fill default values
|
||||
createPlaybook({
|
||||
title: 'Playbook title' + Date.now(),
|
||||
channelNameTemplate: 'Channel template',
|
||||
runSummaryTemplate: 'run summary template',
|
||||
channelMode: 'link_existing_channel',
|
||||
channelId: testChannel.id,
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert we are at playbooks tab
|
||||
cy.findByText('Select a playbook').should('be.visible');
|
||||
|
||||
// # Click on the playbook
|
||||
cy.findAllByText(playbook.title).eq(0).click();
|
||||
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// # Change to create new channel
|
||||
cy.findByTestId('create-channel-radio').click();
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'channels_rhs_runlist',
|
||||
playbookId: playbook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
hasPlaybookChanged: true,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: true,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
}},
|
||||
]);
|
||||
|
||||
// * Verify we are on the channel just created
|
||||
cy.url().should('include', `/${testTeam.name}/channels/test-run-name`);
|
||||
|
||||
// * Verify channel name
|
||||
cy.get('h2').contains('Beginning of Test Run Name');
|
||||
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
// * Verify run RHS
|
||||
cy.contains('Test Run Name');
|
||||
cy.contains('run summary template');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,498 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
/* eslint-disable no-only-tests/no-only-tests */
|
||||
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('channels > rhs > status update', () => {
|
||||
const defaultReminderMessage = '# Default reminder message';
|
||||
let testTeam;
|
||||
let testChannel;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let testRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, channel, user}) => {
|
||||
testTeam = team;
|
||||
testChannel = channel;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser,
|
||||
broadcastChannelIds: [testChannel.id],
|
||||
reminderTimerDefaultSeconds: 3600,
|
||||
reminderMessageTemplate: defaultReminderMessage,
|
||||
retrospectiveEnabled: false,
|
||||
broadcastEnabled: true,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
const name = 'Playbook Run (' + now + ')';
|
||||
const channelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: name,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
testRun = run;
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channelName}`);
|
||||
});
|
||||
|
||||
describe('post update dialog', () => {
|
||||
it('renders description correctly', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Check description
|
||||
cy.findByTestId('update_run_status_description').contains(`This update for the run ${testRun.name} will be broadcasted to one channel and one direct message.`);
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('description link navigates to run overview', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Click overview link
|
||||
cy.findByTestId('run-overview-link').click();
|
||||
});
|
||||
|
||||
// * Check that we are now in run overview page
|
||||
cy.url().should('include', `/playbooks/runs/${testRun.id}`);
|
||||
|
||||
// * Check that the run actions modal is already opened
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('exist');
|
||||
});
|
||||
|
||||
it('prevents posting an update message with only whitespace', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Type the invalid data
|
||||
cy.findByTestId('update_run_status_textbox').clear().type(' {enter} {enter} ');
|
||||
|
||||
// * Verify submit is disabled.
|
||||
cy.get('button.confirm').should('be.disabled');
|
||||
|
||||
// # Enter valid data
|
||||
cy.findByTestId('update_run_status_textbox').type('valid update');
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Verify that the Post update dialog has gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
});
|
||||
|
||||
it('lets users with no access to the playbook post an update', () => {
|
||||
let channelName;
|
||||
const updateMessage = 'status update ' + Date.now();
|
||||
|
||||
// # Login as sysadmin and create a private playbook and a run
|
||||
cy.apiAdminLogin().then(({user: sysadmin}) => {
|
||||
// # Create a private playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook - Private',
|
||||
memberIDs: [sysadmin.id], // Make it accesible only to sysadmin
|
||||
inviteUsersEnabled: true,
|
||||
invitedUserIds: [testUser.id], // Invite the test user
|
||||
}).then((playbook) => {
|
||||
// # Create a new playbook run
|
||||
const now = Date.now();
|
||||
const name = 'Playbook Run (' + now + ')';
|
||||
channelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: name,
|
||||
ownerUserId: sysadmin.id,
|
||||
}).then((run) => {
|
||||
cy.apiAddUsersToRun(run.id, [testUser.id]);
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
// # Login as the test user
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channelName}`);
|
||||
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Enter valid data
|
||||
cy.findByTestId('update_run_status_textbox').type(updateMessage);
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Verify that the Post update dialog has gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
|
||||
// * Verify that the status update was posted.
|
||||
cy.getLastPost().within(() => {
|
||||
cy.findByText(updateMessage).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('confirms finishing the run, and remembers changes and reminder when canceled', () => {
|
||||
const updateMessage = 'This is the update text to test with.';
|
||||
const reminderTime = '1 day';
|
||||
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the first message is there.
|
||||
cy.findByTestId('update_run_status_textbox').within(() => {
|
||||
cy.findByText(defaultReminderMessage).should('exist');
|
||||
});
|
||||
|
||||
// # Type text to test for later
|
||||
cy.findByTestId('update_run_status_textbox').clear().type(updateMessage);
|
||||
|
||||
// # Set a new reminder to test for later
|
||||
cy.openReminderSelector();
|
||||
cy.selectReminderTime(reminderTime);
|
||||
|
||||
// # Mark the run as finished
|
||||
cy.findByTestId('mark-run-as-finished').click({force: true});
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Confirmation should appear
|
||||
cy.get('.modal-header').should('be.visible').contains('Confirm finish run');
|
||||
|
||||
// # Cancel
|
||||
cy.get('#cancelModalButton').click({force: true});
|
||||
|
||||
// * Verify post update has the same information
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the message was remembered
|
||||
cy.findByTestId('update_run_status_textbox').within(() => {
|
||||
cy.findByText(updateMessage).should('exist');
|
||||
});
|
||||
|
||||
// * Verify the reminder was remembered
|
||||
cy.get('#reminder_timer_datetime').contains(reminderTime);
|
||||
|
||||
// * Marked run is still checked
|
||||
cy.findByTestId('mark-run-as-finished').within(() => {
|
||||
cy.get('[type="checkbox"]').should('be.checked');
|
||||
});
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Confirmation should appear
|
||||
cy.get('.modal-header').should('be.visible').contains('Confirm finish run');
|
||||
|
||||
// # Submit
|
||||
cy.get('#confirmModalButton').click({force: true});
|
||||
|
||||
// * Verify the status update was posted.
|
||||
cy.getStyledComponent('CustomPostContent').within(() => {
|
||||
cy.findByText(updateMessage).should('exist');
|
||||
});
|
||||
|
||||
// * Verify the run was finished.
|
||||
cy.getLastPost().contains(`@${testUser.username} marked ${testRun.name} as finished.`);
|
||||
});
|
||||
|
||||
describe('prevents user from losing changes', () => {
|
||||
it('cancel, go back and save', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Type the invalid data
|
||||
cy.findByTestId('update_run_status_textbox').clear().type('My valid and important changes that I don\'t want to lose');
|
||||
|
||||
// * Click cancel
|
||||
cy.findByTestId('modal-cancel-button').click();
|
||||
});
|
||||
|
||||
// * Go back from unsaved changes modal
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
cy.findByTestId('modal-cancel-button').click();
|
||||
});
|
||||
|
||||
// # Delay in between the modal switch to ensure the
|
||||
// # animation has fully happened
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
|
||||
// * Verify that the Post update and unsaved changes modals have gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
cy.get('#confirm-modal-light').should('not.exist');
|
||||
});
|
||||
|
||||
it('click overview link, go back and save', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Type the invalid data
|
||||
cy.findByTestId('update_run_status_textbox').clear().type('My valid and important changes that I don\'t want to lose');
|
||||
|
||||
// # Click overview link
|
||||
cy.findByTestId('run-overview-link').click();
|
||||
});
|
||||
|
||||
// Verify that the confirmation modal is shown
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
// * Go back from unsaved changes modal
|
||||
cy.findByTestId('modal-cancel-button').click();
|
||||
});
|
||||
|
||||
// # Delay in between the modal switch to ensure the
|
||||
// # animation has fully happened
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
|
||||
// * Verify that the Post update and unsaved changes modals have gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
cy.get('#confirm-modal-light').should('not.exist');
|
||||
});
|
||||
|
||||
it('cancel and discard explicitly', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Type the invalid data
|
||||
cy.findByTestId('update_run_status_textbox').clear().type('My valid and important changes that I don\'t want to lose');
|
||||
|
||||
// * Click cancel
|
||||
cy.findByTestId('modal-cancel-button').click();
|
||||
});
|
||||
|
||||
// * Discard explicitly from unsaved changes
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Verify that the Post update and unsaved changes modals have gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
cy.get('#confirm-modal-light').should('not.exist');
|
||||
});
|
||||
|
||||
it('click overview link and discard explicitly', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Type the invalid data
|
||||
cy.findByTestId('update_run_status_textbox').clear().type('My valid and important changes that I don\'t want to lose');
|
||||
|
||||
// # Click overview link
|
||||
cy.findByTestId('run-overview-link').click();
|
||||
});
|
||||
|
||||
// * Discard explicitly from unsaved changes
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Assert that we are at run overview page.
|
||||
cy.url().should('include', `/playbooks/runs/${testRun.id}`);
|
||||
|
||||
// * Verify that the Post update and unsaved changes modals have gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
cy.get('#confirm-modal-light').should('not.exist');
|
||||
|
||||
// * Verify that the run actions modal is opened.
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('shows the last update in update message', () => {
|
||||
it('shows the default when we have not made an update before', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the first message is there.
|
||||
cy.findByTestId('update_run_status_textbox').within(() => {
|
||||
cy.findByText(defaultReminderMessage).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when we have made a previous update', () => {
|
||||
const now = Date.now();
|
||||
const firstMessage = 'Update - ' + now;
|
||||
|
||||
// # Create a first status update
|
||||
cy.updateStatus(firstMessage);
|
||||
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the first message is there.
|
||||
cy.findByTestId('update_run_status_textbox').within(() => {
|
||||
cy.findByText(firstMessage).should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('the default reminder', () => {
|
||||
it('shows the configured default when we have not made a previous update', () => {
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the default is as expected
|
||||
cy.get('#reminder_timer_datetime').within(() => {
|
||||
cy.get('[class$=singleValue]').should('have.text', '1 hour');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the last reminder we typed in: 15 minutes', () => {
|
||||
const now = Date.now();
|
||||
const firstMessage = 'Update - ' + now;
|
||||
|
||||
// # Create a first status update
|
||||
cy.updateStatus(firstMessage, '15 minutes');
|
||||
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the default is as expected
|
||||
cy.get('#reminder_timer_datetime').within(() => {
|
||||
cy.get('[class$=singleValue]').should('have.text', '15 minutes');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the last reminder we typed in: 90 minutes', () => {
|
||||
const now = Date.now();
|
||||
const firstMessage = 'Update - ' + now;
|
||||
|
||||
// # Create a first status update
|
||||
cy.updateStatus(firstMessage, '90 minutes');
|
||||
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the default is as expected
|
||||
cy.get('#reminder_timer_datetime').within(() => {
|
||||
cy.get('[class$=singleValue]').should('have.text', '1 hour, 30 minutes');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the last reminder we typed in: 7 days', () => {
|
||||
const now = Date.now();
|
||||
const firstMessage = 'Update - ' + now;
|
||||
|
||||
// # Create a first status update
|
||||
cy.updateStatus(firstMessage, '7 days');
|
||||
|
||||
// # Run the `/playbook update` slash command.
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// # Get the dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// * Verify the default is as expected
|
||||
cy.get('#reminder_timer_datetime').within(() => {
|
||||
cy.get('[class$=singleValue]').should('have.text', '7 days');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('playbook with disabled status updates', () => {
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser,
|
||||
broadcastChannelId: testChannel.id,
|
||||
statusUpdateEnabled: false,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
describe('omit status update dialog when status updates are disabled', () => {
|
||||
it('shows the default when we have not made an update before', () => {
|
||||
// * Check if RHS section is loaded
|
||||
cy.get('#rhs-about').should('exist');
|
||||
|
||||
// * Check if Post Update section is omitted
|
||||
cy.get('#rhs-post-update').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > rhs > template', () => {
|
||||
let team1;
|
||||
let testUser;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiAdminLogin().then(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
team1 = team;
|
||||
testUser = user;
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('create playbook', () => {
|
||||
describe('open new playbook creation modal and navigates to playbooks', () => {
|
||||
it('after clicking on Use', () => {
|
||||
// # Switch to playbooks DM channel
|
||||
cy.visit(`/${team1.name}/messages/@playbooks`);
|
||||
|
||||
// * Checking the bot badge as an indicator of page
|
||||
// * stability / rendering finished
|
||||
cy.findByText('BOT').should('be.visible');
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Return first template (Blank)
|
||||
cy.contains('Blank').click();
|
||||
|
||||
// * Assert playbooks creation modal is shown.
|
||||
cy.get('#playbooks_create').should('exist');
|
||||
|
||||
// # Click create playbook button.
|
||||
cy.get('button[data-testid=modal-confirm-button]').click();
|
||||
|
||||
// * Assert expected playbook template title in outline.
|
||||
cy.findByTestId('playbook-editor-title').contains('Blank');
|
||||
});
|
||||
|
||||
it('after clicking on title', () => {
|
||||
// # Switch to playbooks DM channel
|
||||
cy.visit(`/${team1.name}/messages/@playbooks`);
|
||||
|
||||
// * Checking the bot badge as an indicator of page
|
||||
// * stability / rendering finished
|
||||
cy.findByText('BOT').should('be.visible');
|
||||
|
||||
// # Open playbooks RHS.
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// # Return first template (Blank)
|
||||
cy.contains('Use').click();
|
||||
|
||||
// * Assert playbooks creation modal is shown.
|
||||
cy.get('#playbooks_create').should('exist');
|
||||
|
||||
// # Click create playbook button.
|
||||
cy.get('button[data-testid=modal-confirm-button]').click();
|
||||
|
||||
// * Assert expected playbook template title in outline.
|
||||
cy.findByTestId('playbook-editor-title').contains('Blank');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > rhs > title', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let playbookRunChannelName;
|
||||
let testPlaybookRun;
|
||||
|
||||
const getHeaderTitle = () => cy.get('#rhsContainer').find('.sidebar--right__title');
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
testPlaybookRun = run;
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
});
|
||||
|
||||
it('has title', () => {
|
||||
// * Verify the title is displayed
|
||||
getHeaderTitle().contains('Run details');
|
||||
});
|
||||
|
||||
it('has following button', () => {
|
||||
// * Verify the following button is displayed
|
||||
getHeaderTitle().find('button.unfollowButton').contains('Following');
|
||||
|
||||
// * Verify the follow button is not displayed
|
||||
getHeaderTitle().find('button.followButton').should('not.exist');
|
||||
});
|
||||
|
||||
it('can stop following', () => {
|
||||
// # Click the following button
|
||||
getHeaderTitle().find('button.unfollowButton').click();
|
||||
|
||||
// * Verify the following button is not displayed
|
||||
getHeaderTitle().find('button.unfollowButton').should('not.exist');
|
||||
|
||||
// * Verify the follow button is displayed
|
||||
getHeaderTitle().find('button.followButton').contains('Follow');
|
||||
});
|
||||
|
||||
it('can navigate to RDP', () => {
|
||||
// # Click the title
|
||||
getHeaderTitle().findByTestId('rhs-title').click();
|
||||
|
||||
// * assert url is RDP
|
||||
cy.url().should('include', `/playbooks/runs/${testPlaybookRun.id}`);
|
||||
});
|
||||
});
|
||||
417
e2e-tests/cypress/tests/integration/playbooks/channels/rhs_spec.js
Обычный файл
417
e2e-tests/cypress/tests/integration/playbooks/channels/rhs_spec.js
Обычный файл
@@ -0,0 +1,417 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
|
||||
describe('channels > rhs', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('does not open', () => {
|
||||
it('when navigating to a non-playbook run channel', () => {
|
||||
// # Navigate to the application
|
||||
cy.visit(`/${testTeam.name}/`);
|
||||
|
||||
// # Select a channel without a playbook run.
|
||||
cy.get('#sidebarItem_off-topic').click({force: true});
|
||||
|
||||
// # Wait until the channel loads enough to show the post textbox.
|
||||
cy.get('#post-create').should('exist');
|
||||
|
||||
// # Wait a bit longer to be confident.
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
|
||||
it('when navigating to a playbook run channel with the RHS already open', () => {
|
||||
// # Navigate to the application.
|
||||
cy.visit(`/${testTeam.name}/`);
|
||||
|
||||
// # Select a channel without a playbook run.
|
||||
cy.get('#sidebarItem_off-topic').click({force: true});
|
||||
|
||||
// # Run the playbook after loading the application
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Open the flagged posts RHS
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.find('#channelHeaderFlagButton').length > 0) {
|
||||
cy.get('#channelHeaderFlagButton').click({force: true});
|
||||
} else {
|
||||
cy.findByRole('button', {name: 'Saved posts'}).
|
||||
click({force: true});
|
||||
}
|
||||
});
|
||||
|
||||
// # Open the playbook run channel from the LHS.
|
||||
cy.get(`#sidebarItem_${playbookRunChannelName}`).click({force: true});
|
||||
|
||||
// # Wait until the channel loads enough to show the post textbox.
|
||||
cy.get('#post-create').should('exist');
|
||||
|
||||
// # Wait a bit longer to be confident.
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
|
||||
it('when navigating directly to a finished playbook run channel', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # End the playbook run
|
||||
cy.apiFinishRun(playbookRun.id);
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Wait a bit longer to be confident.
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
|
||||
it('for an existing, finished playbook run channel opened from the lhs', () => {
|
||||
// # Run the playbook before loading the application
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # End the playbook run
|
||||
cy.apiFinishRun(playbookRun.id);
|
||||
});
|
||||
|
||||
// # Navigate to a channel without a playbook run.
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Ensure the channel is loaded before continuing (allows redux to sync).
|
||||
cy.findByTestId('post_textbox').should('exist');
|
||||
|
||||
// # Open the playbook run channel from the LHS.
|
||||
cy.get(`#sidebarItem_${playbookRunChannelName}`).click({force: true});
|
||||
|
||||
// # Wait a bit longer to be confident.
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
|
||||
it('for a new, finished playbook run channel opened from the lhs', () => {
|
||||
// # Navigate to the application.
|
||||
cy.visit(`/${testTeam.name}/`);
|
||||
|
||||
// # Ensure the channel is loaded before continuing (allows redux to sync).
|
||||
cy.findByTestId('post_textbox').should('exist');
|
||||
|
||||
// # Select a channel without a playbook run.
|
||||
cy.get('#sidebarItem_off-topic').click({force: true});
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
|
||||
// # Run the playbook after loading the application
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # Wait a bit longer to avoid websocket events potentially being out-of-order.
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # End the playbook run
|
||||
cy.apiFinishRun(playbookRun.id);
|
||||
});
|
||||
|
||||
// # Wait because this test is flaky if we move too quickly
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// # Open the playbook run channel from the LHS.
|
||||
cy.get(`#sidebarItem_${playbookRunChannelName}`).click({force: true});
|
||||
|
||||
// # Wait a bit longer to be confident.
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
|
||||
it('when starting a new run of a newly-created playbook created from RHS in a newly-created channel', () => {
|
||||
// # Create a new channel
|
||||
const channelName = 'playbook-test-' + Date.now();
|
||||
cy.apiCreateChannel(testTeam.id, channelName, channelName, 'O').then(({channel}) => {
|
||||
// # Navigate to the new channel
|
||||
cy.visit(`/${testTeam.name}/channels/${channel.name}`);
|
||||
|
||||
// # Open RHS
|
||||
cy.getPlaybooksAppBarIcon().click();
|
||||
|
||||
// # Wait a bit
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # open start run dialog
|
||||
cy.findByTestId('rhs-runlist-start-run').click();
|
||||
|
||||
// # Create a new playbook
|
||||
cy.findByText('Create new playbook').click();
|
||||
|
||||
// # confirm new playbook creation (with defaults)
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify we're in the playbook edit screen
|
||||
cy.findByTestId('playbook-members');
|
||||
|
||||
// # Run the playbook
|
||||
cy.findByTestId('run-playbook').click();
|
||||
cy.findByTestId('run-name-input').type('Playbook Run');
|
||||
|
||||
// # Link to the new channel
|
||||
cy.findByTestId('link-existing-channel-radio').click();
|
||||
cy.get('#link-existing-channel-selector input').type(`${channel.name}{enter}`, {force: true});
|
||||
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// # Wait a bit
|
||||
cy.wait(TIMEOUTS.FIVE_SEC);
|
||||
|
||||
// * Verify the playbook run RHS is not open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('opens', () => {
|
||||
it('when navigating directly to an ongoing playbook run channel', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify the playbook run RHS is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText(playbookRunName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('for a new, ongoing playbook run channel opened from the lhs', () => {
|
||||
// # Navigate to the application.
|
||||
cy.visit(`/${testTeam.name}/`);
|
||||
|
||||
// # Ensure the channel is loaded before continuing (allows redux to sync).
|
||||
cy.findByTestId('post_textbox').should('exist');
|
||||
|
||||
// # Select a channel without a playbook run.
|
||||
cy.get('#sidebarItem_off-topic').click({force: true});
|
||||
|
||||
// # Run the playbook after loading the application
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Open the playbook run channel from the LHS.
|
||||
cy.get(`#sidebarItem_${playbookRunChannelName}`).click({force: true});
|
||||
|
||||
// * Verify the playbook run RHS is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText(playbookRunName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('for an existing, ongoing playbook run channel opened from the lhs', () => {
|
||||
// # Run the playbook before loading the application
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate to a channel without a playbook run.
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Ensure the channel is loaded before continuing (allows redux to sync).
|
||||
cy.findByTestId('post_textbox').should('exist');
|
||||
|
||||
// # Open the playbook run channel from the LHS.
|
||||
cy.get(`#sidebarItem_${playbookRunChannelName}`).click({force: true});
|
||||
|
||||
// * Verify the playbook run RHS is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText(playbookRunName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('when starting a playbook run', () => {
|
||||
// # Navigate to the application and a channel without a playbook run
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Start a playbook run with a slash command
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
|
||||
cy.startPlaybookRunWithSlashCommand('Playbook', playbookRunName);
|
||||
|
||||
// * Verify the playbook run RHS is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText(playbookRunName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('when starting a playbook run when rhs is already open', () => {
|
||||
// # Navigate to the application and a channel without a playbook run
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Wait until the channel loads enough to show the post textbox.
|
||||
cy.get('#post-create').should('exist');
|
||||
|
||||
// # Open the saved posts RHS
|
||||
cy.findByRole('button', {name: 'Saved posts'}).
|
||||
click({force: true});
|
||||
|
||||
// * Verify Saved Posts is open
|
||||
cy.get('.sidebar--right__title').should('contain.text', 'Saved Posts');
|
||||
|
||||
// # Start a playbook run with a slash command
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
cy.startPlaybookRunWithSlashCommand('Playbook', playbookRunName);
|
||||
|
||||
// * Verify the playbook run RHS is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText(playbookRunName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('when navigating directly to a finished playbook run channel and clicking on the button', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # End the playbook run
|
||||
cy.apiFinishRun(playbookRun.id);
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// * Verify no active runs screen shows
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByTestId('no-active-runs').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('is toggled', () => {
|
||||
it('by icon in channel header', () => {
|
||||
// # Size the viewport to show plugin icons even when RHS is open
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Navigate to the application and a channel without a playbook run
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// * Verify RHS Home is open.
|
||||
cy.get('#rhsContainer').should('exist').within(() => {
|
||||
cy.findByText('Playbooks').should('exist');
|
||||
});
|
||||
|
||||
// # Click the icon
|
||||
cy.getPlaybooksAppBarIcon().should('be.visible').click();
|
||||
|
||||
// * Verify the playbook run RHS is no longer open.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > run dialog', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
});
|
||||
|
||||
// # Create a second playbook, so as to force dropdown.
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Second Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate to the application
|
||||
cy.visit(`${testTeam.name}`);
|
||||
|
||||
// # Trigger the playbook run creation dialog
|
||||
cy.openPlaybookRunDialogFromSlashCommand();
|
||||
|
||||
// * Verify the playbook run creation dialog has opened
|
||||
cy.get('#interactiveDialogModal').should('exist').within(() => {
|
||||
cy.findByText('Start run').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('cannot create a playbook run without filling required fields', () => {
|
||||
cy.get('#interactiveDialogModal').within(() => {
|
||||
cy.findByText('Start run').should('exist');
|
||||
|
||||
// # Attempt to submit
|
||||
cy.get('#interactiveDialogSubmit').click();
|
||||
});
|
||||
|
||||
// * Verify it didn't submit
|
||||
cy.get('#interactiveDialogModal').should('exist');
|
||||
|
||||
// * Verify required fields
|
||||
cy.findByTestId('autoCompleteSelector').contains('Playbook');
|
||||
cy.findByTestId('autoCompleteSelector').contains('This field is required.');
|
||||
cy.findByTestId('playbookRunName').contains('This field is required.');
|
||||
});
|
||||
|
||||
it('rejects invalid channel names', () => {
|
||||
cy.selectPlaybookFromDropdown('Playbook');
|
||||
|
||||
const invalidPlaybookRunName = ' ';
|
||||
cy.get('#interactiveDialogModal').within(() => {
|
||||
cy.findByTestId('playbookRunNameinput').type(invalidPlaybookRunName, {force: true});
|
||||
});
|
||||
|
||||
cy.get('#interactiveDialogModal').within(() => {
|
||||
cy.findByText('Start run').should('exist');
|
||||
|
||||
// # Attempt to submit
|
||||
cy.get('#interactiveDialogSubmit').click();
|
||||
});
|
||||
|
||||
// * Verify it didn't submit
|
||||
cy.get('#interactiveDialogModal').should('exist');
|
||||
|
||||
// * Verify error message
|
||||
cy.get('#interactiveDialogModal').within(() => {
|
||||
cy.get('div.error-text').contains('unable to create playbook run');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows expected metadata', () => {
|
||||
cy.get('#interactiveDialogModal').within(() => {
|
||||
// * Shows current user as owner.
|
||||
cy.findByText(`${testUser.first_name} ${testUser.last_name}`).should('exist');
|
||||
|
||||
// * Verify playbook dropdown prompt
|
||||
cy.findByText('Playbook').should('exist');
|
||||
|
||||
// * Verify playbook run name prompt
|
||||
cy.findByText('Run name').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('is canceled when cancel is clicked', () => {
|
||||
// # Populate the interactive dialog
|
||||
const playbookRunName = 'New Run' + Date.now();
|
||||
cy.get('#interactiveDialogModal').within(() => {
|
||||
cy.findByTestId('playbookRunNameinput').type('Playbook', {force: true});
|
||||
});
|
||||
|
||||
// # Cancel the interactive dialog
|
||||
cy.get('#interactiveDialogCancel').click();
|
||||
|
||||
// * Verify the modal is no longer displayed
|
||||
cy.get('#interactiveDialogModal').should('not.exist');
|
||||
|
||||
// * Verify the playbook run did not get created
|
||||
cy.apiGetAllPlaybookRuns(testTeam.id).then((response) => {
|
||||
const allPlaybookRuns = response.body;
|
||||
const playbookRun = allPlaybookRuns.items.find((inc) => inc.name === playbookRunName);
|
||||
expect(playbookRun).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
111
e2e-tests/cypress/tests/integration/playbooks/channels/run_spec.js
Обычный файл
111
e2e-tests/cypress/tests/integration/playbooks/channels/run_spec.js
Обычный файл
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > run', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPrivateChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: team.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [user.id],
|
||||
});
|
||||
|
||||
// # Create a private channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'private-channel',
|
||||
'Private Channel',
|
||||
'P',
|
||||
).then(({channel}) => {
|
||||
testPrivateChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show plugin icons even when RHS is open
|
||||
cy.viewport('macbook-13');
|
||||
});
|
||||
|
||||
describe('via slash command', () => {
|
||||
it('while viewing a public channel', () => {
|
||||
// # Visit a public channel
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// * Verify that playbook run can be started with slash command
|
||||
const playbookRunName = 'Public ' + Date.now();
|
||||
cy.startPlaybookRunWithSlashCommand('Playbook', playbookRunName);
|
||||
cy.verifyPlaybookRunActive(testTeam.id, playbookRunName);
|
||||
});
|
||||
|
||||
it('while viewing a private channel', () => {
|
||||
// # Visit a private channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testPrivateChannel.name}`);
|
||||
|
||||
// * Verify that playbook run can be started with slash command
|
||||
const playbookRunName = 'Private ' + Date.now();
|
||||
cy.startPlaybookRunWithSlashCommand('Playbook', playbookRunName);
|
||||
cy.verifyPlaybookRunActive(testTeam.id, playbookRunName);
|
||||
});
|
||||
});
|
||||
|
||||
describe('via post menu', () => {
|
||||
it('while viewing a public channel', () => {
|
||||
// # Visit a public channel
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// * Verify that playbook run can be started from post menu
|
||||
const playbookRunName = 'Public - ' + Date.now();
|
||||
cy.startPlaybookRunFromPostMenu('Playbook', playbookRunName);
|
||||
cy.verifyPlaybookRunActive(testTeam.id, playbookRunName);
|
||||
});
|
||||
|
||||
it('while viewing a private channel', () => {
|
||||
// # Visit a private channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testPrivateChannel.name}`);
|
||||
|
||||
// * Verify that playbook run can be started from post menu
|
||||
const playbookRunName = 'Private - ' + Date.now();
|
||||
cy.startPlaybookRunFromPostMenu('Playbook', playbookRunName);
|
||||
cy.verifyPlaybookRunActive(testTeam.id, playbookRunName);
|
||||
});
|
||||
});
|
||||
|
||||
it('always as channel admin', () => {
|
||||
// # Visit a public channel
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// # Start a playbook run with a slash command
|
||||
const playbookRunName = 'Public ' + Date.now();
|
||||
cy.startPlaybookRunWithSlashCommand('Playbook', playbookRunName);
|
||||
cy.verifyPlaybookRunActive(testTeam.id, playbookRunName);
|
||||
|
||||
// # Open the channel header
|
||||
cy.get('#channelHeaderTitle').click();
|
||||
|
||||
// * Verify the ability to edit the channel header exists
|
||||
cy.get('#channelEditHeader').should('exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,558 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import {switchToChannel} from '../../../channels/mark_as_unread/helpers';
|
||||
|
||||
describe('channels > slash command > owner', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
let testPlaybook;
|
||||
let playbookRunName;
|
||||
let playbookRunChannelName;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [testUser.id],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
const now = Date.now();
|
||||
playbookRunName = `Playbook Run (${now})`;
|
||||
playbookRunChannelName = `playbook-run-${now}`;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
});
|
||||
|
||||
describe('single run channel', () => {
|
||||
it('check', () => {
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook check ');
|
||||
|
||||
// * Verify suggestions number: a single run with 4 tasks + 1 title
|
||||
cy.get('.slash-command').should('have.length', 5);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook check 1 1');
|
||||
|
||||
// * Verify the task is checked
|
||||
cy.get('[data-rbd-droppable-id="1"]').find('.checkbox').eq(1).should('be.checked');
|
||||
});
|
||||
|
||||
it('check add', () => {
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook checkadd 1 new-task');
|
||||
|
||||
// * Verify the task was added
|
||||
cy.get('[data-rbd-droppable-id="1"]').contains('new-task');
|
||||
});
|
||||
|
||||
it('check remove', () => {
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook checkremove 1 1');
|
||||
|
||||
// * Verify the task was added
|
||||
cy.get('[data-rbd-droppable-id="1"]').contains('Step 2').should('not.exist');
|
||||
});
|
||||
|
||||
it('owner', () => {
|
||||
// # Run a slash command
|
||||
cy.uiPostMessageQuickly('/playbook owner');
|
||||
|
||||
// * Verify the message.
|
||||
cy.verifyEphemeralMessage(`@${testUser.username} is the current owner for this playbook run.`);
|
||||
|
||||
// # Run a slash command
|
||||
cy.uiPostMessageQuickly(`/playbook owner @${testUser2.username}`);
|
||||
|
||||
// * Verify that the owner was set.
|
||||
cy.uiPostMessageQuickly('/playbook owner');
|
||||
cy.verifyEphemeralMessage(`@${testUser2.username} is the current owner for this playbook run.`);
|
||||
});
|
||||
|
||||
it('timeline', () => {
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook timeline');
|
||||
|
||||
// * Verify the message.
|
||||
cy.verifyEphemeralMessage(`Timeline for ${playbookRunName}`);
|
||||
});
|
||||
|
||||
it('finish', () => {
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook finish');
|
||||
|
||||
// * Verify confirm modal is visible.
|
||||
cy.get('#interactiveDialogModalLabel').should('exist');
|
||||
|
||||
// # Confirm finish
|
||||
cy.get('#interactiveDialogSubmit').click();
|
||||
|
||||
// * Verify that the run is finished.
|
||||
cy.get('#rhsContainer').findByTestId('badge').contains('Finished');
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple runs in the channel', () => {
|
||||
let playbookRuns;
|
||||
let testPrivatePlaybook;
|
||||
let testPublicPlaybook;
|
||||
let testPublicChannel;
|
||||
let channelName;
|
||||
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create private playbook, channel mode set to link existing channel
|
||||
cy.apiCreatePlaybook({
|
||||
makePublic: false,
|
||||
createPublicPlaybookRun: false,
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook private',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [testUser.id],
|
||||
channelMode: 'link_existing_channel',
|
||||
}).then((playbook) => {
|
||||
testPrivatePlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create public playbook, channel mode set to link existing channel
|
||||
cy.apiCreatePlaybook({
|
||||
makePublic: true,
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook public',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [testUser.id],
|
||||
channelMode: 'link_existing_channel',
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
playbookRuns = [];
|
||||
const now = Date.now();
|
||||
channelName = 'public-channel-' + now;
|
||||
|
||||
// # Create channel for runs
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
channelName,
|
||||
'public channel',
|
||||
'O',
|
||||
).then(({channel: publicChannel}) => {
|
||||
testPublicChannel = publicChannel;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPrivatePlaybook.id,
|
||||
playbookRunName: 'run write access ' + now,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testPublicChannel.id,
|
||||
}).then((playbookRun) => {
|
||||
cy.apiAddUsersToRun(playbookRun.id, [testUser2.id]);// add test user to participants list
|
||||
playbookRuns.push(playbookRun);
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'run view access' + now,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testPublicChannel.id,
|
||||
}).then((playbookRun2) => {
|
||||
playbookRuns.push(playbookRun2);
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPrivatePlaybook.id,
|
||||
playbookRunName: 'run no access' + now,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testPublicChannel.id,
|
||||
}).then((playbookRun3) => {
|
||||
playbookRuns.push(playbookRun3);
|
||||
|
||||
// # Add testUser2 to the channel
|
||||
cy.apiAddUserToChannel(testPublicChannel.id, testUser2.id);
|
||||
|
||||
// # Login as testUser2
|
||||
cy.apiLogin(testUser2);
|
||||
|
||||
// # Navigate directly to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testPublicChannel.name}`);
|
||||
switchToChannel(testPublicChannel);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('check', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook check 1 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Command expects three arguments: the run number, the checklist number and the item number.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook check 2 1 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook check 0 1 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Become a participant to interact with this run');
|
||||
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook check ');
|
||||
|
||||
// * Verify suggestions number: 2 runs * 4 tasks + 1 title
|
||||
cy.get('.slash-command').should('have.length', 9);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook check 1 1 1');
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// * Verify number of runs
|
||||
cy.get('[data-testid="run-list-card"]').should('have.length', 2);
|
||||
|
||||
// # Open run details view
|
||||
cy.findByText(playbookRuns[0].name).click({force: true});
|
||||
});
|
||||
|
||||
// * Verify the task is checked
|
||||
cy.get('[data-rbd-droppable-id="1"]').find('.checkbox').eq(1).should('be.checked');
|
||||
});
|
||||
|
||||
it('check add', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook checkadd 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Command expects two arguments: the run number and the checklist number.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook checkadd 2 1 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook checkadd 0 1 new-task');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Become a participant to interact with this run');
|
||||
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook checkadd ');
|
||||
|
||||
// * Verify suggestions number: 2 runs * 2 checklists + 1 title
|
||||
cy.get('.slash-command').should('have.length', 5);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook checkadd 1 1 new-task');
|
||||
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// * Verify number of runs
|
||||
cy.get('[data-testid="run-list-card"]').should('have.length', 2);
|
||||
|
||||
// # Open run details view
|
||||
cy.findByText(playbookRuns[0].name).click({force: true});
|
||||
});
|
||||
|
||||
// * Verify the task was added
|
||||
cy.get('[data-rbd-droppable-id="1"]').contains('new-task');
|
||||
});
|
||||
|
||||
it('check remove', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook checkremove 1 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Command expects three arguments: the run number, the checklist number and the item number.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook checkremove 2 0 1');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook checkremove 0 1 0');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Become a participant to interact with this run');
|
||||
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook checkremove ');
|
||||
|
||||
// * Verify suggestions number: 2 runs * 4 tasks + 1 title
|
||||
cy.get('.slash-command').should('have.length', 9);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook checkremove 1 1 1');
|
||||
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// * Verify number of runs
|
||||
cy.get('[data-testid="run-list-card"]').should('have.length', 2);
|
||||
|
||||
// # Open run details view
|
||||
cy.findByText(playbookRuns[0].name).click({force: true});
|
||||
});
|
||||
|
||||
// * Verify the task was added
|
||||
cy.get('[data-rbd-droppable-id="1"]').contains('Step 2').should('not.exist');
|
||||
});
|
||||
|
||||
it('owner', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook owner');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('/playbook owner expects at most one argument.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook owner 2');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook owner 0');
|
||||
|
||||
// * Verify the message.
|
||||
cy.verifyEphemeralMessage(`@${testUser.username} is the current owner for this playbook run.`);
|
||||
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook owner ');
|
||||
|
||||
// * Verify suggestions number: 2 runs + 1 title
|
||||
cy.get('.slash-command').should('have.length', 3);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly(`/playbook owner 0 @${testUser2.username}`);
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Become a participant to interact with this run');
|
||||
|
||||
// # Run a slash command on a run with write access
|
||||
cy.uiPostMessageQuickly(`/playbook owner 1 @${testUser2.username}`);
|
||||
|
||||
// * Verify that the owner was set.
|
||||
cy.uiPostMessageQuickly('/playbook owner 1');
|
||||
cy.verifyEphemeralMessage(`@${testUser2.username} is the current owner for this playbook run.`);
|
||||
});
|
||||
|
||||
it('finish', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook finish');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Command expects one argument: the run number.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook finish 2');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook finish 0');
|
||||
|
||||
// * Verify the message.
|
||||
cy.verifyEphemeralMessage(`userID ${testUser2.id} is not an admin or channel member`);
|
||||
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook finish ');
|
||||
|
||||
// * Verify suggestions number: 2 runs + 1 title
|
||||
cy.get('.slash-command').should('have.length', 3);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
cy.get('#rhsContainer').within(() => {
|
||||
// * Verify number of runs
|
||||
cy.get('[data-testid="run-list-card"]').should('have.length', 2);
|
||||
|
||||
// # Open run details view
|
||||
cy.findByText(playbookRuns[0].name).click({force: true});
|
||||
});
|
||||
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook finish 1');
|
||||
|
||||
// * Verify confirm modal is visible.
|
||||
cy.get('#interactiveDialogModalLabel').should('exist');
|
||||
|
||||
// # Confirm finish
|
||||
cy.get('#interactiveDialogSubmit').click();
|
||||
|
||||
// * Verify that the run is finished.
|
||||
cy.get('#rhsContainer').findByTestId('badge').contains('Finished');
|
||||
});
|
||||
|
||||
it('timeline', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook timeline');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Command expects one argument: the run number.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook timeline 2');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Run a slash command on a run with view access
|
||||
cy.uiPostMessageQuickly('/playbook timeline 0');
|
||||
|
||||
// * Verify the message.
|
||||
cy.verifyEphemeralMessage(`Timeline for ${playbookRuns[1].name}`);
|
||||
});
|
||||
|
||||
it('update', () => {
|
||||
// # Run a slash command with not enough parameters
|
||||
cy.uiPostMessageQuickly('/playbook update');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Command expects one argument: the run number.');
|
||||
|
||||
// # Run a slash command wrong run number
|
||||
cy.uiPostMessageQuickly('/playbook update 2');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Invalid run number');
|
||||
|
||||
// # Type a command
|
||||
cy.findByTestId('post_textbox').clear().type('/playbook update ');
|
||||
|
||||
// * Verify suggestions number: 2 runs + 1 title
|
||||
cy.get('.slash-command').should('have.length', 3);
|
||||
|
||||
// # Clear input
|
||||
cy.findByTestId('post_textbox').clear();
|
||||
|
||||
// # Run a slash command with correct parameters
|
||||
cy.uiPostMessageQuickly('/playbook update 1');
|
||||
|
||||
// # Get dialog modal.
|
||||
cy.getStatusUpdateDialog().within(() => {
|
||||
// # Enter valid data
|
||||
cy.findByTestId('update_run_status_textbox').type('valid update');
|
||||
|
||||
// # Submit the dialog.
|
||||
cy.get('button.confirm').click();
|
||||
});
|
||||
|
||||
// * Verify that the Post update dialog has gone.
|
||||
cy.getStatusUpdateDialog().should('not.exist');
|
||||
|
||||
// * Verify that the status update was posted.
|
||||
cy.getLastPost().within(() => {
|
||||
cy.findByText('posted an update for').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > slash command > info', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
let testPlaybook;
|
||||
let testPlaybookRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [testUser.id],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'Playbook Run',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Reset the owner back to testUser as necessary.
|
||||
cy.apiChangePlaybookRunOwner(testPlaybookRun.id, testUser.id);
|
||||
});
|
||||
|
||||
describe('/playbook info', () => {
|
||||
it('should show an error when not in a playbook run channel', () => {
|
||||
// # Navigate to a non-playbook run channel.
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Run a slash command to show the playbook run's info.
|
||||
cy.uiPostMessageQuickly('/playbook info');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('This command only works when run from a playbook run channel.');
|
||||
});
|
||||
|
||||
it('should open the RHS when it is not open', () => {
|
||||
// # Navigate directly to the application and the playbook run channel.
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// # Close the RHS, which is opened by default when navigating to a playbook run channel.
|
||||
cy.get('#searchResultsCloseButton').click();
|
||||
|
||||
// * Verify that the RHS is indeed closed.
|
||||
cy.get('#rhsContainer').should('not.exist');
|
||||
|
||||
// # Run a slash command to show the playbook run's info.
|
||||
cy.uiPostMessageQuickly('/playbook info');
|
||||
|
||||
// * Verify that the RHS is now open.
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
});
|
||||
|
||||
it('should show an ephemeral post when the RHS is already open', () => {
|
||||
// # Navigate directly to the application and the playbook run channel.
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// * Verify that the RHS is open.
|
||||
cy.get('#rhsContainer').should('be.visible');
|
||||
|
||||
// # Run a slash command to show the playbook run's info.
|
||||
cy.uiPostMessageQuickly('/playbook info');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Your playbook run details are already open in the right hand side of the channel.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,222 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > slash command > owner', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
let testPlaybook;
|
||||
let testPlaybookRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [testUser.id],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'Playbook Run',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Reset the owner back to testUser as necessary.
|
||||
cy.apiChangePlaybookRunOwner(testPlaybookRun.id, testUser.id);
|
||||
});
|
||||
|
||||
describe('/playbook owner', () => {
|
||||
it('should show an error when not in a playbook run channel', () => {
|
||||
// # Navigate to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Run a slash command to show the current owner
|
||||
cy.uiPostMessageQuickly('/playbook owner');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('This command only works when run from a playbook run channel.');
|
||||
});
|
||||
|
||||
it('should show the current owner', () => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// # Run a slash command to show the current owner
|
||||
cy.uiPostMessageQuickly('/playbook owner');
|
||||
|
||||
// * Verify the expected owner.
|
||||
cy.verifyEphemeralMessage(`@${testUser.username} is the current owner for this playbook run.`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/playbook owner @username', () => {
|
||||
it('should show an error when not in a playbook run channel', () => {
|
||||
// # Navigate to a non-playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner ${testUser2.username}`);
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('This command only works when run from a playbook run channel.');
|
||||
});
|
||||
|
||||
describe('should show an error when the user is not found', () => {
|
||||
beforeEach(() => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
});
|
||||
|
||||
it('when the username has no @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly('/playbook owner unknown');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Unable to find user @unknown');
|
||||
});
|
||||
|
||||
it('when the username has an @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly('/playbook owner @unknown');
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('Unable to find user @unknown');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not show an error when the user is not in the channel', () => {
|
||||
beforeEach(() => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// # Ensure the user3 is not part of the channel.
|
||||
cy.uiPostMessageQuickly(`/kick ${testUser2.username}`);
|
||||
});
|
||||
|
||||
it('when the username has no @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner ${testUser2.username}`);
|
||||
|
||||
// * Verify the owner has changed.
|
||||
cy.findByTestId('owner-profile-selector').contains(testUser2.username);
|
||||
});
|
||||
|
||||
it('when the username has an @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner @${testUser2.username}`);
|
||||
|
||||
// * Verify the owner has changed.
|
||||
cy.findByTestId('owner-profile-selector').contains(testUser2.username);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should show a message when the user is already the owner', () => {
|
||||
beforeEach(() => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
});
|
||||
|
||||
it('when the username has no @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner ${testUser.username}`);
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage(`User @${testUser.username} is already owner of this playbook run.`);
|
||||
});
|
||||
|
||||
it('when the username has an @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner @${testUser.username}`);
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage(`User @${testUser.username} is already owner of this playbook run.`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should change the current owner', () => {
|
||||
beforeEach(() => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// # Ensure the testUser2 is part of the channel.
|
||||
cy.uiPostMessageQuickly(`/invite ${testUser2.username}`);
|
||||
});
|
||||
|
||||
it('when the username has no @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner ${testUser2.username}`);
|
||||
|
||||
// # Verify the owner has changed.
|
||||
cy.findByTestId('owner-profile-selector').contains(testUser2.username);
|
||||
});
|
||||
|
||||
it('when the username has an @-prefix', () => {
|
||||
// # Run a slash command to change the current owner
|
||||
cy.uiPostMessageQuickly(`/playbook owner @${testUser2.username}`);
|
||||
|
||||
// # Verify the owner has changed.
|
||||
cy.findByTestId('owner-profile-selector').contains(testUser2.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show an error when specifying more than one username', () => {
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/playbook-run`);
|
||||
|
||||
// # Run a slash command with too many parameters
|
||||
cy.uiPostMessageQuickly(`/playbook owner ${testUser.username} ${testUser2.username}`);
|
||||
|
||||
// * Verify the expected error message.
|
||||
cy.verifyEphemeralMessage('/playbook owner expects at most one argument.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > slash command > test', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
let testPlaybook;
|
||||
let testPlaybookRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [testUser.id],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'Playbook Run',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Reset the owner back to testUser as necessary.
|
||||
cy.apiChangePlaybookRunOwner(testPlaybookRun.id, testUser.id);
|
||||
});
|
||||
|
||||
describe('as a regular user', () => {
|
||||
before(() => {
|
||||
// # Login as sysadmin.
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Set EnableTesting to true.
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableTesting: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as user-1
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate to a channel.
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('fails to run subcommand bulk-data', () => {
|
||||
// # Execute the bulk-data command.
|
||||
cy.uiPostMessageQuickly('/playbook test bulk-data');
|
||||
|
||||
// * Verify the ephemeral message warns that the user is not admin.
|
||||
cy.verifyEphemeralMessage('Running the test command is restricted to system administrators.');
|
||||
});
|
||||
|
||||
it('fails to run subcommand create-playbook-run', () => {
|
||||
// # Execute the create-playbook-run command.
|
||||
cy.uiPostMessageQuickly('/playbook test create-playbook-run');
|
||||
|
||||
// * Verify the ephemeral message warns that the user is not admin.
|
||||
cy.verifyEphemeralMessage('Running the test command is restricted to system administrators.');
|
||||
});
|
||||
|
||||
it('fails to run subcommand self', () => {
|
||||
// # Execute the self command.
|
||||
cy.uiPostMessageQuickly('/playbook test self');
|
||||
|
||||
// * Verify the ephemeral message warns that the user is not admin.
|
||||
cy.verifyEphemeralMessage('Running the test command is restricted to system administrators.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as an admin', () => {
|
||||
describe('with EnableTesting set to false', () => {
|
||||
before(() => {
|
||||
// # Login as sysadmin.
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Set EnableTesting to false.
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableTesting: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as sysadmin.
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Navigate to a channel.
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('fails to run subcommand bulk-data', () => {
|
||||
// # Execute the bulk-data command.
|
||||
cy.uiPostMessageQuickly('/playbook test bulk-data');
|
||||
|
||||
// * Verify the ephemeral message warns that the user is not admin.
|
||||
cy.verifyEphemeralMessage('Setting EnableTesting must be set to true to run the test command.');
|
||||
});
|
||||
|
||||
it('fails to run subcommand create-playbook-run', () => {
|
||||
// # Execute the create-playbook-run command.
|
||||
cy.uiPostMessageQuickly('/playbook test create-playbook-run');
|
||||
|
||||
// * Verify the ephemeral message warns that the user is not admin.
|
||||
cy.verifyEphemeralMessage('Setting EnableTesting must be set to true to run the test command.');
|
||||
});
|
||||
|
||||
it('fails to run subcommand self', () => {
|
||||
// # Execute the self command.
|
||||
cy.uiPostMessageQuickly('/playbook test self');
|
||||
|
||||
// * Verify the ephemeral message warns that the user is not admin.
|
||||
cy.verifyEphemeralMessage('Setting EnableTesting must be set to true to run the test command.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with EnableTesting set to true', () => {
|
||||
before(() => {
|
||||
// # Login as sysadmin.
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Set EnableTesting to true.
|
||||
cy.apiUpdateConfig({
|
||||
ServiceSettings: {
|
||||
EnableTesting: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as sysadmin.
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Navigate to a channel.
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
describe('with subcommand self', () => {
|
||||
it('asks for confirmation', () => {
|
||||
// # Execute the self command.
|
||||
cy.uiPostMessageQuickly('/playbook test self');
|
||||
|
||||
// * Verify the ephemeral message asks for the confirmation keywords.
|
||||
cy.verifyEphemeralMessage('Are you sure you want to self-test (which will nuke the database and delete all data -- instances, configuration)? All data will be lost. To self-test, type /playbook test self CONFIRM TEST SELF');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with subcommand create', () => {
|
||||
it('fails to run with no arguments', () => {
|
||||
// # Execute the create-playbook-run command with no arguments.
|
||||
cy.uiPostMessageQuickly('/playbook test create-playbook-run');
|
||||
|
||||
// * Verify the ephemeral message warns about the parameters.
|
||||
cy.verifyEphemeralMessage('The command expects three parameters: <playbook_id> <timestamp> <name>');
|
||||
});
|
||||
|
||||
it('fails to run with one argument', () => {
|
||||
// # Execute the create-playbook-run command with one argument.
|
||||
cy.uiPostMessageQuickly(`/playbook test create-playbook-run ${testPlaybook.id}`);
|
||||
|
||||
// * Verify the ephemeral message warns about the parameters.
|
||||
cy.verifyEphemeralMessage('The command expects three parameters: <playbook_id> <timestamp> <name>');
|
||||
});
|
||||
|
||||
it('fails to run with two arguments', () => {
|
||||
// # Execute the create-playbook-run command with two arguments.
|
||||
cy.uiPostMessageQuickly(`/playbook test create-playbook-run ${testPlaybook.id} 2020-01-01`);
|
||||
|
||||
// * Verify the ephemeral message warns about the parameters.
|
||||
cy.verifyEphemeralMessage('The command expects three parameters: <playbook_id> <timestamp> <name>');
|
||||
});
|
||||
|
||||
it('fails to run with a malformed playbook ID', () => {
|
||||
// # Execute the create-playbook-run command with all arguments, but a malformed plabook ID.
|
||||
cy.uiPostMessageQuickly('/playbook test create-playbook-run unknownID 2020-01-01 The playbook run name');
|
||||
|
||||
// * Verify the ephemeral message warns about the ID.
|
||||
cy.verifyEphemeralMessage('The first parameter, <playbook_id>, must be a valid ID.');
|
||||
});
|
||||
|
||||
it('fails to run with a valid, but unknown playbook ID', () => {
|
||||
// # Execute the create-playbook-run command with all arguments, but an unknown plabook ID.
|
||||
cy.uiPostMessageQuickly('/playbook test create-playbook-run abcdefghijklmnopqrstuvwxyz 2020-01-01 The playbook run name');
|
||||
|
||||
// * Verify the ephemeral message warns about the parameter.
|
||||
cy.verifyEphemeralMessage('The playbook with ID \'abcdefghijklmnopqrstuvwxyz\' does not exist.');
|
||||
});
|
||||
|
||||
it('fails to run with a malformed date', () => {
|
||||
// # Execute the create-playbook-run command with all arguments, but a malformed creation timestamp.
|
||||
cy.uiPostMessageQuickly(`/playbook test create-playbook-run ${testPlaybook.id} 2020-1-1 The playbook run name`);
|
||||
|
||||
// * Verify the ephemeral message warns about the parameter.
|
||||
cy.verifyEphemeralMessage('Timestamp \'2020-1-1\' could not be parsed as a date. If you want the playbook run to start on January 2, 2006, the timestamp should be \'2006-01-02\'.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,322 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('channels > slash command > todo', () => {
|
||||
let team1;
|
||||
let team2;
|
||||
let testUser;
|
||||
let testOtherUser;
|
||||
let run1;
|
||||
let run2;
|
||||
let run3;
|
||||
let run4;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
team1 = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: otherUser}) => {
|
||||
testOtherUser = otherUser;
|
||||
|
||||
// # Add this new user to the team
|
||||
cy.apiAddUserToTeam(team1.id, testOtherUser.id);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: team1.id,
|
||||
title: 'Playbook One',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
checklists: [
|
||||
{
|
||||
title: 'Playbook One - Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Playbook One - Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then(({id: playbookId}) => {
|
||||
// # Create two runs in team 1.
|
||||
const now = Date.now();
|
||||
cy.apiRunPlaybook({
|
||||
teamId: team1.id,
|
||||
playbookId,
|
||||
playbookRunName: 'Playbook Run (' + now + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
run1 = run;
|
||||
});
|
||||
|
||||
const now2 = Date.now() + 100;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: team1.id,
|
||||
playbookId,
|
||||
playbookRunName: 'Playbook Run (' + now2 + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
run2 = run;
|
||||
});
|
||||
});
|
||||
|
||||
// # Create a second team to test cross-team notifications
|
||||
cy.apiCreateTeam('team2', 'Team 2').then(({team: secondTeam}) => {
|
||||
team2 = secondTeam;
|
||||
|
||||
cy.apiAdminLogin();
|
||||
cy.apiAddUserToTeam(team2.id, testUser.id);
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: team2.id,
|
||||
title: 'Playbook Two',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
checklists: [
|
||||
{
|
||||
title: 'Playbook Two - Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Playbook Two - Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then(({id: playbookId}) => {
|
||||
// # Create one run in team 2.
|
||||
const now = Date.now() + 200;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: team2.id,
|
||||
playbookId,
|
||||
playbookRunName: 'Playbook Run (' + now + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
run3 = run;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// # Create another playbook with runs owned by another user
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: team1.id,
|
||||
title: 'Playbook Other',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
checklists: [
|
||||
{
|
||||
title: 'Playbook Other - Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Playbook Other - Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then(({id: playbookId}) => {
|
||||
// # Login as testOtherUser
|
||||
cy.apiLogin(testOtherUser);
|
||||
|
||||
// # Create a run in team 1, with testOtherUser as owner and inviting testUser
|
||||
const now = Date.now();
|
||||
cy.apiRunPlaybook({
|
||||
teamId: team1.id,
|
||||
playbookId,
|
||||
playbookRunName: 'Other Playbook Run (' + now + ')',
|
||||
ownerUserId: testOtherUser.id,
|
||||
}).then((run) => {
|
||||
run4 = run;
|
||||
|
||||
// # Invite testUser to the channel
|
||||
// cy.apiAddUserToChannel(run.channel_id, testUser.id);
|
||||
cy.apiAddUsersToRun(run.id, [testUser.id]);
|
||||
|
||||
// # Force this run to be overdue
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: run4.id,
|
||||
message: 'no message 4',
|
||||
reminder: 1,
|
||||
});
|
||||
});
|
||||
|
||||
// # Create a run in team 1, with testOtherUser as owner but not inviting testUser
|
||||
const now2 = Date.now() + 100;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: team1.id,
|
||||
playbookId,
|
||||
playbookRunName: 'Other Playbook Run (' + now2 + ')',
|
||||
ownerUserId: testOtherUser.id,
|
||||
}).then((run) => {
|
||||
// # Force this run to be overdue
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: run.id,
|
||||
message: 'no message 5',
|
||||
reminder: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('/playbook todo should show', () => {
|
||||
it('three runs', () => {
|
||||
// # Navigate to a non-playbook run channel.
|
||||
cy.visit(`/${team2.name}/channels/town-square`);
|
||||
|
||||
// # Run a slash command to show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
cy.getLastPost().within((post) => {
|
||||
// * Should show titles
|
||||
cy.wrap(post).contains('You have 0 runs overdue.');
|
||||
cy.wrap(post).contains('You have 0 assigned tasks.');
|
||||
cy.wrap(post).contains('You have 4 runs currently in progress:');
|
||||
|
||||
// * Should show four active runs
|
||||
cy.get('li').then((liItems) => {
|
||||
expect(liItems[0]).to.contain.text(run4.name);
|
||||
expect(liItems[1]).to.contain.text(run1.name);
|
||||
expect(liItems[2]).to.contain.text(run2.name);
|
||||
expect(liItems[3]).to.contain.text(run3.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('four assigned tasks', () => {
|
||||
// # assign self four tasks
|
||||
cy.apiChangeChecklistItemAssignee(run1.id, 0, 0, testUser.id);
|
||||
cy.apiChangeChecklistItemAssignee(run1.id, 1, 1, testUser.id);
|
||||
cy.apiChangeChecklistItemAssignee(run2.id, 0, 1, testUser.id);
|
||||
cy.apiChangeChecklistItemAssignee(run3.id, 1, 0, testUser.id);
|
||||
|
||||
// # Navigate to a non-playbook run channel.
|
||||
cy.visit(`/${team2.name}/channels/town-square`);
|
||||
|
||||
// # Run a slash command to show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
cy.getLastPost().within((post) => {
|
||||
// * Should show titles
|
||||
cy.wrap(post).contains('You have 0 runs overdue.');
|
||||
cy.wrap(post).contains('You have 4 total assigned tasks:');
|
||||
|
||||
// * Should show 3 runs w/ tasks
|
||||
cy.get('.post__body a').then((links) => {
|
||||
expect(links[0]).to.contain.text(run1.name);
|
||||
expect(links[1]).to.contain.text(run2.name);
|
||||
expect(links[2]).to.contain.text(run3.name);
|
||||
});
|
||||
|
||||
cy.get('.post__body li').then((items) => {
|
||||
// * first run
|
||||
expect(items[0]).to.contain.text('Playbook One - Stage 1: Step 1');
|
||||
expect(items[1]).to.contain.text('Playbook One - Stage 2: Step 2');
|
||||
|
||||
// * second run
|
||||
expect(items[2]).to.contain.text('Playbook One - Stage 1: Step 2');
|
||||
|
||||
// * third run
|
||||
expect(items[3]).to.contain.text('Playbook Two - Stage 2: Step 1');
|
||||
});
|
||||
});
|
||||
|
||||
// # check two of the items via API
|
||||
cy.apiSetChecklistItemState(run1.id, 0, 0, 'closed');
|
||||
cy.apiSetChecklistItemState(run3.id, 1, 0, 'closed');
|
||||
|
||||
// # Show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
// * Should show 2 tasks
|
||||
cy.getLastPost().within((post) => {
|
||||
// * Should show titles
|
||||
cy.wrap(post).contains('You have 0 runs overdue.');
|
||||
cy.wrap(post).contains('You have 2 total assigned tasks:');
|
||||
|
||||
// * Should show 2 runs w/ tasks
|
||||
cy.get('.post__body a').then((links) => {
|
||||
expect(links[0]).to.contain.text(run1.name);
|
||||
expect(links[1]).to.contain.text(run2.name);
|
||||
});
|
||||
|
||||
cy.get('.post__body li').then((items) => {
|
||||
// * first run
|
||||
expect(items[0]).to.contain.text('Playbook One - Stage 2: Step 2');
|
||||
|
||||
// * second run
|
||||
expect(items[1]).to.contain.text('Playbook One - Stage 1: Step 2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('two overdue status updates', () => {
|
||||
// # set two updates with short timers
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: run1.id,
|
||||
message: 'no message 1',
|
||||
reminder: 1,
|
||||
});
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: run3.id,
|
||||
message: 'no message 3',
|
||||
reminder: 1,
|
||||
});
|
||||
|
||||
cy.wait(1100);
|
||||
|
||||
// # Switch to playbooks DM channel
|
||||
cy.visit(`/${team2.name}/messages/@playbooks`);
|
||||
|
||||
// # Run a slash command to show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
// # Should show two runs overdue -- ignoring the rest
|
||||
cy.getLastPost().within(() => {
|
||||
cy.get('.post__body li').then((liItems) => {
|
||||
expect(liItems[0]).to.contain.text(run1.name);
|
||||
expect(liItems[1]).to.contain.text(run3.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,192 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
|
||||
describe('channels > update request post', () => {
|
||||
let testTeam;
|
||||
let testParticipant;
|
||||
let testChannelMemberOnly;
|
||||
let testPlaybookRun;
|
||||
let testPlaybookRun2;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testParticipant = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: channelMemberOnly}) => {
|
||||
testChannelMemberOnly = channelMemberOnly;
|
||||
|
||||
// # Add testChannelMemberOnly to the testTeam
|
||||
cy.apiAddUserToTeam(testTeam.id, testChannelMemberOnly.id);
|
||||
|
||||
// # Login as testChannelMemberOnly
|
||||
cy.apiLogin(testChannelMemberOnly);
|
||||
|
||||
// # Enable threads view
|
||||
cy.apiSaveCRTPreference(testChannelMemberOnly.id, 'on');
|
||||
});
|
||||
|
||||
// # Login as testParticipant
|
||||
cy.apiLogin(testParticipant);
|
||||
|
||||
// # Enable threads view
|
||||
cy.apiSaveCRTPreference(testParticipant.id, 'on');
|
||||
|
||||
// # Create a public playbook with 2 runs in the same channel
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((playbook) => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: 'Test Run',
|
||||
ownerUserId: testParticipant.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
|
||||
// # Add testChannelMemberOnly to the channel, but not the run.
|
||||
cy.apiAddUserToChannel(playbookRun.channel_id, testChannelMemberOnly.id);
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: 'Test Run 2',
|
||||
ownerUserId: testParticipant.id,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
}).then((playbookRun2) => {
|
||||
testPlaybookRun2 = playbookRun2;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('displays interactive post', () => {
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testParticipant);
|
||||
|
||||
// # Post a status update, with a reminder in 1 second.
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testPlaybookRun2.id,
|
||||
message: 'status update 2',
|
||||
reminder: 1,
|
||||
});
|
||||
|
||||
// # Post a status update, with a reminder in 2 second.
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testPlaybookRun.id,
|
||||
message: 'status update',
|
||||
reminder: 2,
|
||||
});
|
||||
|
||||
// Ensure the status update reminder gets posted
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
});
|
||||
|
||||
describe('as a participant', () => {
|
||||
beforeEach(() => {
|
||||
// # Navigate to the application
|
||||
cy.visit(`${testTeam.name}/channels/test-run`);
|
||||
});
|
||||
|
||||
it('in the run channel', () => {
|
||||
cy.getLastPost().then((element) => {
|
||||
// # Verify the expected message text
|
||||
cy.get(element).contains(`@${testParticipant.username}, please provide a status update for ${testPlaybookRun.name}.`);
|
||||
|
||||
// # Verify interactive message button to post an update
|
||||
cy.get(element).find('button').contains('Post update');
|
||||
});
|
||||
});
|
||||
|
||||
it('reset reminder', () => {
|
||||
cy.getLastPost().within(() => {
|
||||
// * Snooze reminder
|
||||
cy.getStyledComponent('StyledSelect').click().type('{downArrow}{downArrow}{enter}');
|
||||
|
||||
// # Verify interactive message button to post an update has dissapeared
|
||||
cy.findByText('(message deleted)').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('in threads view', () => {
|
||||
// # Find the update request post and post a reply to make it show up in threads view
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
// Open RHS
|
||||
cy.clickPostCommentIcon(lastPostId);
|
||||
|
||||
// Post a reply message
|
||||
cy.postMessageReplyInRHS('test reply');
|
||||
|
||||
// # Navigate to the threads view
|
||||
cy.get('#sidebarItem_threads').click();
|
||||
|
||||
// # Verify the expected text in the list view
|
||||
cy.get('.ThreadItem').first().contains(`@${testParticipant.username}, please provide a status update for ${testPlaybookRun.name}.`);
|
||||
|
||||
// # Click to open details
|
||||
cy.get('.ThreadItem').first().click();
|
||||
|
||||
// # Verify post still rendered
|
||||
cy.get(`#rhsPost_${lastPostId}`).contains(`@${testParticipant.username}, please provide a status update for ${testPlaybookRun.name}.`);
|
||||
|
||||
// # Verify interactive message button to post an update
|
||||
cy.get(`#rhsPost_${lastPostId}`).find('button').contains('Post update');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as a channel member only', () => {
|
||||
beforeEach(() => {
|
||||
// # Login as testChannelMemberOnly
|
||||
cy.apiLogin(testChannelMemberOnly);
|
||||
|
||||
// # Navigate to the application
|
||||
cy.visit(`${testTeam.name}/channels/test-run`);
|
||||
});
|
||||
|
||||
it('in the run channel', () => {
|
||||
cy.getLastPost().then((element) => {
|
||||
// # Verify the expected message text
|
||||
cy.get(element).contains(`@${testParticipant.username}, please provide a status update for ${testPlaybookRun.name}.`);
|
||||
});
|
||||
});
|
||||
|
||||
it('in threads view', () => {
|
||||
// # Find the update request post and post a reply to make it show up in threads view
|
||||
cy.getLastPostId().then((lastPostId) => {
|
||||
// Open RHS
|
||||
cy.clickPostCommentIcon(lastPostId);
|
||||
|
||||
// Post a reply message
|
||||
cy.postMessageReplyInRHS('test reply');
|
||||
|
||||
// # Navigate to the threads view
|
||||
cy.get('#sidebarItem_threads').click();
|
||||
|
||||
// # Verify the expected text in the list view
|
||||
cy.get('.ThreadItem').first().contains(`@${testParticipant.username}, please provide a status update for ${testPlaybookRun.name}.`);
|
||||
|
||||
// # Click to open details
|
||||
cy.get('.ThreadItem').first().click();
|
||||
|
||||
// # Verify post still rendered
|
||||
cy.get(`#rhsPost_${lastPostId}`).contains(`@${testParticipant.username}, please provide a status update for ${testPlaybookRun.name}.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
178
e2e-tests/cypress/tests/integration/playbooks/digest_spec.js
Обычный файл
178
e2e-tests/cypress/tests/integration/playbooks/digest_spec.js
Обычный файл
@@ -0,0 +1,178 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('digest messages', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1', command: '/invalid'},
|
||||
{title: 'Step 2', command: '/echo VALID'},
|
||||
{title: 'Step 3', command: '/playbook check 0 0'},
|
||||
{title: 'Step 4'},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('digest message >', () => {
|
||||
let testRun;
|
||||
before(() => {
|
||||
const runName = 'Playbook Run (' + Date.now() + ')';
|
||||
|
||||
// # Start a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: runName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
testRun = run;
|
||||
|
||||
// # Set a timer that will expire.
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: run.id,
|
||||
message: 'no message 1',
|
||||
reminder: 1,
|
||||
});
|
||||
cy.apiChangeChecklistItemAssignee(run.id, 0, 0, testUser.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('has one run overdue and links to RDP', () => {
|
||||
// # Switch to playbooks DM channel
|
||||
cy.visit(`/${testTeam.name}/messages/@playbooks`);
|
||||
|
||||
// # Wait until the channel loads enough to show the post textbox.
|
||||
cy.get('#post-create').should('exist');
|
||||
|
||||
// # Run a slash command to show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
// # assert two blocks: inprogress+overdue
|
||||
cy.get('ul').should('have.length', 3);
|
||||
|
||||
// * CLick the first link - overdue status
|
||||
cy.get('ul a').eq(0).click();
|
||||
});
|
||||
|
||||
// # assert url is RDP
|
||||
cy.url().should('contain', '/playbooks/runs/' + testRun.id + '?from=digest_overduestatus');
|
||||
|
||||
// # assert telemetry tracks correctly the origin
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_details',
|
||||
type: 'page',
|
||||
properties: {
|
||||
from: 'digest_overduestatus',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('has one run in progress and links to RDP', () => {
|
||||
// # Switch to playbooks DM channel
|
||||
cy.visit(`/${testTeam.name}/messages/@playbooks`);
|
||||
|
||||
// # Wait until the channel loads enough to show the post textbox.
|
||||
cy.get('#post-create').should('exist');
|
||||
|
||||
// # Run a slash command to show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
// # assert two blocks: inprogress+overdue
|
||||
cy.get('ul').should('have.length', 3);
|
||||
|
||||
// * CLick the second link - inprogress
|
||||
cy.get('ul a').eq(1).click();
|
||||
});
|
||||
|
||||
// # assert url is RDP
|
||||
cy.url().should('contain', '/playbooks/runs/' + testRun.id + '?from=digest_runsinprogress');
|
||||
|
||||
// # assert telemetry tracks correctly the origin
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_details',
|
||||
type: 'page',
|
||||
properties: {
|
||||
from: 'digest_runsinprogress',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('has one run with one assigned task and links to RDP', () => {
|
||||
// # Switch to playbooks DM channel
|
||||
cy.visit(`/${testTeam.name}/messages/@playbooks`);
|
||||
|
||||
// # Wait until the channel loads enough to show the post textbox.
|
||||
cy.get('#post-create').should('exist');
|
||||
|
||||
// # Run a slash command to show the to-do list.
|
||||
cy.uiPostMessageQuickly('/playbook todo');
|
||||
|
||||
cy.getLastPost().within(() => {
|
||||
// # assert two blocks: inprogress+overdue
|
||||
cy.get('ul').should('have.length', 3);
|
||||
|
||||
// * CLick link - assigned task
|
||||
cy.get('p a').click();
|
||||
});
|
||||
|
||||
// # assert url is RDP
|
||||
cy.url().should('contain', '/playbooks/runs/' + testRun.id + '?from=digest_assignedtask');
|
||||
|
||||
// # assert telemetry tracks correctly the origin
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_details',
|
||||
type: 'page',
|
||||
properties: {
|
||||
from: 'digest_assignedtask',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
396
e2e-tests/cypress/tests/integration/playbooks/lhs_spec.js
Обычный файл
396
e2e-tests/cypress/tests/integration/playbooks/lhs_spec.js
Обычный файл
@@ -0,0 +1,396 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
/* eslint-disable no-only-tests/no-only-tests */
|
||||
|
||||
import {HALF_SEC} from '../../fixtures/timeouts';
|
||||
import {stubClipboard} from '../../utils';
|
||||
|
||||
describe('lhs', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPublicPlaybook;
|
||||
let testPrivatePlaybook;
|
||||
let playbookRun;
|
||||
let testViewerUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a private playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Private Playbook',
|
||||
memberIDs: [],
|
||||
public: false,
|
||||
}).then((playbook) => {
|
||||
testPrivatePlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
});
|
||||
|
||||
const getRunDropdownItemByText = (groupName, runName, itemName) => {
|
||||
// # Click on run at LHS
|
||||
cy.findByTestId(groupName).findByTestId(runName).click();
|
||||
|
||||
// # Click dot menu
|
||||
cy.findByTestId(groupName).
|
||||
findByTestId(runName).
|
||||
findByTestId('menuButton').
|
||||
click({force: true});
|
||||
|
||||
cy.findByTestId('dropdownmenu').should('be.visible');
|
||||
|
||||
return cy.findByTestId('dropdownmenu').findByText(itemName).should('be.visible');
|
||||
};
|
||||
|
||||
describe('navigate', () => {
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name(' + Date.now() + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
playbookRun = run;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit('/playbooks/runs');
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRun.name).should('be.visible');
|
||||
});
|
||||
|
||||
cy.wait;
|
||||
});
|
||||
|
||||
it('click run', () => {
|
||||
// # Click on run at LHS
|
||||
cy.findByTestId('Runs').findByTestId(playbookRun.name).click();
|
||||
|
||||
// * assert telemetry
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
type: 'page',
|
||||
name: 'run_details',
|
||||
properties: {
|
||||
from: 'playbooks_lhs',
|
||||
role: 'participant',
|
||||
playbookrun_id: playbookRun.id,
|
||||
playbook_id: testPublicPlaybook.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('run dot menu', () => {
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name(' + Date.now() + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
playbookRun = run;
|
||||
});
|
||||
});
|
||||
|
||||
it('shows on click', () => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
// # Click dot menu
|
||||
cy.findByTestId('Runs').
|
||||
findByTestId(playbookRun.name).
|
||||
findByTestId('menuButton').
|
||||
click({force: true});
|
||||
|
||||
// * Assert context menu is opened
|
||||
cy.findByTestId('dropdownmenu').should('be.visible');
|
||||
});
|
||||
|
||||
it.skip('can copy link', () => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # Click on Copy link menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Copy link').click();
|
||||
|
||||
// * Verify clipboard content
|
||||
cy.get('@clipboard').its('contents').should('contain', `/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
|
||||
it('can favorite / unfavorite', () => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
// # Click on favorite menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Favorite').click();
|
||||
|
||||
// * Verify the run is added to favorites
|
||||
cy.findByTestId('Favorite').findByTestId(playbookRun.name).should('exist');
|
||||
|
||||
// # Click on unfavorite menu item
|
||||
getRunDropdownItemByText('Favorite', playbookRun.name, 'Unfavorite').click();
|
||||
|
||||
// * Verify the run is removed from favorites
|
||||
cy.findByTestId('Favorite').should('not.exist');
|
||||
});
|
||||
|
||||
it('lhs refresh on follow/unfollow', () => {
|
||||
cy.apiLogin(testViewerUser);
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
// # The assertions here guard against the click() on 194
|
||||
// # happening on a detached element.
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
cy.findByTestId('runinfo-following').should('be.visible').within(() => {
|
||||
// # Verify follower icon
|
||||
cy.findAllByTestId('profile-option', {exact: false}).should('have.length', 1);
|
||||
cy.findByText('Follow').should('be.visible').click();
|
||||
|
||||
// # Verify icons update
|
||||
cy.findAllByTestId('profile-option', {exact: false}).should('have.length', 2);
|
||||
});
|
||||
|
||||
// * Verify that the run was added to the lhs
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRun.name).should('exist');
|
||||
|
||||
// # Click on unfollow menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Unfollow').click();
|
||||
|
||||
// * Verify that the run is removed lhs
|
||||
cy.findByTestId('Runs').findByTestId(playbookRun.name).should('not.exist');
|
||||
|
||||
// # assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
type: 'track',
|
||||
name: 'playbookrun_follow',
|
||||
properties: {
|
||||
from: 'run_details',
|
||||
playbookrun_id: playbookRun.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'track',
|
||||
name: 'playbookrun_unfollow',
|
||||
properties: {
|
||||
from: 'playbooks_lhs',
|
||||
playbookrun_id: playbookRun.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('leave run', () => {
|
||||
// # Add viewer user to the channel
|
||||
cy.apiAddUsersToRun(playbookRun.id, [testViewerUser.id]);
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
// # Click on leave menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Leave and unfollow run').click();
|
||||
|
||||
// * Verify that owner can't leave.
|
||||
cy.get('#confirmModal').should('not.exist');
|
||||
|
||||
// # Change the owner to testViewerUser
|
||||
cy.findByTestId('runinfo-owner').findByTestId('assignee-profile-selector').click();
|
||||
cy.get('.playbook-react-select').findByText('@' + testViewerUser.username).click();
|
||||
|
||||
// # Wait for owner to change
|
||||
cy.wait(HALF_SEC);
|
||||
|
||||
// # Click on leave menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Leave and unfollow run').click();
|
||||
|
||||
// * Click leave confirmation
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
type: 'track',
|
||||
name: 'playbookrun_leave',
|
||||
properties: {
|
||||
from: 'playbooks_lhs',
|
||||
playbookrun_id: playbookRun.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('leave run - no permanent access', () => {
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPrivatePlaybook.id,
|
||||
playbookRunName: 'the run name(' + Date.now() + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
playbookRun = run;
|
||||
|
||||
cy.apiAddUsersToRun(playbookRun.id, [testViewerUser.id]);
|
||||
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('leave run, when on rdp of the same run', () => {
|
||||
// # Click on leave menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Leave and unfollow run').click();
|
||||
|
||||
// # confirm modal
|
||||
cy.get('#confirmModal').should('be.visible').within(() => {
|
||||
cy.get('#confirmModalButton').click();
|
||||
});
|
||||
|
||||
// * Verify that user was redirected to the run list page
|
||||
cy.url().should('include', 'playbooks/runs?sort=');
|
||||
});
|
||||
|
||||
it('leave run, when not on rdp of the same run', () => {
|
||||
// # Visit playbooks list page
|
||||
cy.visit('/playbooks/playbooks');
|
||||
|
||||
// # Click on leave menu item
|
||||
getRunDropdownItemByText('Runs', playbookRun.name, 'Leave and unfollow run').click();
|
||||
|
||||
// # confirm modal
|
||||
cy.get('#confirmModal').should('be.visible').within(() => {
|
||||
cy.get('#confirmModalButton').click();
|
||||
});
|
||||
|
||||
// * Verify that user was not redirected to the run list page
|
||||
cy.url().should('not.include', 'playbooks/runs?sort=');
|
||||
});
|
||||
});
|
||||
|
||||
describe('playbook dot menu', () => {
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'the run name(' + Date.now() + ')',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
|
||||
// # Visit the playbooks page
|
||||
cy.visit('/playbooks/playbooks');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows on click', () => {
|
||||
// # Click dot menu
|
||||
cy.findByTestId('Playbooks').
|
||||
findByTestId(testPublicPlaybook.title).
|
||||
findByTestId('menuButton').
|
||||
click({force: true});
|
||||
|
||||
// * Assert context menu is opened
|
||||
cy.findByTestId('dropdownmenu').should('be.visible');
|
||||
});
|
||||
|
||||
it('can copy link', () => {
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # Click on Copy link menu item
|
||||
getRunDropdownItemByText('Playbooks', testPublicPlaybook.title, 'Copy link').click();
|
||||
|
||||
// * Verify clipboard content
|
||||
cy.get('@clipboard').
|
||||
its('contents').
|
||||
should('contain', `/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
});
|
||||
|
||||
it('can favorite / unfavorite', () => {
|
||||
// # Click on favorite menu item
|
||||
getRunDropdownItemByText('Playbooks', testPublicPlaybook.title, 'Favorite').click();
|
||||
|
||||
// * Verify the playbook is added to favorites
|
||||
cy.findByTestId('Favorite').findByTestId(testPublicPlaybook.title).should('exist');
|
||||
|
||||
// # Click on unfavorite menu item
|
||||
getRunDropdownItemByText('Favorite', testPublicPlaybook.title, 'Unfavorite').click();
|
||||
|
||||
// * Verify the playbook is removed from favorites
|
||||
cy.findByTestId('Playbooks').findByTestId(testPublicPlaybook.title).should('exist');
|
||||
});
|
||||
|
||||
it('can leave', () => {
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # Click on Leave menu item
|
||||
getRunDropdownItemByText('Playbooks', testPublicPlaybook.title, 'Leave').click();
|
||||
|
||||
// * Verify the playbook is removed from the list
|
||||
cy.findByTestId('Playbooks').findByTestId(testPublicPlaybook.title).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
72
e2e-tests/cypress/tests/integration/playbooks/navigation_spec.js
Обычный файл
72
e2e-tests/cypress/tests/integration/playbooks/navigation_spec.js
Обычный файл
@@ -0,0 +1,72 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('navigation', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as user-1
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: team.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: 'Playbook Run',
|
||||
ownerUserId: user.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate to the application
|
||||
cy.visit(`/${testTeam.name}/`);
|
||||
});
|
||||
|
||||
it('switches to playbooks list view via sidebar view all button', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// * Verify that playbooks are shown
|
||||
cy.findByTestId('titlePlaybook').should('exist').contains('Playbooks');
|
||||
});
|
||||
|
||||
it('switches to playbook runs list view via sidebar view all button', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Switch to playbook runs
|
||||
cy.findByTestId('playbookRunsLHSButton').click();
|
||||
|
||||
// * Verify that playbook runs are shown
|
||||
cy.findByTestId('titlePlaybookRun').should('exist').contains('Runs');
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbooks > edit', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create a second test user in this team
|
||||
cy.apiCreateUser().then((payload) => {
|
||||
testUser2 = payload.user;
|
||||
cy.apiAddUserToTeam(testTeam.id, payload.user.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('rdp information refresh', () => {
|
||||
let testPlaybook;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
userId: testUser.id,
|
||||
public: true,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
// Navigate to the playbook page
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
});
|
||||
});
|
||||
|
||||
it('add / remove a member', () => {
|
||||
// # Open playbook access modal
|
||||
cy.findByTestId('playbook-members').click();
|
||||
|
||||
// # Add a new member
|
||||
cy.findByTestId('add-people-input').type(testUser2.username);
|
||||
cy.wait(500);
|
||||
cy.findByTestId('profile-option-' + testUser2.username).click({force: true});
|
||||
|
||||
// * Verify that user was added
|
||||
cy.findByTestId('members-list').findByText(testUser2.username).should('exist');
|
||||
|
||||
// # Close playbook access modal
|
||||
cy.get('.close > [aria-hidden="true"]').click();
|
||||
|
||||
// * Verify members number
|
||||
cy.findByTestId('playbook-members').findByText('2').should('exist');
|
||||
|
||||
// # Open playbook access modal
|
||||
cy.findByTestId('playbook-members').click();
|
||||
|
||||
// # Open dropdown and remove user
|
||||
cy.findByText('Playbook Member').click();
|
||||
cy.findByTestId('dropdownmenu').findByText('Remove').click();
|
||||
|
||||
// * Verify that user was removed
|
||||
cy.findByTestId('members-list').findByText(testUser2.username).should('not.exist');
|
||||
|
||||
// # Close playbook access modal
|
||||
cy.get('.close > [aria-hidden="true"]').click();
|
||||
|
||||
// * Verify members number
|
||||
cy.findByTestId('playbook-members').findByText('1').should('exist');
|
||||
});
|
||||
|
||||
it('change to private', () => {
|
||||
// # Open playbook access modal
|
||||
cy.findByTestId('playbook-members').click();
|
||||
|
||||
// # Click on convert to private
|
||||
cy.findByText('Convert to private playbook').click();
|
||||
|
||||
// * Check that confirm modal is open
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
|
||||
// # Confirm convert to private
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
|
||||
// * Verify that playbook is private
|
||||
cy.findByText('Convert to private playbook').should('not.exist');
|
||||
|
||||
// # Close playbook access modal
|
||||
cy.get('.close > [aria-hidden="true"]').click();
|
||||
|
||||
// * Verify lock icon is visible
|
||||
cy.findByTestId('playbook-editor-header').get('.icon-lock-outline').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbooks > creation button', () => {
|
||||
let testSysadmin;
|
||||
let testTeam;
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin}) => {
|
||||
testSysadmin = sysadmin;
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
// # Creating this playbook ensures the list view
|
||||
// # specifically is shown in the backstage content section.
|
||||
// # Without it there is a brief flicker from the list view
|
||||
// # to the no content view, which causes some flake
|
||||
// # on clicking the 'Create playbook' button
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as user-1
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Size the viewport to show playbooks without weird scrolling issues
|
||||
cy.viewport('macbook-13');
|
||||
});
|
||||
|
||||
it('opens playbook creation page with New Playbook button', () => {
|
||||
const playbookName = 'Untitled Playbook';
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click 'New Playbook' button
|
||||
cy.findByTestId('titlePlaybook').findByText('Create playbook').click();
|
||||
cy.get('#playbooks_create').findByText('Create playbook').click();
|
||||
|
||||
// * Verify playbook outline page opened
|
||||
verifyPlaybookOutlineOpened(playbookName);
|
||||
|
||||
// * Verify playbook was added to the LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookName).should('exist');
|
||||
});
|
||||
|
||||
it('auto creates a playbook with "Blank" template option', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click 'Blank'
|
||||
cy.findByText('Blank').click();
|
||||
|
||||
const playbookName = `@${testUser.username}'s Blank`;
|
||||
|
||||
// * Verify playbook outline opened
|
||||
verifyPlaybookOutlineOpened(playbookName);
|
||||
|
||||
// * Verify playbook was added to the LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookName).should('exist');
|
||||
});
|
||||
|
||||
it('opens Service Outage Incident page from its template option (multiple teams)', () => {
|
||||
cy.apiCreateTeam('second-team', 'Second Team').then(() => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click 'Incident Resolution'
|
||||
cy.findByText('Incident Resolution').click();
|
||||
|
||||
const playbookName = `@${testUser.username}'s Incident Resolution`;
|
||||
|
||||
// * Verify playbook outline opened
|
||||
verifyPlaybookOutlineOpened(playbookName);
|
||||
|
||||
// * Verify the playbook was added to the lhs of current team
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookName).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
let restrictedTestTeam;
|
||||
let restrictedTestUser;
|
||||
|
||||
describe('user is lacking permissions to create playbooks', () => {
|
||||
before(() => {
|
||||
cy.apiLogin(testSysadmin);
|
||||
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
restrictedTestUser = createdUser;
|
||||
});
|
||||
|
||||
cy.apiCreateTeam('restricted-team', 'Restricted Team').then(({team: createdTeam}) => {
|
||||
restrictedTestTeam = createdTeam;
|
||||
cy.apiAddUserToTeam(restrictedTestTeam.id, restrictedTestUser.id);
|
||||
});
|
||||
|
||||
cy.apiCreateScheme('Restricted Team Scheme', 'team').then(({scheme}) => {
|
||||
cy.apiSetTeamScheme(restrictedTestTeam.id, scheme.id);
|
||||
cy.apiGetRolesByNames([scheme.default_team_user_role]).then(({roles}) => {
|
||||
const role = roles[0];
|
||||
|
||||
// Remove permissions to create playbooks
|
||||
const permissions = role.permissions.filter((perm) => !(/playbook_(private|public)_create/).test(perm));
|
||||
cy.apiPatchRole(role.id, {permissions});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as user with restricted permissions
|
||||
cy.apiLogin(restrictedTestUser);
|
||||
});
|
||||
|
||||
it('create playbook entry in LHS dropdown should not exist', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Open menu dropdown
|
||||
cy.findByTestId('create-playbook-dropdown-toggle').click();
|
||||
|
||||
cy.get('#CreatePlaybookDropdown').within(() => {
|
||||
// * Verify create playbook entry is missing
|
||||
cy.findByText('Create New Playbook').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('permission notice should be shown if no playbooks exist', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// * Verify notice about missing permissions and no playbooks is shown
|
||||
cy.findByText('There are no playbooks to view. You don\'t have permission to create playbooks in this workspace.').should('exist');
|
||||
});
|
||||
|
||||
it('create playbook button should not exist if playbooks exist', () => {
|
||||
// # Create a playbook for the team
|
||||
cy.apiLogin(testSysadmin).then(() => {
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: restrictedTestTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
});
|
||||
|
||||
// # Login as user with restricted permissions
|
||||
cy.apiLogin(restrictedTestUser);
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// * Verify create playbook button is missing
|
||||
cy.findByTestId('titlePlaybook').findByText('Create playbook').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function verifyPlaybookOutlineOpened(playbookName) {
|
||||
// * Verify the page url contains 'playbooks/playbooks/new'
|
||||
cy.url().should('contain', '/outline');
|
||||
|
||||
// * Verify the playbook name matches the one provided
|
||||
cy.findByTestId('playbook-editor-title').within(() => {
|
||||
cy.findByText(playbookName).should('be.visible');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
//
|
||||
import * as TIMEOUTS from '../../../../fixtures/timeouts';
|
||||
|
||||
describe('playbooks > edit > task actions', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
|
||||
// # Add this new user to the team
|
||||
cy.apiAddUserToTeam(team.id, testUser2.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('modal', () => {
|
||||
let testPlaybook;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Create a playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
checklists: [{
|
||||
title: 'Test Checklist',
|
||||
items: [
|
||||
{title: 'Test Task'},
|
||||
],
|
||||
}],
|
||||
memberIDs: [
|
||||
testUser.id,
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
});
|
||||
});
|
||||
|
||||
const editTask = () => {
|
||||
cy.findByTestId('checkbox-item-container').within(() => {
|
||||
cy.findByText('Test Task').trigger('mouseover');
|
||||
cy.findByTestId('hover-menu-edit-button').click();
|
||||
});
|
||||
};
|
||||
|
||||
it('disallows no keywords', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify no actions are configured
|
||||
cy.findByText('Task Actions').should('exist');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, []);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isFalse(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a single keyword', () => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'taskactions_updated',
|
||||
type: 'track',
|
||||
properties: {
|
||||
playbook_id: testPlaybook.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows multiple keywords', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add multiple keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
cy.get('input').eq(0).type('keyword2{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1', 'keyword2']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows multi-word phrases', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a phrase
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('a phrase with multiple words{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['a phrase with multiple words']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows removing previously configured keywords', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add multiple keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
cy.get('input').eq(0).type('keyword2{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Re-open the dialog
|
||||
cy.findByText('1 action').click();
|
||||
|
||||
// Remove one trigger keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.findByText('keyword1').next().click();
|
||||
});
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword2']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('disables when all keywords removed', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add multiple keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
cy.get('input').eq(0).type('keyword2{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Re-open the dialog
|
||||
cy.findByText('1 action').click();
|
||||
|
||||
// Remove all trigger keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.findByText('keyword1').next().click();
|
||||
cy.findByText('keyword2').next().click();
|
||||
});
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('Task Actions');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, []);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isFalse(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('disallows a user without keywords', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a user
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify no actions are configured
|
||||
cy.findByText('Task Actions').should('exist');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, []);
|
||||
assert.deepEqual(trigger.user_ids, [testUser.id]);
|
||||
assert.isFalse(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a single user', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Add a user
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions and user
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, [testUser.id]);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows configuring multiple users', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Add two users
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser2.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions and user
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, [testUser.id, testUser2.id]);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects unknown user', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Type an unknown user
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@unknown', {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Click away
|
||||
cy.get('.modal-body').click();
|
||||
|
||||
// Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions and user
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows removing previously configured users', () => {
|
||||
// Open the task actions modal
|
||||
editTask();
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Add two users
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser2.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Re-open the dialog
|
||||
cy.findByText('1 action').click();
|
||||
|
||||
// Remove one user keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.findByText(testUser.username).parent().parent().next().click();
|
||||
});
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
const trigger = JSON.parse(playbook.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbook.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, [testUser2.id]);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,570 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
/* eslint-disable no-only-tests/no-only-tests */
|
||||
|
||||
describe('playbooks > edit_metrics', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
let testPlaybook;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Set a bigger viewport so the action don't scroll out of view
|
||||
cy.viewport('macbook-16');
|
||||
cy.intercept('PUT', '/plugins/playbooks/api/v0/playbooks/**').as('addMetric');
|
||||
});
|
||||
|
||||
describe('adding and editing metrics', () => {
|
||||
it('can add 4, but not 5 metrics; can save and re-edit with metrics saved', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
|
||||
// # Switch to Outline tab and focus retro section
|
||||
cy.findByText('Outline').click();
|
||||
cy.get('#retrospective').scrollIntoView();
|
||||
|
||||
// # Add and verify metric
|
||||
addMetric('Duration', 'test duration', '0:0:1', 'test description');
|
||||
verifyViewMetric(0, 'test duration', '1 minute per run', 'test description');
|
||||
|
||||
// # Add and verify metric
|
||||
addMetric('Cost', 'test dollars', '2', 'test description 2');
|
||||
verifyViewMetric(1, 'test dollars', '2 per run', 'test description 2');
|
||||
|
||||
// # Add and verify metric
|
||||
addMetric('Integer', 'test integer', '4', 'test descr 3');
|
||||
verifyViewMetric(2, 'test integer', '4 per run', 'test descr 3');
|
||||
|
||||
// # Add and verify metric
|
||||
addMetric('Duration', 'test duration 2', '0:0:2', 'test description 4');
|
||||
verifyViewMetric(3, 'test duration 2', '2 minutes per run', 'test description 4');
|
||||
|
||||
// * Verify Add Metric button is inactive
|
||||
cy.findByRole('button', {name: 'Add Metric'}).should('be.disabled');
|
||||
|
||||
// * Verify we have four valid metrics and are editing none.
|
||||
verifyViewsAndEdits(4, 0);
|
||||
|
||||
// Refresh the page
|
||||
cy.reload();
|
||||
|
||||
// * Verify we saved the metrics
|
||||
verifyViewMetric(0, 'test duration', '1 minute per run', 'test description');
|
||||
verifyViewMetric(1, 'test dollars', '2 per run', 'test description 2');
|
||||
verifyViewMetric(2, 'test integer', '4 per run', 'test descr 3');
|
||||
verifyViewMetric(3, 'test duration 2', '2 minutes per run', 'test description 4');
|
||||
|
||||
// # Edit all 4 metrics and repeat the test
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.get('input[type=text]').eq(2).clear().type('12:8:97');
|
||||
saveMetric();
|
||||
cy.findAllByTestId('edit-metric').eq(1).click();
|
||||
cy.get('textarea').eq(0).clear().type('a new description');
|
||||
saveMetric();
|
||||
cy.findAllByTestId('edit-metric').eq(2).click();
|
||||
cy.get('input[type=text]').eq(2).clear().type('7777777');
|
||||
saveMetric();
|
||||
cy.findAllByTestId('edit-metric').eq(3).click();
|
||||
cy.get('input[type=text]').eq(1).clear().type('test duration 2!!!');
|
||||
saveMetric();
|
||||
|
||||
// # Refresh the page
|
||||
cy.reload();
|
||||
|
||||
// * Verify we saved the metrics
|
||||
verifyViewMetric(0, 'test duration', '12 days, 9 hours, 37 minutes per run', 'test description');
|
||||
verifyViewMetric(1, 'test dollars', '2 per run', 'a new description');
|
||||
verifyViewMetric(2, 'test integer', '7777777 per run', 'test descr 3');
|
||||
verifyViewMetric(3, 'test duration 2!!!', '2 minutes per run', 'test description 4');
|
||||
|
||||
// # Now test: verifies when clicking "Add", for duration type
|
||||
// # (using the previous state)
|
||||
|
||||
// # Edit the first metric
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
|
||||
// * Metrics need a title
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
saveMetric();
|
||||
cy.getStyledComponent('ErrorText').contains('Please add a title for your metric.');
|
||||
|
||||
// * Metrics need a unique title
|
||||
cy.get('input[type=text]').eq(1).type('test dollars');
|
||||
saveMetric();
|
||||
cy.getStyledComponent('ErrorText').
|
||||
contains('A metric with the same name already exists. Please add a unique name for each metric.');
|
||||
|
||||
// * A duration target needs to be in the correct format (no letters)
|
||||
cy.get('input[type=text]').eq(1).clear().wait(100).type('test duration again');
|
||||
cy.get('input[type=text]').eq(2).clear().type('a');
|
||||
saveMetric();
|
||||
cy.getStyledComponent('ErrorText').
|
||||
contains('Please enter a duration in the format: dd:hh:mm (e.g., 12:00:00), or leave the target blank.');
|
||||
|
||||
// * A duration target needs to be in the correct format (mm:dd:ss)
|
||||
cy.get('input[type=text]').eq(2).clear().type('0:123:0');
|
||||
saveMetric();
|
||||
cy.getStyledComponent('ErrorText').
|
||||
contains('Please enter a duration in the format: dd:hh:mm (e.g., 12:00:00), or leave the target blank.');
|
||||
|
||||
// # A duration can have 1 or 2 numbers in each position
|
||||
cy.get('input[type=text]').eq(2).clear().type('2:12:1');
|
||||
saveMetric();
|
||||
verifyViewMetric(0, 'test duration again', '2 days, 12 hours, 1 minute per run', 'test description');
|
||||
|
||||
// * Verify we have four valid metrics and are editing none.
|
||||
verifyViewsAndEdits(4, 0);
|
||||
|
||||
// # Now test: on clicking edit, closes & saves current editing metric, and switches
|
||||
// # (using the previous state)
|
||||
|
||||
// # Edit the second metric
|
||||
cy.findAllByTestId('edit-metric').eq(1).click();
|
||||
|
||||
// * Verify editing correct metric, and only this metric
|
||||
cy.getStyledComponent('EditContainer').should('have.length', 1).within(() => {
|
||||
cy.get('input[type=text]').eq(0).should('have.value', 'test dollars');
|
||||
});
|
||||
cy.getStyledComponent('ViewContainer').should('have.length', 3);
|
||||
|
||||
// # Switch to editing third metric (second is in edit mode, so this is the third:)
|
||||
cy.findAllByTestId('edit-metric').eq(1).click();
|
||||
|
||||
// * Verify editing correct metric, and only this metric
|
||||
cy.getStyledComponent('EditContainer').should('have.length', 1).within(() => {
|
||||
cy.get('input[type=text]').eq(0).should('have.value', 'test integer');
|
||||
});
|
||||
cy.getStyledComponent('ViewContainer').should('have.length', 3);
|
||||
|
||||
// # Edit third metric's title, switch to another metric
|
||||
cy.getStyledComponent('EditContainer').should('have.length', 1).within(() => {
|
||||
cy.get('input[type=text]').eq(0).clear().type('test integer222');
|
||||
});
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
|
||||
// * Verify the title on the third metric (the second in view mode) was saved on switching
|
||||
verifyViewMetric(1, 'test integer222', '7777777 per run', 'test descr 3');
|
||||
|
||||
// * Verify we have three valid metrics and are editing one.
|
||||
verifyViewsAndEdits(3, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('adding and editing metrics (new playbook)', () => {
|
||||
it('verifies when clicking "Add Metric", for Currency type, and switches to new edit', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
|
||||
// # Switch to Outline tab and focus retro section
|
||||
cy.findByText('Outline').click();
|
||||
cy.get('#retrospective').scrollIntoView();
|
||||
|
||||
// # Add and verify 1st metric
|
||||
addMetric('Integer', 'test integer!', '12314123', 'test description');
|
||||
verifyViewMetric(0, 'test integer!', '12314123 per run', 'test description');
|
||||
|
||||
// # Add metric
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Cost').click();
|
||||
});
|
||||
|
||||
// # Don't fill in the metric's details
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
|
||||
// * Metrics need a title
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Integer').click();
|
||||
});
|
||||
cy.getStyledComponent('ErrorText').contains('Please add a title for your metric.');
|
||||
|
||||
// * Metrics need a unique title
|
||||
cy.get('input[type=text]').eq(1).type('test integer!');
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Integer').click();
|
||||
});
|
||||
cy.getStyledComponent('ErrorText').
|
||||
contains('A metric with the same name already exists. Please add a unique name for each metric.');
|
||||
|
||||
// # Fill in title
|
||||
cy.get('input[type=text]').eq(1).clear().type('test currency!');
|
||||
|
||||
// * A Currency target cannot be text
|
||||
cy.get('input[type=text]').eq(2).clear().type('z');
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Integer').click();
|
||||
});
|
||||
cy.getStyledComponent('ErrorText').contains('Please enter a number, or leave the target blank.');
|
||||
|
||||
// * A Currency target /can/ be blank, so can the description, and Add next Integer metric
|
||||
cy.get('input[type=text]').eq(2).clear();
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Integer').click();
|
||||
});
|
||||
cy.getStyledComponent('EditContainer').should('be.visible');
|
||||
|
||||
// * Verify metric was added without target or description.
|
||||
verifyViewMetric(1, 'test currency!', '', '');
|
||||
|
||||
// * Verify we have two valid metrics and are editing next one.
|
||||
verifyViewsAndEdits(2, 1);
|
||||
|
||||
// # Now test: verifies when clicking edit button, for Currency type, and switches to next edit
|
||||
// # (using the previous state)
|
||||
|
||||
// # Don't fill in the metric's details
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
|
||||
// * Metrics need a title
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.getStyledComponent('ErrorText').contains('Please add a title for your metric.');
|
||||
|
||||
// * Metrics need a unique title
|
||||
cy.get('input[type=text]').eq(1).type('test currency!');
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.getStyledComponent('ErrorText').
|
||||
contains('A metric with the same name already exists. Please add a unique name for each metric.');
|
||||
|
||||
// # Fill in title
|
||||
cy.get('input[type=text]').eq(1).clear().type('test integer #2!!');
|
||||
|
||||
// * An Integer target cannot be text
|
||||
cy.get('input[type=text]').eq(2).clear().type('arsoton');
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.getStyledComponent('ErrorText').contains('Please enter a number, or leave the target blank.');
|
||||
|
||||
// * An Integer target /can/ be blank, so can the description, and edit first metric
|
||||
cy.get('input[type=text]').eq(2).clear();
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
|
||||
// * Verify we're editing the first metric, and only this metric
|
||||
cy.getStyledComponent('EditContainer').should('have.length', 1).within(() => {
|
||||
cy.get('input[type=text]').eq(0).should('have.value', 'test integer!');
|
||||
});
|
||||
cy.getStyledComponent('ViewContainer').should('have.length', 2);
|
||||
|
||||
// # Stop editing
|
||||
saveMetric();
|
||||
|
||||
// * Verify metric was added without target or description.
|
||||
verifyViewMetric(2, 'test integer #2!!', '', '');
|
||||
|
||||
// * Verify we have three valid metrics and are editing none.
|
||||
verifyViewsAndEdits(3, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete metric', () => {
|
||||
it.skip('verifies when clicking delete button; saved metrics have different confirmation text; deleted metrics are deleted', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
|
||||
// # Switch to Outline tab and focus retro section
|
||||
cy.findByText('Outline').click();
|
||||
cy.get('#retrospective').scrollIntoView();
|
||||
|
||||
// # Add and verify 1st metric
|
||||
addMetric('Integer', 'test integer!', '12314123', 'test description');
|
||||
verifyViewMetric(0, 'test integer!', '12314123 per run', 'test description');
|
||||
|
||||
// # Add metric
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Cost').click();
|
||||
});
|
||||
|
||||
// # Don't fill in the metric's details
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
|
||||
// * Metrics need a title
|
||||
cy.get('input[type=text]').eq(1).clear();
|
||||
cy.findAllByTestId('delete-metric').eq(0).click();
|
||||
cy.getStyledComponent('ErrorText').contains('Please add a title for your metric.');
|
||||
|
||||
// * Metrics need a unique title
|
||||
cy.get('input[type=text]').eq(1).type('test integer!');
|
||||
cy.findAllByTestId('delete-metric').eq(0).click();
|
||||
cy.getStyledComponent('ErrorText').
|
||||
contains('A metric with the same name already exists. Please add a unique name for each metric.');
|
||||
|
||||
// # Fill in title
|
||||
cy.get('input[type=text]').eq(1).clear().type('test currency!');
|
||||
|
||||
// * A Currency target cannot be text
|
||||
cy.get('input[type=text]').eq(2).clear().type('z');
|
||||
cy.findAllByTestId('delete-metric').eq(0).click();
|
||||
cy.getStyledComponent('ErrorText').contains('Please enter a number, or leave the target blank.');
|
||||
|
||||
// # Remove error text and type another invalid entry
|
||||
cy.get('input[type=text]').eq(2).clear().type('invalid');
|
||||
|
||||
// * Verify that we're allowed to delete a metric we are currently editing (even if it's invalid)
|
||||
cy.findAllByTestId('delete-metric').eq(1).click();
|
||||
cy.get('#confirm-modal-light').should('be.visible').contains('Are you sure you want to delete?');
|
||||
|
||||
// # Should see the confirmation /without/ extra text because we haven't saved this metric yet
|
||||
cy.get('#confirm-modal-light').
|
||||
should('not.contain.text', 'You will still be able to access historical data for this metric.');
|
||||
|
||||
// # Dismiss
|
||||
cy.findByRole('button', {name: 'Cancel'}).click();
|
||||
|
||||
// * A Currency target /can/ be blank, so can the description, try to delete first metric
|
||||
cy.get('input[type=text]').eq(2).clear();
|
||||
cy.findAllByTestId('delete-metric').eq(0).click();
|
||||
|
||||
cy.get('#confirm-modal-light').
|
||||
should('contain.text', 'If you delete this metric, the values for it will not be collected for any future runs.');
|
||||
|
||||
// # Delete first metric
|
||||
cy.findByRole('button', {name: 'Delete metric'}).click();
|
||||
|
||||
// * Verify metric
|
||||
verifyViewsAndEdits(1, 0);
|
||||
verifyViewMetric(0, 'test currency!', '', '');
|
||||
|
||||
// # Make sure we can still edit and add a metric after deleting one (testing that the metrics
|
||||
// component's state isn't broken)
|
||||
addMetric('Integer', 'test integer 2!', '123', 'test description');
|
||||
verifyViewMetric(1, 'test integer 2!', '123 per run', 'test description');
|
||||
cy.findAllByTestId('delete-metric').eq(1).click();
|
||||
cy.findByRole('button', {name: 'Delete metric'}).click();
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.get('input[type=text]').eq(1).clear().type('test currency 2!');
|
||||
saveMetric();
|
||||
verifyViewsAndEdits(1, 0);
|
||||
verifyViewMetric(0, 'test currency 2!', '', '');
|
||||
|
||||
// # Make sure we can add a metric and then delete it, then can keep editing
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Cost').click();
|
||||
});
|
||||
cy.findAllByTestId('delete-metric').eq(1).click();
|
||||
cy.findByRole('button', {name: 'Delete metric'}).click();
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.get('input[type=text]').eq(1).clear().type('test currency 3!');
|
||||
saveMetric();
|
||||
verifyViewsAndEdits(1, 0);
|
||||
verifyViewMetric(0, 'test currency 3!', '', '');
|
||||
|
||||
// # Make sure we can add a metric and then delete it, then can keep adding
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Cost').click();
|
||||
});
|
||||
cy.findAllByTestId('delete-metric').eq(1).click();
|
||||
cy.findByRole('button', {name: 'Delete metric'}).click();
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText('Cost').click();
|
||||
});
|
||||
cy.findAllByTestId('delete-metric').eq(1).click();
|
||||
cy.findByRole('button', {name: 'Delete metric'}).click();
|
||||
verifyViewsAndEdits(1, 0);
|
||||
verifyViewMetric(0, 'test currency 3!', '', '');
|
||||
|
||||
// # Refresh and verify one is saved
|
||||
cy.reload();
|
||||
verifyViewsAndEdits(1, 0);
|
||||
verifyViewMetric(0, 'test currency 3!', '', '');
|
||||
|
||||
// # Delete metric
|
||||
cy.findAllByTestId('delete-metric').eq(0).click();
|
||||
|
||||
// # Should see the confirmation /with/ extra text
|
||||
cy.get('#confirm-modal-light').
|
||||
should('contain.text', 'If you delete this metric, the values for it will not be collected for any future runs. You will still be able to access historical data for this metric.');
|
||||
|
||||
// # Delete first metric
|
||||
cy.findByRole('button', {name: 'Delete metric'}).click();
|
||||
|
||||
// * Verify
|
||||
verifyViewsAndEdits(0, 0);
|
||||
|
||||
// # Refresh and verify deleted
|
||||
cy.reload();
|
||||
verifyViewsAndEdits(0, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nullable and 0-able targets', () => {
|
||||
it('can add 0 targets and no (null) targets', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
|
||||
// # Switch to Outline tab and focus retro section
|
||||
cy.findByText('Outline').click();
|
||||
cy.get('#retrospective').scrollIntoView();
|
||||
|
||||
// # Add and verify duration
|
||||
addMetric('Duration', 'test duration', '0:0:0', 'test description');
|
||||
verifyViewMetric(0, 'test duration', '0 seconds per run', 'test description');
|
||||
|
||||
// # Verify it shows 0:0:0, then turn it into null.
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '00:00:00').
|
||||
clear();
|
||||
saveMetric();
|
||||
|
||||
// # Verify that the 'Target' section is gone
|
||||
cy.getStyledComponent('ViewContainer').
|
||||
getStyledComponent('DetailDiv').
|
||||
should('have.length', 1);
|
||||
|
||||
verifyViewMetric(0, 'test duration', '', 'test description');
|
||||
|
||||
// * Verify it has null value when editing again.
|
||||
cy.findAllByTestId('edit-metric').eq(0).click();
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '');
|
||||
saveMetric();
|
||||
|
||||
// # Add and verify currency
|
||||
addMetric('Cost', 'test money', '0', 'test description 2');
|
||||
cy.wait('@addMetric');
|
||||
verifyViewMetric(1, 'test money', '0', 'test description 2');
|
||||
|
||||
// # Verify it shows 0, then turn it into null.
|
||||
cy.findAllByTestId('edit-metric').eq(1).click();
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '0').
|
||||
clear();
|
||||
saveMetric();
|
||||
cy.getStyledComponent('ViewContainer').should('have.length', 2).eq(1).within(() => {
|
||||
// # Verify that the 'Target' section is gone
|
||||
cy.getStyledComponent('DetailDiv').
|
||||
should('have.length', 1);
|
||||
});
|
||||
|
||||
verifyViewMetric(1, 'test money', '', 'test description 2');
|
||||
|
||||
// * Verify it has null value when editing again.
|
||||
cy.findAllByTestId('edit-metric').eq(1).click();
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '');
|
||||
saveMetric();
|
||||
|
||||
// # Add and verify Integer
|
||||
addMetric('Integer', 'test number', '0', 'test description 3');
|
||||
cy.wait('@addMetric');
|
||||
verifyViewMetric(2, 'test number', '0', 'test description 3');
|
||||
|
||||
// # Verify it shows 0, then turn it into null.
|
||||
cy.findAllByTestId('edit-metric').eq(2).click();
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '0').
|
||||
clear();
|
||||
saveMetric();
|
||||
cy.getStyledComponent('ViewContainer').should('have.length', 3).eq(2).within(() => {
|
||||
// # Verify that the 'Target' section is gone
|
||||
cy.getStyledComponent('DetailDiv').
|
||||
should('have.length', 1);
|
||||
});
|
||||
|
||||
verifyViewMetric(2, 'test number', '', 'test description 3');
|
||||
|
||||
// * Verify it has null value when editing again.
|
||||
cy.findAllByTestId('edit-metric').eq(2).click();
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '');
|
||||
saveMetric();
|
||||
|
||||
// * Verify we have three valid metrics and are editing none.
|
||||
verifyViewsAndEdits(3, 0);
|
||||
|
||||
// # Refresh
|
||||
cy.reload();
|
||||
|
||||
// * Verify we saved the metrics
|
||||
verifyViewMetric(0, 'test duration', '', 'test description');
|
||||
verifyViewMetric(1, 'test money', '', 'test description 2');
|
||||
verifyViewMetric(2, 'test number', '', 'test description 3');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const addMetric = (type, title, target, description) => {
|
||||
const fullType = type === 'Duration' ? 'Duration (in dd:hh:mm)' : type;
|
||||
|
||||
// # Add the requested metric
|
||||
cy.findByRole('button', {name: 'Add Metric'}).click();
|
||||
cy.findByTestId('dropdownmenu').within(() => {
|
||||
cy.findByText(fullType).click();
|
||||
});
|
||||
|
||||
// # Fill in the metric's details
|
||||
cy.get('input[type=text]').eq(1).type(title).
|
||||
tab().type(target).
|
||||
tab().type(description);
|
||||
|
||||
// # Add the metric
|
||||
saveMetric();
|
||||
cy.wait('@addMetric');
|
||||
};
|
||||
|
||||
const verifyViewMetric = (index, title, target, description) => {
|
||||
cy.getStyledComponent('ViewContainer').should('have.length.of.at.least', index + 1).eq(index).within(() => {
|
||||
cy.getStyledComponent('Title').should('have.text', title);
|
||||
|
||||
if (target) {
|
||||
cy.getStyledComponent('DetailDiv').eq(0).contains(target);
|
||||
}
|
||||
|
||||
if (description) {
|
||||
const idx = target ? 1 : 0;
|
||||
cy.getStyledComponent('DetailDiv').eq(idx).contains(description);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const verifyViewsAndEdits = (numViews, numEdits) => {
|
||||
if (numViews === 0) {
|
||||
cy.getStyledComponent('ViewContainer').should('not.exist');
|
||||
} else {
|
||||
cy.getStyledComponent('ViewContainer').should('have.length', numViews);
|
||||
}
|
||||
|
||||
if (numEdits === 0) {
|
||||
cy.getStyledComponent('EditContainer').should('not.exist');
|
||||
} else {
|
||||
cy.getStyledComponent('EditContainer').should('have.length', numEdits);
|
||||
}
|
||||
};
|
||||
|
||||
function saveMetric() {
|
||||
cy.get('#retrospective-metrics').within(() => {
|
||||
cy.findByRole('button', {name: 'Save'}).click();
|
||||
});
|
||||
}
|
||||
1708
e2e-tests/cypress/tests/integration/playbooks/playbooks/edit_spec.js
Обычный файл
1708
e2e-tests/cypress/tests/integration/playbooks/playbooks/edit_spec.js
Обычный файл
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbooks > feedback', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as admin to create team and user below.
|
||||
cy.apiAdminLogin();
|
||||
|
||||
// # Setup a team, user and playbook for each test.
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Test Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Login as the newly created testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
});
|
||||
|
||||
it('playbooks shows prompt in global header, with experimental feature flag', () => {
|
||||
// # Enable experimental feature flag
|
||||
cy.apiAdminLogin().then(() => {
|
||||
cy.apiEnsureFeatureFlag('enableexperimentalfeatures', true);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
// # Visit the playbooks product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Verify Give Feedback link is configured to open in a new tab.
|
||||
cy.findByText('Give feedback').invoke('attr', 'target').should('eq', '_blank');
|
||||
|
||||
// # Verify Give Feedback link href
|
||||
cy.findByText('Give feedback').invoke('attr', 'href').should('match', /playbooks-feedback/);
|
||||
});
|
||||
|
||||
it('playbooks shows prompt in global header, without experimental feature flag', () => {
|
||||
// # Disable experimental feature flag
|
||||
cy.apiAdminLogin().then(() => {
|
||||
cy.apiEnsureFeatureFlag('enableexperimentalfeatures', false);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
// # Visit the playbooks product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Verify Give Feedback link is configured to open in a new tab.
|
||||
cy.findByText('Give feedback').invoke('attr', 'target').should('eq', '_blank');
|
||||
|
||||
// # Verify Give Feedback link href
|
||||
cy.findByText('Give feedback').invoke('attr', 'href').should('match', /playbooks-feedback/);
|
||||
});
|
||||
|
||||
it('playbooks shows prompt in rhs header', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
const playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Navigate directly to the application and the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// # Verify Give Feedback link is configured to open in a new tab.
|
||||
cy.findByText('Give feedback').invoke('attr', 'target').should('eq', '_blank');
|
||||
|
||||
// # Verify Give Feedback link href
|
||||
cy.findByText('Give feedback').invoke('attr', 'href').should('match', /playbooks-feedback/);
|
||||
});
|
||||
});
|
||||
216
e2e-tests/cypress/tests/integration/playbooks/playbooks/list_spec.js
Обычный файл
216
e2e-tests/cypress/tests/integration/playbooks/playbooks/list_spec.js
Обычный файл
@@ -0,0 +1,216 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbooks > list', () => {
|
||||
const playbookTitle = 'The Playbook Name';
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
// # Login as user-1
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookTitle,
|
||||
memberIDs: [],
|
||||
});
|
||||
|
||||
// # Create an archived public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook archived',
|
||||
memberIDs: [],
|
||||
}).then(({id}) => cy.apiArchivePlaybook(id));
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
it('has "Playbooks" in heading', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// * Assert contents of heading.
|
||||
cy.findByTestId('titlePlaybook').should('exist').contains('Playbooks');
|
||||
});
|
||||
|
||||
it('join/leave playbook', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click on the dot menu
|
||||
cy.findByTestId('menuButtonActions').click();
|
||||
|
||||
// # Click on leave
|
||||
cy.findByText('Leave').click();
|
||||
|
||||
// * Verify it has disappeared from the LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookTitle).should('not.exist');
|
||||
|
||||
// # Join a playbook
|
||||
cy.findByTestId('join-playbook').click();
|
||||
|
||||
// * Verify it has appeared in LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookTitle).should('exist');
|
||||
});
|
||||
|
||||
it('can duplicate playbook', () => {
|
||||
// # Login as testUser2
|
||||
cy.apiLogin(testUser2);
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click on the dot menu
|
||||
cy.findByTestId('menuButtonActions').click();
|
||||
|
||||
// # Click on duplicate
|
||||
cy.findByText('Duplicate').click();
|
||||
|
||||
// * Verify that playbook got duplicated
|
||||
cy.findByText('Copy of ' + playbookTitle).should('exist');
|
||||
|
||||
// * Verify that the current user is a member and can run the playbook.
|
||||
cy.findByText('Copy of ' + playbookTitle).closest('[data-testid="playbook-item"]').within(() => {
|
||||
cy.findByTestId('run-playbook').should('exist');
|
||||
cy.findByTestId('join-playbook').should('not.exist');
|
||||
});
|
||||
|
||||
// * Verify that the duplicated playbook is shown in the LHS
|
||||
cy.findByTestId('Playbooks').within(() => {
|
||||
cy.findByText('Copy of ' + playbookTitle).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
context('archived playbooks', () => {
|
||||
it('does not show them by default', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// * Assert the archived playbook is not there.
|
||||
cy.findAllByTestId('playbook-title').should((titles) => {
|
||||
expect(titles).to.have.length(2);
|
||||
});
|
||||
});
|
||||
it('shows them upon click on the filter', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click the With Archived button
|
||||
cy.findByTestId('with-archived').click();
|
||||
|
||||
// * Assert the archived playbook is there.
|
||||
cy.findAllByTestId('playbook-title').should((titles) => {
|
||||
expect(titles).to.have.length(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('can import playbook', () => {
|
||||
let validPlaybookExport;
|
||||
let invalidTypePlaybookExport;
|
||||
|
||||
const bufferToCypressFile = (fileName, fileData, fileType) => ({
|
||||
fileName,
|
||||
contents: fileData,
|
||||
mimeType: fileType,
|
||||
});
|
||||
|
||||
before(() => {
|
||||
// # Load fixtures and convert to File
|
||||
cy.fixture('playbook-export.json', null).then((buffer) => {
|
||||
validPlaybookExport = bufferToCypressFile('export.json', buffer, 'application/json');
|
||||
});
|
||||
cy.fixture('mp3-audio-file.mp3', null).then((buffer) => {
|
||||
invalidTypePlaybookExport = bufferToCypressFile('audio.mp3', buffer, 'audio/mpeg');
|
||||
});
|
||||
});
|
||||
|
||||
it('triggered by drag and drop', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Drop loaded fixture onto playbook list
|
||||
cy.findByTestId('playbook-list-scroll-container').selectFile(validPlaybookExport, {
|
||||
action: 'drag-drop',
|
||||
});
|
||||
|
||||
// * Verify that a new playbook was created.
|
||||
cy.findByTestId('playbook-editor-title').should('contain', 'Example Playbook');
|
||||
});
|
||||
|
||||
it('triggered by using button/input', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
cy.findByTestId('titlePlaybook').within(() => {
|
||||
// # Select loaded fixture for upload
|
||||
cy.findByTestId('playbook-import-input').selectFile(validPlaybookExport, {force: true});
|
||||
});
|
||||
|
||||
// * Verify that a new playbook was created.
|
||||
cy.findByTestId('playbook-editor-title').should('contain', 'Example Playbook');
|
||||
});
|
||||
|
||||
it('fails to import invalid file type', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
cy.findByTestId('titlePlaybook').within(() => {
|
||||
// # Select loaded fixture for upload
|
||||
cy.findByTestId('playbook-import-input').selectFile(invalidTypePlaybookExport, {force: true});
|
||||
});
|
||||
|
||||
// * Verify that an error message is displayed.
|
||||
cy.findByText('The file must be a valid JSON playbook template.').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,468 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import {stubClipboard} from '../../../utils';
|
||||
|
||||
describe('playbooks > overview', () => {
|
||||
let testTeam;
|
||||
let testOtherTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
let testPublicPlaybook;
|
||||
let testPlaybookOnTeamForSwitching;
|
||||
let testPlaybookOnOtherTeamForSwitching;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
// # Create another team
|
||||
cy.apiCreateTeam('second-team', 'Second Team').then(({team: createdTeam}) => {
|
||||
testOtherTeam = createdTeam;
|
||||
cy.apiAddUserToTeam(testOtherTeam.id, testUser.id);
|
||||
|
||||
// # Create a dedicated run follower
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, createdUser.id);
|
||||
cy.apiAddUserToTeam(testOtherTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create another user
|
||||
cy.apiCreateUser().then(({user: anotherUser}) => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
retrospectiveTemplate: 'Retro template text',
|
||||
retrospectiveReminderIntervalSeconds: 60 * 60 * 24 * 7, // 7 days
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a private playbook with only the current user
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Private Only Mine Playbook',
|
||||
memberIDs: [testUser.id],
|
||||
});
|
||||
|
||||
// # Create a private playbook with multiple users
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Private Shared Playbook',
|
||||
memberIDs: [testUser.id, anotherUser.id],
|
||||
});
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Switch A',
|
||||
memberIDs: [],
|
||||
retrospectiveTemplate: 'Retro template text',
|
||||
retrospectiveReminderIntervalSeconds: 60 * 60 * 24 * 7, // 7 days
|
||||
}).then((playbook) => {
|
||||
testPlaybookOnTeamForSwitching = playbook;
|
||||
});
|
||||
|
||||
// # Create a public playbook on another team
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testOtherTeam.id,
|
||||
title: 'Switch B',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPlaybookOnOtherTeamForSwitching = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
it('redirects to not found error if the playbook is unknown', () => {
|
||||
// # Visit the URL of a non-existing playbook
|
||||
cy.visit('/playbooks/playbooks/an_unknown_id');
|
||||
|
||||
// * Verify that the user has been redirected to the playbooks not found error page
|
||||
cy.url().should('include', '/playbooks/error?type=playbooks');
|
||||
});
|
||||
|
||||
describe('should switch to channels and prompt to run when clicking run', () => {
|
||||
const openAndRunPlaybook = (team) => {
|
||||
// # Navigate directly to town square on the team
|
||||
cy.visit(`${team.name}/channels/town-square`);
|
||||
|
||||
// # Open Playbooks
|
||||
cy.get('[aria-label="Product switch menu"]').click({force: true});
|
||||
cy.get('a[href="/playbooks"]').click({force: true});
|
||||
|
||||
// Click through to open the playbook
|
||||
cy.findByTestId('playbooksLHSButton').click({force: true});
|
||||
cy.get('[placeholder="Search for a playbook"]').type(testPlaybookOnTeamForSwitching.title);
|
||||
cy.findByTestId('playbook-title').click({force: true});
|
||||
|
||||
// # Click Run Playbook
|
||||
cy.findByTestId('run-playbook').click({force: true});
|
||||
|
||||
// * Verify the playbook run creation dialog has opened
|
||||
cy.get('#playbooks_run_playbook_dialog').should('exist').within(() => {
|
||||
cy.findByText('Start run').should('exist');
|
||||
});
|
||||
};
|
||||
|
||||
it('for testPlaybookOnTeamForSwitching from its own team', () => {
|
||||
openAndRunPlaybook(testTeam, testPlaybookOnTeamForSwitching);
|
||||
});
|
||||
|
||||
it('for testPlaybookOnTeamForSwitching from another team', () => {
|
||||
openAndRunPlaybook(testOtherTeam, testPlaybookOnTeamForSwitching);
|
||||
});
|
||||
|
||||
it('for testPlaybookOnOtherTeamForSwitching from its own team', () => {
|
||||
openAndRunPlaybook(testTeam, testPlaybookOnOtherTeamForSwitching);
|
||||
});
|
||||
|
||||
it('for testPlaybookOnOtherTeamForSwitchingOnOtherTeam from another team', () => {
|
||||
openAndRunPlaybook(testOtherTeam, testPlaybookOnOtherTeamForSwitching);
|
||||
});
|
||||
|
||||
it('on direct navigation to a playbook', () => {
|
||||
// # Navigate directly to the playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybookOnTeamForSwitching.id}`);
|
||||
|
||||
// # Click Run Playbook
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
// * Verify the playbook run creation dialog has opened
|
||||
cy.get('#playbooks_run_playbook_dialog').should('exist').within(() => {
|
||||
cy.findByText('Start run').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy playbook link', () => {
|
||||
// # Navigate directly to the playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
|
||||
// # trigger the tooltip
|
||||
cy.get('.icon-link-variant').trigger('mouseover', {force: true});
|
||||
|
||||
// * Verify tooltip text
|
||||
cy.get('#copy-playbook-link-tooltip').should('contain', 'Copy link to');
|
||||
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
// # click on copy button
|
||||
cy.get('.icon-link-variant').click({force: true}).then(() => {
|
||||
// * Verify that tooltip text changed
|
||||
cy.get('#copy-playbook-link-tooltip').should('contain', 'Copied!');
|
||||
|
||||
// * Verify clipboard content
|
||||
cy.get('@clipboard').its('contents').should('contain', `/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should duplicate playbook', () => {
|
||||
// # Login as testUser2
|
||||
cy.apiLogin(testUser2);
|
||||
|
||||
// # Navigate directly to the playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
|
||||
// # Click on playbook title
|
||||
cy.findByTestId('playbook-editor-title').click();
|
||||
|
||||
// # Click on duplicate
|
||||
cy.findByText('Duplicate').click();
|
||||
|
||||
// * Verify that playbook got duplicated
|
||||
cy.findByTestId('playbook-editor-title').should('contain', `Copy of ${testPublicPlaybook.title}`);
|
||||
|
||||
// * Verify that the current user is a member and can run the playbook.
|
||||
cy.findByTestId('run-playbook').should('exist');
|
||||
cy.findByTestId('join-playbook').should('not.exist');
|
||||
|
||||
// * Verify that the current user is the only member.
|
||||
cy.findByTestId('playbook-members').should('contain', '1');
|
||||
});
|
||||
|
||||
describe('checklists', () => {
|
||||
describe('header', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
description: 'Cypress Playbook',
|
||||
memberIDs: [],
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
retrospectiveTemplate: 'Cypress test template',
|
||||
}).then((playbook) => {
|
||||
cy.visit(`/playbooks/playbooks/${playbook.id}/outline`);
|
||||
});
|
||||
});
|
||||
|
||||
it('has title', () => {
|
||||
cy.get('#checklists').within(() => {
|
||||
cy.findByText('Tasks').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows checklists', () => {
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
description: 'Cypress Playbook',
|
||||
memberIDs: [],
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
retrospectiveTemplate: 'Cypress test template',
|
||||
}).then((playbook) => {
|
||||
cy.visit(`/playbooks/playbooks/${playbook.id}`);
|
||||
});
|
||||
|
||||
// # Switch to Outline section
|
||||
cy.findByText('Outline').click();
|
||||
|
||||
// * Verify checklist and associated steps
|
||||
cy.get('#checklists').within(() => {
|
||||
cy.findByText('Stage 1').should('exist');
|
||||
cy.findByText('Step 1').should('exist');
|
||||
cy.findByText('Step 2').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('shows correct retrospective timer and template text', () => {
|
||||
cy.visit(`/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
cy.findByText('Outline').click();
|
||||
|
||||
cy.get('#retrospective').within(() => {
|
||||
cy.findByText('7 days').should('exist');
|
||||
cy.findByText('Retro template text').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows statistics in usage tab', () => {
|
||||
// # Start playbook run.
|
||||
const now = Date.now();
|
||||
const playbookRunName = `Run (${now})`;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # Go to usage view
|
||||
cy.visit(`/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
|
||||
// * Verify basic information.
|
||||
cy.findByText('Runs currently in progress').next().should('contain', '1');
|
||||
cy.findByText('Participants currently active').next().should('contain', '1');
|
||||
cy.findByText('Runs finished in the last 30 days').next().should('contain', '0');
|
||||
|
||||
// # End the run so those metrics change.
|
||||
cy.apiFinishRun(playbookRun.id).then(() => {
|
||||
cy.reload();
|
||||
|
||||
// * Verify changes.
|
||||
cy.findByText('Runs currently in progress').next().should('contain', '0');
|
||||
cy.findByText('Participants currently active').next().should('contain', '0');
|
||||
cy.findByText('Runs finished in the last 30 days').next().should('contain', '1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('start a run', () => {
|
||||
// # Visit playbook page
|
||||
cy.visit(`/playbooks/playbooks/${testPublicPlaybook.id}`);
|
||||
|
||||
// # Click Run Playbook
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
// # Enter the run name
|
||||
cy.findByTestId('run-name-input').clear().type('run1234567');
|
||||
|
||||
// # Click start run button
|
||||
cy.get('button[data-testid=modal-confirm-button]').click();
|
||||
|
||||
// * Verify the run is added to lhs
|
||||
cy.findByTestId('Runs').findByTestId('run1234567').should('exist');
|
||||
});
|
||||
|
||||
describe('archiving', () => {
|
||||
const playbookTitle = 'Playbook (' + Date.now() + ')';
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: playbookTitle,
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
it('shows intended UI and disallows further updates', () => {
|
||||
// # Programmatically archive it
|
||||
cy.apiArchivePlaybook(testPlaybook.id);
|
||||
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
|
||||
// * Verify we're on the right playbook
|
||||
cy.get('[class^="Title-"]').contains(playbookTitle);
|
||||
|
||||
// * Verify we can see the archived badge
|
||||
cy.get('.icon-archive-outline').should('be.visible');
|
||||
|
||||
// * Verify the run button is disabled
|
||||
cy.findByTestId('run-playbook').should('be.disabled');
|
||||
|
||||
// # Attempt to edit the playbook
|
||||
cy.apiGetPlaybook(testPlaybook.id).then((playbook) => {
|
||||
// # New title
|
||||
playbook.title = 'new Title!!!';
|
||||
|
||||
// * Verify update fails
|
||||
cy.apiUpdatePlaybook(playbook, 400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('start a run', () => {
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
it('start a run, create a new channel', () => {
|
||||
// # Visit playbook page
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}`);
|
||||
|
||||
// # Click Run Playbook
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
// * Verify that channel configuration matches playbook config
|
||||
cy.findByTestId('link-existing-channel-radio').should('not.be.checked');
|
||||
cy.get('#link-existing-channel-selector').should('not.exist');
|
||||
cy.findByTestId('create-channel-radio').should('be.checked');
|
||||
cy.findByTestId('create-private-channel-radio').should('be.checked');
|
||||
|
||||
// # Enter the run name
|
||||
const runName = 'run' + Date.now();
|
||||
cy.findByTestId('run-name-input').clear().type(runName);
|
||||
|
||||
// # Click start run button
|
||||
cy.get('button[data-testid=modal-confirm-button]').click();
|
||||
|
||||
// * Verify the run is added to lhs
|
||||
cy.findByTestId('Runs').findByTestId(runName).should('exist');
|
||||
|
||||
// * Verify the channel is created
|
||||
cy.findByTestId('runinfo-channel-link').contains(runName);
|
||||
});
|
||||
|
||||
it('start a run in existing channel', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Select the action section.
|
||||
cy.get('#actions #link-existing-channel').within(() => {
|
||||
// # Enable link to existing channel
|
||||
cy.get('input[type=radio]').click();
|
||||
|
||||
// * Verify that the toggle is checked and input is enabled
|
||||
cy.get('input[type=radio]').should('be.checked');
|
||||
cy.get('input[type=text]').should('not.be.disabled');
|
||||
|
||||
// # Select channel
|
||||
cy.findByText('Select a channel').click().type('Town{enter}');
|
||||
});
|
||||
|
||||
// # Click Run Playbook
|
||||
cy.findByTestId('run-playbook').click({force: true});
|
||||
|
||||
// # Enter the run name
|
||||
const runName = 'run' + Date.now();
|
||||
cy.findByTestId('run-name-input').clear().type(runName);
|
||||
|
||||
// * Verify that channel configuration matches playbook config
|
||||
cy.findByTestId('link-existing-channel-radio').should('be.checked');
|
||||
cy.get('#link-existing-channel-selector').get('input[type=text]').should('be.enabled');
|
||||
cy.findByTestId('create-channel-radio').should('not.be.checked');
|
||||
cy.findByTestId('create-private-channel-radio').should('not.exist');
|
||||
|
||||
// # Click start run button
|
||||
cy.get('button[data-testid=modal-confirm-button]').click();
|
||||
|
||||
// * Verify the run is added to lhs
|
||||
cy.findByTestId('Runs').findByTestId(runName).should('exist');
|
||||
|
||||
// * Verify the channel is created
|
||||
cy.findByTestId('runinfo-channel-link').contains('Town');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbooks > list pagination', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
const ExtraPlaybooks = 20;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as user-1
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
});
|
||||
|
||||
// # Populate the DB with more elements to force the pagination
|
||||
for (let i = 0; i < ExtraPlaybooks; i++) {
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Elements before',
|
||||
memberIDs: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
it('reset page to 0 after search for an name with one value', () => {
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to Playbooks
|
||||
cy.findByTestId('playbooksLHSButton').click();
|
||||
|
||||
// # Click on next page
|
||||
cy.findByText('Next').click();
|
||||
|
||||
// # Click on Search input
|
||||
cy.get('input[placeholder="Search for a playbook"]').type('Playbook');
|
||||
|
||||
// * Verify the page display the first page
|
||||
cy.findByText('1–1 of 1 total');
|
||||
|
||||
// * Verify that previous isn't exist
|
||||
cy.findByText('Previous').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,578 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
const RUN_NAME_MAX_LENGTH = 64;
|
||||
|
||||
describe('playbooks > start a run', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
makePublic: true,
|
||||
memberIDs: [testUser.id],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
// This data is intentionally changed here instead of via api
|
||||
const fillPBE = ({name, summary, channelMode, channelNameToLink, defaultOwnerEnabled}) => {
|
||||
// # fill channel name temaplte
|
||||
if (name) {
|
||||
cy.get('#create-new-channel input[type="text"]').clear().type('Channel template');
|
||||
}
|
||||
|
||||
// # fill summary template
|
||||
if (summary) {
|
||||
cy.contains('run summary template').dblclick();
|
||||
cy.focused().type('run summary template');
|
||||
cy.findByRole('button', {name: /save/i}).click();
|
||||
}
|
||||
if (channelMode === 'create_new_channel') {
|
||||
cy.get('#create-new-channel input[type="radio"]').eq(0).click();
|
||||
} else if (channelMode === 'link_to_existing_channel') {
|
||||
cy.get('#link-existing-channel input[type="radio"]').click();
|
||||
}
|
||||
|
||||
if (channelNameToLink) {
|
||||
cy.get('#link-existing-channel').within(() => {
|
||||
cy.findByText('Select a channel').click().type(`${channelNameToLink}{enter}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (defaultOwnerEnabled) {
|
||||
cy.get('#assign-owner').within(() => {
|
||||
// * Verify that the toggle is unchecked
|
||||
cy.get('label input').should('not.be.checked');
|
||||
|
||||
// # Click on the toggle to enable the setting
|
||||
cy.get('label input').click({force: true});
|
||||
|
||||
// * Verify that the toggle is checked
|
||||
cy.get('label input').should('be.checked');
|
||||
});
|
||||
}
|
||||
};
|
||||
describe('from playbook list', () => {
|
||||
it('defaults', () => {
|
||||
// # Visit playbook list
|
||||
cy.visit('/playbooks/playbooks');
|
||||
|
||||
// # Click "Run" button on the first playbook
|
||||
cy.findAllByTestId('playbook-item').first().within(() => {
|
||||
cy.findByText('Run').click();
|
||||
});
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is filled
|
||||
cy.findByTestId('run-name-input').clear().type('Run name');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_list',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Run name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('from playbook editor', () => {
|
||||
describe('pbe configured as create new channel', () => {
|
||||
it('defaults', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// Fill default values
|
||||
fillPBE({name: 'Channel template', summary: 'run summary template', channelMode: 'create_new_channel', defaultOwnerEnabled: true});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is filled
|
||||
cy.findByTestId('run-name-input').should('have.value', 'Channel template');
|
||||
|
||||
// * Assert template summary is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_editor',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: false,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Channel template');
|
||||
|
||||
// * Verify run summary
|
||||
cy.findByTestId('run-summary-section').contains('run summary template');
|
||||
});
|
||||
|
||||
it('change title/summary', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Fill default values
|
||||
fillPBE({name: 'Channel template', summary: 'run summary template', channelMode: 'create_new_channel'});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template are filled (and force wait to them)
|
||||
cy.findByTestId('run-name-input').should('have.value', 'Channel template');
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Fill run summary
|
||||
cy.findByTestId('run-summary-input').clear().type('Test Run Summary');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_editor',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: true,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Test Run Name');
|
||||
|
||||
// * Verify run summary
|
||||
cy.findByTestId('run-summary-section').contains('Test Run Summary');
|
||||
});
|
||||
|
||||
it('change to link to existing channel does not default to current channel', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Fill default values
|
||||
fillPBE({name: 'Channel template', summary: 'run summary template', channelMode: 'create_new_channel', defaultOwnerEnabled: true});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// # Change to link to existing channel
|
||||
cy.findByTestId('link-existing-channel-radio').click();
|
||||
|
||||
// * Assert selected channel is unchanged
|
||||
cy.findByText('Select a channel').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('change to link to existing channel', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Fill default values
|
||||
fillPBE({name: 'Channel template', summary: 'run summary template', channelMode: 'create_new_channel', defaultOwnerEnabled: true});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// # Change to link to existing channel
|
||||
cy.findByTestId('link-existing-channel-radio').click();
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// * Assert cta is disabled
|
||||
cy.findByTestId('modal-confirm-button').should('be.disabled');
|
||||
|
||||
// # Fill Town square as the channel to be linked
|
||||
cy.findByText('Select a channel').click().type('Town{enter}');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_editor',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'link_existing_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: true,
|
||||
hasChannelIdChanged: true,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Test Run Name');
|
||||
|
||||
// # Click channel link
|
||||
cy.findByTestId('runinfo-channel-link').click();
|
||||
|
||||
// * Verify we are on town square
|
||||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pbe configured as linked to existing channel', () => {
|
||||
it('defaults', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Fill default values
|
||||
fillPBE({summary: 'run summary template', channelMode: 'link_to_existing_channel', channelNameToLink: 'Town'});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is empty
|
||||
cy.findByTestId('run-name-input').should('be.empty');
|
||||
|
||||
// * Assert template summary is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// * Assert button is still disabled
|
||||
cy.findByTestId('modal-confirm-button').should('be.disabled');
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_editor',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'link_existing_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Test Run Name');
|
||||
|
||||
// * Verify run summary
|
||||
cy.findByTestId('run-summary-section').contains('run summary template');
|
||||
|
||||
// # Click channel link
|
||||
cy.findByTestId('runinfo-channel-link').click();
|
||||
|
||||
// * Verify we are on town square
|
||||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('fill initially empty channel', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Fill default values
|
||||
fillPBE({summary: 'run summary template', channelMode: 'link_to_existing_channel'});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is empty
|
||||
cy.findByTestId('run-name-input').should('be.empty');
|
||||
|
||||
// * Assert template summary is filled
|
||||
cy.findByTestId('run-summary-input').should('have.value', 'run summary template');
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// * Assert button is still disabled
|
||||
cy.findByTestId('modal-confirm-button').should('be.disabled');
|
||||
|
||||
// # Fill Town square as the channel to be linked
|
||||
cy.findByText('Select a channel').click().type('Town{enter}');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_editor',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'link_existing_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: false,
|
||||
hasChannelIdChanged: true,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Test Run Name');
|
||||
|
||||
// * Verify run summary
|
||||
cy.findByTestId('run-summary-section').contains('run summary template');
|
||||
|
||||
// # Click channel link
|
||||
cy.findByTestId('runinfo-channel-link').click();
|
||||
|
||||
// * Verify we are on town square
|
||||
cy.url().should('include', `/${testTeam.name}/channels/town-square`);
|
||||
});
|
||||
|
||||
it('change to create new channel', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// Fill default values
|
||||
fillPBE({name: 'Channel template', summary: 'run summary template', channelMode: 'link_to_existing_channel', channelNameToLink: 'Town'});
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Change to create new channel
|
||||
cy.findByTestId('create-channel-radio').click();
|
||||
|
||||
// # Fill run name
|
||||
cy.findByTestId('run-name-input').clear().type('Test Run Name');
|
||||
|
||||
// # Click start button
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
});
|
||||
|
||||
// * Assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_create',
|
||||
type: 'track',
|
||||
properties: {
|
||||
place: 'backstage_playbook_editor',
|
||||
playbookId: testPlaybook.id,
|
||||
channelMode: 'create_new_channel',
|
||||
hasPlaybookChanged: false,
|
||||
hasNameChanged: true,
|
||||
hasSummaryChanged: false,
|
||||
hasChannelModeChanged: true,
|
||||
hasChannelIdChanged: false,
|
||||
hasPublicChanged: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify we are on RDP
|
||||
cy.url().should('include', '/playbooks/runs/');
|
||||
cy.url().should('include', '?from=run_modal');
|
||||
|
||||
// * Verify run name
|
||||
cy.get('h1').contains('Test Run Name');
|
||||
|
||||
// # Click channel link
|
||||
cy.findByTestId('runinfo-channel-link').click();
|
||||
|
||||
// * Verify we are on channel Test Run Name
|
||||
cy.url().should('include', `/${testTeam.name}/channels/test-run-name`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('start run modal > invalid user input', () => {
|
||||
it('submit button is disabled when run name is empty', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is empty
|
||||
cy.findByTestId('run-name-input').should('have.value', '');
|
||||
|
||||
// * Assert start button is disabled
|
||||
cy.findByTestId('modal-confirm-button').should('have.attr', 'disabled');
|
||||
});
|
||||
});
|
||||
|
||||
it('error is shown when maximum length of run name is exceeded', () => {
|
||||
// # Visit the selected playbook
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Click start a run button
|
||||
cy.findByTestId('run-playbook').click();
|
||||
|
||||
cy.get('#root-portal.modal-open').within(() => {
|
||||
// # Wait the modal to render
|
||||
cy.wait(500);
|
||||
|
||||
// * Assert template name is empty
|
||||
cy.findByTestId('run-name-input').should('have.value', '');
|
||||
|
||||
// # Type run name that exceeds maximum length
|
||||
cy.findByTestId('run-name-input').type('a'.repeat(RUN_NAME_MAX_LENGTH + 1));
|
||||
|
||||
// * Assert error shown and contains maximum length in message
|
||||
cy.findByTestId('run-name-error').should('contain', RUN_NAME_MAX_LENGTH);
|
||||
|
||||
// * Assert start button is disabled
|
||||
cy.findByTestId('modal-confirm-button').should('have.attr', 'disabled');
|
||||
|
||||
// # Delete last character via backspace
|
||||
cy.findByTestId('run-name-input').type('{backspace}');
|
||||
|
||||
// * Assert that error is not shown anymore
|
||||
cy.findByTestId('run-name-error').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbooks > edit status update', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPlaybook;
|
||||
let testChannel;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public channel
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'public-channel',
|
||||
'Public Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
testChannel = channel;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreateTestPlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
userId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Set a bigger viewport so the action don't scroll out of view
|
||||
cy.viewport('macbook-16');
|
||||
});
|
||||
|
||||
describe('status update enable/disable', () => {
|
||||
it('can enable/disable status update', () => {
|
||||
// # Visit the selected playbook outline tab
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// * Verify status update message
|
||||
cy.findAllByTestId('status-update-section').should('exist').within(() => {
|
||||
cy.contains('A status update is expected every');
|
||||
cy.contains('1 day');
|
||||
cy.contains('no channels');
|
||||
cy.contains('no outgoing webhooks');
|
||||
});
|
||||
|
||||
// # Disable status update
|
||||
cy.findAllByTestId('status-update-toggle').eq(0).click();
|
||||
|
||||
// * Verify status update message
|
||||
cy.findAllByTestId('status-update-section').should('exist').within(() => {
|
||||
cy.contains('Status updates are not expected.');
|
||||
cy.contains('A status update is expected every').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edit channels and webhooks', () => {
|
||||
it('can enable/disable status update', () => {
|
||||
// # Visit the selected playbook outline tab
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Select a channel
|
||||
cy.findAllByTestId('status-update-broadcast-channels').click();
|
||||
cy.get('#playbook-automation-broadcast').contains('Town Square').click({force: true});
|
||||
cy.findAllByTestId('status-update-broadcast-channels').click();
|
||||
|
||||
// # Refresh the page
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// # Add webhooks
|
||||
cy.findAllByTestId('status-update-webhooks').click();
|
||||
cy.findAllByTestId('webhooks-input').type('http://hook1.com{enter}http://hook2.com{enter}http://hook3.com{enter}');
|
||||
cy.findAllByTestId('checklist-item-save-button').click();
|
||||
|
||||
// # Refresh the page
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// * Verify status update message
|
||||
cy.findAllByTestId('status-update-section').should('exist').within(() => {
|
||||
cy.contains('1 channel');
|
||||
cy.contains('3 outgoing webhooks');
|
||||
});
|
||||
|
||||
// # Disable status update
|
||||
cy.findAllByTestId('status-update-toggle').eq(0).click();
|
||||
|
||||
// * Verify status update message
|
||||
cy.get('#status-updates').within(() => {
|
||||
cy.findByText('Status updates are not expected.').should('exist');
|
||||
});
|
||||
|
||||
// # Re-enable status update
|
||||
cy.findAllByTestId('status-update-toggle').eq(0).click();
|
||||
|
||||
// # Refresh the page
|
||||
cy.visit(`/playbooks/playbooks/${testPlaybook.id}/outline`);
|
||||
|
||||
// * Verify that channels and webhooks persist
|
||||
cy.get('#status-updates').within(() => {
|
||||
cy.contains('1 channel').should('exist');
|
||||
cy.contains('3 outgoing webhooks').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('status enabled, broadcasts disabled, but channels and webhooks specified', () => {
|
||||
it('can enable/disable status update', () => {
|
||||
const broadcastChannelIds = [testChannel.id];
|
||||
const webhookOnStatusUpdateURLs = ['https://one.com', 'https://two.com'];
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook #### (' + Date.now() + ')',
|
||||
userId: testUser.id,
|
||||
broadcastChannelIds,
|
||||
webhookOnStatusUpdateURLs,
|
||||
}).then((playbook) => {
|
||||
// # Visit the selected playbook outline tab
|
||||
cy.visit(`/playbooks/playbooks/${playbook.id}/outline`);
|
||||
|
||||
// * Verify status update message. Status update should be enabled, but message should say `updates will be posted to no channels and no outgoing webhooks`
|
||||
cy.findAllByTestId('status-update-section').should('exist').within(() => {
|
||||
cy.contains('A status update is expected every');
|
||||
cy.contains('no channels');
|
||||
cy.contains('no outgoing webhooks');
|
||||
});
|
||||
|
||||
// * Verify selected channels style
|
||||
cy.findAllByTestId('status-update-broadcast-channels').click();
|
||||
cy.get('.playbook-react-select__option').contains('Public Channel').
|
||||
invoke('css', 'text-decoration').
|
||||
should('equal', 'line-through solid rgba(63, 67, 80, 0.48)');
|
||||
|
||||
// # Close select options
|
||||
cy.findAllByTestId('status-update-broadcast-channels').click();
|
||||
|
||||
// # Open webhooks text area
|
||||
cy.findAllByTestId('status-update-webhooks').click();
|
||||
|
||||
// * Verify webhooks text style
|
||||
cy.findAllByTestId('webhooks-input').
|
||||
invoke('css', 'text-decoration').
|
||||
should('equal', 'line-through solid rgba(63, 67, 80, 0.48)');
|
||||
|
||||
// # Edit webhooks
|
||||
cy.findAllByTestId('webhooks-input').type('http://hook1.com{enter}http://hook2.com{enter}http://hook3.com{enter}');
|
||||
cy.findAllByTestId('checklist-item-save-button').click();
|
||||
|
||||
// # Select a channel
|
||||
cy.findAllByTestId('status-update-broadcast-channels').click();
|
||||
cy.get('#playbook-automation-broadcast').contains('Town Square').click({force: true});
|
||||
cy.findAllByTestId('status-update-broadcast-channels').click();
|
||||
|
||||
// * Verify status update message.
|
||||
cy.findAllByTestId('status-update-section').should('exist').within(() => {
|
||||
cy.contains('A status update is expected every');
|
||||
cy.contains('2 channels');
|
||||
cy.contains('4 outgoing webhooks');
|
||||
});
|
||||
|
||||
// # Refresh the page
|
||||
cy.visit(`/playbooks/playbooks/${playbook.id}/outline`);
|
||||
|
||||
// * Verify status update message.
|
||||
cy.findAllByTestId('status-update-section').should('exist').within(() => {
|
||||
cy.contains('A status update is expected every');
|
||||
cy.contains('2 channels');
|
||||
cy.contains('4 outgoing webhooks');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
262
e2e-tests/cypress/tests/integration/playbooks/runs/list_spec.js
Обычный файл
262
e2e-tests/cypress/tests/integration/playbooks/runs/list_spec.js
Обычный файл
@@ -0,0 +1,262 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > list', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testAnotherUser;
|
||||
let testPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
// # Create another user
|
||||
cy.apiCreateUser().then(({user: anotherUser}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
testAnotherUser = anotherUser;
|
||||
cy.apiAddUserToTeam(testTeam.id, anotherUser.id);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
makePublic: true,
|
||||
memberIDs: [testUser.id, testAnotherUser.id],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show all
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
it('has "Runs" and team name in heading', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to playbook runs
|
||||
cy.findByTestId('playbookRunsLHSButton').click();
|
||||
|
||||
// * Assert contents of heading.
|
||||
cy.findByTestId('titlePlaybookRun').should('exist').contains('Runs');
|
||||
});
|
||||
|
||||
it('loads playbook run details page when clicking on a playbook run', () => {
|
||||
// # Run the playbook
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Switch to runs
|
||||
cy.findByTestId('playbookRunsLHSButton').click();
|
||||
|
||||
// # Find the playbook run and click to open details view
|
||||
cy.get('#playbookRunList').within(() => {
|
||||
cy.findByText(playbookRunName).click();
|
||||
});
|
||||
|
||||
// * Verify that the header contains the playbook run name
|
||||
cy.findByTestId('run-header-section').get('h1').contains(playbookRunName);
|
||||
});
|
||||
|
||||
describe('filters my runs only', () => {
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Run a playbook with testUser as a participant
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'testUsers Run',
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
|
||||
// # Login as testAnotherUser
|
||||
cy.apiLogin(testAnotherUser);
|
||||
|
||||
// # Run a playbook with testAnotherUser as a participant
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'testAnotherUsers Run',
|
||||
|
||||
// ownerUserId: testUser.id,
|
||||
ownerUserId: testAnotherUser.id,
|
||||
});
|
||||
});
|
||||
|
||||
it('for testUser', () => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks/runs');
|
||||
|
||||
cy.get('#playbookRunList').within(() => {
|
||||
// # Make sure both runs are visible by default
|
||||
cy.findByText('testUsers Run').should('be.visible');
|
||||
cy.findByText('testAnotherUsers Run').should('be.visible');
|
||||
|
||||
// # Filter to only my runs
|
||||
cy.findByTestId('my-runs-only').click();
|
||||
|
||||
// # Verify runs by testAnotherUser are not visible
|
||||
cy.findByText('testAnotherUsers Run').should('not.exist');
|
||||
|
||||
// # Verify runs by testUser remain visible
|
||||
cy.findByText('testUsers Run').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('for testAnotherUser', () => {
|
||||
// # Login as testAnotherUser
|
||||
cy.apiLogin(testAnotherUser);
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
cy.get('#playbookRunList').within(() => {
|
||||
// Make sure both runs are visible by default
|
||||
cy.findByText('testUsers Run').should('be.visible');
|
||||
cy.findByText('testAnotherUsers Run').should('be.visible');
|
||||
|
||||
// # Filter to only my runs
|
||||
cy.findByTestId('my-runs-only').click();
|
||||
|
||||
// # Verify runs by testUser are not visible
|
||||
cy.findByText('testUsers Run').should('not.exist');
|
||||
|
||||
// # Verify runs by testAnotherUser remain visible
|
||||
cy.findByText('testAnotherUsers Run').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filters Finished runs correctly', () => {
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Run a playbook with testUser as a participant
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'testUsers Run to be finished',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbook) => {
|
||||
cy.apiFinishRun(playbook.id);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows finished runs', () => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Open the product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
cy.get('#playbookRunList').within(() => {
|
||||
// # Make sure runs are visible by default, and finished is not
|
||||
cy.findByText('testUsers Run').should('be.visible');
|
||||
cy.findByText('testAnotherUsers Run').should('be.visible');
|
||||
cy.findByText('testUsers Run to be finished').should('not.exist');
|
||||
|
||||
// # Filter to finished runs as well
|
||||
cy.findByTestId('finished-runs').click();
|
||||
|
||||
// # Verify runs remain visible
|
||||
cy.findByText('testUsers Run').should('be.visible');
|
||||
cy.findByText('testAnotherUsers Run').should('be.visible');
|
||||
|
||||
// # Verify finished run is visible
|
||||
cy.findByText('testUsers Run to be finished').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('LHS run list', () => {
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
const runs = [
|
||||
{
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'run-sort-check 0',
|
||||
ownerUserId: testUser.id,
|
||||
},
|
||||
{
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'run-sort-check 1',
|
||||
ownerUserId: testUser.id,
|
||||
},
|
||||
{
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'run-sort-check 2',
|
||||
ownerUserId: testUser.id,
|
||||
},
|
||||
{
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: 'run-sort-check 3',
|
||||
ownerUserId: testUser.id,
|
||||
},
|
||||
];
|
||||
|
||||
Promise.all(runs.map((run) => {
|
||||
return new Promise((resolve) => cy.apiRunPlaybook(run).then(resolve));
|
||||
})).then(() => {
|
||||
cy.visit('/playbooks');
|
||||
});
|
||||
});
|
||||
|
||||
it('lhs run list sorted by name', () => {
|
||||
cy.findByTestId('lhs-navigation').within(() => {
|
||||
cy.get('li:contains(run-sort-check)').each((item, index) => {
|
||||
// * Verify run list order
|
||||
cy.wrap(item).should('have.text', 'run-sort-check ' + index);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,506 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
/* eslint-disable no-only-tests/no-only-tests */
|
||||
|
||||
import {getRandomId} from '../../../utils';
|
||||
|
||||
describe('runs > permissions', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testOtherTeam;
|
||||
|
||||
let playbookMember;
|
||||
let runParticipant;
|
||||
let runFollower;
|
||||
let teamMember;
|
||||
let nonTeamMember;
|
||||
let sysadminInTeam;
|
||||
let sysadminNotInTeam;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create a dedicated playbook member
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
playbookMember = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a dedicated run participant
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
runParticipant = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a dedicated run follower
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
runFollower = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a dedicated member in team 1
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
teamMember = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a dedicated sysadmin in team 1
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin: createdUser}) => {
|
||||
sysadminInTeam = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a public playbook and corresponding run with a public channel in
|
||||
// team 1. This is to ensure the list isn't empty for users who can't access the
|
||||
// run under test.
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (Team 1)',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((createdPlaybook) => {
|
||||
// Create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: createdPlaybook.id,
|
||||
playbookRunName: getRandomId(),
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
});
|
||||
|
||||
// # Create another team
|
||||
cy.apiCreateTeam('second-team', 'Second Team').then(({team: createdTeam}) => {
|
||||
testOtherTeam = createdTeam;
|
||||
|
||||
// # Create a dedicated member not in team 1
|
||||
cy.apiCreateUser().then(({user: createdUser}) => {
|
||||
nonTeamMember = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testOtherTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a dedicated sysadmin not in team 1
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin: createdUser}) => {
|
||||
sysadminNotInTeam = createdUser;
|
||||
|
||||
cy.apiAddUserToTeam(testOtherTeam.id, createdUser.id);
|
||||
});
|
||||
|
||||
// # Create a public playbook and corresponding run with a public channel in
|
||||
// team 2. This is to ensure the list isn't empty for users who can't access the
|
||||
// run under test.
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testOtherTeam.id,
|
||||
title: 'Playbook (Team 2)',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((createdPlaybook) => {
|
||||
// Create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testOtherTeam.id,
|
||||
playbookId: createdPlaybook.id,
|
||||
playbookRunName: getRandomId(),
|
||||
ownerUserId: nonTeamMember.id,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run with private channel from a public playbook', () => {
|
||||
let playbook;
|
||||
let run;
|
||||
|
||||
before(() => {
|
||||
// # Login as the user setup during initialization.
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook, configured to create private channels for runs
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: false,
|
||||
}).then((createdPlaybook) => {
|
||||
playbook = createdPlaybook;
|
||||
|
||||
// Create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: getRandomId(),
|
||||
ownerUserId: runParticipant.id,
|
||||
}).then((createdRun) => {
|
||||
run = createdRun;
|
||||
|
||||
// Have the dedicated participant join the run
|
||||
cy.apiAddUsersToRun(run.id, [runParticipant.id]);
|
||||
|
||||
// # Have the dedicated follower follow this playbook run
|
||||
cy.apiLogin(runFollower);
|
||||
cy.apiFollowPlaybookRun(run.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be visible', () => {
|
||||
// XXX: Skipping this test, since public playbooks currently have no members. This will
|
||||
// likely change in the future, so keeping the skeleton.
|
||||
it.skip('to playbook members', () => {
|
||||
assertRunIsVisible(run, playbookMember);
|
||||
});
|
||||
|
||||
it('to run participants', () => {
|
||||
assertRunIsVisible(run, runParticipant);
|
||||
});
|
||||
|
||||
it('to run followers', () => {
|
||||
assertRunIsVisible(run, runFollower);
|
||||
});
|
||||
|
||||
it('to team members', () => {
|
||||
assertRunIsVisible(run, teamMember);
|
||||
});
|
||||
|
||||
it('to admins in the team', () => {
|
||||
assertRunIsVisible(run, sysadminInTeam);
|
||||
});
|
||||
|
||||
// XXX: The following asserts that while sysadmins don't see runs from other teams in
|
||||
// the list, they still have access to view the overview directly. Once we support
|
||||
// sudo-admins, we should change this behaviour to be consistent with normal users.
|
||||
it('to admins not in the team (overview only)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunOverviewIsVisible(run);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not be visible', () => {
|
||||
it('to non-team members', () => {
|
||||
assertRunIsNotVisible(run, nonTeamMember);
|
||||
});
|
||||
|
||||
it('to admins not in the team (list only)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunIsNotVisibleInList(run);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run with public channel from a public playbook', () => {
|
||||
let playbook;
|
||||
let run;
|
||||
|
||||
before(() => {
|
||||
// # Login as the user setup during initialization.
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook, configured to create public channels for runs
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((createdPlaybook) => {
|
||||
playbook = createdPlaybook;
|
||||
|
||||
// Create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: getRandomId(),
|
||||
ownerUserId: runParticipant.id,
|
||||
}).then((createdRun) => {
|
||||
run = createdRun;
|
||||
|
||||
// Have the dedicated participant join the run
|
||||
cy.apiAddUsersToRun(run.id, [runParticipant.id]);
|
||||
|
||||
// # Have the dedicated follower follow this playbook run
|
||||
cy.apiLogin(runFollower);
|
||||
cy.apiFollowPlaybookRun(run.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be visible', () => {
|
||||
// XXX: Skipping this test, since public playbooks currently have no members. This will
|
||||
// likely change in the future.
|
||||
it.skip('to playbook members', () => {
|
||||
assertRunIsVisible(run, playbookMember);
|
||||
});
|
||||
|
||||
it('to run participants', () => {
|
||||
assertRunIsVisible(run, runParticipant);
|
||||
});
|
||||
|
||||
it('to run followers', () => {
|
||||
assertRunIsVisible(run, runFollower);
|
||||
});
|
||||
|
||||
it('to team members', () => {
|
||||
assertRunIsVisible(run, teamMember);
|
||||
});
|
||||
|
||||
it('to admins in the team', () => {
|
||||
assertRunIsVisible(run, sysadminInTeam);
|
||||
});
|
||||
|
||||
// XXX: The following asserts that while sysadmins don't see runs from other teams in
|
||||
// the list, they still have access to view the overview directly. Once we support
|
||||
// sudo-admins, we should change this behaviour to be consistent with normal users.
|
||||
it('to admins not in the team (overview only)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunOverviewIsVisible(run);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not be visible', () => {
|
||||
it('to non-team members', () => {
|
||||
assertRunIsNotVisible(run, nonTeamMember);
|
||||
});
|
||||
|
||||
it('to admins not in the team (list only)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunIsNotVisibleInList(run);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run with private channel from a private playbook', () => {
|
||||
let playbook;
|
||||
let run;
|
||||
|
||||
before(() => {
|
||||
// # Login as the user setup during initialization.
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create private playbook, configured to create private channels for runs
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
makePublic: false,
|
||||
memberIDs: [testUser.id, playbookMember.id],
|
||||
createPublicPlaybookRun: false,
|
||||
}).then((createdPlaybook) => {
|
||||
playbook = createdPlaybook;
|
||||
|
||||
// Login as the playbook member authorized to start a run
|
||||
cy.apiLogin(playbookMember);
|
||||
|
||||
// Create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: getRandomId(),
|
||||
ownerUserId: runParticipant.id,
|
||||
}).then((createdRun) => {
|
||||
run = createdRun;
|
||||
|
||||
// Have the dedicated participant join the run
|
||||
cy.apiAddUsersToRun(run.id, [runParticipant.id]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be visible', () => {
|
||||
it('to playbook members', () => {
|
||||
assertRunIsVisible(run, playbookMember);
|
||||
});
|
||||
|
||||
it('to run participants', () => {
|
||||
assertRunIsVisible(run, runParticipant);
|
||||
});
|
||||
|
||||
// Skipping this test, since followers cannot follow a run with a private channel from
|
||||
// a private playbook. (But leaving it for clarity in the code.)
|
||||
it.skip('to run followers', () => {
|
||||
assertRunIsVisible(run, runFollower);
|
||||
});
|
||||
|
||||
it('to admins in the team', () => {
|
||||
assertRunIsVisible(run, sysadminInTeam);
|
||||
});
|
||||
|
||||
// XXX: The following asserts that while sysadmins don't see runs from other teams in
|
||||
// the list, they still have access to view the run directly. Once we support
|
||||
// sudo-admins, we should change this behaviour to be consistent with normal users.
|
||||
it('to admins not in the team (run directly)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunOverviewIsVisible(run);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not be visible', () => {
|
||||
it('to team members', () => {
|
||||
assertRunIsNotVisible(run, teamMember);
|
||||
});
|
||||
|
||||
it('to non-team members', () => {
|
||||
assertRunIsNotVisible(run, nonTeamMember);
|
||||
});
|
||||
|
||||
it('to admins not in the team (list only)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunIsNotVisibleInList(run);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run with public channel from a private playbook', () => {
|
||||
let playbook;
|
||||
let run;
|
||||
|
||||
before(() => {
|
||||
// # Login as the user setup during initialization.
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create private playbook, configured to create private channels for runs
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook',
|
||||
memberIDs: [testUser.id, playbookMember.id],
|
||||
makePublic: false,
|
||||
createPublicPlaybookRun: true,
|
||||
}).then((createdPlaybook) => {
|
||||
playbook = createdPlaybook;
|
||||
|
||||
// Login as the playbook member authorized to start a run
|
||||
cy.apiLogin(playbookMember);
|
||||
|
||||
// Create a run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: getRandomId(),
|
||||
ownerUserId: runParticipant.id,
|
||||
}).then((createdRun) => {
|
||||
run = createdRun;
|
||||
|
||||
// Have the dedicated participant join the run
|
||||
cy.apiAddUsersToRun(run.id, [runParticipant.id]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should be visible', () => {
|
||||
it('to playbook members', () => {
|
||||
assertRunIsVisible(run, playbookMember);
|
||||
});
|
||||
|
||||
it('to run participants', () => {
|
||||
assertRunIsVisible(run, runParticipant);
|
||||
});
|
||||
|
||||
// Skipping this test, since followers cannot follow a run with a private channel from
|
||||
// a private playbook. (But leaving it for clarity in the code.)
|
||||
it.skip('to run followers', () => {
|
||||
assertRunIsVisible(run, runFollower);
|
||||
});
|
||||
|
||||
it('to admins in the team', () => {
|
||||
assertRunIsVisible(run, sysadminInTeam);
|
||||
});
|
||||
|
||||
// XXX: The following asserts that while sysadmins don't see runs from other teams in
|
||||
// the list, they still have access to view the run directly. Once we support
|
||||
// sudo-admins, we should change this behaviour to be consistent with normal users.
|
||||
it('to admins not in the team (run directly)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunOverviewIsVisible(run);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should not be visible', () => {
|
||||
it('to team members', () => {
|
||||
assertRunIsNotVisible(run, teamMember);
|
||||
});
|
||||
|
||||
it('to non-team members', () => {
|
||||
assertRunIsNotVisible(run, nonTeamMember);
|
||||
});
|
||||
|
||||
it('to admins not in the team (list only)', () => {
|
||||
cy.apiLogin(sysadminNotInTeam);
|
||||
|
||||
assertRunIsNotVisibleInList(run);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const assertRunIsVisible = (run, user) => {
|
||||
// # Login as the user in question
|
||||
cy.apiLogin(user);
|
||||
|
||||
// # Open Runs
|
||||
cy.visit('/playbooks/runs');
|
||||
|
||||
// # Find the playbook run and click to open details view
|
||||
cy.get('#playbookRunList').within(() => {
|
||||
cy.findByText(run.name).click();
|
||||
});
|
||||
|
||||
// * Verify that the details loaded
|
||||
cy.findByTestId('run-header-section').get('h1').contains(run.name);
|
||||
};
|
||||
|
||||
const assertRunOverviewIsVisible = (run) => {
|
||||
// # Opening the playbook run directly
|
||||
cy.visit(`/playbooks/runs/${run.id}`);
|
||||
|
||||
// * Verify that the details loaded
|
||||
cy.findByTestId('run-header-section').get('h1').contains(run.name);
|
||||
};
|
||||
|
||||
const assertRunIsNotVisible = (run, user) => {
|
||||
// # Login as the user in question
|
||||
cy.apiLogin(user);
|
||||
|
||||
assertRunIsNotVisibleInList(run, user);
|
||||
assertRunOverviewIsNotVisible(run, user);
|
||||
};
|
||||
|
||||
const assertRunIsNotVisibleInList = (run) => {
|
||||
// # Open Runs
|
||||
cy.visit('/playbooks/runs');
|
||||
|
||||
// * Verify the playbook run is not visible
|
||||
cy.get('#playbookRunList').within(() => {
|
||||
cy.findByText(run.name).should('not.exist');
|
||||
});
|
||||
};
|
||||
|
||||
const assertRunOverviewIsNotVisible = (run) => {
|
||||
// # Opening the playbook run directly
|
||||
cy.visit(`/playbooks/runs/${run.id}`);
|
||||
|
||||
// * Verify the not found error screen
|
||||
cy.get('.error__container').within(() => {
|
||||
cy.findByText('Run not found').should('be.visible');
|
||||
cy.findByText('The run you\'re requesting is private or does not exist.').should('be.visible');
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testPublicPlaybook;
|
||||
let testPlaybookRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to not found error if the playbook run is unknown', () => {
|
||||
// # Visit the URL of a non-existing playbook run
|
||||
cy.visit('/playbooks/runs/an_unknown_id');
|
||||
|
||||
// * Verify that the user has been redirected to the playbook runs not found error page
|
||||
cy.url().should('include', '/playbooks/error?type=playbook_runs');
|
||||
});
|
||||
|
||||
it('telemetry is triggered', () => {
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Visit the URL of a non-existing playbook run
|
||||
cy.visit(`/playbooks/runs/${testPlaybookRun.id}`);
|
||||
|
||||
// * assert telemetry pageview
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_details',
|
||||
type: 'page',
|
||||
properties: {
|
||||
from: '',
|
||||
role: 'participant',
|
||||
playbookrun_id: testPlaybookRun.id,
|
||||
playbook_id: testPublicPlaybook.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
// Note that this test checks the basic behavior in Run details page as participant / viewer
|
||||
// It relies on the Channel RHS Checklist test to cover the full behavior of the checklists
|
||||
|
||||
describe('runs > run details page > checklist', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
const taskIndex = 0;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
const getChecklist = () => cy.findByTestId('run-checklist-section');
|
||||
const getChecklistTasks = () => getChecklist().findAllByTestId('checkbox-item-container');
|
||||
|
||||
const commonTests = () => {
|
||||
it('is visible', () => {
|
||||
// * Verify the tasks section is present
|
||||
getChecklist().should('be.visible');
|
||||
});
|
||||
|
||||
it('has title', () => {
|
||||
// * Verify the task section has a title
|
||||
getChecklist().find('h3').contains('Tasks');
|
||||
});
|
||||
|
||||
it('can see the tasks', () => {
|
||||
// * Verify tasks are shown
|
||||
getChecklistTasks().should('have.length', 4);
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
|
||||
it('click marks task as done', () => {
|
||||
// # Click first task
|
||||
getChecklistTasks().eq(taskIndex).find('.checkbox').check({force: true});
|
||||
|
||||
// * Assert checkbox is checked
|
||||
getChecklistTasks().eq(taskIndex).find('.checkbox').should('be.checked');
|
||||
});
|
||||
|
||||
it('has hover menu', () => {
|
||||
// # Hover over the checklist item
|
||||
getChecklistTasks().eq(taskIndex).trigger('mouseover');
|
||||
|
||||
// # Click dot menu
|
||||
getChecklistTasks().eq(taskIndex).findByTitle('More').click({force: true});
|
||||
|
||||
// * Assert actions are available
|
||||
cy.findByRole('button', {name: 'Skip task'}).should('be.visible');
|
||||
cy.findByRole('button', {name: 'Duplicate task'}).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('click does not work', () => {
|
||||
// # Click first task
|
||||
getChecklistTasks().eq(taskIndex).find('.checkbox').should('have.attr', 'readonly');
|
||||
});
|
||||
|
||||
it('has not hover menu', () => {
|
||||
// # Hover over the checklist item
|
||||
getChecklistTasks().eq(taskIndex).trigger('mouseover');
|
||||
|
||||
// * Check that the hover menu is not rendered
|
||||
getChecklistTasks().eq(taskIndex).findByTitle('More').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > finish', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPlaybookRun;
|
||||
let testPublicPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name(' + Date.now() + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('is hidden as viewer', () => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${testPlaybookRun.id}`);
|
||||
});
|
||||
|
||||
// * Assert that finish section does not exist
|
||||
cy.findByTestId('run-finish-section').should('not.exist');
|
||||
});
|
||||
|
||||
it('is visible', () => {
|
||||
// * Verify the finish section is present
|
||||
cy.findByTestId('run-finish-section').should('be.visible');
|
||||
});
|
||||
|
||||
it('has a placeholder visible', () => {
|
||||
// * Verify the placeholder is present
|
||||
cy.findByTestId('run-finish-section').contains('Time to wrap up?');
|
||||
});
|
||||
|
||||
describe('finish run', () => {
|
||||
it('can be confirmed', () => {
|
||||
// # Click finish run button
|
||||
cy.findByTestId('run-finish-section').find('button').click();
|
||||
|
||||
// * Check that status badge is in-progress
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
|
||||
// * Check that finish run modal is open and has the right title
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModal').find('h1').contains('Confirm finish run');
|
||||
|
||||
// # Click on confirm
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
|
||||
// * Assert finish section is not visible anymore
|
||||
cy.findByTestId('run-finish-section').should('not.exist');
|
||||
|
||||
// * Assert status badge is finished
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('Finished');
|
||||
|
||||
// * Verify run has been removed from LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(testPlaybookRun.name).should('not.exist');
|
||||
});
|
||||
|
||||
it('can be canceled', () => {
|
||||
// # Click on finish run
|
||||
cy.findByTestId('run-finish-section').find('button').click();
|
||||
|
||||
// * Check that status badge is in-progress
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
|
||||
// * Check that finish run modal is open
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModal').find('h1').contains('Confirm finish run');
|
||||
|
||||
// # Click on cancel
|
||||
cy.get('#confirmModal').get('#cancelModalButton').click();
|
||||
|
||||
// * Check that status badge is still in-progress
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
|
||||
// * Check that section is still visible
|
||||
cy.findByTestId('run-finish-section').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,886 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
/* eslint-disable no-only-tests/no-only-tests */
|
||||
|
||||
import {stubClipboard} from '../../../utils';
|
||||
|
||||
describe('runs > run details page > header', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testPublicPlaybookAndChannel;
|
||||
let playbookRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// # Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
createPublicPlaybookRun: true,
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybookAndChannel = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const openRunActionsModal = () => {
|
||||
// # Click on the run actions modal button
|
||||
cy.findByRole('button', {name: /Run Actions/i}).click({force: true});
|
||||
|
||||
// * Verify that the modal is shown
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('exist');
|
||||
};
|
||||
|
||||
const saveRunActionsModal = () => {
|
||||
// # Click on the Save button without changing anything
|
||||
cy.findByRole('button', {name: /Save/i}).click();
|
||||
|
||||
// * Verify that the modal is no longer there
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('not.exist');
|
||||
};
|
||||
|
||||
const getHeader = () => {
|
||||
return cy.findByTestId('run-header-section');
|
||||
};
|
||||
|
||||
const getHeaderIcon = (selector) => {
|
||||
return getHeader().find(selector);
|
||||
};
|
||||
|
||||
const getDropdownItemByText = (text) => {
|
||||
cy.findByTestId('run-header-section').find('h1').click();
|
||||
return cy.findByTestId('dropdownmenu').findByText(text);
|
||||
};
|
||||
|
||||
const commonHeaderTests = () => {
|
||||
it('shows the title', () => {
|
||||
// * Assert title is shown in h1 inside header
|
||||
cy.findByTestId('run-header-section').find('h1').contains(playbookRun.name);
|
||||
});
|
||||
|
||||
it('shows the in-progress status badge', () => {
|
||||
// * Assert in progress status badge
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
});
|
||||
|
||||
it('has a copy-link icon', () => {
|
||||
// # Mouseover on the icon
|
||||
getHeaderIcon('.icon-link-variant').trigger('mouseover');
|
||||
|
||||
// * Assert tooltip is shown
|
||||
cy.get('#copy-run-link-tooltip').should('contain', 'Copy link to run');
|
||||
|
||||
stubClipboard().as('clipboard');
|
||||
getHeaderIcon('.icon-link-variant').click().then(() => {
|
||||
// * Verify that tooltip text changed
|
||||
cy.get('#copy-run-link-tooltip').should('contain', 'Copied!');
|
||||
|
||||
// * Verify clipboard content
|
||||
cy.get('@clipboard').its('contents').should('contain', `/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const commonContextDropdownTests = () => {
|
||||
it('shows on click', () => {
|
||||
// # Click title
|
||||
cy.findByTestId('run-header-section').find('h1').click();
|
||||
|
||||
// * Assert context menu is opened
|
||||
cy.findByTestId('dropdownmenu').should('be.visible');
|
||||
});
|
||||
|
||||
it('can copy link', () => {
|
||||
stubClipboard().as('clipboard');
|
||||
|
||||
getDropdownItemByText('Copy link').click().then(() => {
|
||||
// * Verify clipboard content
|
||||
cy.get('@clipboard').its('contents').should('contain', `/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name(' + Date.now() + ')',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
playbookRun = run;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
describe('title, icons and buttons', () => {
|
||||
commonHeaderTests();
|
||||
|
||||
it('has not participate button', () => {
|
||||
// * Assert button is not showed
|
||||
getHeader().findByText('Participate').should('not.exist');
|
||||
});
|
||||
|
||||
describe('run actions', () => {
|
||||
describe('modal behaviour', () => {
|
||||
it('shows and hides as expected', () => {
|
||||
// * Verify that the run actions modal is shown when clicking on the button
|
||||
openRunActionsModal();
|
||||
|
||||
// # Click on the Cancel button
|
||||
cy.findByRole('button', {name: /Cancel/i}).click();
|
||||
|
||||
// * Verify that the modal is no longer there
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('not.exist');
|
||||
|
||||
// # Open the run actions modal
|
||||
openRunActionsModal();
|
||||
|
||||
// Intercept all telemetry calls
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// * Verify that saving the modal hides it
|
||||
saveRunActionsModal();
|
||||
|
||||
// * assert telemetry call
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_update_actions',
|
||||
type: 'track',
|
||||
properties: {
|
||||
playbookrun_id: playbookRun.id,
|
||||
playbook_id: playbookRun.playbook_id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('can not save an invalid form', () => {
|
||||
// * Verify that the run actions modal is shown when clicking on the button
|
||||
openRunActionsModal();
|
||||
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).within(() => {
|
||||
// # click on webhooks toggle
|
||||
cy.findByText('Send outgoing webhook').click();
|
||||
|
||||
// # Type an invalid webhook URL
|
||||
cy.getStyledComponent('TextArea').clear().type('invalidurl');
|
||||
|
||||
// # Click outside textarea
|
||||
cy.findByText('Run Actions').click();
|
||||
|
||||
// * Assert the error message is displayed
|
||||
cy.findByText('Invalid webhook URLs').should('be.visible');
|
||||
|
||||
// # Click save
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Assert that modal is still open
|
||||
cy.findByText('Run Actions').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('honours the settings from the playbook', () => {
|
||||
cy.apiCreateChannel(
|
||||
testTeam.id,
|
||||
'action-channel',
|
||||
'Action Channel',
|
||||
'O',
|
||||
).then(({channel}) => {
|
||||
// # Create a different playbook with both settings enabled and populated with data,
|
||||
// # and then start a run from it
|
||||
const broadcastChannelIds = [channel.id];
|
||||
const webhookOnStatusUpdateURLs = ['https://one.com', 'https://two.com'];
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook' + Date.now(),
|
||||
broadcastEnabled: true,
|
||||
broadcastChannelIds,
|
||||
webhookOnStatusUpdateEnabled: true,
|
||||
webhookOnStatusUpdateURLs,
|
||||
}).then((playbook) => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: 'Run with actions preconfigured',
|
||||
ownerUserId: testUser.id,
|
||||
});
|
||||
});
|
||||
|
||||
// # Navigate to the run page
|
||||
cy.visit(`/${testTeam.name}/channels/run-with-actions-preconfigured`);
|
||||
cy.findByRole('button', {name: /Run details/i}).click({force: true});
|
||||
|
||||
// # Open the run actions modal
|
||||
openRunActionsModal();
|
||||
|
||||
// * Verify that the broadcast-to-channels toggle is checked
|
||||
cy.findByText('Broadcast update to selected channels').parent().within(() => {
|
||||
cy.get('input').should('be.checked');
|
||||
});
|
||||
|
||||
// * Verify that the channel is in the selector
|
||||
cy.findByText(channel.display_name);
|
||||
|
||||
// * Verify that the send-webhooks toggle is checked
|
||||
cy.findByText('Send outgoing webhook').parent().within(() => {
|
||||
cy.get('input').should('be.checked');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('trigger: when a status update is posted', () => {
|
||||
describe('action: Broadcast update to selected channels', () => {
|
||||
it('shows channel information on first load', () => {
|
||||
// # Open the run actions modal
|
||||
openRunActionsModal();
|
||||
|
||||
// # Enable broadcast to channels
|
||||
cy.findByText('Broadcast update to selected channels').click();
|
||||
|
||||
// # Select a couple of channels
|
||||
cy.findByText('Select channels').click().type('town square{enter}off-topic{enter}');
|
||||
|
||||
// # Save the changes
|
||||
saveRunActionsModal();
|
||||
|
||||
// # Reload the page, so that the store is not pre-populated when visiting Channels
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}/overview`);
|
||||
|
||||
// # Open the run actions modal
|
||||
openRunActionsModal();
|
||||
|
||||
// * Check that the channels previously added are shown with their full name,
|
||||
// * verifying that the store has been populated by the modal component.
|
||||
cy.findByText('Town Square').should('exist');
|
||||
cy.findByText('Off-Topic').should('exist');
|
||||
});
|
||||
|
||||
it('broadcasts to two channels configured when it is enabled', () => {
|
||||
// # Open the run actions modal
|
||||
openRunActionsModal();
|
||||
|
||||
// # Enable broadcast to channels
|
||||
cy.findByText('Broadcast update to selected channels').click();
|
||||
|
||||
// # Select a couple of channels
|
||||
cy.findByText('Select channels').click().type('town square{enter}off-topic{enter}', {delay: 100});
|
||||
|
||||
// # Save the changes
|
||||
saveRunActionsModal();
|
||||
|
||||
// # Post a status update, with a reminder in 1 second.
|
||||
const message = 'Status update - ' + Date.now();
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: playbookRun.id,
|
||||
message,
|
||||
});
|
||||
|
||||
// # Navigate to the town square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify that the last post contains the status update
|
||||
cy.getLastPost().then((post) => {
|
||||
cy.get(post).contains(message);
|
||||
});
|
||||
|
||||
// # Navigate to the off-topic channel
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// * Verify that the last post contains the status update
|
||||
cy.getLastPost().then((post) => {
|
||||
cy.get(post).contains(message);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not broadcast if it is disabled, even if there are channels configured', () => {
|
||||
// # Open the run actions modal
|
||||
openRunActionsModal();
|
||||
|
||||
// # Enable broadcast to channels
|
||||
cy.findByText('Broadcast update to selected channels').click();
|
||||
|
||||
// # Select a couple of channels
|
||||
cy.findByText('Select channels').click().type('town square{enter}off-topic{enter}', {delay: 100});
|
||||
|
||||
// # Disable broadcast to channels
|
||||
cy.findByText('Broadcast update to selected channels').click();
|
||||
|
||||
// # Save the changes
|
||||
saveRunActionsModal();
|
||||
|
||||
// # Post a status update, with a reminder in 1 second.
|
||||
const message = 'Status update - ' + Date.now();
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: playbookRun.id,
|
||||
message,
|
||||
});
|
||||
|
||||
// # Navigate to the town square channel
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
|
||||
// * Verify that the last post does not contain the status update
|
||||
cy.getLastPost().then((post) => {
|
||||
cy.get(post).contains(message).should('not.exist');
|
||||
});
|
||||
|
||||
// # Navigate to the off-topic channel
|
||||
cy.visit(`/${testTeam.name}/channels/off-topic`);
|
||||
|
||||
// * Verify that the last post does not contain the status update
|
||||
cy.getLastPost().then((post) => {
|
||||
cy.get(post).contains(message).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('context menu', () => {
|
||||
commonContextDropdownTests();
|
||||
|
||||
it('can rename run', () => {
|
||||
// # Click on rename run
|
||||
getDropdownItemByText('Rename run').click();
|
||||
|
||||
cy.findByTestId('run-header-section').within(() => {
|
||||
// # Type a new name
|
||||
cy.findByTestId('rendered-editable-text').clear().type('The new fancy name');
|
||||
|
||||
// # Save
|
||||
cy.findByTestId('checklist-item-save-button').click();
|
||||
|
||||
// * Assert name is updated
|
||||
cy.get('h1').contains('The new fancy name');
|
||||
});
|
||||
|
||||
cy.reload();
|
||||
|
||||
cy.findByTestId('run-header-section').within(() => {
|
||||
// * Assert name is persisted
|
||||
cy.get('h1').contains('The new fancy name');
|
||||
});
|
||||
});
|
||||
|
||||
describe('finish run', () => {
|
||||
it('can be confirmed', () => {
|
||||
// * Check that status badge is in-progress
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
|
||||
// # Click on finish run
|
||||
getDropdownItemByText('Finish run').click();
|
||||
|
||||
// # Check that finish run modal is open
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModal').find('h1').contains('Confirm finish run');
|
||||
|
||||
// # Click on confirm
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
|
||||
// * Assert option is not anymore in context dropdown
|
||||
getDropdownItemByText('Finish run').should('not.exist');
|
||||
|
||||
// * Assert status badge is finished
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('Finished');
|
||||
});
|
||||
|
||||
it('can be canceled', () => {
|
||||
// * Check that status badge is in-progress
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
|
||||
// # Click on finish run
|
||||
getDropdownItemByText('Finish run').click();
|
||||
|
||||
// * Check that finish run modal is open
|
||||
cy.get('#confirmModal').should('be.visible');
|
||||
cy.get('#confirmModal').find('h1').contains('Confirm finish run');
|
||||
|
||||
// # Click on cancel
|
||||
cy.get('#confirmModal').get('#cancelModalButton').click();
|
||||
|
||||
// * Assert option is not anymore in context dropdown
|
||||
getDropdownItemByText('Finish run').should('be.visible');
|
||||
|
||||
// * Assert status badge is still in progress
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
});
|
||||
});
|
||||
|
||||
describe('run actions', () => {
|
||||
it('modal can be opened', () => {
|
||||
// # Click on finish run
|
||||
getDropdownItemByText('Run actions').click();
|
||||
|
||||
// * Assert modal pop up
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('exist');
|
||||
|
||||
// # Click on cancel
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).findByTestId('modal-cancel-button').click();
|
||||
|
||||
// * Assert modal disappeared
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('leave run', () => {
|
||||
it('can leave run', () => {
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Add viewer user to the channel
|
||||
cy.apiAddUsersToRun(playbookRun.id, [testViewerUser.id]);
|
||||
cy.findAllByTestId('timeline-item', {exact: false}).should('have.length', 3);
|
||||
|
||||
// # Change the owner to testViewerUser
|
||||
cy.apiChangePlaybookRunOwner(playbookRun.id, testViewerUser.id);
|
||||
cy.findByTestId('assignee-profile-selector').should('contain', testViewerUser.username);
|
||||
|
||||
// # Click on leave run
|
||||
getDropdownItemByText('Leave and unfollow run').click();
|
||||
|
||||
// # confirm modal
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
|
||||
// NOTE: this check fails because the front doesn't receive updated run object. Will deal in separate PR.
|
||||
// * Assert that the Participate button is shown
|
||||
getHeader().findByText('Participate').should('be.visible');
|
||||
|
||||
// * Verify run has been removed from LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRun.name).should('not.exist');
|
||||
|
||||
// # assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_leave',
|
||||
type: 'track',
|
||||
properties: {
|
||||
from: 'run_details',
|
||||
playbookrun_id: playbookRun.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
let playbookRunChannelName;
|
||||
let playbookRunName;
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
const now = Date.now();
|
||||
playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
playbookRun = run;
|
||||
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
describe('title, icons and buttons', () => {
|
||||
commonHeaderTests();
|
||||
|
||||
describe('Favorite', () => {
|
||||
it('add and remove from LHS', () => {
|
||||
// # Click fav icon
|
||||
getHeader().getStyledComponent('StarButton').click();
|
||||
|
||||
// * Assert run appears in LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRunName).should('exist');
|
||||
|
||||
// # Click fav icon again (unfav)
|
||||
getHeader().getStyledComponent('StarButton').click();
|
||||
|
||||
// * Assert run disappeared from LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRunName).should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Participate', () => {
|
||||
it('shows button', () => {
|
||||
// * Assert that the button is shown
|
||||
getHeader().findByText('Participate').should('be.visible');
|
||||
});
|
||||
|
||||
describe('Join action enabled', () => {
|
||||
it('click button to show modal and cancel', () => {
|
||||
// * Assert that component is rendered
|
||||
getHeader().findByText('Participate').should('be.visible');
|
||||
|
||||
// # Click Participate button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('You’ll also be added to the channel linked to this run.').should('exist');
|
||||
|
||||
// # cancel modal
|
||||
cy.findByTestId('modal-cancel-button').click();
|
||||
|
||||
// * Assert modal is not shown
|
||||
cy.get('#become-participant-modal').should('not.exist');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # Visit the channel run
|
||||
cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Assert user has not been added to the channel
|
||||
cy.getLastPost().should('not.contain', 'Someone');
|
||||
cy.getLastPost().should('not.contain', testViewerUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('click button to show modal and confirm when private channel', () => {
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// * Assert component is rendered
|
||||
getHeader().findByText('Participate').should('be.visible');
|
||||
|
||||
// # Click start-participating button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('You’ll also be added to the channel linked to this run.').should('exist');
|
||||
|
||||
// # confirm modal
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Assert that modal is not shown
|
||||
cy.get('#become-participant-modal').should('not.exist');
|
||||
|
||||
// * Verify run has been added to LHS
|
||||
verifyRunHasBeenAddedToLHS(playbookRunName);
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify the user was added to the channel
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.contains('You and');
|
||||
cy.contains('joined the channel');
|
||||
});
|
||||
});
|
||||
|
||||
// * assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_participate',
|
||||
type: 'track',
|
||||
properties: {
|
||||
from: 'run_details',
|
||||
playbookrun_id: playbookRun.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('click button and confirm to when public channel', () => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
const now = Date.now();
|
||||
playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
|
||||
// # Create a run with public chanel
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybookAndChannel.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
cy.apiLogin(testViewerUser);
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${run.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
|
||||
// * Assert that component is rendered
|
||||
getHeader().findByText('Participate').should('be.visible');
|
||||
|
||||
// # Click start-participating button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('You’ll also be added to the channel linked to this run.').should('exist');
|
||||
|
||||
// # confirm modal
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Assert that modal is not shown
|
||||
cy.get('#become-participant-modal').should('not.exist');
|
||||
|
||||
// * Verify run has been added to LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRunName).should('exist');
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the user was added to the channel
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.contains('You and');
|
||||
cy.contains('joined the channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('Join action disabled', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Disable join action
|
||||
cy.apiUpdateRun(playbookRun.id, {createChannelMemberOnNewParticipant: false});
|
||||
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
|
||||
it('join the run with private channel, request to join the channel', () => {
|
||||
// # Click start-participating button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('Request access to the channel linked to this run').should('exist');
|
||||
|
||||
// # Select checkbox
|
||||
cy.findByTestId('also-add-to-channel').click({force: true});
|
||||
|
||||
// # confirm modal
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Assert that modal is not shown
|
||||
cy.get('#become-participant-modal').should('not.exist');
|
||||
|
||||
// * Verify run has been added to LHS
|
||||
verifyRunHasBeenAddedToLHS(playbookRunName);
|
||||
|
||||
// # Login as testUser to check if join request was posted in the channel
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the request was sent to the channel
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.contains(`@${testViewerUser.username} is a run participant and wants join this channel. Any member of the channel can invite them.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('join the run with private channel, no request to join the channel', () => {
|
||||
// # Click start-participating button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('Request access to the channel linked to this run').should('exist');
|
||||
|
||||
// # confirm modal
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Assert that modal is not shown
|
||||
cy.get('#become-participant-modal').should('not.exist');
|
||||
|
||||
// * Verify run has been added to LHS
|
||||
verifyRunHasBeenAddedToLHS(playbookRunName);
|
||||
|
||||
// # Login as testUser to check if join request was posted in the channel
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the request was sent to the channel
|
||||
cy.getLastPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.contains(`@${testViewerUser.username} is a run participant and wants join this channel. Any member of the channel can invite them.`).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('join run with public channel, join the channel', () => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
const now = Date.now();
|
||||
playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
|
||||
// Create a run with public chanel
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybookAndChannel.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((run) => {
|
||||
cy.apiLogin(testViewerUser);
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${run.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
|
||||
// * Assert that component is rendered
|
||||
getHeader().findByText('Participate').should('be.visible');
|
||||
|
||||
// # Click start-participating button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('You’ll also be added to the channel linked to this run.').should('exist');
|
||||
|
||||
// # confirm modal
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Assert that modal is not shown
|
||||
cy.get('#become-participant-modal').should('not.exist');
|
||||
|
||||
// * Verify run has been added to LHS
|
||||
cy.findByTestId('lhs-navigation').findByText(playbookRunName).should('exist');
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Verify that the user was added to the channel
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.contains('You and');
|
||||
cy.contains('joined the channel');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('run actions', () => {
|
||||
describe('modal behaviour', () => {
|
||||
it('modal can be opened read-only', () => {
|
||||
// # Click on run actions
|
||||
getDropdownItemByText('Run actions').click();
|
||||
|
||||
// * Assert modal pop up
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('exist');
|
||||
|
||||
// * Assert there are no buttons
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).findByTestId('modal-cancel-button').should('not.exist');
|
||||
cy.findByRole('button', {name: /Save/i}).should('not.exist');
|
||||
|
||||
// # Close modal
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).find('.close').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('context menu', () => {
|
||||
commonContextDropdownTests();
|
||||
|
||||
it('can not rename run', () => {
|
||||
// # There's no rename option
|
||||
getDropdownItemByText('Rename run').should('not.exist');
|
||||
});
|
||||
|
||||
it('can not finish run', () => {
|
||||
// * There's no finish run item
|
||||
getDropdownItemByText('Finish run').should('not.exist');
|
||||
});
|
||||
|
||||
describe('run actions', () => {
|
||||
it('modal can be opened read-only', () => {
|
||||
// # Click on finish run
|
||||
getDropdownItemByText('Run actions').click();
|
||||
|
||||
// * Assert modal pop up
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).should('exist');
|
||||
|
||||
// * Assert there are no buttons
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).findByTestId('modal-cancel-button').should('not.exist');
|
||||
cy.findByRole('button', {name: /Save/i}).should('not.exist');
|
||||
|
||||
// # Close modal
|
||||
cy.findByRole('dialog', {name: /Run Actions/i}).find('.close').click();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const verifyRunHasBeenAddedToLHS = (playbookRunName) => {
|
||||
// * Verify run has been added to LHS
|
||||
cy.findByTestId('lhs-navigation').
|
||||
should('be.visible').
|
||||
findByText(playbookRunName).
|
||||
should('be.visible');
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > restart run', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
|
||||
// const taskIndex = 0;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Stage 2',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
],
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('restart run', () => {
|
||||
it('can be confirmed', () => {
|
||||
cy.intercept('PUT', `/plugins/playbooks/api/v0/runs/${testRun.id}/finish`).as('routeFinish');
|
||||
cy.intercept('PUT', `/plugins/playbooks/api/v0/runs/${testRun.id}/restore`).as('routeRestore');
|
||||
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
|
||||
// # Click finish run button
|
||||
cy.findByTestId('run-finish-section').find('button').click();
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
|
||||
cy.wait('@routeFinish');
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('Finished');
|
||||
|
||||
cy.findByTestId('runDropdown').click();
|
||||
cy.get('.restartRun').find('span').contains('Restart run');
|
||||
|
||||
cy.get('.restartRun').click();
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
cy.wait('@routeRestore');
|
||||
cy.findByTestId('run-header-section').findByTestId('badge').contains('In Progress');
|
||||
cy.findByTestId('lhs-navigation').findByText(testRun.name).should('exist');
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,500 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
const editAndPublishRetro = () => {
|
||||
getRetro().within(() => {
|
||||
// # Start editing
|
||||
cy.findByTestId('retro-report-text').click();
|
||||
|
||||
// * Verify the provided template text is pre-filled
|
||||
cy.focused().should('include.text', 'This is a retrospective template.');
|
||||
|
||||
// # Change the retro text
|
||||
cy.focused().clear().type('Edited retrospective.');
|
||||
|
||||
// # Save it by clicking outside the text area
|
||||
cy.findByText('Report').click();
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
});
|
||||
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
// * Verify we're showing the publish retro confirmation modal
|
||||
cy.findByText('Are you sure you want to publish?');
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
});
|
||||
|
||||
// * Verify that retro got published
|
||||
getRetro().get('.icon-check-all').should('be.visible');
|
||||
};
|
||||
|
||||
const getMetricInput = (index) => getRetro().getStyledComponent('InputContainer').eq(index);
|
||||
|
||||
const verifyMetricInput = (index, title, target, description, placeholder) => {
|
||||
getMetricInput(index).within(() => {
|
||||
cy.getStyledComponent('Title').contains(title);
|
||||
|
||||
if (target) {
|
||||
cy.getStyledComponent('TargetTitle').contains(target);
|
||||
} else {
|
||||
cy.getStyledComponent('TargetTitle').should('not.exist');
|
||||
}
|
||||
|
||||
if (description) {
|
||||
cy.getStyledComponent('HelpText').contains(description);
|
||||
}
|
||||
if (placeholder) {
|
||||
cy.get('input').should('have.attr', 'placeholder', placeholder);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getRetro = () => cy.findByTestId('run-retrospective-section');
|
||||
|
||||
describe('runs > run details page', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testPublicPlaybookWithMetrics;
|
||||
let testRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
retrospectiveTemplate: 'This is a retrospective template.',
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
metrics: [
|
||||
{
|
||||
title: 'title1',
|
||||
description: 'description1',
|
||||
type: 'metric_duration',
|
||||
target: 720000,
|
||||
},
|
||||
{
|
||||
title: 'title2',
|
||||
description: 'description2',
|
||||
type: 'metric_currency',
|
||||
target: 40,
|
||||
},
|
||||
{
|
||||
title: 'title3',
|
||||
description: 'description3',
|
||||
type: 'metric_integer',
|
||||
target: 30,
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybookWithMetrics = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('retrospective', () => {
|
||||
const commonTests = () => {
|
||||
it('is visible', () => {
|
||||
// * Verify the retrospective section is present
|
||||
getRetro().should('be.visible');
|
||||
});
|
||||
|
||||
it('has title', () => {
|
||||
// * Verify the retrospective section has a title
|
||||
getRetro().find('h3').contains('Retrospective');
|
||||
});
|
||||
|
||||
it('has template text', () => {
|
||||
// * Verify the retrospective text is rendered
|
||||
getRetro().findByTestId('retro-report-text').contains('This is a retrospective template.');
|
||||
});
|
||||
|
||||
it('has no metrics', () => {
|
||||
// * Verify there are no metric for this playbook
|
||||
getRetro().getStyledComponent('InputContainer').should('not.exist');
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('publishing posts to run channel', () => {
|
||||
editAndPublishRetro();
|
||||
|
||||
// # Switch to the run channel
|
||||
cy.findByTestId('runinfo-channel-link').click();
|
||||
|
||||
// * Verify the modified retro text is posted
|
||||
cy.getStyledComponent('CustomPostContent').should('exist').contains('Edited retrospective.');
|
||||
});
|
||||
|
||||
it('can be published once', () => {
|
||||
editAndPublishRetro();
|
||||
|
||||
// * Verify the button is disabled
|
||||
getRetro().findByText('Publish').should('not.be.enabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create test playbook run
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Login as the test viewer
|
||||
cy.apiLogin(testViewerUser);
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('text is not clickable', () => {
|
||||
getRetro().findByTestId('retro-report-text').click();
|
||||
getRetro().find('textarea').should('not.exist');
|
||||
});
|
||||
|
||||
it('there is no publish button', () => {
|
||||
getRetro().findByText('Publish').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('metrics', () => {
|
||||
const commonTests = () => {
|
||||
it('inputs info(title, target, description) and order', () => {
|
||||
// * Verify the created metrics
|
||||
verifyMetricInput(0, 'title1', '12 minutes', 'description1', 'Add value (in dd:hh:mm)');
|
||||
verifyMetricInput(1, 'title2', '40', 'description2', 'Add value');
|
||||
verifyMetricInput(2, 'title3', '30', 'description3', 'Add value');
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybookWithMetrics.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('inputs, null and zero values', () => {
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
createPublicPlaybookRun: true,
|
||||
metrics: [
|
||||
{
|
||||
title: 'title1',
|
||||
description: 'description1',
|
||||
type: 'metric_duration',
|
||||
target: null,
|
||||
},
|
||||
{
|
||||
title: 'title2',
|
||||
description: 'description2',
|
||||
type: 'metric_currency',
|
||||
target: 0,
|
||||
},
|
||||
{
|
||||
title: 'title3',
|
||||
description: 'description3',
|
||||
type: 'metric_integer',
|
||||
target: 30,
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
// # Navigate directly to the retro tab
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
// * Verify changes are reflected
|
||||
verifyMetricInput(0, 'title1', null, 'description1', 'Add value (in dd:hh:mm)');
|
||||
verifyMetricInput(1, 'title2', '0', 'description2', 'Add value');
|
||||
verifyMetricInput(2, 'title3', '30', 'description3', 'Add value');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('auto save', () => {
|
||||
getRetro().within(() => {
|
||||
// # Enter metric values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('12:11:10').
|
||||
tab().type('56').
|
||||
tab().type('123');
|
||||
|
||||
// # Click outside
|
||||
cy.findByText('Retrospective').click({force: true});
|
||||
cy.wait(2000);
|
||||
|
||||
// * Validate if values persist
|
||||
cy.get('input[type=text]').eq(0).should('have.value', '12:11:10');
|
||||
cy.get('input[type=text]').eq(1).should('have.value', '56');
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '123');
|
||||
|
||||
// # Enter new values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).clear().type('12:00:10').
|
||||
tab().clear().type('20').
|
||||
tab().clear().type('21');
|
||||
});
|
||||
|
||||
// # Wait 2 sec to auto save
|
||||
cy.wait(2000);
|
||||
|
||||
// # Reload page
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
|
||||
getRetro().within(() => {
|
||||
// * Validate if values are saved
|
||||
cy.get('input[type=text]').eq(0).should('have.value', '12:00:10');
|
||||
cy.get('input[type=text]').eq(1).should('have.value', '20');
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '21');
|
||||
});
|
||||
});
|
||||
|
||||
it('save empty and zero values', () => {
|
||||
getRetro().within(() => {
|
||||
// # Enter metric values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).clear().type('00:00:00').
|
||||
tab().type('7').
|
||||
tab().type('0');
|
||||
|
||||
// # Click outside
|
||||
cy.findByText('Retrospective').click({force: true});
|
||||
|
||||
// * Validate if values persist
|
||||
cy.get('input[type=text]').eq(0).should('have.value', '00:00:00');
|
||||
cy.get('input[type=text]').eq(1).should('have.value', '7');
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '0');
|
||||
|
||||
// # Clear first two metrics values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).clear().
|
||||
tab().clear();
|
||||
|
||||
// # Click outside
|
||||
cy.findByText('Retrospective').click({force: true});
|
||||
|
||||
// * Validate if values persist
|
||||
cy.get('input[type=text]').eq(0).should('have.value', '');
|
||||
cy.get('input[type=text]').eq(1).should('have.value', '');
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '0');
|
||||
});
|
||||
});
|
||||
|
||||
it('only valid values are saved. check error messages', () => {
|
||||
getRetro().within(() => {
|
||||
// # Enter invalid metric values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('5').
|
||||
tab().type('56d').
|
||||
tab().type('125');
|
||||
|
||||
// * Validate error messages
|
||||
cy.getStyledComponent('ErrorText').eq(0).contains('Please enter a duration in the format: dd:hh:mm (e.g., 12:00:00).');
|
||||
cy.getStyledComponent('ErrorText').eq(1).contains('Please enter a number.');
|
||||
|
||||
// # Click outside
|
||||
cy.findByText('Retrospective').click({force: true});
|
||||
});
|
||||
|
||||
// # Reload page and navigate to the retro tab
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
|
||||
getRetro().within(() => {
|
||||
// * Validate that values are not saved
|
||||
cy.get('input[type=text]').eq(0).should('have.value', '');
|
||||
cy.get('input[type=text]').eq(1).should('have.value', '');
|
||||
cy.get('input[type=text]').eq(2).should('have.value', '125');
|
||||
|
||||
// # Enter new metric values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('s').
|
||||
tab().type('d').
|
||||
tab().type('k');
|
||||
});
|
||||
});
|
||||
|
||||
it('publish retro', () => {
|
||||
getRetro().within(() => {
|
||||
// # Enter metric invalid values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('20:00:12d').
|
||||
tab().type('56').
|
||||
tab().type('125v');
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
});
|
||||
|
||||
// * Verify we're not showing the publish retro confirmation modal
|
||||
cy.get('#confirm-modal-light').should('not.exist');
|
||||
|
||||
getRetro().within(() => {
|
||||
//# Enter empty metric values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).clear().
|
||||
tab().clear().
|
||||
tab().clear().type(24);
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
|
||||
// * Validate error messages
|
||||
cy.getStyledComponent('ErrorText').eq(0).contains('Please fill in the metric value.');
|
||||
cy.getStyledComponent('ErrorText').eq(1).contains('Please fill in the metric value.');
|
||||
cy.getStyledComponent('ErrorText').should('have.length', 2);
|
||||
});
|
||||
|
||||
// * Verify we're not showing the publish retro confirmation modal
|
||||
cy.get('#confirm-modal-light').should('not.exist');
|
||||
|
||||
getRetro().within(() => {
|
||||
//# Enter valid metric values
|
||||
cy.get('input[type=text]').eq(0).click();
|
||||
cy.get('input[type=text]').eq(0).type('09:87:12').
|
||||
tab().type(123);
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
});
|
||||
|
||||
cy.get('#confirm-modal-light').within(() => {
|
||||
// * Verify we're showing the publish retro confirmation modal
|
||||
cy.findByText('Are you sure you want to publish?');
|
||||
|
||||
// # Publish
|
||||
cy.findByRole('button', {name: 'Publish'}).click();
|
||||
});
|
||||
|
||||
getRetro().within(() => {
|
||||
// * Verify that retro got published
|
||||
cy.get('.icon-check-all').should('be.visible');
|
||||
|
||||
// * Verify that metrics inputs are disabled
|
||||
cy.get('input[type=text]').each(($el) => {
|
||||
cy.wrap($el).should('not.be.enabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
before(() => {
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybookWithMetrics.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('are not editable', () => {
|
||||
// * Verify that inputs are disabled
|
||||
getMetricInput(0).find('input').should('be.disabled');
|
||||
getMetricInput(1).find('input').should('be.disabled');
|
||||
getMetricInput(2).find('input').should('be.disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,306 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
/* eslint-disable no-only-tests/no-only-tests */
|
||||
|
||||
describe('runs > run details page > status update', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
let playbookRunChannelName;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
const now = Date.now();
|
||||
const playbookRunName = 'Playbook Run (' + now + ')';
|
||||
playbookRunChannelName = 'playbook-run-' + now;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
describe('as participant', () => {
|
||||
it('is visible', () => {
|
||||
// * Verify the status update section is present
|
||||
cy.findByTestId('run-statusupdate-section').should('be.visible');
|
||||
});
|
||||
|
||||
it('has no title', () => {
|
||||
// * Verify the title
|
||||
cy.findByTestId('run-statusupdate-section').find('h3').should('not.exist');
|
||||
});
|
||||
|
||||
describe('post update', () => {
|
||||
it('button disappears if we finish the run', () => {
|
||||
// * Check that post update button is visible
|
||||
cy.findByTestId('run-statusupdate-section').findByTestId('post-update-button').should('be.visible');
|
||||
|
||||
// # Click finish button and confirm modal
|
||||
cy.findByTestId('run-finish-section').find('button').click();
|
||||
cy.get('#confirmModal').get('#confirmModalButton').click();
|
||||
|
||||
// * Check that post update button does not exist anymore
|
||||
cy.findByTestId('run-statusupdate-section').findByTestId('post-update-button').should('not.exist');
|
||||
});
|
||||
|
||||
it('button triggers post update modal', () => {
|
||||
// * Check due date
|
||||
cy.findByTestId('update-due-date-text').contains('Update due');
|
||||
cy.findByTestId('update-due-date-time').contains('in 24 hours');
|
||||
|
||||
// # Click post update
|
||||
cy.findByTestId('run-statusupdate-section').findByTestId('post-update-button').click();
|
||||
|
||||
// * Assert modal is opened
|
||||
cy.getStatusUpdateDialog().should('be.visible');
|
||||
|
||||
// # Write message
|
||||
cy.findByTestId('update_run_status_textbox').clear().type('my nice update');
|
||||
cy.get('#reminder_timer_datetime').within(() => {
|
||||
cy.get('input').type('15 minutes', {delay: 200, force: true}).type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Post update
|
||||
cy.getStatusUpdateDialog().findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Check new due date
|
||||
cy.findByTestId('update-due-date-text').contains('Update due');
|
||||
cy.findByTestId('update-due-date-time').contains('in 15 minutes');
|
||||
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # go to channel
|
||||
cy.visit(`/${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * check that post has been added
|
||||
cy.getLastPost().contains('my nice update');
|
||||
|
||||
// * assert telemetry pageview
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'run_status_update',
|
||||
type: 'page',
|
||||
properties: {
|
||||
channel_type: 'P',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('request an update', () => {
|
||||
it('is disabled if the run is finished', () => {
|
||||
cy.apiFinishRun(testRun.id).then(() => {
|
||||
// # reload url
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
|
||||
// # Click on kebab menu
|
||||
cy.findByTestId('run-statusupdate-section').getStyledComponent('Kebab').click();
|
||||
|
||||
// # click on request update option (force because is disabled)
|
||||
cy.findByText('Request update...').click({force: true});
|
||||
|
||||
// * assert modal is not opened
|
||||
cy.get('#confirmModalButton').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('requests and confirm', () => {
|
||||
// # Click on kebab menu
|
||||
cy.findByTestId('run-statusupdate-section').getStyledComponent('Kebab').click();
|
||||
|
||||
cy.findByTestId('dropdownmenu').within(($dropdown) => {
|
||||
cy.wrap($dropdown).children().should('have.length', 2);
|
||||
|
||||
// # Click on request update
|
||||
cy.findByText('Request update...').click();
|
||||
});
|
||||
|
||||
// # Click on modal confirmation
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
// # Go to channel
|
||||
cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Assert that message has been sent
|
||||
cy.getLastPost().contains(`${testUser.username} requested a status update for ${testRun.name}.`);
|
||||
});
|
||||
|
||||
it('requests and cancel', () => {
|
||||
// # Click on kebab menu
|
||||
cy.findByTestId('run-statusupdate-section').getStyledComponent('Kebab').click();
|
||||
cy.findByTestId('dropdownmenu').within(($dropdown) => {
|
||||
cy.wrap($dropdown).children().should('have.length', 2);
|
||||
|
||||
// # Click on request update
|
||||
cy.findByText('Request update...').click();
|
||||
});
|
||||
|
||||
// # Click on modal confirmation
|
||||
cy.get('#cancelModalButton').click();
|
||||
|
||||
// # Go to channel
|
||||
cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Assert that message has not been sent
|
||||
cy.getLastPost().should('not.contain', `${testUser.username} requested a status update for ${testRun.name}.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
it('is visible', () => {
|
||||
// * Verify the status update section is present
|
||||
cy.findByTestId('run-statusupdate-section').should('be.visible');
|
||||
});
|
||||
|
||||
it('has a title', () => {
|
||||
// * Verify the title
|
||||
cy.findByTestId('run-statusupdate-section').find('h3').contains('Recent status update');
|
||||
});
|
||||
|
||||
it('has placeholder', () => {
|
||||
// * Verify the placeholder
|
||||
cy.findByTestId('run-statusupdate-section').find('i').contains('No updates have been posted yet');
|
||||
});
|
||||
|
||||
it('has a due date', () => {
|
||||
// * Verify the due date
|
||||
cy.findByTestId('update-due-date-text').contains('Update due');
|
||||
cy.findByTestId('update-due-date-time').contains('in 24 hours');
|
||||
});
|
||||
|
||||
it('shows the most recent update', () => {
|
||||
// # Login as participant
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
|
||||
// # Click post update
|
||||
cy.findByTestId('run-statusupdate-section').
|
||||
should('be.visible').
|
||||
findByTestId('post-update-button').click();
|
||||
|
||||
// * Assert modal is opened
|
||||
cy.getStatusUpdateDialog().should('be.visible');
|
||||
|
||||
// # Write message
|
||||
cy.findByTestId('update_run_status_textbox').clear().type('my nice update');
|
||||
cy.get('#reminder_timer_datetime').within(() => {
|
||||
cy.get('input').type('15 minutes', {delay: 200, force: true}).type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Post update
|
||||
cy.getStatusUpdateDialog().findByTestId('modal-confirm-button').click();
|
||||
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
|
||||
// * Check new due date
|
||||
cy.findByTestId('update-due-date-text').contains('Update due');
|
||||
cy.findByTestId('update-due-date-time').contains('in 15 minutes');
|
||||
|
||||
// * Assert the recent updated text
|
||||
cy.findByTestId('status-update-card').contains('my nice update');
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('requests an update and confirm', () => {
|
||||
// # Click on request update
|
||||
cy.findByTestId('run-statusupdate-section').
|
||||
should('be.visible').
|
||||
findByText('Request update...').click();
|
||||
|
||||
// # Click on modal confirmation
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # Go to channel
|
||||
cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Assert that message has been sent
|
||||
cy.getLastPost().contains(`${testUser.username} requested a status update for ${testPublicPlaybook.name}.`);
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('requests an update and cancel', () => {
|
||||
// # Click request update
|
||||
cy.findByTestId('run-statusupdate-section').
|
||||
should('be.visible').
|
||||
findByText('Request update...').click();
|
||||
|
||||
// # Click on modal confirmation
|
||||
cy.get('#cancelModalButton').click();
|
||||
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
// # Go to channel
|
||||
cy.visit(`${testTeam.name}/channels/${playbookRunChannelName}`);
|
||||
|
||||
// * Assert that message has been sent
|
||||
cy.getLastPost().should('not.contain', `${testUser.username} requested a status update for ${testPublicPlaybook.name}].`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > summary', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testRun;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
const commonTests = () => {
|
||||
it('is visible', () => {
|
||||
// * Verify the summary section is present
|
||||
cy.findByTestId('run-summary-section').should('be.visible');
|
||||
});
|
||||
|
||||
it('has title', () => {
|
||||
// * Verify the summary section is present
|
||||
cy.findByTestId('run-summary-section').find('h3').contains('Summary');
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
|
||||
it('has a placeholder', () => {
|
||||
// * Assert the placeholder content
|
||||
cy.findByTestId('run-summary-section').findByTestId('rendered-text').contains('Add a run summary');
|
||||
});
|
||||
|
||||
it('can be edited', () => {
|
||||
// # Mouseover the summary
|
||||
cy.findByTestId('run-summary-section').trigger('mouseover');
|
||||
|
||||
cy.findByTestId('run-summary-section').within(() => {
|
||||
// # Click the edit icon
|
||||
cy.findByTestId('hover-menu-edit-button').click();
|
||||
|
||||
// # Write a summary
|
||||
cy.findByTestId('editabletext-markdown-textbox2').clear().type('This is my new summary');
|
||||
|
||||
// # Save changes
|
||||
cy.findByTestId('checklist-item-save-button').click();
|
||||
|
||||
// * Assert that data has changed
|
||||
cy.findByTestId('rendered-text').contains('This is my new summary');
|
||||
});
|
||||
|
||||
// * Assert last edition date is visible
|
||||
cy.findByTestId('run-summary-section').contains('Last edited');
|
||||
});
|
||||
|
||||
it('can be canceled', () => {
|
||||
// # Mouseover the summary
|
||||
cy.findByTestId('run-summary-section').trigger('mouseover');
|
||||
|
||||
cy.findByTestId('run-summary-section').within(() => {
|
||||
// # Click the edit icon
|
||||
cy.findByTestId('hover-menu-edit-button').click();
|
||||
|
||||
// # Write a summary
|
||||
cy.findByTestId('editabletext-markdown-textbox2').clear().type('This is my new summary');
|
||||
|
||||
// # Cancel changes
|
||||
cy.findByText('Cancel').click();
|
||||
|
||||
// * Assert that data has not changed
|
||||
cy.findByTestId('rendered-text').contains('Add a run summary');
|
||||
});
|
||||
|
||||
// * Assert last edition date is not visible
|
||||
cy.findByTestId('run-summary-section').should('not.contain', 'Last edited');
|
||||
});
|
||||
|
||||
it('can not be edited once run is finished', () => {
|
||||
// # Finish the run
|
||||
cy.apiFinishRun(testRun.id);
|
||||
|
||||
// # Mouseover the summary
|
||||
cy.findByTestId('run-summary-section').trigger('mouseover');
|
||||
|
||||
// * Verify that the edit button is not rendered
|
||||
cy.findByTestId('run-summary-section').findByTestId('hover-menu-edit-button').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('has a placeholder', () => {
|
||||
// * Assert the placeholder content
|
||||
cy.findByTestId('run-summary-section').findByTestId('rendered-text').contains('There\'s no summary');
|
||||
});
|
||||
|
||||
it('can not be edited', () => {
|
||||
// # Mouseover the summary
|
||||
cy.findByTestId('run-summary-section').trigger('mouseover');
|
||||
|
||||
// * Verify that the edit button is not rendered
|
||||
cy.findByTestId('run-summary-section').findByTestId('hover-menu-edit-button').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,712 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
import * as TIMEOUTS from '../../../fixtures/timeouts';
|
||||
|
||||
describe('runs > task actions', () => {
|
||||
let testPlaybook;
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateUser().then(({user: user2}) => {
|
||||
testUser2 = user2;
|
||||
|
||||
// # Add this new user to the team
|
||||
cy.apiAddUserToTeam(team.id, testUser2.id);
|
||||
});
|
||||
|
||||
// # Create a playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Playbook (' + Date.now() + ')',
|
||||
checklists: [{
|
||||
title: 'Test Checklist',
|
||||
items: [
|
||||
{title: 'Test Task'},
|
||||
],
|
||||
}],
|
||||
memberIDs: [
|
||||
testUser.id,
|
||||
],
|
||||
}).then((playbook) => {
|
||||
testPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # intercepts telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
describe('keywords trigger - mark task as done', () => {
|
||||
let testPlaybookRun;
|
||||
|
||||
const getChecklist = () => cy.findByTestId('run-checklist-section');
|
||||
const getChecklistTasks = () => getChecklist().findAllByTestId('checkbox-item-container');
|
||||
|
||||
beforeEach(() => {
|
||||
// # Run a playbook
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: `the run name ${Date.now()}`,
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('disallows no keywords', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify no actions are configured
|
||||
cy.findByText('Task Actions').should('exist');
|
||||
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, []);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isFalse(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a single keyword', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # assert telemetry data
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'taskactions_updated',
|
||||
type: 'track',
|
||||
properties: {
|
||||
playbookrun_id: testPlaybookRun.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser2.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('allows multiple keywords', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add multiple keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
cy.get('input').eq(0).type('keyword2{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1', 'keyword2']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword2 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser2.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('allows multi-word phrases', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a phrase
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('a phrase with multiple words{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['a phrase with multiple words']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and a phrase with multiple words happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser2.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('allows removing previously configured keywords', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add multiple keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
cy.get('input').eq(0).type('keyword2{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// # Re-open the dialog
|
||||
cy.findByText('1 action').click();
|
||||
|
||||
// # Remove one trigger keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.findByText('keyword1').next().click();
|
||||
});
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword2']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Post without activating trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action not activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('not.be.checked');
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword2 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser2.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('disables when all keywords removed', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add multiple keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
cy.get('input').eq(0).type('keyword2{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// # Re-open the dialog
|
||||
cy.findByText('1 action').click();
|
||||
|
||||
// # Remove all trigger keywords
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.findByText('keyword1').next().click();
|
||||
cy.findByText('keyword2').next().click();
|
||||
});
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions
|
||||
cy.findByText('Task Actions');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, []);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isFalse(actions.enabled);
|
||||
});
|
||||
|
||||
// # Post without activating trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh keyword1 keyword2 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action not activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('not.be.checked');
|
||||
});
|
||||
|
||||
it('disallows a user without keywords', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a user
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify no actions are configured
|
||||
cy.findByText('Task Actions');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, []);
|
||||
assert.deepEqual(trigger.user_ids, [testUser.id]);
|
||||
assert.isFalse(actions.enabled);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows a single user', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Add a user
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions and user
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, [testUser.id]);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Post without activating trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action not activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('not.be.checked');
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser,
|
||||
message: `hello from ${testUser.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('allows configuring multiple users', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Add two users
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser2.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions and user
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, [testUser.id, testUser2.id]);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser,
|
||||
message: `hello from ${testUser.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser.username} checked off checklist item "Test Task"`);
|
||||
|
||||
// # Reset-uncheck task
|
||||
cy.apiSetChecklistItemState(testPlaybookRun.id, 0, 0, '');
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('not.be.checked');
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser2.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('rejects unknown user', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Type an unknown user
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@unknown', {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Click away
|
||||
cy.get('.modal-body').click();
|
||||
|
||||
// # Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions and user
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser,
|
||||
message: `hello from ${testUser.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
|
||||
it('allows removing previously configured users', () => {
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Add two users
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
cy.get('input').eq(1).
|
||||
type('@' + testUser2.username, {force: true}).
|
||||
wait(TIMEOUTS.ONE_SEC).
|
||||
type('{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Attempt to enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// # Re-open the dialog
|
||||
cy.findByText('1 action').click();
|
||||
|
||||
// # Remove one user keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.findByText(testUser.username).parent().parent().next().click();
|
||||
});
|
||||
|
||||
// Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(testPlaybookRun.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, [testUser2.id]);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
|
||||
// # Post without activating trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser,
|
||||
message: `hello from ${testUser.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action NOT activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('not.be.checked');
|
||||
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testPlaybookRun.channel_id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testPlaybookRun.channel_id,
|
||||
});
|
||||
|
||||
// * Verify action activated
|
||||
getChecklistTasks().eq(0).find('input[type="checkbox"]').should('be.checked');
|
||||
cy.findAllByTestId('timeline-item task_state_modified').findByText(`${testUser2.username} checked off checklist item "Test Task"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('keywords trigger - mark task as done, multiple runs in a channel', () => {
|
||||
let testChannel;
|
||||
let testPlaybookRun1;
|
||||
let testPlaybookRun2;
|
||||
|
||||
const configureTaskAction = (run) => {
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${run.id}`);
|
||||
|
||||
// # Open the task actions modal
|
||||
cy.findByText('Task Actions').click();
|
||||
|
||||
// # Add a keyword
|
||||
cy.get('.modal-body').within(() => {
|
||||
cy.get('input').eq(0).type('keyword1{enter}', {force: true});
|
||||
});
|
||||
|
||||
// # Enable the trigger
|
||||
cy.findByText('Mark the task as done').click();
|
||||
|
||||
// # Save the dialog
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify configured actions
|
||||
cy.findByText('1 action');
|
||||
cy.apiGetPlaybookRun(run.id).then(({body: playbookRun}) => {
|
||||
const trigger = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].trigger.payload);
|
||||
const actions = JSON.parse(playbookRun.checklists[0].items[0].task_actions[0].actions[0].payload);
|
||||
|
||||
assert.deepEqual(trigger.keywords, ['keyword1']);
|
||||
assert.deepEqual(trigger.user_ids, []);
|
||||
assert.isTrue(actions.enabled);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cy.apiCreateChannel(testTeam.id, 'channel', 'Channel').then(({channel}) => {
|
||||
testChannel = channel;
|
||||
|
||||
// # Run #1
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: `the run name ${Date.now()}`,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testChannel.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun1 = playbookRun;
|
||||
configureTaskAction(testPlaybookRun1);
|
||||
});
|
||||
|
||||
// # Run #2
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPlaybook.id,
|
||||
playbookRunName: `the run name ${Date.now()}`,
|
||||
ownerUserId: testUser.id,
|
||||
channelId: testChannel.id,
|
||||
}).then((playbookRun) => {
|
||||
testPlaybookRun2 = playbookRun;
|
||||
configureTaskAction(testPlaybookRun2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers', () => {
|
||||
// # Attempt to activate trigger
|
||||
cy.apiAddUserToChannel(testChannel.id, testUser2.id);
|
||||
cy.postMessageAs({
|
||||
sender: testUser2,
|
||||
message: `hello from ${testUser2.username}: ${Date.now()}, oh and keyword1 happened`,
|
||||
channelId: testChannel.id,
|
||||
});
|
||||
|
||||
// Give the system a chance to effect the task actions.
|
||||
cy.wait(TIMEOUTS.HALF_SEC);
|
||||
|
||||
// * Verify action activated ion testPlaybookRun1
|
||||
cy.apiGetPlaybookRun(testPlaybookRun1.id).then(({body: playbookRun}) => {
|
||||
assert.equal(playbookRun.checklists[0].items[0].state, 'closed');
|
||||
});
|
||||
|
||||
// * Verify action activated in testPlaybookRun2
|
||||
cy.apiGetPlaybookRun(testPlaybookRun2.id).then(({body: playbookRun}) => {
|
||||
assert.equal(playbookRun.checklists[0].items[0].state, 'closed');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,266 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > rhs > participants', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testUser2;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testUser2 = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testUser2.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the-run-name' + Date.now(),
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Add viewer user to the channel
|
||||
cy.apiAddUsersToRun(testRun.id, [testUser2.id]);
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('as participant', () => {
|
||||
it('switching between manage modes', () => {
|
||||
navigateToParticipantsList();
|
||||
|
||||
// # Switch to manage mode
|
||||
cy.findByRole('button', {name: 'Manage'}).click();
|
||||
|
||||
// * Verify that we are in manage mode
|
||||
cy.findByRole('button', {name: 'Manage'}).should('not.exist');
|
||||
|
||||
// # Switch to normal mode
|
||||
cy.findByRole('button', {name: 'Done'}).click();
|
||||
|
||||
// * Verify that we are in normal mode
|
||||
cy.findByRole('button', {name: 'Manage'}).should('exist');
|
||||
});
|
||||
|
||||
it('change owner', () => {
|
||||
navigateToParticipantsList();
|
||||
|
||||
// * Verify run owner
|
||||
cy.findByTestId('run-owner').contains(testUser.username);
|
||||
|
||||
// # Switch to manage mode
|
||||
cy.findByRole('button', {name: 'Manage'}).click();
|
||||
|
||||
// # Change owner
|
||||
cy.findByTestId(testUser2.id).findByTestId('menuButton').click();
|
||||
cy.findByTestId('dropdownmenu').findByText('Make run owner').click();
|
||||
|
||||
// # Wait for changes to apply
|
||||
cy.wait(2000);
|
||||
|
||||
// * Verify the owner has changed
|
||||
cy.findByTestId('run-owner').contains(testUser2.username);
|
||||
});
|
||||
|
||||
it('remove participant', () => {
|
||||
navigateToParticipantsList();
|
||||
|
||||
// * Verify run owner
|
||||
cy.findByTestId('run-owner').contains(testUser.username);
|
||||
|
||||
// # Switch to manage mode
|
||||
cy.findByRole('button', {name: 'Manage'}).click();
|
||||
|
||||
// # remove participant
|
||||
cy.findByTestId(testUser2.id).findByTestId('menuButton').click();
|
||||
cy.findByTestId('dropdownmenu').findByText('Remove from run').click();
|
||||
|
||||
// * Verify the user has been removed
|
||||
cy.findByTestId(testUser2.id).should('not.exist');
|
||||
});
|
||||
|
||||
describe('add participant', () => {
|
||||
it('join action enabled', () => {
|
||||
navigateToParticipantsList();
|
||||
|
||||
// * Verify run owner
|
||||
cy.findByTestId('run-owner').contains(testUser.username);
|
||||
|
||||
// # show add participant modal
|
||||
cy.findByRole('button', {name: 'Add'}).click();
|
||||
|
||||
// # Select two new participants
|
||||
cy.get('#profile-autocomplete').click().type(testUser2.username + '{enter}', {delay: 400});
|
||||
cy.get('#profile-autocomplete').click().type(testViewerUser.username + '{enter}', {delay: 400});
|
||||
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('Participants will also be added to the channel linked to this run').should('exist');
|
||||
|
||||
// # Add selected participant
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify telemetry
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_participate',
|
||||
type: 'track',
|
||||
properties: {
|
||||
from: 'run_details',
|
||||
trigger: 'add_participant',
|
||||
count: '2',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// * Verify the users have been added
|
||||
cy.findByTestId(testUser2.id).should('exist');
|
||||
cy.findByTestId(testViewerUser.id).should('exist');
|
||||
});
|
||||
|
||||
it('join action disabled', () => {
|
||||
cy.apiUpdateRun(testRun.id, {createChannelMemberOnNewParticipant: false});
|
||||
navigateToParticipantsList();
|
||||
|
||||
// * Verify run owner
|
||||
cy.findByTestId('run-owner').contains(testUser.username);
|
||||
|
||||
// # show add participant modal
|
||||
cy.findByRole('button', {name: 'Add'}).click();
|
||||
|
||||
// # Select two new participants
|
||||
cy.get('#profile-autocomplete').click().type(testViewerUser.username + '{enter}', {delay: 400});
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('Also add people to the channel linked to this run').should('exist');
|
||||
|
||||
// # Add selected participant
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify the user has been added to the run
|
||||
cy.findByTestId(testViewerUser.id).should('exist');
|
||||
|
||||
// # Intercept fetching channel members
|
||||
cy.intercept('channels/members/me/view').as('members');
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testRun.name}`);
|
||||
|
||||
// * Verify that no users were invited
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
// just to wait until the username is fetched
|
||||
cy.contains('Someone').should('not.exist');
|
||||
cy.contains('You were added to the channel by @playbooks.');
|
||||
cy.contains(`@${testViewerUser.username}`).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('join action disabled, checkbox selected', () => {
|
||||
cy.apiUpdateRun(testRun.id, {createChannelMemberOnNewParticipant: false});
|
||||
navigateToParticipantsList();
|
||||
|
||||
// * Verify run owner
|
||||
cy.findByTestId('run-owner').contains(testUser.username);
|
||||
|
||||
// # show add participant modal
|
||||
cy.findByRole('button', {name: 'Add'}).click();
|
||||
|
||||
// # Select two new participants
|
||||
cy.get('#profile-autocomplete').click().type(testViewerUser.username + '{enter}', {delay: 400});
|
||||
|
||||
// * Verify modal message is correct
|
||||
cy.findByText('Also add people to the channel linked to this run').should('exist');
|
||||
|
||||
// # Select checkbox
|
||||
cy.findByTestId('also-add-to-channel').click({force: true});
|
||||
|
||||
// # Add selected participant
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// * Verify the user has been added to the run
|
||||
cy.findByTestId(testViewerUser.id).should('exist');
|
||||
|
||||
// # Navigate to the playbook run channel
|
||||
cy.visit(`/${testTeam.name}/channels/${testRun.name}`);
|
||||
|
||||
// * Verify that the user was added to the channel
|
||||
cy.getFirstPostId().then((id) => {
|
||||
cy.get(`#postMessageText_${id}`).within(() => {
|
||||
cy.contains('Someone').should('not.exist');
|
||||
cy.contains(`@${testViewerUser.username}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('no manage button', () => {
|
||||
navigateToParticipantsList();
|
||||
|
||||
// * Verify that there is no manage button
|
||||
cy.findByRole('button', {name: 'Manage'}).should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const navigateToParticipantsList = () => {
|
||||
// # Click on participants row
|
||||
cy.findByTestId('runinfo-participants').click();
|
||||
};
|
||||
@@ -0,0 +1,549 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > run info', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
|
||||
const getHeader = () => {
|
||||
return cy.findByTestId('run-header-section');
|
||||
};
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
const getRHSSection = (title) => cy.findByRole('complementary').contains('section', title);
|
||||
|
||||
describe('> overview', () => {
|
||||
const getOverviewEntry = (entryName) => (
|
||||
getRHSSection('Overview').findByTestId(`runinfo-${entryName}`)
|
||||
);
|
||||
|
||||
const commonTests = () => {
|
||||
it('Playbook entry links to the playbook', () => {
|
||||
// # Click on the Playbook entry
|
||||
getOverviewEntry('playbook').within(() => cy.getStyledComponent('ItemLink').click());
|
||||
|
||||
// * Verify the we're in the right playbook page
|
||||
cy.url().should('include', '/playbooks/playbooks');
|
||||
cy.findByTestId('playbook-editor-title').contains(testPublicPlaybook.title);
|
||||
});
|
||||
|
||||
it('Owner entry shows the owner', () => {
|
||||
// * Verify that the owner is shown
|
||||
getOverviewEntry('owner').contains(testUser.username);
|
||||
});
|
||||
|
||||
it('Participants entry shows the participants', () => {
|
||||
// * Verify that the participants are rendered
|
||||
getOverviewEntry('participants').within(() => {
|
||||
cy.getStyledComponent('Participants').within(() => {
|
||||
cy.getStyledComponent('UserPic').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking on Participants show the full list of participants', () => {
|
||||
// * Click on the Participants entry
|
||||
getOverviewEntry('participants').click();
|
||||
|
||||
cy.findByRole('complementary').within(() => {
|
||||
// * Verify that the Participants RHS is shown
|
||||
cy.findByTestId('rhs-title').contains('Participants');
|
||||
|
||||
// * Verify that the back button is shown
|
||||
cy.findByTestId('rhs-back-button').should('exist');
|
||||
|
||||
// * Verify that the participants list shows the number of participants
|
||||
cy.findByText('1 Participant');
|
||||
|
||||
// * Verify that the participants list contains the test user
|
||||
cy.findByText(`@${testUser.username}`);
|
||||
|
||||
// # Click on the back button
|
||||
cy.findByTestId('rhs-back-button').click();
|
||||
|
||||
// * Verify that the RHS is back to Run info
|
||||
cy.findByTestId('rhs-title').contains('Run info');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
|
||||
it('Following button can be toggled', () => {
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
getOverviewEntry('following').within(() => {
|
||||
// * Verify that the user shows in the following list
|
||||
cy.getStyledComponent('UserRow').within(() => {
|
||||
cy.getStyledComponent('UserPic').should('have.length', 1);
|
||||
});
|
||||
|
||||
// # Click the Following button
|
||||
cy.findByRole('button', {name: /Following/}).click({force: true});
|
||||
|
||||
// * Verify that it now says (exactly) Follow
|
||||
cy.findByRole('button', {name: /^Follow$/}).should('exist');
|
||||
|
||||
// * Verify that the user no longer shows in the following list
|
||||
cy.getStyledComponent('UserRow').should('not.exist');
|
||||
|
||||
// # Click the Follow button
|
||||
cy.findByRole('button', {name: /^Follow$/}).click({force: true});
|
||||
|
||||
// * Verify that it now says Following
|
||||
cy.findByRole('button', {name: /Following/}).should('exist');
|
||||
});
|
||||
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'playbookrun_unfollow',
|
||||
type: 'track',
|
||||
from: 'run_details',
|
||||
playbookrun_id: testRun.id,
|
||||
},
|
||||
{
|
||||
name: 'playbookrun_follow',
|
||||
type: 'track',
|
||||
from: 'run_details',
|
||||
playbookrun_id: testRun.id,
|
||||
},
|
||||
], {waitForCalls: 3});
|
||||
});
|
||||
|
||||
it('click channel link navigates to run\'s channel', () => {
|
||||
// * Assert channel name
|
||||
getOverviewEntry('channel').contains('the run name');
|
||||
|
||||
// # Click on channel item
|
||||
getOverviewEntry('channel').within(() => cy.getStyledComponent('ItemLink').click());
|
||||
|
||||
// * Assert we navigated correctly
|
||||
cy.url().should('include', `${testTeam.name}/channels/the-run-name`);
|
||||
});
|
||||
|
||||
it('channel is still there when the run is finished', () => {
|
||||
cy.apiFinishRun(testRun.id).then(() => {
|
||||
// # Reload page
|
||||
cy.reload();
|
||||
|
||||
// * Assert channel name
|
||||
getOverviewEntry('channel').contains('the run name');
|
||||
|
||||
// # Click on channel item
|
||||
getOverviewEntry('channel').within(() => cy.getStyledComponent('ItemLink').click());
|
||||
|
||||
// * Assert we navigated correctly
|
||||
cy.url().should('include', `${testTeam.name}/channels/the-run-name`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('Following button can be toggled', () => {
|
||||
getOverviewEntry('following').within(() => {
|
||||
// * Verify that the user is not in the following list
|
||||
cy.getStyledComponent('UserRow').within(() => {
|
||||
cy.getStyledComponent('UserPic').should('have.length', 1);
|
||||
});
|
||||
|
||||
// # Click the Follow button
|
||||
cy.findByRole('button', {name: /^Follow$/}).click({force: true});
|
||||
|
||||
// * Verify that it now says Following
|
||||
cy.findByRole('button', {name: /Following/}).should('exist');
|
||||
|
||||
// * Verify that the user is now in the following list
|
||||
cy.getStyledComponent('UserRow').within(() => {
|
||||
cy.getStyledComponent('UserPic').should('have.length', 2);
|
||||
});
|
||||
|
||||
// # Click the Follow button
|
||||
cy.findByRole('button', {name: /Following/}).click({force: true});
|
||||
|
||||
// * Verify that it now says (exactly) Follow
|
||||
cy.findByRole('button', {name: /^Follow$/}).should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('there is no channel link but can request to join', () => {
|
||||
// * Assert that the section exists with label Private
|
||||
getOverviewEntry('channel').contains('Private');
|
||||
|
||||
// * Assert that link does not exist
|
||||
getOverviewEntry('channel').within(() => {
|
||||
cy.get('a').should('not.exist');
|
||||
});
|
||||
|
||||
// * Assert that request-join button does not exist
|
||||
getOverviewEntry('channel').within(() => {
|
||||
cy.get('button').should('not.exist');
|
||||
});
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
// # Click Participate button
|
||||
getHeader().findByText('Participate').click();
|
||||
|
||||
// * Assert that modal is shown
|
||||
cy.get('#become-participant-modal').should('exist');
|
||||
|
||||
// # Confirm modal
|
||||
cy.findByTestId('modal-confirm-button').click();
|
||||
|
||||
// Assert that request-join button doesn't exist
|
||||
getOverviewEntry('channel').within(() => {
|
||||
cy.get('button').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('> key metrics', () => {
|
||||
describe('playbook without metrics', () => {
|
||||
describe('it should not render', () => {
|
||||
it('as participant', () => {
|
||||
// * assert metrics does not exist
|
||||
getRHSSection('Key Metrics').should('not.exist');
|
||||
});
|
||||
|
||||
it('as viewer', () => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
|
||||
// * assert metrics does not exist
|
||||
getRHSSection('Key Metrics').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('playbook with metrics (enabled retro)', () => {
|
||||
let playbookWithMetrics;
|
||||
let runWithMetrics;
|
||||
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook with metrics
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook with metrics',
|
||||
memberIDs: [],
|
||||
metrics: [
|
||||
{
|
||||
title: 'Duration',
|
||||
description: 'duration',
|
||||
type: 'metric_duration',
|
||||
target: 6000,
|
||||
},
|
||||
{
|
||||
title: 'Currency',
|
||||
description: 'currency',
|
||||
type: 'metric_currency',
|
||||
target: 100,
|
||||
},
|
||||
{
|
||||
title: 'Integer',
|
||||
description: 'integer',
|
||||
type: 'metric_integer',
|
||||
target: 1,
|
||||
},
|
||||
],
|
||||
}).then((playbook) => {
|
||||
playbookWithMetrics = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbookWithMetrics.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
runWithMetrics = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
const commonTests = () => {
|
||||
it('key metrics is present', () => {
|
||||
getRHSSection('Key Metrics').should('exist');
|
||||
});
|
||||
|
||||
it('link scrolls to retrospective', () => {
|
||||
// # click in view retro link
|
||||
cy.findByRole('link', {name: /View Retrospective/}).click({force: true});
|
||||
|
||||
// * verify that URL has been changed
|
||||
cy.url().should('contain', '#playbook-run-retrospective');
|
||||
});
|
||||
|
||||
it('metric items scroll to corresponding metric', () => {
|
||||
getRHSSection('Key Metrics').within(() => {
|
||||
playbookWithMetrics.metrics.forEach((metric) => {
|
||||
// # Click on metric
|
||||
cy.findByText(metric.title).click({force: true});
|
||||
|
||||
// * Verify that url changed (and therefore we scrolled)
|
||||
cy.url().should('contain', `#playbook-run-retrospective${metric.id}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
|
||||
it('metric items show Add value if empty', () => {
|
||||
getRHSSection('Key Metrics').within(() => {
|
||||
playbookWithMetrics.metrics.forEach((metric) => {
|
||||
// * Verify that we show a placeholder when empty
|
||||
cy.findByText(metric.title).parent().contains('Add value...');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('click on metric items, type and see the result in the RHS', () => {
|
||||
const testData = {
|
||||
metric_duration: {
|
||||
input: '12:06:03',
|
||||
expected: '12d, 6h, 3m',
|
||||
},
|
||||
metric_currency: {
|
||||
input: '5000',
|
||||
expected: '5000',
|
||||
},
|
||||
metric_integer: {
|
||||
input: '42',
|
||||
expected: '42',
|
||||
},
|
||||
};
|
||||
|
||||
// # Type the values for the metrics
|
||||
getRHSSection('Key Metrics').within(() => {
|
||||
playbookWithMetrics.metrics.forEach((metric) => {
|
||||
// # Click on the metric row
|
||||
cy.findByText(metric.title).click();
|
||||
|
||||
// # Seems there's a re-render between clicking the title and
|
||||
// # typing that occasionally leads to dropped keystrokes in
|
||||
// # .type(). Wait for it to avoid.
|
||||
cy.wait(1000);
|
||||
|
||||
// # Type a value for the metric
|
||||
cy.focused().type(testData[metric.type].input);
|
||||
});
|
||||
});
|
||||
|
||||
// * Verify that the RHS is updated with those values
|
||||
getRHSSection('Key Metrics').within(() => {
|
||||
playbookWithMetrics.metrics.forEach((metric) => {
|
||||
// * Verify that the metric was updated in the RHS
|
||||
cy.findByText(metric.title).parent().contains(testData[metric.type].expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${runWithMetrics.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
|
||||
it('metric items show - if empty', () => {
|
||||
getRHSSection('Key Metrics').within(() => {
|
||||
playbookWithMetrics.metrics.forEach((metric) => {
|
||||
// * verify that values are shown as - when empty
|
||||
cy.findByText(metric.title).parent().contains('-');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('playbook with metrics (disabled retro)', () => {
|
||||
let playbookWithMetrics;
|
||||
let runWithMetrics;
|
||||
|
||||
before(() => {
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook with metrics
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook with metrics',
|
||||
memberIDs: [],
|
||||
metrics: [
|
||||
{
|
||||
title: 'Integer',
|
||||
description: 'integer',
|
||||
type: 'metric_integer',
|
||||
target: 1,
|
||||
},
|
||||
],
|
||||
retrospectiveEnabled: false,
|
||||
}).then((playbook) => {
|
||||
playbookWithMetrics = playbook;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: playbookWithMetrics.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
runWithMetrics = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
const commonTests = () => {
|
||||
it('key metrics is hidden', () => {
|
||||
getRHSSection('Key Metrics').should('not.exist');
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${runWithMetrics.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('> recent activity', () => {
|
||||
const commonTests = () => {
|
||||
it('recent activity is present and it contains a timeline', () => {
|
||||
getRHSSection('Recent Activity').within(() => {
|
||||
// * assert that section is shown
|
||||
cy.findByTestId('rhs-timeline').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
it('link switches the RHS to Timeline', () => {
|
||||
getRHSSection('Recent Activity').within(() => {
|
||||
// * click link to see all timeline
|
||||
cy.findByText('View all').click({force: true});
|
||||
});
|
||||
|
||||
cy.findByRole('complementary').within(() => {
|
||||
// * verify we changed to RHS-timeline
|
||||
cy.findByTestId('rhs-title').contains('Timeline');
|
||||
cy.findByTestId('rhs-back-button').should('exist');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
});
|
||||
});
|
||||
});
|
||||
129
e2e-tests/cypress/tests/integration/playbooks/runs/rdp_rhs_spec.js
Обычный файл
129
e2e-tests/cypress/tests/integration/playbooks/runs/rdp_rhs_spec.js
Обычный файл
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > RHS', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
const getRHS = () => cy.findByRole('complementary');
|
||||
|
||||
const getHeaderButton = (name) => cy.findByTestId(`rhs-header-button-${name}`);
|
||||
|
||||
const checkRHSTitle = (expectedTitle) => {
|
||||
getRHS().within(() => {
|
||||
cy.findByTestId('rhs-title').contains(expectedTitle);
|
||||
});
|
||||
};
|
||||
|
||||
const commonTests = () => {
|
||||
it('timeline button toggles timeline in the RHS', () => {
|
||||
// * Verify that the run info RHS is open
|
||||
checkRHSTitle('Run info');
|
||||
|
||||
// # Click on the header timeline button
|
||||
getHeaderButton('timeline').click();
|
||||
|
||||
// * Verify that the run info RHS changed to Timeline
|
||||
checkRHSTitle('Timeline');
|
||||
|
||||
// # Wait so we don't double-click
|
||||
cy.wait(500);
|
||||
|
||||
// # Click again on the header timeline button
|
||||
getHeaderButton('timeline').click();
|
||||
|
||||
// * Verify that the RHS is closed
|
||||
getRHS().should('not.exist');
|
||||
});
|
||||
|
||||
it('info button toggles info in the RHS', () => {
|
||||
// * Verify that the run info RHS is open
|
||||
checkRHSTitle('Run info');
|
||||
|
||||
// # Click on the header info button
|
||||
getHeaderButton('info').click();
|
||||
|
||||
// * Verify that the RHS is now closed
|
||||
getRHS().should('not.exist');
|
||||
|
||||
// # Wait so we don't double-click
|
||||
cy.wait(500);
|
||||
|
||||
// # Click again on the header info button
|
||||
getHeaderButton('info').click();
|
||||
|
||||
// * Verify that the run info RHS is open again
|
||||
checkRHSTitle('Run info');
|
||||
});
|
||||
};
|
||||
|
||||
describe('as participant', () => {
|
||||
commonTests();
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
commonTests();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('runs > run details page > status update', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
|
||||
const getRHS = () => cy.findByRole('complementary');
|
||||
const getStatusUpdates = () => getRHS().findAllByTestId('status-update-card');
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
|
||||
// # Visit the playbook run
|
||||
cy.visit(`/playbooks/runs/${playbookRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('as participant', () => {
|
||||
it('rhs can not be open when there is no updates', () => {
|
||||
// * Assert that the link is not present
|
||||
cy.findByTestId('run-statusupdate-section').findByText('View all updates').should('not.exist');
|
||||
});
|
||||
|
||||
it('link opens the RHS when there are updates', () => {
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testRun.id,
|
||||
message: 'message 1',
|
||||
reminder: 300,
|
||||
});
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testRun.id,
|
||||
message: 'message 2',
|
||||
reminder: 300,
|
||||
});
|
||||
|
||||
// # Click View all updates link
|
||||
cy.findByTestId('run-statusupdate-section').findByText('View all updates').click();
|
||||
|
||||
// * Assert RHS is open and have the correct title/subtitle
|
||||
getRHS().should('be.visible');
|
||||
getRHS().findByTestId('rhs-title').contains('Status updates');
|
||||
getRHS().findByTestId('rhs-subtitle').contains(testRun.name);
|
||||
|
||||
// * Assert that we have both updates in reverse order
|
||||
getStatusUpdates().should('have.length', 2);
|
||||
getStatusUpdates().eq(0).contains('message 2');
|
||||
getStatusUpdates().eq(0).contains(testUser.username);
|
||||
getStatusUpdates().eq(1).contains('message 1');
|
||||
getStatusUpdates().eq(1).contains(testUser.username);
|
||||
});
|
||||
});
|
||||
|
||||
describe('as viewer', () => {
|
||||
beforeEach(() => {
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('rhs can not be open when there is no updates', () => {
|
||||
// * Assert that the link is not present
|
||||
cy.findByTestId('run-statusupdate-section').findByText('View all updates').should('not.exist');
|
||||
});
|
||||
|
||||
it('link opens the RHS when there are updates', () => {
|
||||
cy.apiLogin(testUser).then(() => {
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testRun.id,
|
||||
message: 'message 1',
|
||||
reminder: 300,
|
||||
});
|
||||
cy.apiUpdateStatus({
|
||||
playbookRunId: testRun.id,
|
||||
message: 'message 2',
|
||||
reminder: 300,
|
||||
});
|
||||
});
|
||||
|
||||
cy.apiLogin(testViewerUser).then(() => {
|
||||
// # Click View all updates link
|
||||
cy.findByTestId('run-statusupdate-section').findByText('View all updates').click();
|
||||
|
||||
// * Assert RHS is open and have the correct title/subtitle
|
||||
getRHS().should('be.visible');
|
||||
getRHS().findByTestId('rhs-title').contains('Status updates');
|
||||
getRHS().findByTestId('rhs-subtitle').contains(testRun.name);
|
||||
|
||||
// * Assert that we have both updates in reverse order
|
||||
getStatusUpdates().should('have.length', 2);
|
||||
getStatusUpdates().eq(0).contains('message 2');
|
||||
getStatusUpdates().eq(0).contains(testUser.username);
|
||||
getStatusUpdates().eq(1).contains('message 1');
|
||||
getStatusUpdates().eq(1).contains(testUser.username);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
213
e2e-tests/cypress/tests/integration/playbooks/runs/taskinbox_spec.js
Обычный файл
213
e2e-tests/cypress/tests/integration/playbooks/runs/taskinbox_spec.js
Обычный файл
@@ -0,0 +1,213 @@
|
||||
// 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)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('Task Inbox >', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
|
||||
let testViewerUser;
|
||||
let testPublicPlaybook;
|
||||
let testRun;
|
||||
|
||||
before(() => {
|
||||
cy.apiInitSetup().then(({team, user}) => {
|
||||
testTeam = team;
|
||||
testUser = user;
|
||||
|
||||
cy.apiCreateCustomAdmin().then(({sysadmin: adminUser}) => {
|
||||
cy.apiAddUserToTeam(testTeam.id, adminUser.id);
|
||||
});
|
||||
|
||||
// Create another user in the same team
|
||||
cy.apiCreateUser().then(({user: viewer}) => {
|
||||
testViewerUser = viewer;
|
||||
cy.apiAddUserToTeam(testTeam.id, testViewerUser.id);
|
||||
});
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
// # Create a public playbook
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Public Playbook',
|
||||
checklists: [
|
||||
{
|
||||
title: 'Stage 1',
|
||||
items: [
|
||||
{title: 'Step 1'},
|
||||
{title: 'Step 2'},
|
||||
{title: 'Step 3'},
|
||||
{title: 'Step 4'},
|
||||
],
|
||||
},
|
||||
],
|
||||
memberIDs: [],
|
||||
}).then((playbook) => {
|
||||
testPublicPlaybook = playbook;
|
||||
|
||||
cy.apiRunPlaybook({
|
||||
teamId: testTeam.id,
|
||||
playbookId: testPublicPlaybook.id,
|
||||
playbookRunName: 'the run name',
|
||||
ownerUserId: testUser.id,
|
||||
}).then((playbookRun) => {
|
||||
testRun = playbookRun;
|
||||
cy.apiChangeChecklistItemAssignee(testRun.id, 0, 0, testUser.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// # Size the viewport to show the RHS without covering posts.
|
||||
cy.viewport('macbook-13');
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
|
||||
cy.visit(`/playbooks/runs/${testRun.id}`);
|
||||
cy.assertRunDetailsPageRenderComplete(testUser.username);
|
||||
});
|
||||
|
||||
const getRHS = () => cy.get('#playbooks-backstage-sidebar-right');
|
||||
|
||||
it('icon in global header, with experimental feature flag', () => {
|
||||
// # Enable experimental feature flag
|
||||
cy.apiAdminLogin().then(() => {
|
||||
cy.apiEnsureFeatureFlag('enableexperimentalfeatures', true);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
// # Visit the playbooks product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Verify icon present in global header icon to open
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
});
|
||||
|
||||
it('icon in global header, without experimental feature flag', () => {
|
||||
// # Disable experimental feature flag
|
||||
cy.apiAdminLogin().then(() => {
|
||||
cy.apiEnsureFeatureFlag('enableexperimentalfeatures', false);
|
||||
|
||||
// # Login as testUser
|
||||
cy.apiLogin(testUser);
|
||||
});
|
||||
|
||||
// # Visit the playbooks product
|
||||
cy.visit('/playbooks');
|
||||
|
||||
// # Verify icon present in global header icon to open
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
});
|
||||
|
||||
it('icon toggles taskinbox view', () => {
|
||||
// # Intercept all calls to telemetry
|
||||
cy.interceptTelemetry();
|
||||
|
||||
// # Click on global header icon to open
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
|
||||
// * assert RHS is shown
|
||||
getRHS().should('be.visible');
|
||||
|
||||
// * assert telemetry pageview
|
||||
cy.expectTelemetryToContain([
|
||||
{
|
||||
name: 'task_inbox',
|
||||
type: 'page',
|
||||
},
|
||||
]);
|
||||
|
||||
// * assert zero case
|
||||
getRHS().within(() => {
|
||||
cy.getStyledComponent('HeaderTitle').contains('Your tasks');
|
||||
cy.getStyledComponent('Body').contains('1 assigned');
|
||||
});
|
||||
|
||||
// # Click on global header icon to close
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
|
||||
// * assert RHS is not shown
|
||||
getRHS().should('not.exist');
|
||||
});
|
||||
|
||||
it('show unassigned tasks from runs I own', () => {
|
||||
// # Click on global header icon to open
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
|
||||
// * assert 4 tasks are shown (all tasks from runs I own enabled by default)
|
||||
getRHS().within(() => {
|
||||
cy.getStyledComponent('TaskList').within(() => {
|
||||
cy.getStyledComponent('Container').should('have.length', 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('show only assigned tasks', () => {
|
||||
// # Click on global header icon to open
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
|
||||
getRHS().within(() => {
|
||||
cy.getStyledComponent('TaskList').within(() => {
|
||||
// * assert 4 tasks are shown
|
||||
cy.getStyledComponent('Container').should('have.length', 4);
|
||||
});
|
||||
|
||||
// # Click on filters
|
||||
cy.findByText('Filters').click();
|
||||
});
|
||||
|
||||
// # Deactivate show alltasks
|
||||
cy.findByText('Show all tasks from runs I own').click();
|
||||
|
||||
cy.getStyledComponent('TaskList').within(() => {
|
||||
// * assert 1 tasks are shown
|
||||
cy.getStyledComponent('Container').should('have.length', 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('tasks can be checked', () => {
|
||||
// # Click on global header icon to open
|
||||
cy.findByTestId('header-task-inbox-icon').click();
|
||||
|
||||
getRHS().within(() => {
|
||||
cy.getStyledComponent('TaskList').within(() => {
|
||||
// * assert 4 tasks are shown
|
||||
cy.getStyledComponent('Container').should('have.length', 4);
|
||||
|
||||
// # Check the first task
|
||||
cy.getStyledComponent('Container').eq(0).within(() => {
|
||||
cy.get('input').click();
|
||||
});
|
||||
|
||||
// * assert 3 tasks are shown
|
||||
cy.getStyledComponent('Container').should('have.length', 3);
|
||||
});
|
||||
|
||||
// # Click on filters
|
||||
cy.findByText('Filters').click();
|
||||
});
|
||||
|
||||
// # Activate checked task visibility in filters
|
||||
cy.findByText('Show checked tasks').click();
|
||||
|
||||
getRHS().within(() => {
|
||||
cy.getStyledComponent('TaskList').within(() => {
|
||||
// * assert 4 tasks are shown
|
||||
cy.getStyledComponent('Container').should('have.length', 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
121
e2e-tests/cypress/tests/integration/playbooks/tours_spec_ignore_.js
Обычный файл
121
e2e-tests/cypress/tests/integration/playbooks/tours_spec_ignore_.js
Обычный файл
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// ***************************************************************
|
||||
// - [#] indicates a test step (e.g. # Go to a page)
|
||||
// - [*] indicates an assertion (e.g. * Check the title)
|
||||
// ***************************************************************
|
||||
|
||||
// Stage: @prod
|
||||
// Group: @playbooks
|
||||
|
||||
describe('playbook tour points', () => {
|
||||
let testTeam;
|
||||
let testUser;
|
||||
let testSysadmin;
|
||||
beforeEach(() => {
|
||||
cy.apiInitSetup({promoteNewUserAsAdmin: true}).then(({team, user: sysadmin}) => {
|
||||
testTeam = team;
|
||||
testSysadmin = sysadmin;
|
||||
|
||||
// # Create a user with tutorials enabled
|
||||
cy.apiCreateUser({bypassTutorial: false}).then(({user: userWithTours}) => {
|
||||
testUser = userWithTours;
|
||||
cy.apiAddUserToTeam(team.id, testUser.id);
|
||||
cy.apiLogin(userWithTours);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// # Ensure apiInitSetup() can run again
|
||||
cy.apiLogin(testSysadmin);
|
||||
});
|
||||
|
||||
it('creation tour', () => {
|
||||
// # Open creation view from RHS
|
||||
cy.visit(`/${testTeam.name}/channels/town-square`);
|
||||
cy.get('#incidentIcon').click({force: true});
|
||||
cy.findByRole('button', {name: /create playbook/i}).click();
|
||||
cy.url().should('contain', '/playbooks/playbooks/new');
|
||||
|
||||
// * Verify the tutorial steps
|
||||
cy.contains('Create and assign tasks').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('Set up assumptions').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('Keep stakeholders updated').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('Learn AND reflect').should('be.visible');
|
||||
cy.findByRole('button', {name: /done/i}).click();
|
||||
});
|
||||
|
||||
it('preview tour', () => {
|
||||
// # Make a playbook to preview
|
||||
cy.apiCreatePlaybook({
|
||||
teamId: testTeam.id,
|
||||
title: 'Preview Tour Test Playbook',
|
||||
memberIDs: [],
|
||||
}).then(() => {
|
||||
// # Open the playbook
|
||||
cy.visit('/playbooks/playbooks');
|
||||
cy.findByText('Preview Tour Test Playbook').click();
|
||||
|
||||
// * Verify the tutorial steps
|
||||
cy.contains('Welcome to the playbook preview page!').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('different sections of the playbook').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('Ready to run your playbook?').should('be.visible');
|
||||
cy.findByRole('button', {name: /done/i}).click();
|
||||
});
|
||||
});
|
||||
|
||||
describe('run tour', () => {
|
||||
beforeEach(() => {
|
||||
// # Disable the preview tour which we would otherwise see
|
||||
cy.apiSaveUserPreference([{
|
||||
user_id: testUser.id,
|
||||
category: 'playbook_preview',
|
||||
name: testUser.id,
|
||||
value: '999',
|
||||
}], testUser.id);
|
||||
|
||||
// # Start a run from the tutorial template
|
||||
cy.visit('/playbooks/playbooks');
|
||||
cy.findByText('Learn how to use playbooks').click();
|
||||
cy.findByRole('button', {name: /run playbook/i}).click({force: true});
|
||||
|
||||
// * Verify the tour confirmation modal is shown (other tours don't have one)
|
||||
cy.contains('auto-created your run').should('be.visible');
|
||||
});
|
||||
|
||||
it('follows the tour when chosen from modal', () => {
|
||||
// # Accept the tour
|
||||
cy.contains('quick tour').click();
|
||||
|
||||
// * Verify the tutorial steps
|
||||
cy.contains('See who is involved').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('Post status updates').should('be.visible');
|
||||
cy.findByRole('button', {name: /next/i}).click();
|
||||
|
||||
cy.contains('Track progress and ownership').should('be.visible');
|
||||
cy.findByRole('button', {name: /done/i}).click();
|
||||
});
|
||||
|
||||
it('does not follow the tour when dismissed from modal', () => {
|
||||
// # Dismiss the tour
|
||||
cy.findByRole('button', {name: /let me explore/i}).click();
|
||||
|
||||
// * Verify the first step is _not_ shown
|
||||
cy.contains('See who is involved').should('not.exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
Ссылка в новой задаче
Block a user