From 7a7209d338526a173c0b8f098e62e62c89c7a09d Mon Sep 17 00:00:00 2001 From: Udi Kalifon Date: Tue, 2 Jul 2019 14:03:20 +0200 Subject: [PATCH] Testing infra for the system dashboards This is the basic Protractor (Selenium) infrastructure to test the dashboards. Included is also a small test to read some of the counters in the dashboards page, and compare them to what we get from the CLI. This PR includes PRs 2287 and 2288 from Dan Trainor, who added custom data-test-id attributes on the elements inthe dashboards, to support the automation so it will be easier to find the elements. Also huge thanks to Honza Pokorny, Jason Rist and Jiri Tomasek who helped with the code and with rebases. Basic steps to run the tests: yarn install yarn webdriver-update export BRIDGE_BASE_ADDRESS='https://*.*.*.redhat.com' export BRIDGE_AUTH_USERNAME=kubeadmin export BRIDGE_AUTH_PASSWORD=22Z.... oc login -s *.*.*.redhat.com:6443 -u kubeadmin -p 22ZW.... export KUBECONFIG=/home/ukalifon/.kube/config export NO_HEADLESS=true # optional - if you want to see the browser yarn run test-suite --suite baremetalSmokeTests --params.openshift true --- frontend/integration-tests/protractor.conf.ts | 106 ++++++++++++++++-- .../tests/dashboards/dashboard.scenario.ts | 26 +++++ .../views/dashboards/dashboards.view.ts | 38 +++++++ .../inventory-card/InventoryItem.tsx | 20 +++- .../tests/dashboards/dashboard.scenario.ts | 27 +++++ frontend/packages/metal3-plugin/package.json | 7 +- .../dashboard/InventoryCard.tsx | 8 ++ .../overview-dashboard/inventory-card.tsx | 24 +++- 8 files changed, 239 insertions(+), 17 deletions(-) create mode 100644 frontend/integration-tests/tests/dashboards/dashboard.scenario.ts create mode 100644 frontend/integration-tests/views/dashboards/dashboards.view.ts create mode 100644 frontend/packages/metal3-plugin/integration-tests/tests/dashboards/dashboard.scenario.ts diff --git a/frontend/integration-tests/protractor.conf.ts b/frontend/integration-tests/protractor.conf.ts index df2759ecb31..46205de12cc 100644 --- a/frontend/integration-tests/protractor.conf.ts +++ b/frontend/integration-tests/protractor.conf.ts @@ -8,11 +8,6 @@ import * as ConsoleReporter from 'jasmine-console-reporter'; import * as failFast from 'protractor-fail-fast'; import { createWriteStream, writeFileSync } from 'fs'; import { format } from 'util'; -import { - resolvePluginPackages, - reducePluginTestSuites, - mergeTestSuites, -} from '@console/plugin-sdk/src/codegen'; const tap = !!process.env.TAP; @@ -194,10 +189,103 @@ export const config: Config = { failFast.clean(); return new Promise((resolve) => htmlReporter.afterLaunch(resolve.bind(this, exitCode))); }, - suites: mergeTestSuites( - testSuites, - reducePluginTestSuites(resolvePluginPackages(), __dirname, suite), - ), + suites: { + dashboardsTests: ['tests/dashboards/dashboard.scenario.ts'], + dashboardsBareMetalHosts: [ + '../packages/metal3-plugin/integration-tests/tests/dashboards/dashboard.scenario.ts', + ], + filter: suite(['tests/filter.scenario.ts']), + annotation: suite(['tests/modal-annotations.scenario.ts']), + environment: suite(['tests/environment.scenario.ts']), + secrets: suite(['tests/secrets.scenario.ts']), + storage: suite(['tests/storage.scenario.ts']), + crud: suite([ + 'tests/crud.scenario.ts', + 'tests/secrets.scenario.ts', + 'tests/filter.scenario.ts', + 'tests/modal-annotations.scenario.ts', + 'tests/environment.scenario.ts', + ]), + monitoring: suite(['tests/monitoring.scenario.ts']), + newApp: suite(['tests/overview/overview.scenario.ts', 'tests/deploy-image.scenario.ts']), + olmFull: suite([ + '../packages/operator-lifecycle-manager/integration-tests/scenarios/descriptors.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/operator-hub.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/global-installmode.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/single-installmode.scenario.ts', + ]), + performance: suite(['tests/performance.scenario.ts']), + serviceCatalog: suite([ + 'tests/service-catalog/service-catalog.scenario.ts', + 'tests/service-catalog/service-broker.scenario.ts', + 'tests/service-catalog/service-class.scenario.ts', + 'tests/service-catalog/service-binding.scenario.ts', + 'tests/developer-catalog.scenario.ts', + ]), + overview: suite(['tests/overview/overview.scenario.ts']), + crdExtensions: suite(['tests/crd-extensions.scenario.ts']), + e2e: suite([ + 'tests/crud.scenario.ts', + 'tests/secrets.scenario.ts', + 'tests/storage.scenario.ts', + 'tests/filter.scenario.ts', + 'tests/modal-annotations.scenario.ts', + 'tests/environment.scenario.ts', + 'tests/overview/overview.scenario.ts', + 'tests/deploy-image.scenario.ts', + 'tests/performance.scenario.ts', + 'tests/monitoring.scenario.ts', + 'tests/crd-extensions.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/descriptors.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/operator-hub.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/global-installmode.scenario.ts', + '../packages/operator-lifecycle-manager/integration-tests/scenarios/single-installmode.scenario.ts', + ]), + release: suite([ + 'tests/crud.scenario.ts', + 'tests/secrets.scenario.ts', + 'tests/filter.scenario.ts', + 'tests/environment.scenario.ts', + 'tests/overview/overview.scenario.ts', + 'tests/deploy-image.scenario.ts', + 'tests/performance.scenario.ts', + 'tests/monitoring.scenario.ts', + 'tests/crd-extensions.scenario.ts', + ]), + 'kubevirt-plugin': suite([ + '../packages/kubevirt-plugin/integration-tests/tests/vm.wizard.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.yaml.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.actions.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.migration.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.resources.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.clone.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.detail.flavor.scenario.ts', + '../packages/kubevirt-plugin/integration-tests/tests/vm.template.wizard.scenario.ts', + ]), + all: suite([ + 'tests/crud.scenario.ts', + 'tests/overview/overview.scenareio.ts', + 'tests/secrets.scenario.ts', + 'tests/storage.scenario.ts', + 'tests/olm/**/*.scenario.ts', + 'tests/service-catalog/**/*.scenario.ts', + 'tests/filter.scenario.ts', + 'tests/modal-annotations.scenario.ts', + 'tests/deploy-image.scenario.ts', + 'tests/operator-hub/operator-hub.scenario.ts', + 'tests/developer-catalog.scenario.ts', + 'tests/monitoring.scenario.ts', + 'tests/devconsole/dev-perspective.scenario.ts', + 'tests/devconsole/git-import-flow.scenario.ts', + 'tests/crd-extensions.scenario.ts', + ]), + clusterSettings: suite(['tests/cluster-settings.scenario.ts']), + login: ['tests/login.scenario.ts'], + devconsole: [ + 'tests/devconsole/dev-perspective.scenario.ts', + 'tests/devconsole/git-import-flow.scenario.ts', + ], + }, params: { // Set to 'true' to enable OpenShift resources in the crud scenario. // Use a string rather than boolean so it can be specified on the command line: diff --git a/frontend/integration-tests/tests/dashboards/dashboard.scenario.ts b/frontend/integration-tests/tests/dashboards/dashboard.scenario.ts new file mode 100644 index 00000000000..f3b2ced8fb5 --- /dev/null +++ b/frontend/integration-tests/tests/dashboards/dashboard.scenario.ts @@ -0,0 +1,26 @@ +import { browser, ExpectedConditions as until } from 'protractor'; +const execSync = require('child_process').execSync; + +import { appHost } from '../../protractor.conf'; +import * as dashboardView from '../../views/dashboards/dashboards.view'; + +describe('Inventory card', () => { + beforeAll(async () => { + await browser.get(`${appHost}/dashboards`); + await dashboardView.isLoaded(); + // wait until the counters in the inventory card show up + await browser.wait( + until.textToBePresentInElement(dashboardView.inventoryNodesItemLabel, ' Nodes'), + 10000, + ); + }); + + it('Node count is displayed', async () => { + // get the number of ready and not ready nodes from the CLI + const output = execSync('oc get nodes -o json', { encoding: 'utf-8' }); + const nodes = JSON.parse(output); + const displayedLabel = await dashboardView.inventoryNodesItemLabel.getText(); + // comparing if the dashboards are displaying ${nodes.items.length} nodes total + expect(displayedLabel).toEqual(`${nodes.items.length} Nodes`); + }); +}); diff --git a/frontend/integration-tests/views/dashboards/dashboards.view.ts b/frontend/integration-tests/views/dashboards/dashboards.view.ts new file mode 100644 index 00000000000..4ac9a083727 --- /dev/null +++ b/frontend/integration-tests/views/dashboards/dashboards.view.ts @@ -0,0 +1,38 @@ +import { browser, $, $$ } from 'protractor'; +import { waitForNone } from '../../protractor.conf'; + +export const untilNoLoadersPresent = waitForNone($$('.co-m-loader')); +export const isLoaded = () => browser.wait(untilNoLoadersPresent).then(() => browser.sleep(2000)); + +export const inventoryNodesItemLabel = $('[data-test-id="console-dashboard-inventory-node"]') + .$('.co-inventory-card__item-title') + .$('a'); +export const inventoryNodesDownCounter = $('[data-test-id="console-dashboard-inventory-node"]').$( + '[data-test-id="console-dashboard-inventory-count-notready"]', +); + +export const inventoryPodsItemLabel = $('[data-test-id="console-dashboard-inventory-pod"]') + .$('.co-inventory-card__item-title') + .$('a'); +export const inventoryPodsDownCounter = $('[data-test-id="console-dashboard-inventory-pod"]').$( + '[data-test-id="console-dashboard-inventory-count-crashloopbackoff-failed"]', +); + +export const inventoryHostsItemLabel = $( + '[data-test-id="console-dashboard-inventory-baremetalhost"]', +) + .$('.co-inventory-card__item-title') + .$('a'); +export const inventoryHostsDownCounter = $( + '[data-test-id="console-dashboard-inventory-baremetalhost"]', +).$('[data-test-id="console-dashboard-inventory-count-notready"]'); + +// Utility function: getTextIfPresent +export async function getTextIfPresent(elem, textIfNotPresent = '') { + if (await elem.isPresent()) { + return elem.getText(); + } + return new Promise((resolve) => { + resolve(textIfNotPresent); + }); +} diff --git a/frontend/packages/console-shared/src/components/dashboard/inventory-card/InventoryItem.tsx b/frontend/packages/console-shared/src/components/dashboard/inventory-card/InventoryItem.tsx index 69837c679fe..020b157699c 100644 --- a/frontend/packages/console-shared/src/components/dashboard/inventory-card/InventoryItem.tsx +++ b/frontend/packages/console-shared/src/components/dashboard/inventory-card/InventoryItem.tsx @@ -69,6 +69,7 @@ const InventoryItem: React.FC = React.memo( error = false, TitleComponent, ExpandedComponent, + ...props }) => { const [expanded, setExpanded] = React.useState(false); const onClick = React.useCallback(() => setExpanded(!expanded), [expanded]); @@ -86,7 +87,7 @@ const InventoryItem: React.FC = React.memo( id={title} className="co-inventory-card__accordion-toggle" > -
+
{isLoading && !error &&
} {TitleComponent ? {titleMessage} : titleMessage} @@ -108,7 +109,10 @@ const InventoryItem: React.FC = React.memo( ) : ( -
+
{isLoading && !error &&
} {TitleComponent ? {titleMessage} : titleMessage} @@ -152,6 +156,7 @@ const StatusLink = connectToFlags( const statusItems = encodeURIComponent(statusIDs.join(',')); const namespacePath = namespace ? `ns/${namespace}` : 'all-namespaces'; const path = basePath || `/k8s/${namespacePath}/${kind.plural}`; + const cleanStatusItems = statusIDs.join('-').toLowerCase(); const to = filterType && statusItems.length > 0 ? `${path}?rowFilter-${filterType}=${statusItems}` : path; const statusGroupIcons = getStatusGroupIcons(flags); @@ -160,7 +165,12 @@ const StatusLink = connectToFlags(
{groupIcon} - {count} + + {count} +
); @@ -189,6 +199,7 @@ export const ResourceInventoryItem = connectToFlags( flags = {}, ExpandedComponent, basePath, + ...rest }) => { const TitleComponent = React.useCallback( (props) => ( @@ -211,6 +222,7 @@ export const ResourceInventoryItem = connectToFlags( error={error} TitleComponent={showLink ? TitleComponent : null} ExpandedComponent={ExpandedComponent} + data-test-id={rest['data-test-id']} > {top3Groups.map((key) => showLink ? ( @@ -257,6 +269,7 @@ type InventoryItemProps = { error?: boolean; TitleComponent?: React.ComponentType<{}>; ExpandedComponent?: React.ComponentType<{}>; + 'data-test-id'?: string; }; type StatusProps = WithFlagsProps & { @@ -295,4 +308,5 @@ type ResourceTitleComponentComponent = { kind: K8sKind; namespace: string; basePath?: string; + 'data-test-id'?: string; }; diff --git a/frontend/packages/metal3-plugin/integration-tests/tests/dashboards/dashboard.scenario.ts b/frontend/packages/metal3-plugin/integration-tests/tests/dashboards/dashboard.scenario.ts new file mode 100644 index 00000000000..94e1cf6618b --- /dev/null +++ b/frontend/packages/metal3-plugin/integration-tests/tests/dashboards/dashboard.scenario.ts @@ -0,0 +1,27 @@ +import { execSync } from 'child_process'; +import { browser, ExpectedConditions as until } from 'protractor'; +import { appHost } from '../../../../../integration-tests/protractor.conf.ts'; +import * as dashboardView from '../../../../../integration-tests/views/dashboards/dashboards.view'; + +describe('Inventory card', () => { + beforeAll(async () => { + await browser.get(`${appHost}/dashboards`); + await dashboardView.isLoaded(); + // wait until the counters in the inventory card show up + await browser.wait( + until.textToBePresentInElement(dashboardView.inventoryHostsItemLabel, ' Bare Metal Hosts'), + 10000, + ); + }); + + it('Host count is displayed', async () => { + // get the hosts and their statuses from the CLI + const output = execSync('oc get baremetalhosts -n openshift-machine-api -o json', { + encoding: 'utf-8', + }); + const hosts = JSON.parse(output); + const displayedLabel = await dashboardView.inventoryHostsItemLabel.getText(); + // comparing if the dashboards are displaying ${hosts.items.length} hosts total + expect(displayedLabel).toEqual(`${hosts.items.length} Bare Metal Hosts`); + }); +}); diff --git a/frontend/packages/metal3-plugin/package.json b/frontend/packages/metal3-plugin/package.json index 0e7bf5b18be..e410bcd7b76 100644 --- a/frontend/packages/metal3-plugin/package.json +++ b/frontend/packages/metal3-plugin/package.json @@ -8,6 +8,11 @@ "@console/shared": "0.0.0-fixed" }, "consolePlugin": { - "entry": "src/plugin.ts" + "entry": "src/plugin.ts", + "integrationTests": { + "dashboardsBareMetalHosts": [ + "integration-tests/tests/dashboards/dashboard.scenario.ts" + ] + } } } diff --git a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx index 98d08ec8716..c4c382c97cc 100644 --- a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx +++ b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/dashboard/InventoryCard.tsx @@ -97,10 +97,18 @@ const InventoryCard: React.FC = () => { TitleComponent={DiskTitleComponent} /> >>>>>> 2657874c6... Testing infra for the system dashboards /> diff --git a/frontend/public/components/dashboard/dashboards-page/overview-dashboard/inventory-card.tsx b/frontend/public/components/dashboard/dashboards-page/overview-dashboard/inventory-card.tsx index c8cce3be6f0..1d82c7929fb 100644 --- a/frontend/public/components/dashboard/dashboards-page/overview-dashboard/inventory-card.tsx +++ b/frontend/public/components/dashboard/dashboards-page/overview-dashboard/inventory-card.tsx @@ -49,6 +49,7 @@ const ClusterInventoryItem = withDashboardResources( useAbbr, additionalResources, expandedComponent, + ...props }: ClusterInventoryItemProps) => { React.useEffect(() => { const resource = getFirehoseResource(model); @@ -104,6 +105,7 @@ const ClusterInventoryItem = withDashboardResources( useAbbr={useAbbr} additionalResources={additionalResourcesData} ExpandedComponent={expandedComponent ? ExpandedComponent : null} + data-test-id={props['data-test-id']} /> ); }, @@ -120,14 +122,26 @@ export const InventoryCard = connectToFlags( Cluster Inventory - - - + + + + data-test-id="console-dashboard-inventory-pvcm" + /> {items.map((item) => ( ))} @@ -149,4 +164,5 @@ type ClusterInventoryItemProps = DashboardItemProps & { useAbbr?: boolean; additionalResources?: FirehoseResource[]; expandedComponent?: LazyLoader; + 'data-test-id'?: string; };