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

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

@@ -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('Youll 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('Youll 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('Youll 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('Youll 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();
});
});
});

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

@@ -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);
});
});
});
});

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

@@ -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);
});
});
});
});