From d9a4c2c66dc75e2bd2c2631b8f6b566b16bc6a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milorad=20FIlipovi=C4=87?= Date: Thu, 9 Feb 2023 16:00:55 +0100 Subject: [PATCH] test(editor): Add user management e2e tests (#5438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✅ Added initial UM test using new commands * ✅ Added rest of the UM tests --- cypress/e2e/17-sharing.cy.ts | 53 ---------- cypress/e2e/18-user-management.cy.ts | 96 +++++++++++++++++++ cypress/pages/settings-users.ts | 37 +++++++ .../N8nActionToggle/ActionToggle.vue | 2 +- .../src/components/N8nUsersList/UsersList.vue | 1 + .../src/components/DeleteUserModal.vue | 6 +- 6 files changed, 140 insertions(+), 55 deletions(-) delete mode 100644 cypress/e2e/17-sharing.cy.ts create mode 100644 cypress/e2e/18-user-management.cy.ts diff --git a/cypress/e2e/17-sharing.cy.ts b/cypress/e2e/17-sharing.cy.ts deleted file mode 100644 index ba93c3b72e..0000000000 --- a/cypress/e2e/17-sharing.cy.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants'; - -/** - * User A - Instance owner - * User B - User, owns C1, W1, W2 - * User C - User, owns C2 - * - * W1 - Workflow owned by User B, shared with User C - * W2 - Workflow owned by User B - * - * C1 - Credential owned by User B - * C2 - Credential owned by User C, shared with User A and User B - */ - -const instanceOwner = { - email: `${DEFAULT_USER_EMAIL}A`, - password: DEFAULT_USER_PASSWORD, - firstName: 'User', - lastName: 'A', -}; - -const users = [ - { - email: `${DEFAULT_USER_EMAIL}B`, - password: DEFAULT_USER_PASSWORD, - firstName: 'User', - lastName: 'B', - }, - { - email: `${DEFAULT_USER_EMAIL}C`, - password: DEFAULT_USER_PASSWORD, - firstName: 'User', - lastName: 'C', - }, -]; - -describe('Sharing', () => { - before(() => { - cy.resetAll(); - cy.setupOwner(instanceOwner); - }); - - beforeEach(() => { - cy.on('uncaught:exception', (err, runnable) => { - expect(err.message).to.include('Not logged in'); - return false; - }); - }); - - it(`should invite User A and UserB to instance`, () => { - cy.inviteUsers({ instanceOwner, users }); - }); -}); diff --git a/cypress/e2e/18-user-management.cy.ts b/cypress/e2e/18-user-management.cy.ts new file mode 100644 index 0000000000..6c0f9c84d4 --- /dev/null +++ b/cypress/e2e/18-user-management.cy.ts @@ -0,0 +1,96 @@ +import { MainSidebar } from './../pages/sidebar/main-sidebar'; +import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants'; +import { SettingsSidebar, SettingsUsersPage, WorkflowPage, WorkflowsPage } from '../pages'; + +/** + * User A - Instance owner + * User B - User, owns C1, W1, W2 + * User C - User, owns C2 + * + * W1 - Workflow owned by User B, shared with User C + * W2 - Workflow owned by User B + * + * C1 - Credential owned by User B + * C2 - Credential owned by User C, shared with User A and User B + */ + +const instanceOwner = { + email: `${DEFAULT_USER_EMAIL}A`, + password: DEFAULT_USER_PASSWORD, + firstName: 'User', + lastName: 'A', +}; + +const users = [ + { + email: `${DEFAULT_USER_EMAIL}B`, + password: DEFAULT_USER_PASSWORD, + firstName: 'User', + lastName: 'B', + }, + { + email: `${DEFAULT_USER_EMAIL}C`, + password: DEFAULT_USER_PASSWORD, + firstName: 'User', + lastName: 'C', + }, +]; + +const usersSettingsPage = new SettingsUsersPage(); +const workflowPage = new WorkflowPage(); + +describe('User Management', () => { + before(() => { + cy.resetAll(); + cy.setupOwner(instanceOwner); + }); + + beforeEach(() => { + cy.on('uncaught:exception', (err, runnable) => { + expect(err.message).to.include('Not logged in'); + return false; + }); + }); + + it(`should invite User B and User C to instance`, () => { + cy.inviteUsers({ instanceOwner, users }); + }); + + it('should prevent non-owners to access UM settings', () => { + usersSettingsPage.actions.loginAndVisit(users[0].email, users[0].password, false) + }); + + it('should allow instance owner to access UM settings', () => { + usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true); + }); + + it('should properly render UM settings page for instance owners', () => { + usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true); + // All items in user list should be there + usersSettingsPage.getters.userListItems().should('have.length', 3); + // List item for current user should have the `Owner` badge + usersSettingsPage.getters.userItem(instanceOwner.email).find('.n8n-badge:contains("Owner")').should('exist'); + // Other users list items should contain action pop-up list + usersSettingsPage.getters.userActionsToggle(users[0].email).should('exist'); + usersSettingsPage.getters.userActionsToggle(users[1].email).should('exist'); + }); + + it('should delete user and their data', () => { + usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true); + usersSettingsPage.actions.opedDeleteDialog(users[0].email); + usersSettingsPage.getters.deleteDataRadioButton().realClick(); + usersSettingsPage.getters.deleteDataInput().type('delete all data'); + usersSettingsPage.getters.deleteUserButton().realClick(); + workflowPage.getters.successToast().should('contain', 'User deleted'); + }); + + it('should delete user and transfer their data', () => { + usersSettingsPage.actions.loginAndVisit(instanceOwner.email, instanceOwner.password, true); + usersSettingsPage.actions.opedDeleteDialog(users[1].email); + usersSettingsPage.getters.transferDataRadioButton().realClick(); + usersSettingsPage.getters.userSelectDropDown().realClick(); + usersSettingsPage.getters.userSelectOptions().first().realClick(); + usersSettingsPage.getters.deleteUserButton().realClick(); + workflowPage.getters.successToast().should('contain', 'User deleted'); + }); +}); diff --git a/cypress/pages/settings-users.ts b/cypress/pages/settings-users.ts index ae0048c937..e52d43a809 100644 --- a/cypress/pages/settings-users.ts +++ b/cypress/pages/settings-users.ts @@ -1,5 +1,12 @@ +import { SettingsSidebar } from './sidebar/settings-sidebar'; +import { MainSidebar } from './sidebar/main-sidebar'; +import { WorkflowsPage } from './workflows'; import { BasePage } from './base'; +const workflowsPage = new WorkflowsPage(); +const mainSidebar = new MainSidebar(); +const settingsSidebar = new SettingsSidebar(); + export class SettingsUsersPage extends BasePage { url = '/settings/users'; getters = { @@ -7,8 +14,38 @@ export class SettingsUsersPage extends BasePage { inviteButton: () => cy.getByTestId('settings-users-invite-button').last(), inviteUsersModal: () => cy.getByTestId('inviteUser-modal').last(), inviteUsersModalEmailsInput: () => cy.getByTestId('emails').find('input').first(), + userListItems: () => cy.get('[data-test-id^="user-list-item"]'), + userItem: (email: string) => cy.getByTestId(`user-list-item-${email.toLowerCase()}`), + userActionsToggle: (email: string) => this.getters.userItem(email).find('[data-test-id="action-toggle"]'), + deleteUserAction: () => cy.getByTestId('action-toggle-dropdown').find('li:contains("Delete"):visible'), + confirmDeleteModal: () => cy.getByTestId('deleteUser-modal').last(), + transferDataRadioButton: () => this.getters.confirmDeleteModal().find('[role="radio"]').first(), + deleteDataRadioButton: () => this.getters.confirmDeleteModal().find('[role="radio"]').last(), + userSelectDropDown: () => this.getters.confirmDeleteModal().find('.n8n-select'), + userSelectOptions: () => cy.get('.el-select-dropdown:visible .el-select-dropdown__item'), + deleteUserButton: () => this.getters.confirmDeleteModal().find('button:contains("Delete")'), + deleteDataInput: () => cy.getByTestId('delete-data-input').find('input').first(), }; actions = { goToOwnerSetup: () => this.getters.setUpOwnerButton().click(), + loginAndVisit: (email: string, password: string, isOwner: boolean) => { + cy.signin({ email, password }); + cy.visit(workflowsPage.url); + mainSidebar.actions.goToSettings(); + if (isOwner) { + settingsSidebar.getters.menuItem('Users').click(); + cy.url().should('match', new RegExp(this.url)); + } else { + settingsSidebar.getters.menuItem('Users').should('not.exist'); + // Should be redirected to workflows page if trying to access UM url + cy.visit('/settings/users'); + cy.url().should('match', new RegExp(workflowsPage.url)); + } + }, + opedDeleteDialog: (email: string) => { + this.getters.userActionsToggle(email).click(); + this.getters.deleteUserAction().realClick(); + this.getters.confirmDeleteModal().should('be.visible'); + }, }; } diff --git a/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue b/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue index 1b71a5b3eb..86baf05d21 100644 --- a/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue +++ b/packages/design-system/src/components/N8nActionToggle/ActionToggle.vue @@ -1,5 +1,5 @@