summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/cypress/integration/ui
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts15
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts5
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts124
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts31
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts20
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts15
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts17
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts22
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts24
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts69
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts59
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts45
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts37
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts40
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts37
-rw-r--r--src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts39
16 files changed, 599 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts
new file mode 100644
index 000000000..52994859e
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.e2e-spec.ts
@@ -0,0 +1,15 @@
+import { ApiDocsPageHelper } from '../ui/api-docs.po';
+
+describe('Api Docs Page', () => {
+ const apiDocs = new ApiDocsPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ apiDocs.navigateTo();
+ });
+
+ it('should show the API Docs description', () => {
+ cy.get('.renderedMarkdown').first().contains('This is the official Ceph REST API');
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts
new file mode 100644
index 000000000..c7a8d222d
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/api-docs.po.ts
@@ -0,0 +1,5 @@
+import { PageHelper } from '../page-helper.po';
+
+export class ApiDocsPageHelper extends PageHelper {
+ pages = { index: { url: '#/api-docs', id: 'cd-api-docs' } };
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts
new file mode 100644
index 000000000..9cb84480b
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.e2e-spec.ts
@@ -0,0 +1,124 @@
+import { IscsiPageHelper } from '../block/iscsi.po';
+import { HostsPageHelper } from '../cluster/hosts.po';
+import { MonitorsPageHelper } from '../cluster/monitors.po';
+import { OSDsPageHelper } from '../cluster/osds.po';
+import { PageHelper } from '../page-helper.po';
+import { PoolPageHelper } from '../pools/pools.po';
+import { DaemonsPageHelper } from '../rgw/daemons.po';
+import { DashboardPageHelper } from './dashboard.po';
+
+describe('Dashboard Main Page', () => {
+ const dashboard = new DashboardPageHelper();
+ const daemons = new DaemonsPageHelper();
+ const hosts = new HostsPageHelper();
+ const osds = new OSDsPageHelper();
+ const pools = new PoolPageHelper();
+ const monitors = new MonitorsPageHelper();
+ const iscsi = new IscsiPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ dashboard.navigateTo();
+ });
+
+ describe('Check that all hyperlinks on info cards lead to the correct page and fields exist', () => {
+ it('should ensure that all linked info cards lead to correct page', () => {
+ const expectationMap = {
+ Monitors: 'Monitors',
+ OSDs: 'OSDs',
+ Hosts: 'Hosts',
+ 'Object Gateways': 'Daemons',
+ 'iSCSI Gateways': 'Overview',
+ Pools: 'Pools'
+ };
+
+ for (const [linkText, breadcrumbText] of Object.entries(expectationMap)) {
+ cy.location('hash').should('eq', '#/dashboard');
+ dashboard.clickInfoCardLink(linkText);
+ dashboard.expectBreadcrumbText(breadcrumbText);
+ dashboard.navigateBack();
+ }
+ });
+
+ it('should verify that info cards exist on dashboard in proper order', () => {
+ // Ensures that info cards are all displayed on the dashboard tab while being in the proper
+ // order, checks for card title and position via indexing into a list of all info cards.
+ const order = [
+ 'Cluster Status',
+ 'Hosts',
+ 'Monitors',
+ 'OSDs',
+ 'Managers',
+ 'Object Gateways',
+ 'Metadata Servers',
+ 'iSCSI Gateways',
+ 'Raw Capacity',
+ 'Objects',
+ 'PG Status',
+ 'Pools',
+ 'PGs per OSD',
+ 'Client Read/Write',
+ 'Client Throughput',
+ 'Recovery Throughput',
+ 'Scrubbing'
+ ];
+
+ for (let i = 0; i < order.length; i++) {
+ dashboard.infoCard(i).should('contain.text', order[i]);
+ }
+ });
+
+ it('should verify that info card group titles are present and in the right order', () => {
+ cy.location('hash').should('eq', '#/dashboard');
+ dashboard.infoGroupTitle(0).should('eq', 'Status');
+ dashboard.infoGroupTitle(1).should('eq', 'Capacity');
+ dashboard.infoGroupTitle(2).should('eq', 'Performance');
+ });
+ });
+
+ it('Should check that dashboard cards have correct information', () => {
+ interface TestSpec {
+ cardName: string;
+ regexMatcher?: RegExp;
+ pageObject: PageHelper;
+ }
+ const testSpecs: TestSpec[] = [
+ { cardName: 'Object Gateways', regexMatcher: /(\d+)\s+total/, pageObject: daemons },
+ { cardName: 'Monitors', regexMatcher: /(\d+)\s+\(quorum/, pageObject: monitors },
+ { cardName: 'Hosts', regexMatcher: /(\d+)\s+total/, pageObject: hosts },
+ { cardName: 'OSDs', regexMatcher: /(\d+)\s+total/, pageObject: osds },
+ { cardName: 'Pools', pageObject: pools },
+ { cardName: 'iSCSI Gateways', regexMatcher: /(\d+)\s+total/, pageObject: iscsi }
+ ];
+ for (let i = 0; i < testSpecs.length; i++) {
+ const spec = testSpecs[i];
+ dashboard.navigateTo();
+
+ dashboard.infoCardBodyText(spec.cardName).then((infoCardBodyText: string) => {
+ let dashCount = 0;
+
+ if (spec.regexMatcher) {
+ const match = infoCardBodyText.match(new RegExp(spec.regexMatcher));
+ expect(match).to.length.gt(
+ 1,
+ `Regex ${spec.regexMatcher} did not find a match for card with name ` +
+ `${spec.cardName}`
+ );
+ dashCount = Number(match[1]);
+ } else {
+ dashCount = Number(infoCardBodyText);
+ }
+
+ spec.pageObject.navigateTo();
+ spec.pageObject.getTableCount('total').then((tableCount) => {
+ expect(tableCount).to.eq(
+ dashCount,
+ `Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` +
+ `but did not match table count ${tableCount}`
+ );
+ });
+ });
+ }
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts
new file mode 100644
index 000000000..42d63ef44
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts
@@ -0,0 +1,31 @@
+import { PageHelper } from '../page-helper.po';
+
+export class DashboardPageHelper extends PageHelper {
+ pages = { index: { url: '#/dashboard', id: 'cd-dashboard' } };
+
+ infoGroupTitle(index: number) {
+ return cy.get('.info-group-title').its(index).text();
+ }
+
+ clickInfoCardLink(cardName: string) {
+ cy.get(`cd-info-card[cardtitle="${cardName}"]`).contains('a', cardName).click();
+ }
+
+ infoCard(indexOrTitle: number | string) {
+ cy.get('cd-info-card').as('infoCards');
+
+ if (typeof indexOrTitle === 'number') {
+ return cy.get('@infoCards').its(indexOrTitle);
+ } else {
+ return cy.contains('cd-info-card a', indexOrTitle).parent().parent().parent().parent();
+ }
+ }
+
+ infoCardBodyText(infoCard: string) {
+ return this.infoCard(infoCard).find('.card-text').text();
+ }
+
+ infoCardBody(infoCard: string) {
+ return this.infoCard(infoCard).find('.card-text');
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts
new file mode 100644
index 000000000..ccf16c2b5
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.e2e-spec.ts
@@ -0,0 +1,20 @@
+import { LanguagePageHelper } from './language.po';
+
+describe('Shared pages', () => {
+ const language = new LanguagePageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ language.navigateTo();
+ });
+
+ it('should check default language', () => {
+ language.getLanguageBtn().should('contain.text', 'English');
+ });
+
+ it('should check all available languages', () => {
+ language.getLanguageBtn().click();
+ language.getAllLanguages().should('have.length', 1).should('contain.text', 'English');
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts
new file mode 100644
index 000000000..80e21ba1e
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/language.po.ts
@@ -0,0 +1,15 @@
+import { PageHelper } from '../page-helper.po';
+
+export class LanguagePageHelper extends PageHelper {
+ pages = {
+ index: { url: '#/dashboard', id: 'cd-dashboard' }
+ };
+
+ getLanguageBtn() {
+ return cy.get('cd-language-selector a').first();
+ }
+
+ getAllLanguages() {
+ return cy.get('cd-language-selector button');
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts
new file mode 100644
index 000000000..29c9e9e10
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.e2e-spec.ts
@@ -0,0 +1,17 @@
+import { LoginPageHelper } from './login.po';
+
+describe('Login page', () => {
+ const login = new LoginPageHelper();
+
+ it('should login and navigate to dashboard page', () => {
+ login.navigateTo();
+ login.doLogin();
+ });
+
+ it('should logout when clicking the button', () => {
+ login.navigateTo();
+ login.doLogin();
+
+ login.doLogout();
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts
new file mode 100644
index 000000000..d4d2c6921
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/login.po.ts
@@ -0,0 +1,22 @@
+import { PageHelper } from '../page-helper.po';
+
+export class LoginPageHelper extends PageHelper {
+ pages = {
+ index: { url: '#/login', id: 'cd-login' },
+ dashboard: { url: '#/dashboard', id: 'cd-dashboard' }
+ };
+
+ doLogin() {
+ cy.get('[name=username]').type('admin');
+ cy.get('#password').type('admin');
+ cy.get('[type=submit]').click();
+ cy.get('cd-dashboard').should('exist');
+ }
+
+ doLogout() {
+ cy.get('cd-identity a').click();
+ cy.contains('cd-identity span', 'Sign out').click();
+ cy.get('cd-login').should('exist');
+ cy.location('hash').should('eq', '#/login');
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts
new file mode 100644
index 000000000..fee2d2db9
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.e2e-spec.ts
@@ -0,0 +1,24 @@
+import { NavigationPageHelper } from './navigation.po';
+
+describe('Shared pages', () => {
+ const shared = new NavigationPageHelper();
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ shared.navigateTo();
+ });
+
+ it('should display the vertical menu by default', () => {
+ shared.getVerticalMenu().should('not.have.class', 'active');
+ });
+
+ it('should hide the vertical menu', () => {
+ shared.getMenuToggler().click();
+ shared.getVerticalMenu().should('have.class', 'active');
+ });
+
+ it('should navigate to the correct page', () => {
+ shared.checkNavigations(shared.navigations);
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts
new file mode 100644
index 000000000..a7ecf3af0
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/navigation.po.ts
@@ -0,0 +1,69 @@
+import { PageHelper } from '../page-helper.po';
+
+export class NavigationPageHelper extends PageHelper {
+ pages = {
+ index: { url: '#/dashboard', id: 'cd-dashboard' }
+ };
+
+ navigations = [
+ { menu: 'NFS', component: 'cd-error' },
+ {
+ menu: 'Object Gateway',
+ submenus: [
+ { menu: 'Daemons', component: 'cd-rgw-daemon-list' },
+ { menu: 'Users', component: 'cd-rgw-user-list' },
+ { menu: 'Buckets', component: 'cd-rgw-bucket-list' }
+ ]
+ },
+ { menu: 'Dashboard', component: 'cd-dashboard' },
+ {
+ menu: 'Cluster',
+ submenus: [
+ { menu: 'Hosts', component: 'cd-hosts' },
+ { menu: 'Physical Disks', component: 'cd-error' },
+ { menu: 'Monitors', component: 'cd-monitor' },
+ { menu: 'Services', component: 'cd-error' },
+ { menu: 'OSDs', component: 'cd-osd-list' },
+ { menu: 'Configuration', component: 'cd-configuration' },
+ { menu: 'CRUSH map', component: 'cd-crushmap' },
+ { menu: 'Manager Modules', component: 'cd-mgr-module-list' },
+ { menu: 'Logs', component: 'cd-logs' },
+ { menu: 'Monitoring', component: 'cd-prometheus-tabs' }
+ ]
+ },
+ { menu: 'Pools', component: 'cd-pool-list' },
+ {
+ menu: 'Block',
+ submenus: [
+ { menu: 'Images', component: 'cd-error' },
+ { menu: 'Mirroring', component: 'cd-mirroring' },
+ { menu: 'iSCSI', component: 'cd-iscsi' }
+ ]
+ },
+ { menu: 'File Systems', component: 'cd-cephfs-list' }
+ ];
+
+ getVerticalMenu() {
+ return cy.get('nav[id=sidebar]');
+ }
+
+ getMenuToggler() {
+ return cy.get('[aria-label="toggle sidebar visibility"]');
+ }
+
+ checkNavigations(navs: any) {
+ // The nfs-ganesha, RGW, and block/rbd status requests are mocked to ensure that this method runs in time
+ cy.intercept('/ui-api/nfs-ganesha/status', { fixture: 'nfs-ganesha-status.json' });
+ cy.intercept('/ui-api/rgw/status', { fixture: 'rgw-status.json' });
+ cy.intercept('/ui-api/block/rbd/status', { fixture: 'block-rbd-status.json' });
+
+ navs.forEach((nav: any) => {
+ cy.contains('.simplebar-content li.nav-item a', nav.menu).click();
+ if (nav.submenus) {
+ this.checkNavigations(nav.submenus);
+ } else {
+ cy.get(nav.component).should('exist');
+ }
+ });
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts
new file mode 100644
index 000000000..2ee73a706
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.e2e-spec.ts
@@ -0,0 +1,59 @@
+import { PoolPageHelper } from '../pools/pools.po';
+import { NotificationSidebarPageHelper } from './notification.po';
+
+describe('Notification page', () => {
+ const notification = new NotificationSidebarPageHelper();
+ const pools = new PoolPageHelper();
+ const poolName = 'e2e_notification_pool';
+
+ before(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ pools.navigateTo('create');
+ pools.create(poolName, 8);
+ pools.edit_pool_pg(poolName, 4, false);
+ });
+
+ after(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ pools.navigateTo();
+ pools.delete(poolName);
+ });
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ pools.navigateTo();
+ });
+
+ it('should open notification sidebar', () => {
+ notification.getSidebar().should('not.be.visible');
+ notification.open();
+ notification.getSidebar().should('be.visible');
+ });
+
+ it('should display a running task', () => {
+ notification.getToast().should('not.exist');
+
+ // Check that running task is shown.
+ notification.open();
+ notification.getTasks().contains(poolName).should('exist');
+
+ // Delete pool after task is complete (otherwise we get an error).
+ notification.getTasks().contains(poolName, { timeout: 300000 }).should('not.exist');
+ });
+
+ it('should have notifications', () => {
+ notification.open();
+ notification.getNotifications().should('have.length.gt', 0);
+ });
+
+ it('should clear notifications', () => {
+ notification.getToast().should('not.exist');
+ notification.open();
+ notification.getNotifications().should('have.length.gt', 0);
+ notification.getClearNotficationsBtn().should('be.visible');
+ notification.clearNotifications();
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts
new file mode 100644
index 000000000..12c424e35
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/notification.po.ts
@@ -0,0 +1,45 @@
+import { PageHelper } from '../page-helper.po';
+
+export class NotificationSidebarPageHelper extends PageHelper {
+ getNotificatinoIcon() {
+ return cy.get('cd-notifications a');
+ }
+
+ getSidebar() {
+ return cy.get('cd-notifications-sidebar');
+ }
+
+ getTasks() {
+ return this.getSidebar().find('.card.tc_task');
+ }
+
+ getNotifications() {
+ return this.getSidebar().find('.card.tc_notification');
+ }
+
+ getClearNotficationsBtn() {
+ return this.getSidebar().find('button.btn-block');
+ }
+
+ getCloseBtn() {
+ return this.getSidebar().find('button.close');
+ }
+
+ open() {
+ this.getNotificatinoIcon().click();
+ this.getSidebar().should('be.visible');
+ }
+
+ clearNotifications() {
+ // It can happen that although notifications are cleared, by the time we check the notifications
+ // amount, another notification can appear, so we check it more than once (if needed).
+ this.getClearNotficationsBtn().click();
+ this.getNotifications()
+ .should('have.length.gte', 0)
+ .then(($elems) => {
+ if ($elems.length > 0) {
+ this.clearNotifications();
+ }
+ });
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts
new file mode 100644
index 000000000..c3f325dbb
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.e2e-spec.ts
@@ -0,0 +1,37 @@
+import { RoleMgmtPageHelper } from './role-mgmt.po';
+
+describe('Role Management page', () => {
+ const roleMgmt = new RoleMgmtPageHelper();
+ const role_name = 'e2e_role_mgmt_role';
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ roleMgmt.navigateTo();
+ });
+
+ describe('breadcrumb tests', () => {
+ it('should check breadcrumb on roles tab on user management page', () => {
+ roleMgmt.expectBreadcrumbText('Roles');
+ });
+
+ it('should check breadcrumb on role creation page', () => {
+ roleMgmt.navigateTo('create');
+ roleMgmt.expectBreadcrumbText('Create');
+ });
+ });
+
+ describe('role create, edit & delete test', () => {
+ it('should create a role', () => {
+ roleMgmt.create(role_name, 'An interesting description');
+ });
+
+ it('should edit a role', () => {
+ roleMgmt.edit(role_name, 'A far more interesting description');
+ });
+
+ it('should delete a role', () => {
+ roleMgmt.delete(role_name);
+ });
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts
new file mode 100644
index 000000000..1cc3630a4
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/role-mgmt.po.ts
@@ -0,0 +1,40 @@
+import { PageHelper } from '../page-helper.po';
+
+export class RoleMgmtPageHelper extends PageHelper {
+ pages = {
+ index: { url: '#/user-management/roles', id: 'cd-role-list' },
+ create: { url: '#/user-management/roles/create', id: 'cd-role-form' }
+ };
+
+ create(name: string, description: string) {
+ this.navigateTo('create');
+ // Waits for data to load
+ cy.contains('grafana');
+
+ // fill in fields
+ cy.get('#name').type(name);
+ cy.get('#description').type(description);
+
+ // Click the create button and wait for role to be made
+ cy.get('[data-cy=submitBtn]').click();
+ cy.get('.breadcrumb-item.active').should('not.have.text', 'Create');
+
+ this.getFirstTableCell(name).should('exist');
+ }
+
+ edit(name: string, description: string) {
+ this.navigateEdit(name);
+ // Waits for data to load
+ cy.contains('grafana');
+
+ // fill in fields with new values
+ cy.get('#description').clear().type(description);
+
+ // Click the edit button and check new values are present in table
+ cy.get('[data-cy=submitBtn]').click();
+ cy.get('.breadcrumb-item.active').should('not.have.text', 'Edit');
+
+ this.getFirstTableCell(name).should('exist');
+ this.getFirstTableCell(description).should('exist');
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts
new file mode 100644
index 000000000..92dc77212
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.e2e-spec.ts
@@ -0,0 +1,37 @@
+import { UserMgmtPageHelper } from './user-mgmt.po';
+
+describe('User Management page', () => {
+ const userMgmt = new UserMgmtPageHelper();
+ const user_name = 'e2e_user_mgmt_user';
+
+ beforeEach(() => {
+ cy.login();
+ Cypress.Cookies.preserveOnce('token');
+ userMgmt.navigateTo();
+ });
+
+ describe('breadcrumb tests', () => {
+ it('should check breadcrumb on users tab of user management page', () => {
+ userMgmt.expectBreadcrumbText('Users');
+ });
+
+ it('should check breadcrumb on user creation page', () => {
+ userMgmt.navigateTo('create');
+ userMgmt.expectBreadcrumbText('Create');
+ });
+ });
+
+ describe('user create, edit & delete test', () => {
+ it('should create a user', () => {
+ userMgmt.create(user_name, 'cool_password', 'Jeff', 'realemail@realwebsite.com');
+ });
+
+ it('should edit a user', () => {
+ userMgmt.edit(user_name, 'cool_password_number_2', 'Geoff', 'w@m');
+ });
+
+ it('should delete a user', () => {
+ userMgmt.delete(user_name);
+ });
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts
new file mode 100644
index 000000000..fb2b79129
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/user-mgmt.po.ts
@@ -0,0 +1,39 @@
+import { PageHelper } from '../page-helper.po';
+
+export class UserMgmtPageHelper extends PageHelper {
+ pages = {
+ index: { url: '#/user-management/users', id: 'cd-user-list' },
+ create: { url: '#/user-management/users/create', id: 'cd-user-form' }
+ };
+
+ create(username: string, password: string, name: string, email: string) {
+ this.navigateTo('create');
+
+ // fill in fields
+ cy.get('#username').type(username);
+ cy.get('#password').type(password);
+ cy.get('#confirmpassword').type(password);
+ cy.get('#name').type(name);
+ cy.get('#email').type(email);
+
+ // Click the create button and wait for user to be made
+ cy.get('[data-cy=submitBtn]').click();
+ this.getFirstTableCell(username).should('exist');
+ }
+
+ edit(username: string, password: string, name: string, email: string) {
+ this.navigateEdit(username);
+
+ // fill in fields with new values
+ cy.get('#password').clear().type(password);
+ cy.get('#confirmpassword').clear().type(password);
+ cy.get('#name').clear().type(name);
+ cy.get('#email').clear().type(email);
+
+ // Click the edit button and check new values are present in table
+ const editButton = cy.get('[data-cy=submitBtn]');
+ editButton.click();
+ this.getFirstTableCell(email).should('exist');
+ this.getFirstTableCell(name).should('exist');
+ }
+}