From 3ba1b2b9dc774cc75461b19c1a687a22f7ae3780 Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Mon, 31 Aug 2026 16:51:39 -0400 Subject: [PATCH] feat: add idempotent ensureMonitoringPlugin command --- web/cypress/e2e/monitoring/00.bvt_admin.cy.ts | 2 +- web/cypress/e2e/monitoring/00.bvt_dev.cy.ts | 2 +- .../regression/01.reg_alerts_admin.cy.ts | 24 +++- .../regression/02.reg_metrics_admin_1.cy.ts | 4 +- .../regression/02.reg_metrics_admin_2.cy.ts | 4 +- .../03.reg_legacy_dashboards_admin.cy.ts | 4 +- web/cypress/support/commands/auth-commands.ts | 27 ++--- .../support/commands/image-patch-commands.ts | 107 +++++++++++++----- .../support/commands/operator-commands.ts | 51 +++------ .../support/commands/utility-commands.ts | 60 ---------- 10 files changed, 138 insertions(+), 147 deletions(-) diff --git a/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts b/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts index b55383b06..95e9a6508 100644 --- a/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts +++ b/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts @@ -11,7 +11,7 @@ describe( { tags: ['@alerting', '@legacy-dashboards', '@metrics', '@targets'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { diff --git a/web/cypress/e2e/monitoring/00.bvt_dev.cy.ts b/web/cypress/e2e/monitoring/00.bvt_dev.cy.ts index 52013e81a..94467b318 100644 --- a/web/cypress/e2e/monitoring/00.bvt_dev.cy.ts +++ b/web/cypress/e2e/monitoring/00.bvt_dev.cy.ts @@ -7,7 +7,7 @@ import { CLUSTER_MONITORING_OPERATOR } from '../../support/operators'; describe('BVT: Monitoring - Namespaced', { tags: ['@alerting'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { diff --git a/web/cypress/e2e/monitoring/regression/01.reg_alerts_admin.cy.ts b/web/cypress/e2e/monitoring/regression/01.reg_alerts_admin.cy.ts index bd939ce1b..298156962 100644 --- a/web/cypress/e2e/monitoring/regression/01.reg_alerts_admin.cy.ts +++ b/web/cypress/e2e/monitoring/regression/01.reg_alerts_admin.cy.ts @@ -4,6 +4,7 @@ import { testAlertsCorePlatformHeaderRegression, testAlertsRegression, } from '../../../support/monitoring/01.reg_alerts.cy'; +import { testAlertsRegressionNamespace } from '../../../support/monitoring/04.reg_alerts_namespace.cy'; import { commonPages } from '../../../views/common'; import { nav } from '../../../views/nav'; import { CustomerPerspectiveName } from '@/shared/constants/perspective'; @@ -14,7 +15,7 @@ describe( { tags: ['@alerting', '@metrics'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); cy.switchPerspective('Core platform'); }); @@ -33,3 +34,24 @@ describe( testAlertsRegression(CustomerPerspectiveName.CorePlatform); }, ); + +describe( + 'Regression: Monitoring - Alerts Namespaced (Administrator)', + { tags: ['@alerting'] }, + () => { + before(() => { + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + }); + + beforeEach(() => { + alerts.interceptWatchdogAlert(); + nav.sidenav.clickNavLink(['Observe', 'Alerting']); + commonPages.titleShouldHaveText('Alerting'); + alerts.interceptWatchdogAlert(); + cy.changeNamespace(CLUSTER_MONITORING_OPERATOR.namespace); + }); + + // Run tests in Administrator perspective + testAlertsRegressionNamespace(CustomerPerspectiveName.CorePlatform); + }, +); diff --git a/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_1.cy.ts b/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_1.cy.ts index 03659835e..ae722c09e 100644 --- a/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_1.cy.ts +++ b/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_1.cy.ts @@ -8,7 +8,7 @@ import { CustomerPerspectiveName } from '@/shared/constants/perspective'; // Test suite for Administrator perspective describe('Regression: Monitoring - Metrics (Administrator)', { tags: ['@metrics'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { @@ -27,7 +27,7 @@ describe( { tags: ['@metrics'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { diff --git a/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_2.cy.ts b/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_2.cy.ts index b0798079a..59b8b357b 100644 --- a/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_2.cy.ts +++ b/web/cypress/e2e/monitoring/regression/02.reg_metrics_admin_2.cy.ts @@ -8,7 +8,7 @@ import { CustomerPerspectiveName } from '@/shared/constants/perspective'; // Test suite for Administrator perspective describe('Regression: Monitoring - Metrics (Administrator)', { tags: ['@metrics'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { @@ -27,7 +27,7 @@ describe( { tags: ['@metrics'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { diff --git a/web/cypress/e2e/monitoring/regression/03.reg_legacy_dashboards_admin.cy.ts b/web/cypress/e2e/monitoring/regression/03.reg_legacy_dashboards_admin.cy.ts index 407bdb057..e65d9fa9b 100644 --- a/web/cypress/e2e/monitoring/regression/03.reg_legacy_dashboards_admin.cy.ts +++ b/web/cypress/e2e/monitoring/regression/03.reg_legacy_dashboards_admin.cy.ts @@ -11,7 +11,7 @@ describe( { tags: ['@legacy-dashboards'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { @@ -37,7 +37,7 @@ describe( { tags: ['@legacy-dashboards'] }, () => { before(() => { - cy.beforeBlock(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { diff --git a/web/cypress/support/commands/auth-commands.ts b/web/cypress/support/commands/auth-commands.ts index 6b35cce26..928401d79 100644 --- a/web/cypress/support/commands/auth-commands.ts +++ b/web/cypress/support/commands/auth-commands.ts @@ -35,7 +35,7 @@ declare global { // Moved from operator-commands.ts so all auth concerns live in one file. export const operatorAuthUtils = { - performLoginAndAuth(useSession: boolean): void { + ensureUserPermissions(): void { if (`${Cypress.env('LOGIN_USERNAME')}` === 'kubeadmin') { cy.adminCLI( `oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`, @@ -70,6 +70,9 @@ export const operatorAuthUtils = { `oc adm policy add-role-to-user view ${Cypress.env('LOGIN_USERNAME')} -n default`, ); } + }, + + login(useSession = true): void { cy.exec( `oc get oauthclient openshift-browser-client -o go-template ` + `--template="{{index .redirectURIs 0}}" --kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, @@ -108,12 +111,14 @@ export const operatorAuthUtils = { loginAndAuth(): void { cy.log('Before block'); - operatorAuthUtils.performLoginAndAuth(true); + operatorAuthUtils.ensureUserPermissions(); + operatorAuthUtils.login(); }, loginAndAuthNoSession(): void { cy.log('Before block (no session)'); - operatorAuthUtils.performLoginAndAuth(false); + operatorAuthUtils.ensureUserPermissions(); + operatorAuthUtils.login(false); }, generateCOOSessionKey( @@ -147,20 +152,6 @@ export const operatorAuthUtils = { return [...baseKey, ...envVars.map((v) => v || '')]; }, - generateMPSessionKey(CLUSTER_MONITORING_OPERATOR: { - namespace: string; - operatorName: string; - }): string[] { - const baseKey = [ - Cypress.env('LOGIN_IDP'), - Cypress.env('LOGIN_USERNAME'), - CLUSTER_MONITORING_OPERATOR.namespace, - CLUSTER_MONITORING_OPERATOR.operatorName, - ]; - const envVars = [Cypress.env('SKIP_ALL_INSTALL'), Cypress.env('MP_IMAGE')]; - return [...baseKey, ...envVars.map((v) => v || '')]; - }, - generateKNVSessionKey(CNV: { namespace: string; packageName: string }): string[] { const baseKey = [ Cypress.env('LOGIN_IDP'), @@ -337,7 +328,7 @@ Cypress.Commands.add('relogin', (provider: string, username: string, password: s cy.log('Commands relogin - fetching OAuth URL and performing fresh login'); cy.uiLogout(); - // Get the OAuth URL from the cluster (same as performLoginAndAuth does) + // Get the OAuth URL from the cluster before performing a fresh login. cy.exec( `oc get oauthclient openshift-browser-client -o go-template ` + `--template="{{index .redirectURIs 0}}" --kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, diff --git a/web/cypress/support/commands/image-patch-commands.ts b/web/cypress/support/commands/image-patch-commands.ts index 0cf18d387..b5357bb8b 100644 --- a/web/cypress/support/commands/image-patch-commands.ts +++ b/web/cypress/support/commands/image-patch-commands.ts @@ -3,33 +3,90 @@ import { readyTimeoutMilliseconds } from '../timeouts'; export {}; +function getImage(resource: string, namespace: string): Cypress.Chainable { + return cy + .exec( + `oc get ${resource} -n ${namespace} ` + + `-o jsonpath='{.spec.template.spec.containers[0].image}' ` + + `--kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, + ) + .then((result) => result.stdout.trim()); +} + +function waitForMonitoringPluginImage(namespace: string, expectedImage: string): void { + cy.waitUntil( + () => + cy + .exec( + `oc get pods -l app.kubernetes.io/name=monitoring-plugin -n ${namespace} ` + + `-o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\\n"}{end}' ` + + `--kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, + { failOnNonZeroExit: false }, + ) + .then((result) => { + const images = result.stdout.trim().split('\n').filter(Boolean); + return ( + result.code === 0 && + images.length > 0 && + images.every((image) => image === expectedImage) + ); + }), + { + timeout: readyTimeoutMilliseconds, + interval: 5000, + errorMsg: `Monitoring Plugin pods did not converge to image ${expectedImage}`, + }, + ); +} + export const imagePatchUtils = { + getImage, + setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR: { namespace: string }): void { - cy.log('Set Monitoring Plugin image in operator CSV'); - if (Cypress.env('MP_IMAGE')) { - cy.exec('./cypress/fixtures/cmo/update-monitoring-plugin-image.sh', { - env: { - MP_IMAGE: Cypress.env('MP_IMAGE'), - KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), - MP_NAMESPACE: `${CLUSTER_MONITORING_OPERATOR.namespace}`, - }, - timeout: readyTimeoutMilliseconds, - failOnNonZeroExit: true, - }).then((result) => { - expect(result.code).to.eq(0); - cy.log(`CMO deployment Scaled Down successfully: ${result.stdout}`); - }); + const expectedImage = Cypress.env('MP_IMAGE') as string | undefined; + if (expectedImage) { + cy.log('Check Monitoring Plugin image'); + imagePatchUtils + .getImage('deployment/monitoring-plugin', CLUSTER_MONITORING_OPERATOR.namespace) + .then((currentImage) => { + if (currentImage === expectedImage) { + waitForPodsReady( + 'app.kubernetes.io/name=monitoring-plugin', + CLUSTER_MONITORING_OPERATOR.namespace, + readyTimeoutMilliseconds, + ); + waitForMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR.namespace, expectedImage); + cy.log(`Monitoring Plugin already uses ${expectedImage}`); + return; + } - waitForPodsReady( - 'app.kubernetes.io/name=monitoring-plugin', - CLUSTER_MONITORING_OPERATOR.namespace, - readyTimeoutMilliseconds, - ); - cy.log( - `Monitoring plugin pod is now running in namespace: ` + - `${CLUSTER_MONITORING_OPERATOR.namespace}`, - ); - cy.reload(true); + cy.log( + `Update Monitoring Plugin image from ${currentImage || 'unknown'} to ${expectedImage}`, + ); + cy.exec('./cypress/fixtures/cmo/update-monitoring-plugin-image.sh', { + env: { + MP_IMAGE: expectedImage, + KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), + MP_NAMESPACE: `${CLUSTER_MONITORING_OPERATOR.namespace}`, + }, + timeout: readyTimeoutMilliseconds, + }).then((result) => { + expect(result.code).to.eq(0); + cy.log(`CMO deployment Scaled Down successfully: ${result.stdout}`); + }); + + waitForPodsReady( + 'app.kubernetes.io/name=monitoring-plugin', + CLUSTER_MONITORING_OPERATOR.namespace, + readyTimeoutMilliseconds, + ); + cy.log( + `Monitoring plugin pod is now running in namespace: ` + + `${CLUSTER_MONITORING_OPERATOR.namespace}`, + ); + waitForMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR.namespace, expectedImage); + cy.reload(true); + }); } else { cy.log('MP_IMAGE is NOT set. Skipping patching the image in CMO operator CSV.'); } @@ -58,7 +115,6 @@ export const imagePatchUtils = { MCP_NAMESPACE: `${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, }, timeout: readyTimeoutMilliseconds, - failOnNonZeroExit: true, }).then((result) => { expect(result.code).to.eq(0); cy.log(`COO CSV updated successfully with ${config.componentName} image: ${result.stdout}`); @@ -192,7 +248,6 @@ export const imagePatchUtils = { MP_NAMESPACE: `${CLUSTER_MONITORING_OPERATOR.namespace}`, }, timeout: readyTimeoutMilliseconds, - failOnNonZeroExit: true, }).then((result) => { expect(result.code).to.eq(0); cy.log(`CMO CSV reverted successfully with Monitoring Plugin image: ${result.stdout}`); diff --git a/web/cypress/support/commands/operator-commands.ts b/web/cypress/support/commands/operator-commands.ts index e5b09481c..10bacefb6 100644 --- a/web/cypress/support/commands/operator-commands.ts +++ b/web/cypress/support/commands/operator-commands.ts @@ -22,7 +22,10 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - beforeBlock(CLUSTER_MONITORING_OPERATOR: { namespace: string; operatorName: string }); + ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR: { + namespace: string; + operatorName: string; + }); cleanupMP(CLUSTER_MONITORING_OPERATOR: { namespace: string; operatorName: string }); beforeBlockCOO( CLUSTER_OBSERVABILITY_OPERATOR: { @@ -92,9 +95,13 @@ function collectDebugInfo( return; } cy.aboutModal(); - cy.podImage('monitoring-plugin', CLUSTER_MONITORING_OPERATOR.namespace); + imagePatchUtils + .getImage('deployment/monitoring-plugin', CLUSTER_MONITORING_OPERATOR.namespace) + .then((image) => cy.log(`Monitoring Plugin image: ${image}`)); if (CLUSTER_OBSERVABILITY_OPERATOR && CLUSTER_OBSERVABILITY_OPERATOR.namespace) { - cy.podImage('monitoring', CLUSTER_OBSERVABILITY_OPERATOR.namespace); + imagePatchUtils + .getImage('deployment/monitoring', CLUSTER_OBSERVABILITY_OPERATOR.namespace) + .then((image) => cy.log(`Monitoring Console Plugin image: ${image}`)); } } @@ -200,38 +207,14 @@ function cleanupUIPlugin( // ── Cypress commands ─────────────────────────────────────────────── Cypress.Commands.add( - 'beforeBlock', + 'ensureMonitoringPlugin', (CLUSTER_MONITORING_OPERATOR: { namespace: string; operatorName: string }) => { - if (useSession) { - const sessionKey = operatorAuthUtils.generateMPSessionKey(CLUSTER_MONITORING_OPERATOR); - - cy.session( - sessionKey, - () => { - cy.log('Before block (session)'); - cy.cleanupMP(CLUSTER_MONITORING_OPERATOR); - operatorAuthUtils.loginAndAuthNoSession(); - imagePatchUtils.setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR); - collectDebugInfo(CLUSTER_MONITORING_OPERATOR); - cy.task('clearDownloads'); - cy.log('Before block (session) completed'); - }, - { - cacheAcrossSpecs: true, - validate() { - cy.validateLogin(); - }, - }, - ); - } else { - cy.log('Before block (no session)'); - cy.cleanupMP(CLUSTER_MONITORING_OPERATOR); - operatorAuthUtils.loginAndAuth(); - imagePatchUtils.setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR); - collectDebugInfo(CLUSTER_MONITORING_OPERATOR); - cy.task('clearDownloads'); - cy.log('Before block (no session) completed'); - } + cy.log('Ensure Monitoring Plugin'); + operatorAuthUtils.loginAndAuth(); + imagePatchUtils.setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR); + collectDebugInfo(CLUSTER_MONITORING_OPERATOR); + cy.task('clearDownloads'); + cy.log('Ensure Monitoring Plugin completed'); }, ); diff --git a/web/cypress/support/commands/utility-commands.ts b/web/cypress/support/commands/utility-commands.ts index adc5069a5..c506dd4e1 100644 --- a/web/cypress/support/commands/utility-commands.ts +++ b/web/cypress/support/commands/utility-commands.ts @@ -14,7 +14,6 @@ declare global { clickNavLink(path: string[]): Chainable; changeNamespace(namespace: string): Chainable; aboutModal(): Chainable; - podImage(pod: string, namespace: string): Chainable; assertNamespace(namespace: string, exists: boolean): Chainable; checkForAlertRecursively(attemptsLeft?: number): Chainable; dynamicPluginWorkConsoleAround(): Chainable; @@ -191,65 +190,6 @@ Cypress.Commands.overwrite('log', (log, ...args) => { } }); -Cypress.Commands.add('podImage', (pod: string, namespace: string) => { - cy.log('Get pod image'); - cy.switchPerspective('Core platform', 'Administrator'); - cy.clickNavLink(['Workloads', 'Pods']); - cy.byTestID('page-heading').contains('Pods').should('be.visible'); - cy.get( - 'table tbody tr, [data-ouia-component-id="DataViewTableBasic"], [data-test="empty-box-body"]', - { timeout: 30000 }, - ).should('have.length.greaterThan', 0); - cy.changeNamespace(namespace); - // Wait for the pod table to load after namespace change so the page stabilizes - cy.get('table tbody tr, [data-ouia-component-id="DataViewTableBasic"] tbody tr', { - timeout: 30000, - }).should('have.length.greaterThan', 0); - cy.get('[data-ouia-component-id="DataViewFilters"]', { - timeout: 30000, - }).should('have.length.greaterThan', 0); - cy.wait(10000); - // Re-check for DataViewFilters after the table has stabilized - cy.get('body').then(($body) => { - const hasDataViewFilters = $body.find('[data-ouia-component-id="DataViewFilters"]').length > 0; - let filterSelector: string; - if (hasDataViewFilters) { - const hasFilterByName = $body.find('[placeholder="Filter by name"]').length > 0; - filterSelector = '[placeholder="Filter by name"]'; - if (!hasFilterByName) { - cy.byOUIAID('DataViewFilters') - .find('button') - .contains('Status') - .scrollIntoView() - .should('be.visible') - .click(); - cy.byOUIAID('OUIA-Generated-Menu') - .find('button') - .contains('Name') - .scrollIntoView() - .should('be.visible') - .click(); - } - } else { - filterSelector = '[data-test="name-filter-input"]'; - } - // Separate the visibility assertion from the type action so Cypress - // re-queries the element for each command independently. - cy.get(filterSelector).scrollIntoView().should('be.visible'); - cy.get(filterSelector).type(pod); - }); - cy.get(`a[data-test^="${pod}"]`).eq(0).as('podLink').click(); - cy.byPFRole('rowgroup') - .find('td') - .eq(1) - .scrollIntoView() - .should('be.visible') - .then(($td) => { - cy.log('Pod image: ' + $td.text()); - }); - cy.log('Get pod image completed'); -}); - Cypress.Commands.add('assertNamespace', (namespace: string, exists: boolean) => { cy.log('Asserting Namespace: ' + namespace + ' exists: ' + exists); cy.get('body').then(($body) => {