From: Kiefer Chang Date: Tue, 28 Jul 2020 01:57:33 +0000 (+0800) Subject: mgr/dashboard: add e2e tests for the Orchestrator components X-Git-Tag: v16.1.0~1104^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=9c912d5449280e904ef126f0a74e151a25e781bd;p=ceph.git mgr/dashboard: add e2e tests for the Orchestrator components Fixes: https://tracker.ceph.com/issues/44637 Signed-off-by: Kiefer Chang --- diff --git a/qa/suites/rados/cephadm/dashboard/% b/qa/suites/rados/cephadm/dashboard/% new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/qa/suites/rados/cephadm/dashboard/.qa b/qa/suites/rados/cephadm/dashboard/.qa new file mode 120000 index 000000000000..fea2489fdf6d --- /dev/null +++ b/qa/suites/rados/cephadm/dashboard/.qa @@ -0,0 +1 @@ +../.qa \ No newline at end of file diff --git a/qa/suites/rados/cephadm/dashboard/distro/.qa b/qa/suites/rados/cephadm/dashboard/distro/.qa new file mode 120000 index 000000000000..fea2489fdf6d --- /dev/null +++ b/qa/suites/rados/cephadm/dashboard/distro/.qa @@ -0,0 +1 @@ +../.qa \ No newline at end of file diff --git a/qa/suites/rados/cephadm/dashboard/distro/centos_latest.yaml b/qa/suites/rados/cephadm/dashboard/distro/centos_latest.yaml new file mode 120000 index 000000000000..bd9854e70298 --- /dev/null +++ b/qa/suites/rados/cephadm/dashboard/distro/centos_latest.yaml @@ -0,0 +1 @@ +.qa/distros/supported/centos_latest.yaml \ No newline at end of file diff --git a/qa/suites/rados/cephadm/dashboard/task/test_e2e.yaml b/qa/suites/rados/cephadm/dashboard/task/test_e2e.yaml new file mode 100644 index 000000000000..cb6ffb22fe0c --- /dev/null +++ b/qa/suites/rados/cephadm/dashboard/task/test_e2e.yaml @@ -0,0 +1,23 @@ +roles: +# 3 osd roles on host.a is required for cephadm task. It checks if the cluster is healthy. +# More daemons will be deployed on both hosts in e2e tests. +- - host.a + - osd.0 + - osd.1 + - osd.2 + - mon.a + - mgr.a + - client.0 +- - host.b + - client.1 +tasks: +- install: +- cephadm: +- workunit: + clients: + client.1: + - cephadm/create_iscsi_disks.sh +- workunit: + clients: + client.0: + - cephadm/test_dashboard_e2e.sh diff --git a/qa/workunits/cephadm/create_iscsi_disks.sh b/qa/workunits/cephadm/create_iscsi_disks.sh new file mode 100755 index 000000000000..e43791dc841e --- /dev/null +++ b/qa/workunits/cephadm/create_iscsi_disks.sh @@ -0,0 +1,34 @@ +#!/bin/bash -ex +# Create some file-backed iSCSI targets and attach them locally. + +# Exit if it's not CentOS +if ! grep -q rhel /etc/*-release; then + echo "The script only supports CentOS." + exit 1 +fi + +[ -z "$SUDO" ] && SUDO=sudo + +# 15 GB +DISK_FILE_SIZE="16106127360" + +$SUDO yum install -y targetcli iscsi-initiator-utils + +TARGET_NAME="iqn.2003-01.org.linux-iscsi.$(hostname).x8664:sn.foobar" +$SUDO targetcli /iscsi create ${TARGET_NAME} +$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1/portals delete 0.0.0.0 3260 +$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1/portals create 127.0.0.1 3260 +$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1 set attribute generate_node_acls=1 +$SUDO targetcli /iscsi/${TARGET_NAME}/tpg1 set attribute demo_mode_write_protect=0 + +for i in $(seq 3); do + # Create truncated files, and add them as luns + DISK_FILE="/tmp/disk${i}" + $SUDO truncate --size ${DISK_FILE_SIZE} ${DISK_FILE} + + $SUDO targetcli /backstores/fileio create "lun${i}" ${DISK_FILE} + $SUDO targetcli /iscsi/${TARGET_NAME}/tpg1/luns create "/backstores/fileio/lun${i}" +done + +$SUDO iscsiadm -m discovery -t sendtargets -p 127.0.0.1 +$SUDO iscsiadm -m node -p 127.0.0.1 -T ${TARGET_NAME} -l diff --git a/qa/workunits/cephadm/test_dashboard_e2e.sh b/qa/workunits/cephadm/test_dashboard_e2e.sh new file mode 100755 index 000000000000..34633404723b --- /dev/null +++ b/qa/workunits/cephadm/test_dashboard_e2e.sh @@ -0,0 +1,99 @@ +#!/bin/bash -ex + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DASHBOARD_FRONTEND_DIR=${SCRIPT_DIR}/../../../src/pybind/mgr/dashboard/frontend + +[ -z "$SUDO" ] && SUDO=sudo + +install_common () { + if grep -q debian /etc/*-release; then + $SUDO apt-get update + $SUDO apt-get install -y jq npm + elif grep -q rhel /etc/*-release; then + $SUDO yum install -y jq npm + else + echo "Unsupported distribution." + exit 1 + fi +} + +install_chrome () { + if grep -q debian /etc/*-release; then + $SUDO bash -c 'echo "deb [arch=amd64] https://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list' + curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | $SUDO apt-key add - + $SUDO apt-get update + $SUDO apt-get install -y google-chrome-stable + $SUDO apt-get install -y xvfb + $SUDO rm /etc/apt/sources.list.d/google-chrome.list + elif grep -q rhel /etc/*-release; then + $SUDO dd of=/etc/yum.repos.d/google-chrome.repo status=none < { }); it('should verify that selected footer increases when an entry is clicked', () => { - configuration.getTableSelectedCount().should('eq', 1); + configuration.getTableCount('selected').should('eq', 1); }); it('should check that details table opens (w/o tab header)', () => { @@ -54,12 +54,12 @@ describe('Configuration page', () => { it('should show only modified configurations', () => { configuration.filterTable('Modified', 'yes'); - configuration.getTableFoundCount().should('eq', 1); + configuration.getTableCount('found').should('eq', 1); }); it('should hide all modified configurations', () => { configuration.filterTable('Modified', 'no'); - configuration.getTableFoundCount().should('gt', 1); + configuration.getTableCount('found').should('gt', 1); }); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts index d3e75400bba5..c7a6b91795e1 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/hosts.po.ts @@ -1,10 +1,20 @@ import { PageHelper } from '../page-helper.po'; +const pages = { + index: { url: '#/hosts', id: 'cd-hosts' }, + create: { url: '#/hosts/create', id: 'cd-host-form' } +}; + export class HostsPageHelper extends PageHelper { - pages = { index: { url: '#/hosts', id: 'cd-hosts' } }; + pages = pages; + + columnIndex = { + hostname: 2, + labels: 4 + }; check_for_host() { - this.getTableTotalCount().should('not.be.eq', 0); + this.getTableCount('total').should('not.be.eq', 0); } // function that checks all services links work for first @@ -28,4 +38,82 @@ export class HostsPageHelper extends PageHelper { expect(links_tested).gt(0); }); } + + @PageHelper.restrictTo(pages.index.url) + clickHostTab(hostname: string, tabName: string) { + this.getExpandCollapseElement(hostname).click(); + cy.get('cd-host-details').within(() => { + this.getTab(tabName).click(); + }); + } + + @PageHelper.restrictTo(pages.create.url) + add(hostname: string, exist?: boolean) { + cy.get(`${this.pages.create.id}`).within(() => { + cy.get('#hostname').type(hostname); + cy.get('cd-submit-button').click(); + }); + if (exist) { + cy.get('#hostname').should('have.class', 'ng-invalid'); + } else { + // back to host list + cy.get(`${this.pages.index.id}`); + } + } + + @PageHelper.restrictTo(pages.index.url) + checkExist(hostname: string, exist: boolean) { + this.getTableCell(this.columnIndex.hostname, hostname).should(($elements) => { + const hosts = $elements.map((_, el) => el.textContent).get(); + if (exist) { + expect(hosts).to.include(hostname); + } else { + expect(hosts).to.not.include(hostname); + } + }); + } + + @PageHelper.restrictTo(pages.index.url) + delete(hostname: string) { + super.delete(hostname, this.columnIndex.hostname); + } + + // Add or remove labels on a host, then verify labels in the table + @PageHelper.restrictTo(pages.index.url) + editLabels(hostname: string, labels: string[], add: boolean) { + this.getTableCell(this.columnIndex.hostname, hostname).click(); + this.clickActionButton('edit'); + + // add or remove label badges + if (add) { + cy.get('cd-modal').find('.select-menu-edit').click(); + for (const label of labels) { + cy.contains('cd-modal .badge', new RegExp(`^${label}$`)).should('not.exist'); + cy.get('.popover-body input').type(`${label}{enter}`); + } + } else { + for (const label of labels) { + cy.contains('cd-modal .badge', new RegExp(`^${label}$`)) + .find('.badge-remove') + .click(); + } + } + cy.get('cd-modal cd-submit-button').click(); + + // Verify labels are added or removed from Labels column + // First find row with hostname, then find labels in the row + this.getTableCell(this.columnIndex.hostname, hostname) + .parent() + .find(`datatable-body-cell:nth-child(${this.columnIndex.labels})`) + .should(($ele) => { + const newLabels = $ele.text().split(', '); + for (const label of labels) { + if (add) { + expect(newLabels).to.include(label); + } else { + expect(newLabels).to.not.include(label); + } + } + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts new file mode 100644 index 000000000000..ee8e43f7ef7c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/inventory.po.ts @@ -0,0 +1,22 @@ +import { PageHelper } from '../page-helper.po'; + +const pages = { + index: { url: '#/inventory', id: 'cd-inventory' } +}; + +export class InventoryPageHelper extends PageHelper { + pages = pages; + + identify() { + // Nothing we can do, just verify the form is there + this.getFirstTableCell().click(); + cy.contains('cd-table-actions button', 'Identify').click(); + cy.get('cd-modal').within(() => { + cy.get('#duration').select('15 minutes'); + cy.get('#duration').select('10 minutes'); + cy.get('cd-back-button').click(); + }); + cy.get('cd-modal').should('not.visible'); + cy.get(`${this.pages.index.id}`); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts index 7b610fb39d41..bf5c7d8420e9 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.e2e-spec.ts @@ -22,7 +22,7 @@ describe('OSDs page', () => { describe('check existence of fields on OSD page', () => { it('should check that number of rows and count in footer match', () => { - osds.getTableTotalCount().then((text) => { + osds.getTableCount('total').then((text) => { osds.getTableRows().its('length').should('equal', text); }); }); @@ -38,7 +38,7 @@ describe('OSDs page', () => { }); it('should verify that selected footer increases', () => { - osds.getTableSelectedCount().should('equal', 1); + osds.getTableCount('selected').should('equal', 1); }); it('should show the correct text for the tab labels', () => { diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts index 36e0b4feb55b..7e0da4e7ea00 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/cluster/osds.po.ts @@ -1,5 +1,82 @@ import { PageHelper } from '../page-helper.po'; +const pages = { + index: { url: '#/osd', id: 'cd-osd-list' }, + create: { url: '#/osd/create', id: 'cd-osd-form' } +}; + export class OSDsPageHelper extends PageHelper { - pages = { index: { url: '#/osd', id: 'cd-osd-list' } }; + pages = pages; + + columnIndex = { + id: 4, + status: 5 + }; + + @PageHelper.restrictTo(pages.create.url) + create(deviceType: 'hdd' | 'ssd') { + // Click Primary devices Add button + cy.get('cd-osd-devices-selection-groups[name="Primary"]').as('primaryGroups'); + cy.get('@primaryGroups').find('button').click(); + + // Select all devices with `deviceType` + cy.get('cd-osd-devices-selection-modal').within(() => { + cy.get('.modal-footer .tc_submitButton').as('addButton').should('be.disabled'); + this.filterTable('Type', deviceType); + cy.get('@addButton').click(); + }); + + cy.get('@primaryGroups').within(() => { + this.getTableCount('total').as('newOSDCount'); + }); + + cy.get(`${pages.create.id} .card-footer .tc_submitButton`).click(); + cy.get(`cd-osd-creation-preview-modal .modal-footer .tc_submitButton`).click(); + } + + getRowByID(id: number) { + return this.getTableCell(this.columnIndex.id, `${id}`).parent(); + } + + @PageHelper.restrictTo(pages.index.url) + checkStatus(id: number, status: string[]) { + this.getRowByID(id) + .find(`datatable-body-cell:nth-child(${this.columnIndex.status}) .badge`) + .should(($ele) => { + const allStatus = $ele.toArray().map((v) => v.innerText); + for (const s of status) { + expect(allStatus).to.include(s); + } + }); + } + + @PageHelper.restrictTo(pages.index.url) + ensureNoOsd(id: number) { + cy.get(`datatable-body-row datatable-body-cell:nth-child(${this.columnIndex.id})`).should( + ($ele) => { + const osdIds = $ele.toArray().map((v) => v.innerText); + expect(osdIds).to.not.include(`${id}`); + } + ); + } + + @PageHelper.restrictTo(pages.index.url) + deleteByIDs(osdIds: number[], replace?: boolean) { + this.getTableRows().each(($el) => { + const rowOSD = Number( + $el.find('datatable-body-cell .datatable-body-cell-label').get(this.columnIndex.id - 1) + .textContent + ); + if (osdIds.includes(rowOSD)) { + cy.wrap($el).click(); + } + }); + this.clickActionButton('delete'); + if (replace) { + cy.get('cd-modal label[for="preserve"]').click(); + } + cy.get('cd-modal label[for="confirmation"]').click(); + cy.contains('cd-modal button', 'Delete').click(); + cy.get('cd-modal').should('not.exist'); + } } diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts new file mode 100644 index 000000000000..e7609454a180 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/01-hosts.e2e-spec.ts @@ -0,0 +1,57 @@ +import { HostsPageHelper } from '../cluster/hosts.po'; + +describe('Hosts page', () => { + const hosts = new HostsPageHelper(); + + beforeEach(() => { + cy.login(); + hosts.navigateTo(); + }); + + describe('when Orchestrator is available', () => { + beforeEach(function () { + cy.fixture('orchestrator/inventory.json').as('hosts'); + }); + + it('should not add an exsiting host', function () { + const hostname = Cypress._.sample(this.hosts).name; + hosts.navigateTo('create'); + hosts.add(hostname, true); + }); + + it('should delete a host and add it back', function () { + const host = Cypress._.last(this.hosts)['name']; + hosts.delete(host); + + // add it back + hosts.navigateTo('create'); + hosts.add(host); + hosts.checkExist(host, true); + }); + + it('should display inventory', function () { + for (const host of this.hosts) { + hosts.clickHostTab(host.name, 'Inventory'); + cy.get('cd-host-details').within(() => { + hosts.getTableCount('total').should('be.gte', 0); + }); + } + }); + + it('should display daemons', function () { + for (const host of this.hosts) { + hosts.clickHostTab(host.name, 'Daemons'); + cy.get('cd-host-details').within(() => { + hosts.getTableCount('total').should('be.gte', 0); + }); + } + }); + + it('should edit host labels', function () { + const hostname = Cypress._.sample(this.hosts).name; + const labels = ['foo', 'bar']; + hosts.editLabels(hostname, labels, true); + hosts.editLabels(hostname, labels, false); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/02-hosts-inventory.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/02-hosts-inventory.e2e-spec.ts new file mode 100644 index 000000000000..3b74de223d35 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/02-hosts-inventory.e2e-spec.ts @@ -0,0 +1,25 @@ +import { HostsPageHelper } from '../cluster/hosts.po'; + +describe('Hosts page', () => { + const hosts = new HostsPageHelper(); + + beforeEach(() => { + cy.login(); + hosts.navigateTo(); + }); + + describe('when Orchestrator is available', () => { + beforeEach(function () { + cy.fixture('orchestrator/inventory.json').as('hosts'); + }); + + it('should display correct inventory', function () { + for (const host of this.hosts) { + hosts.clickHostTab(host.name, 'Inventory'); + cy.get('cd-host-details').within(() => { + hosts.getTableCount('total').should('be.eq', host.devices.length); + }); + } + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts new file mode 100644 index 000000000000..41dcdecde3fe --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/03-inventory.e2e-spec.ts @@ -0,0 +1,25 @@ +import { InventoryPageHelper } from '../cluster/inventory.po'; + +describe('Inventory page', () => { + const inventory = new InventoryPageHelper(); + + beforeEach(() => { + cy.login(); + inventory.navigateTo(); + }); + + it('should have correct devices', () => { + cy.fixture('orchestrator/inventory.json').then((hosts) => { + const totalDiskCount = Cypress._.sumBy(hosts, 'devices.length'); + inventory.getTableCount('total').should('be.eq', totalDiskCount); + for (const host of hosts) { + inventory.filterTable('Hostname', host['name']); + inventory.getTableCount('found').should('be.eq', host.devices.length); + } + }); + }); + + it('should identify device', () => { + inventory.identify(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts new file mode 100644 index 000000000000..f1a66e0a9577 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/orchestrator/04-osds.e2e-spec.ts @@ -0,0 +1,48 @@ +import { OSDsPageHelper } from '../cluster/osds.po'; +import { DashboardPageHelper } from '../ui/dashboard.po'; + +describe('OSDs page', () => { + const osds = new OSDsPageHelper(); + const dashboard = new DashboardPageHelper(); + + beforeEach(() => { + cy.login(); + osds.navigateTo(); + }); + + describe('when Orchestrator is available', () => { + it('should create and delete OSDs', () => { + osds.getTableCount('total').as('initOSDCount'); + osds.navigateTo('create'); + osds.create('hdd'); + + cy.get('@newOSDCount').then((newCount) => { + cy.get('@initOSDCount').then((oldCount) => { + const expectedCount = Number(oldCount) + Number(newCount); + + // check total rows + osds.expectTableCount('total', expectedCount); + + // landing page is easier to check OSD status + dashboard.navigateTo(); + dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} total`); + dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} up`); + dashboard.infoCardBody('OSDs').should('contain.text', `${expectedCount} in`); + + expect(Number(newCount)).to.be.gte(2); + // Delete the first OSD we created + osds.navigateTo(); + const deleteOsdId = Number(oldCount); + osds.deleteByIDs([deleteOsdId], false); + osds.ensureNoOsd(deleteOsdId); + + // Replace the second OSD we created + const replaceID = Number(oldCount) + 1; + osds.deleteByIDs([replaceID], true); + // deleting OSDs doesn't work in cephadm right now, skip checking + osds.checkStatus(replaceID, ['destroyed']); + }); + }); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts index b1a2fdfda377..38edfc451604 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/page-helper.po.ts @@ -73,6 +73,10 @@ export abstract class PageHelper { return cy.get('.nav.nav-tabs li'); } + getTab(tabName: string) { + return cy.contains('.nav.nav-tabs li', new RegExp(`^${tabName}$`)); + } + getTabText(index: number) { return this.getTabs().its(index).text(); } @@ -123,42 +127,29 @@ export abstract class PageHelper { return cy.get('cd-table .dataTables_wrapper'); } - getTableTotalCount() { - this.waitDataTableToLoad(); - - return cy.get('.datatable-footer-inner .page-count span').then(($elem) => { - const text = $elem - .filter((_i, e) => e.innerText.includes('total')) - .first() - .text(); - - return Number(text.match(/(\d+)\s+total/)[1]); - }); + private getTableCountSpan(spanType: 'selected' | 'found' | 'total') { + return cy.contains('.datatable-footer-inner .page-count span', spanType); } - getTableSelectedCount() { + // Get 'selected', 'found', or 'total' row count of a table. + getTableCount(spanType: 'selected' | 'found' | 'total') { this.waitDataTableToLoad(); - - return cy.get('.datatable-footer-inner .page-count span').then(($elem) => { + return this.getTableCountSpan(spanType).then(($elem) => { const text = $elem - .filter((_i, e) => e.innerText.includes('selected')) + .filter((_i, e) => e.innerText.includes(spanType)) .first() .text(); - return Number(text.match(/(\d+)\s+selected/)[1]); + return Number(text.match(/(\d+)\s+\w*/)[1]); }); } - getTableFoundCount() { + // Wait until selected', 'found', or 'total' row count of a table equal to a number. + expectTableCount(spanType: 'selected' | 'found' | 'total', count: number) { this.waitDataTableToLoad(); - - return cy.get('.datatable-footer-inner .page-count span').then(($elem) => { - const text = $elem - .filter((_i, e) => e.innerText.includes('found')) - .first() - .text(); - - return Number(text.match(/(\d+)\s+found/)[1]); + this.getTableCountSpan(spanType).should(($elem) => { + const text = $elem.first().text(); + expect(Number(text.match(/(\d+)\s+\w*/)[1])).to.equal(count); }); } @@ -190,6 +181,15 @@ export abstract class PageHelper { } } + getTableCell(columnIndex: number, exactContent: string) { + this.waitDataTableToLoad(); + this.seachTable(exactContent); + return cy.contains( + `datatable-body-row datatable-body-cell:nth-child(${columnIndex})`, + new RegExp(`^${exactContent}$`) + ); + } + getExpandCollapseElement(content?: string) { this.waitDataTableToLoad(); @@ -226,10 +226,14 @@ export abstract class PageHelper { cy.contains(`.tc_filter_option .dropdown-item`, option).click(); } + setPageSize(size: string) { + cy.get('cd-table .dataTables_paginate input').first().clear().type(size); + } + seachTable(text: string) { this.waitDataTableToLoad(); - cy.get('cd-table .dataTables_paginate input').first().clear().type('10'); + this.setPageSize('10'); cy.get('cd-table .search input').first().clear().type(text); } @@ -239,18 +243,28 @@ export abstract class PageHelper { return cy.get('cd-table .search button').click(); } + // Click the action button + clickActionButton(action: string) { + cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu + cy.get(`button.${action}`).click(); // click on "action" menu item + } + /** * This is a generic method to delete table rows. * It will select the first row that contains the provided name and delete it. * After that it will wait until the row is no longer displayed. + * @param name The string to search in table cells. + * @param columnIndex If provided, search string in columnIndex column. */ - delete(name: string) { + delete(name: string, columnIndex?: number) { // Selects row - this.getFirstTableCell(name).click(); + const getRow = columnIndex + ? this.getTableCell.bind(this, columnIndex) + : this.getFirstTableCell.bind(this); + getRow(name).click(); // Clicks on table Delete button - cy.get('.table-actions button.dropdown-toggle').first().click(); // open submenu - cy.get('button.delete').click(); // click on "delete" menu item + this.clickActionButton('delete'); // Confirms deletion cy.get('cd-modal .custom-control-label').click(); @@ -260,6 +274,6 @@ export abstract class PageHelper { cy.get('cd-modal').should('not.exist'); // Waits for item to be removed from table - this.getFirstTableCell(name).should('not.exist'); + getRow(name).should('not.exist'); } } 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 index 397745f9745d..c33112be72e6 100644 --- 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 @@ -110,7 +110,7 @@ describe('Dashboard Main Page', () => { } spec.pageObject.navigateTo(); - spec.pageObject.getTableTotalCount().then((tableCount) => { + spec.pageObject.getTableCount('total').then((tableCount) => { expect(tableCount).to.eq( dashCount, `Text of card "${spec.cardName}" and regex "${spec.regexMatcher}" resulted in ${dashCount} ` + 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 index 02125739723e..42d63ef44117 100644 --- a/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts +++ b/src/pybind/mgr/dashboard/frontend/cypress/integration/ui/dashboard.po.ts @@ -24,4 +24,8 @@ export class DashboardPageHelper extends PageHelper { infoCardBodyText(infoCard: string) { return this.infoCard(infoCard).find('.card-text').text(); } + + infoCardBody(infoCard: string) { + return this.infoCard(infoCard).find('.card-text'); + } }