From f6e7e716953f4e30e3d65c0466651bb697c53ed2 Mon Sep 17 00:00:00 2001 From: Maria A Nunez Date: Mon, 8 Jun 2026 22:33:37 -0400 Subject: [PATCH] Migrate Zephyr manual tests to Cypress E2E (#36971) * MM-T1814: add 'Add a BOT to a team' Cypress E2E test Co-authored-by: Maria A Nunez * MM-T1335: invite guests to public and private channels Extend invitePeople() helper to accept multiple channels and add a spec verifying both a public and a private channel are added to the guest invite list. Co-authored-by: Maria A Nunez * MM-T1340: verify guest invite email and join flow (not_cloud) Add a non-cloud spec covering step 2 of MM-T1340: read the invitation email, open the join link, complete guest signup, and verify the guest is added to the team. Co-authored-by: Maria A Nunez * MM-T1335: assert channel chips via icons and count Verified against a licensed E2E server: assert exactly two channel chips with public and private channel icons rather than an exact text match, which failed due to extra text in the chip label. Co-authored-by: Maria A Nunez * MM-T1340: accept terms and use valid password in guest signup Verified against a licensed E2E server: the signup Create account button is gated on a 14+ char password and the terms/privacy checkbox. Use newTestPassword(), check the agreement, and assert the app loads. Co-authored-by: Maria A Nunez --------- Co-authored-by: Cursor Agent --- .../in_teams_and_channels_spec.ts | 15 +++ .../guest_invitation_email_not_cloud_spec.ts | 106 ++++++++++++++++++ .../guest_invitation_ui_spec.ts | 23 ++++ .../enterprise/guest_accounts/helpers.ts | 17 +-- 4 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_email_not_cloud_spec.ts diff --git a/e2e-tests/cypress/tests/integration/channels/bot_accounts/in_teams_and_channels_spec.ts b/e2e-tests/cypress/tests/integration/channels/bot_accounts/in_teams_and_channels_spec.ts index 69442b8396e..3af6b16a159 100644 --- a/e2e-tests/cypress/tests/integration/channels/bot_accounts/in_teams_and_channels_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/bot_accounts/in_teams_and_channels_spec.ts @@ -30,6 +30,21 @@ describe('Managing bots in Teams and Channels', () => { }); }); + it('MM-T1814 Add a BOT to a team', () => { + cy.makeClient().then(async (client) => { + // # Go to channel + const channel = await client.getChannelByName(team.id, 'town-square'); + cy.visit(`/${team.name}/channels/${channel.name}`); + + // # Invite bot to team (asserts the modal success message) + const bot = await client.createBot(createBotPatch()); + cy.uiInviteMemberToCurrentTeam(bot.username); + + // * Verify system message in-channel + cy.uiWaitUntilMessagePostedIncludes(`@${bot.username} added to the team by you.`); + }); + }); + it('MM-T1815 Add a BOT to a team that has email restricted', () => { cy.makeClient().then(async (client) => { // # Go to channel diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_email_not_cloud_spec.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_email_not_cloud_spec.ts new file mode 100644 index 00000000000..f15bcbb8374 --- /dev/null +++ b/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_email_not_cloud_spec.ts @@ -0,0 +1,106 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +// *************************************************************** +// - [#] indicates a test step (e.g. #. Go to a page) +// - [*] indicates an assertion (e.g. * Check the title) +// - Use element ID when selecting an element. Create one if none. +// *************************************************************** + +// Stage: @prod +// Group: @channels @not_cloud @enterprise @guest_account + +/** + * Note: This test requires Enterprise license to be uploaded and an SMTP server + * (email retrieval), so it is restricted to non-cloud editions. + */ + +import {UserProfile} from '@mattermost/types/users'; + +import { + changeGuestFeatureSettings, + invitePeople, + verifyInvitationSuccess, +} from './helpers'; + +import * as TIMEOUTS from '@/fixtures/timeouts'; +import { + getJoinEmailTemplate, + getRandomId, + newTestPassword, + reUrl, + verifyEmailBody, +} from '@/utils'; + +describe('Guest Account - Guest User Invitation Email Flow', () => { + let sysadmin: Cypress.UserProfile; + let testTeam: Cypress.Team; + + before(() => { + cy.shouldNotRunOnCloudEdition(); + + // * Check if server has license for Guest Accounts + cy.apiRequireLicenseForFeature('GuestAccounts'); + }); + + beforeEach(() => { + // # Login as sysadmin + cy.apiAdminLogin().then(({user}: {user: UserProfile}) => { + sysadmin = user; + }); + + // # Reset Guest Feature settings (Guest Accounts + Email Invitations enabled) + changeGuestFeatureSettings(); + + cy.apiInitSetup().then(({team}) => { + testTeam = team; + + // # Go to town square + cy.visit(`/${team.name}/channels/town-square`); + }); + }); + + it('MM-T1340 Invite Guests - New User not in the system - join via email', () => { + const username = `g${getRandomId()}`; // username has to start with a letter + const email = `${username}@sample.mattermost.com`; + + // # Search and add a new guest by email, who is not part of the team + invitePeople(email, 1, email); + + // * Verify the invitation was sent (step 1) + verifyInvitationSuccess(email, testTeam, 'An invitation email has been sent.'); + + // # Open the invitation email and extract the join link (step 2) + cy.getRecentEmail({username, email}).then((data) => { + const {body: actualEmailBody, subject} = data; + + // * Verify the email subject is about joining the team as a guest + expect(subject).to.contain(`${sysadmin.username} invited you to join the team ${testTeam.display_name} as a guest`); + + // * Verify the email body matches the expected guest invite template + const expectedEmailBody = getJoinEmailTemplate(sysadmin.username, email, testTeam, true); + verifyEmailBody(expectedEmailBody, actualEmailBody); + + // # Extract invitation link from the invitation email + const invitationLink = actualEmailBody[3].match(reUrl)[0]; + + // # Logout as sysadmin and open the invitation link ("Join now") + cy.apiLogout(); + cy.visit(invitationLink); + }); + + // # Create the guest account with email and password (email is pre-filled from the invite) + cy.get('#input_name').type(username); + cy.get('#input_password-input').type(newTestPassword()); + + // # Agree to the terms and privacy policy, then create the account + cy.findByRole('checkbox', {name: 'Terms and privacy policy checkbox'}).check({force: true}); + cy.findByText('Create account').click(); + + // * Verify the guest is taken into the app and added to the team upon successful signup + cy.get('#SidebarContainer', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible'); + + // * Verify a system message indicates the guest has joined + cy.uiWaitUntilMessagePostedIncludes(username); + }); +}); diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_ui_spec.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_ui_spec.ts index 0d438576fb8..f1f5d24e3f6 100644 --- a/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_ui_spec.ts +++ b/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/guest_invitation_ui_spec.ts @@ -52,6 +52,29 @@ describe('Guest Account - Guest User Invitation Flow', () => { }); }); + it('MM-T1335 Invite Guests - Add Public and Private channels', () => { + // # Create a private channel in the test team + const privateChannelDisplayName = `Private ${getRandomId()}`; + cy.apiCreateChannel(testTeam.id, `private-${getRandomId()}`, privateChannelDisplayName, 'P').then(() => { + // # Invite a new guest by email, adding both a public and a private channel + const email = `temp-${getRandomId()}@mattermost.com`; + invitePeople(email, 1, email, ['Town Square', privateChannelDisplayName], false); + + // * Verify both channels are added to the list of channels the guest will be added to + cy.get('.channels-input__control').should('be.visible').within(() => { + cy.get('.channels-input__multi-value').should('have.length', 2); + + // * Verify the public channel (Town Square) is added + cy.findByText('Town Square').should('be.visible'); + cy.get('.public-channel-icon').should('be.visible'); + + // * Verify the private channel is added + cy.contains('.channels-input__multi-value', privateChannelDisplayName).should('be.visible'); + cy.get('.private-channel-icon').should('be.visible'); + }); + }); + }); + it('MM-T4451 Verify UI Elements of Guest User Invitation Flow', () => { // # Open team menu and click 'Invite People' cy.uiOpenTeamMenu('Invite people'); diff --git a/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/helpers.ts b/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/helpers.ts index 3205a0cad71..d2440000bab 100644 --- a/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/helpers.ts +++ b/e2e-tests/cypress/tests/integration/channels/enterprise/guest_accounts/helpers.ts @@ -14,7 +14,7 @@ export function changeGuestFeatureSettings(featureFlag = true, emailInvitation = }); } -export function invitePeople(typeText: string, resultsCount: number, verifyText: string, channelName = 'Town Square', clickInvite = true) { +export function invitePeople(typeText: string, resultsCount: number, verifyText: string, channelName: string | string[] = 'Town Square', clickInvite = true) { // # Open team menu and click 'Invite People' cy.uiOpenTeamMenu('Invite people'); @@ -28,13 +28,16 @@ export function invitePeople(typeText: string, resultsCount: number, verifyText: cy.get('.users-emails-input__menu'). children().should('have.length', resultsCount).eq(0).should('contain', verifyText).click(); - // # Search and add a Channel - cy.get('.channels-input__control').should('be.visible').within(() => { - cy.get('input').typeWithForce(channelName); + // # Search and add one or more channels + const channelNames = Array.isArray(channelName) ? channelName : [channelName]; + channelNames.forEach((name) => { + cy.get('.channels-input__control').should('be.visible').within(() => { + cy.get('input').typeWithForce(name); + }); + cy.get('.channels-input__menu'). + children().should('have.length', 1). + eq(0).should('contain', name).click(); }); - cy.get('.channels-input__menu'). - children().should('have.length', 1). - eq(0).should('contain', channelName).click(); if (clickInvite) { // # Click Invite Guests Button