From ff08a69d08049ec69f9d42ba1ab97f8400f74be0 Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Tue, 15 Sep 2026 15:16:00 -0400 Subject: [PATCH 1/3] 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/01.reg_alerts_dev.cy.ts | 2 +- .../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 | 10 +- web/cypress/support/commands/auth-commands.ts | 15 ++- .../support/commands/image-patch-commands.ts | 109 +++++++++++++----- .../support/commands/operator-commands.ts | 59 +++------- .../support/commands/utility-commands.ts | 60 ---------- 11 files changed, 145 insertions(+), 146 deletions(-) diff --git a/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts b/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts index 99ad41821..c06d9d245 100644 --- a/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts +++ b/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts @@ -10,7 +10,7 @@ describe( { tags: ['@alerting', '@legacy-dashboards', '@metrics', '@targets'] }, () => { before(() => { - cy.beforeBlock(); + 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 cb33d5e27..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(); + 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 e6221a372..58acc2300 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 @@ -3,6 +3,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'; @@ -13,7 +14,7 @@ describe( { tags: ['@alerting', '@metrics'] }, () => { before(() => { - cy.beforeBlock(); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); cy.switchPerspective('Core platform'); }); @@ -32,3 +33,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/01.reg_alerts_dev.cy.ts b/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts index 565d963fe..7558a7d91 100644 --- a/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts +++ b/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts @@ -10,7 +10,7 @@ describe( { tags: ['@alerting'] }, () => { before(() => { - cy.beforeBlock(); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { 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 4fe33ed8e..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(); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { @@ -27,7 +27,7 @@ describe( { tags: ['@metrics'] }, () => { before(() => { - cy.beforeBlock(); + 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 bdecea8d9..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(); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { @@ -27,7 +27,7 @@ describe( { tags: ['@metrics'] }, () => { before(() => { - cy.beforeBlock(); + 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 bc80175b9..a1f019dcd 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,13 +11,13 @@ describe( { tags: ['@legacy-dashboards'] }, () => { before(() => { - cy.beforeBlock(); + cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); }); beforeEach(() => { - // When running only this file, beforeBlock changes the namespace to openshift-monitoring so - // we need to change it back to All Projects before landing to Dashboards page in order to - // have API Performance dashboard loaded by default + // When running only this file, ensureMonitoringPlugin changes the namespace to + // openshift-monitoring so we need to change it back to All Projects before + // landing to Dashboards page in order to have API Performance dashboard loaded by default nav.sidenav.clickNavLink(['Observe', 'Metrics']); commonPages.titleShouldHaveText('Metrics'); cy.changeNamespace('All Projects'); @@ -37,7 +37,7 @@ describe( { tags: ['@legacy-dashboards'] }, () => { before(() => { - cy.beforeBlock(); + 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 8334f8b32..f4109fbe4 100644 --- a/web/cypress/support/commands/auth-commands.ts +++ b/web/cypress/support/commands/auth-commands.ts @@ -40,7 +40,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')}`, @@ -75,7 +75,10 @@ export const operatorAuthUtils = { `oc adm policy add-role-to-user view ${Cypress.env('LOGIN_USERNAME')} -n default`, ); } - cy.adminCLI( + }, + + login(useSession = true): void { + cy.exec( `oc get oauthclient openshift-browser-client -o go-template ` + `--template="{{index .redirectURIs 0}}"`, ).then((result) => { @@ -113,12 +116,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(): string[] { @@ -328,7 +333,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}}"`, diff --git a/web/cypress/support/commands/image-patch-commands.ts b/web/cypress/support/commands/image-patch-commands.ts index b62a541fa..7eb355ac3 100644 --- a/web/cypress/support/commands/image-patch-commands.ts +++ b/web/cypress/support/commands/image-patch-commands.ts @@ -4,33 +4,90 @@ import { CLUSTER_MONITORING_OPERATOR, CLUSTER_OBSERVABILITY_OPERATOR } from '../ 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 = { - setupMonitoringPluginImage(): 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}`); - }); + getImage, + + setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR: { namespace: string }): void { + 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.'); } @@ -52,7 +109,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}`); @@ -186,7 +242,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 ce496d740..a3a82e478 100644 --- a/web/cypress/support/commands/operator-commands.ts +++ b/web/cypress/support/commands/operator-commands.ts @@ -23,8 +23,10 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - beforeBlock(); - cleanupMP(); + ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR: { + namespace: string; + operatorName: string; + }); beforeBlockCOO(options?: COOSetupOptions); cleanupCOO(options?: COOSetupOptions); RemoveClusterAdminRole(); @@ -66,10 +68,14 @@ function collectDebugInfo({ } cy.aboutModal(); if (debugMonitoringPlugin) { - 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 (debugMonitoringConsolePlugin) { - cy.podImage('monitoring', CLUSTER_OBSERVABILITY_OPERATOR.namespace); + imagePatchUtils + .getImage('deployment/monitoring', CLUSTER_OBSERVABILITY_OPERATOR.namespace) + .then((image) => cy.log(`Monitoring Console Plugin image: ${image}`)); } } @@ -170,46 +176,17 @@ function cleanupUIPlugin(opts: Required): void { // ── Cypress commands ─────────────────────────────────────────────── -Cypress.Commands.add('beforeBlock', () => { - if (useSession) { - const sessionKey = operatorAuthUtils.generateMPSessionKey(); - - cy.session( - sessionKey, - () => { - cy.log('Before block (session)'); - cy.cleanupMP(); - operatorAuthUtils.loginAndAuthNoSession(); - imagePatchUtils.setupMonitoringPluginImage(); - collectDebugInfo({ debugMonitoringPlugin: true }); - cy.task('clearDownloads'); - cy.log('Before block (session) completed'); - }, - { - cacheAcrossSpecs: true, - validate() { - cy.validateLogin(); - }, - }, - ); - } else { - cy.log('Before block (no session)'); - cy.cleanupMP(); +Cypress.Commands.add( + 'ensureMonitoringPlugin', + (CLUSTER_MONITORING_OPERATOR: { namespace: string; operatorName: string }) => { + cy.log('Ensure Monitoring Plugin'); operatorAuthUtils.loginAndAuth(); - imagePatchUtils.setupMonitoringPluginImage(); + imagePatchUtils.setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR); collectDebugInfo({ debugMonitoringPlugin: true }); cy.task('clearDownloads'); - cy.log('Before block (no session) completed'); - } -}); - -Cypress.Commands.add('cleanupMP', () => { - if (useSession) { - cy.log('cleanupMP (session)'); - imagePatchUtils.revertMonitoringPluginImage(); - cy.log('cleanupMP (session) completed'); - } -}); + cy.log('Ensure Monitoring Plugin completed'); + }, +); Cypress.Commands.add('beforeBlockCOO', (options?: COOSetupOptions) => { const opts = { ...DEFAULT_COO_OPTIONS, ...options }; 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) => { From 2ebe190984a3b4d694a53035544df203278c8852 Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Wed, 16 Sep 2026 12:21:26 -0400 Subject: [PATCH 2/3] feat: add idempotent ensureMonitoringConsolePlugin command --- .cursor/commands/generate-regression-test.md | 6 +- .../rules/incidents-testing-guidelines.mdc | 4 +- .../performance/03.endurance_test_source.md | 2 +- web/cypress/e2e/coo/01.coo_bvt.cy.ts | 2 +- web/cypress/e2e/coo/02.acm_alerting_ui.cy.ts | 2 +- .../e2e/incidents/00.coo_incidents_e2e.cy.ts | 2 +- web/cypress/e2e/incidents/01.incidents.cy.ts | 2 +- .../02.incidents-mocking-example.cy.ts | 2 +- .../01.performance_benchmark.cy.ts | 2 +- .../02.performance_walkthrough.cy.ts | 2 +- .../regression/01.reg_filtering.cy.ts | 2 +- .../02.reg_ui_charts_comprehensive.cy.ts | 2 +- .../02.reg_ui_tooltip_boundary_times.cy.ts | 2 +- .../03-04.reg_e2e_firing_alerts.cy.ts | 2 +- .../regression/03.reg_api_calls.cy.ts | 4 +- .../regression/04.reg_redux_effects.cy.ts | 2 +- .../regression/05.reg_stress_testing_ui.cy.ts | 2 +- 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 | 5 +- .../regression/01.reg_alerts_dev.cy.ts | 2 +- .../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 +- .../e2e/perses/00.coo_bvt_perses_admin.cy.ts | 2 +- .../e2e/perses/01.coo_list_perses_admin.cy.ts | 4 +- .../e2e/perses/05.coo_tempo_loki_admin.cy.ts | 2 +- .../e2e/perses/99.coo_rbac_perses_user1.cy.ts | 5 +- .../e2e/perses/99.coo_rbac_perses_user2.cy.ts | 3 +- .../e2e/perses/99.coo_rbac_perses_user3.cy.ts | 3 +- .../e2e/perses/99.coo_rbac_perses_user4.cy.ts | 3 +- .../e2e/perses/99.coo_rbac_perses_user5.cy.ts | 3 +- .../e2e/perses/99.coo_rbac_perses_user6.cy.ts | 3 +- .../e2e/virtualization/00.coo_ivt.cy.ts | 2 +- .../virtualization/01.coo_ivt_alerts.cy.ts | 3 +- .../virtualization/02.coo_ivt_metrics_1.cy.ts | 2 +- .../virtualization/02.coo_ivt_metrics_2.cy.ts | 2 +- .../03.coo_ivt_legacy_dashboards.cy.ts | 2 +- .../virtualization/04.coo_ivt_perses.cy.ts | 2 +- web/cypress/fixtures/coo/update-cha-image.sh | 46 --- web/cypress/fixtures/coo/update-mcp-image.sh | 76 ---- web/cypress/support/commands/auth-commands.ts | 71 ++-- .../support/commands/coo-install-commands.ts | 349 ++++++++++++------ .../support/commands/image-patch-commands.ts | 291 +++++++++------ .../support/commands/operator-commands.ts | 113 +++--- .../commands/traces-logging-commands.ts | 18 +- web/cypress/views/operator-hub-page.ts | 13 + 47 files changed, 556 insertions(+), 527 deletions(-) delete mode 100755 web/cypress/fixtures/coo/update-cha-image.sh delete mode 100755 web/cypress/fixtures/coo/update-mcp-image.sh diff --git a/.cursor/commands/generate-regression-test.md b/.cursor/commands/generate-regression-test.md index 3673d16f7..09edab34c 100644 --- a/.cursor/commands/generate-regression-test.md +++ b/.cursor/commands/generate-regression-test.md @@ -94,7 +94,7 @@ import { incidentsPage } from "../../../views/incidents-page"; describe("Regression: [Section Name]", () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); beforeEach(() => { @@ -475,7 +475,7 @@ Continue? (y/n/specify) **Automated checks (AI should verify):** - [ ] File naming matches `XX.reg_.cy.ts` -- [ ] Uses `cy.beforeBlockCOO()` in `before()` hook +- [ ] Uses `cy.ensureMonitoringConsolePlugin()` in `before()` hook - [ ] Uses `incidentsPage.goTo()` in `beforeEach()` - [ ] Uses `cy.mockIncidentFixture()` with valid fixture path - [ ] No emojis in cy.log() statements @@ -526,7 +526,7 @@ import { incidentsPage } from "../../../views/incidents-page"; describe("Regression: Tooltip Positioning", () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); beforeEach(() => { diff --git a/.cursor/rules/incidents-testing-guidelines.mdc b/.cursor/rules/incidents-testing-guidelines.mdc index dc8a7aba2..e7b94163b 100644 --- a/.cursor/rules/incidents-testing-guidelines.mdc +++ b/.cursor/rules/incidents-testing-guidelines.mdc @@ -103,7 +103,7 @@ import { incidentsPage } from '../../../views/incidents-page'; describe('Regression:
', () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); beforeEach(() => { @@ -125,7 +125,7 @@ describe('Regression:
', () => { ### Required Elements - File header comment with purpose and issue reference (e.g., "Verifies: OU-XXX") - Import `incidentsPage` from relative path -- Use `cy.beforeBlockCOO()` in `before()` hook +- Use `cy.ensureMonitoringConsolePlugin()` in `before()` hook - Use `incidentsPage.goTo()` in `beforeEach()` - Use `cy.mockIncidentFixture()` for test data diff --git a/docs/incident_detection/tests/performance/03.endurance_test_source.md b/docs/incident_detection/tests/performance/03.endurance_test_source.md index 714f1f0f5..70f0f4f06 100644 --- a/docs/incident_detection/tests/performance/03.endurance_test_source.md +++ b/docs/incident_detection/tests/performance/03.endurance_test_source.md @@ -25,7 +25,7 @@ describe( { tags: ["@cluster-health-analyzer", "@coo"] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/coo/01.coo_bvt.cy.ts b/web/cypress/e2e/coo/01.coo_bvt.cy.ts index bf36cffe8..f78515e28 100644 --- a/web/cypress/e2e/coo/01.coo_bvt.cy.ts +++ b/web/cypress/e2e/coo/01.coo_bvt.cy.ts @@ -4,7 +4,7 @@ import { troubleshootingPanelPage } from '../../views/troubleshooting-panel'; describe('BVT: COO', { tags: ['@alerting', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); it('1. Admin perspective - Observe Menu', () => { diff --git a/web/cypress/e2e/coo/02.acm_alerting_ui.cy.ts b/web/cypress/e2e/coo/02.acm_alerting_ui.cy.ts index 91222e697..b8491137c 100644 --- a/web/cypress/e2e/coo/02.acm_alerting_ui.cy.ts +++ b/web/cypress/e2e/coo/02.acm_alerting_ui.cy.ts @@ -12,8 +12,8 @@ import { testAlertsRegression, } from 'cypress/support/monitoring/01.reg_alerts.cy'; import { listPage } from 'cypress/views/list-page'; +import { CLUSTER_MONITORING_OPERATOR } from '../../support/operators'; import { CustomerPerspectiveName } from '@/shared/constants/perspective'; -import { CLUSTER_MONITORING_OPERATOR } from 'cypress/support/operators'; const expectedAlerts = ['Watchdog', 'Watchdog-spoke', 'ClusterCPUHealth-jb']; diff --git a/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts b/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts index 9bdbb1fe1..2e15c24a6 100644 --- a/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts +++ b/web/cypress/e2e/incidents/00.coo_incidents_e2e.cy.ts @@ -8,7 +8,7 @@ describe('BVT: Incidents - e2e', { tags: ['@cluster-health-analyzer', '@coo', '@ let currentAlertName: string; before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/01.incidents.cy.ts b/web/cypress/e2e/incidents/01.incidents.cy.ts index 05d6e29fa..adb88f5dc 100644 --- a/web/cypress/e2e/incidents/01.incidents.cy.ts +++ b/web/cypress/e2e/incidents/01.incidents.cy.ts @@ -11,7 +11,7 @@ import { incidentsPage } from '../../views/incidents-page'; describe('BVT: Incidents - UI', { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/02.incidents-mocking-example.cy.ts b/web/cypress/e2e/incidents/02.incidents-mocking-example.cy.ts index e1b0cd44f..e9a944096 100644 --- a/web/cypress/e2e/incidents/02.incidents-mocking-example.cy.ts +++ b/web/cypress/e2e/incidents/02.incidents-mocking-example.cy.ts @@ -11,7 +11,7 @@ import { incidentsPage } from '../../views/incidents-page'; describe('Incidents - Mocking Examples', { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/performance/01.performance_benchmark.cy.ts b/web/cypress/e2e/incidents/performance/01.performance_benchmark.cy.ts index c7ebcae5e..2ad72f98c 100644 --- a/web/cypress/e2e/incidents/performance/01.performance_benchmark.cy.ts +++ b/web/cypress/e2e/incidents/performance/01.performance_benchmark.cy.ts @@ -42,7 +42,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo'], numTestsKeptInMemory: 0 }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/performance/02.performance_walkthrough.cy.ts b/web/cypress/e2e/incidents/performance/02.performance_walkthrough.cy.ts index f095e72ee..26e36d7ae 100644 --- a/web/cypress/e2e/incidents/performance/02.performance_walkthrough.cy.ts +++ b/web/cypress/e2e/incidents/performance/02.performance_walkthrough.cy.ts @@ -26,7 +26,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo'], numTestsKeptInMemory: 0 }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts b/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts index 4975ced40..01e4bf672 100644 --- a/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts +++ b/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts @@ -12,7 +12,7 @@ import { incidentsPage } from '../../../views/incidents-page'; describe('Regression: Incidents Filtering', { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/02.reg_ui_charts_comprehensive.cy.ts b/web/cypress/e2e/incidents/regression/02.reg_ui_charts_comprehensive.cy.ts index 1fc20abc1..179613292 100644 --- a/web/cypress/e2e/incidents/regression/02.reg_ui_charts_comprehensive.cy.ts +++ b/web/cypress/e2e/incidents/regression/02.reg_ui_charts_comprehensive.cy.ts @@ -91,7 +91,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/02.reg_ui_tooltip_boundary_times.cy.ts b/web/cypress/e2e/incidents/regression/02.reg_ui_tooltip_boundary_times.cy.ts index 4deb72bf1..700c13afe 100644 --- a/web/cypress/e2e/incidents/regression/02.reg_ui_tooltip_boundary_times.cy.ts +++ b/web/cypress/e2e/incidents/regression/02.reg_ui_tooltip_boundary_times.cy.ts @@ -18,7 +18,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo', '@xfail'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/03-04.reg_e2e_firing_alerts.cy.ts b/web/cypress/e2e/incidents/regression/03-04.reg_e2e_firing_alerts.cy.ts index ac6b82737..07754a161 100644 --- a/web/cypress/e2e/incidents/regression/03-04.reg_e2e_firing_alerts.cy.ts +++ b/web/cypress/e2e/incidents/regression/03-04.reg_e2e_firing_alerts.cy.ts @@ -26,7 +26,7 @@ describe( let currentAlertName: string; before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/03.reg_api_calls.cy.ts b/web/cypress/e2e/incidents/regression/03.reg_api_calls.cy.ts index 4caf117e8..4b76ded64 100644 --- a/web/cypress/e2e/incidents/regression/03.reg_api_calls.cy.ts +++ b/web/cypress/e2e/incidents/regression/03.reg_api_calls.cy.ts @@ -21,7 +21,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); @@ -134,7 +134,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/04.reg_redux_effects.cy.ts b/web/cypress/e2e/incidents/regression/04.reg_redux_effects.cy.ts index c63ecb248..4d4e171b5 100644 --- a/web/cypress/e2e/incidents/regression/04.reg_redux_effects.cy.ts +++ b/web/cypress/e2e/incidents/regression/04.reg_redux_effects.cy.ts @@ -20,7 +20,7 @@ describe( { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/incidents/regression/05.reg_stress_testing_ui.cy.ts b/web/cypress/e2e/incidents/regression/05.reg_stress_testing_ui.cy.ts index 5b47ca950..2f40f7292 100644 --- a/web/cypress/e2e/incidents/regression/05.reg_stress_testing_ui.cy.ts +++ b/web/cypress/e2e/incidents/regression/05.reg_stress_testing_ui.cy.ts @@ -18,7 +18,7 @@ const MAX_GAP_RELAXED = 500; describe('Regression: Stress Testing UI', { tags: ['@cluster-health-analyzer', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: false, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts b/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts index c06d9d245..3466dedcc 100644 --- a/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts +++ b/web/cypress/e2e/monitoring/00.bvt_admin.cy.ts @@ -10,7 +10,7 @@ describe( { tags: ['@alerting', '@legacy-dashboards', '@metrics', '@targets'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { diff --git a/web/cypress/e2e/monitoring/00.bvt_dev.cy.ts b/web/cypress/e2e/monitoring/00.bvt_dev.cy.ts index 94467b318..90bd18f51 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.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); 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 58acc2300..615626593 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 @@ -1,3 +1,4 @@ +import { CLUSTER_MONITORING_OPERATOR } from 'cypress/support/operators'; import { alerts } from '../../../fixtures/monitoring/alert'; import { testAlertsCorePlatformHeaderRegression, @@ -14,7 +15,7 @@ describe( { tags: ['@alerting', '@metrics'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); cy.switchPerspective('Core platform'); }); @@ -39,7 +40,7 @@ describe( { tags: ['@alerting'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { diff --git a/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts b/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts index 7558a7d91..5e72fccff 100644 --- a/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts +++ b/web/cypress/e2e/monitoring/regression/01.reg_alerts_dev.cy.ts @@ -10,7 +10,7 @@ describe( { tags: ['@alerting'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { 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 ae722c09e..97786d2cb 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.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { @@ -27,7 +27,7 @@ describe( { tags: ['@metrics'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); 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 59b8b357b..543338380 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.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { @@ -27,7 +27,7 @@ describe( { tags: ['@metrics'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); 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 a1f019dcd..ac467e025 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.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { @@ -37,7 +37,7 @@ describe( { tags: ['@legacy-dashboards'] }, () => { before(() => { - cy.ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR); + cy.ensureMonitoringPlugin(); }); beforeEach(() => { diff --git a/web/cypress/e2e/perses/00.coo_bvt_perses_admin.cy.ts b/web/cypress/e2e/perses/00.coo_bvt_perses_admin.cy.ts index 636bfca19..22ad0c228 100644 --- a/web/cypress/e2e/perses/00.coo_bvt_perses_admin.cy.ts +++ b/web/cypress/e2e/perses/00.coo_bvt_perses_admin.cy.ts @@ -8,7 +8,7 @@ describe( { tags: ['@perses-dashboards', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: true, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/perses/01.coo_list_perses_admin.cy.ts b/web/cypress/e2e/perses/01.coo_list_perses_admin.cy.ts index 90db3d529..d1784c1ea 100644 --- a/web/cypress/e2e/perses/01.coo_list_perses_admin.cy.ts +++ b/web/cypress/e2e/perses/01.coo_list_perses_admin.cy.ts @@ -12,7 +12,7 @@ describe( { tags: ['@perses-dashboards', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: true, troubleshootingPanel: false, }); @@ -38,7 +38,7 @@ describe( { tags: ['@perses-dashboards', '@coo'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); cy.switchPerspective('Core platform'); cy.cleanupPersesTestDashboardsBeforeTests(); }); diff --git a/web/cypress/e2e/perses/05.coo_tempo_loki_admin.cy.ts b/web/cypress/e2e/perses/05.coo_tempo_loki_admin.cy.ts index 4467d1bc0..0f29ddd24 100644 --- a/web/cypress/e2e/perses/05.coo_tempo_loki_admin.cy.ts +++ b/web/cypress/e2e/perses/05.coo_tempo_loki_admin.cy.ts @@ -20,7 +20,7 @@ describe( cy.cleanupLoggingUIPlugin(); cy.cleanupExtraDashboards(); - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: true, troubleshootingPanel: false, }); diff --git a/web/cypress/e2e/perses/99.coo_rbac_perses_user1.cy.ts b/web/cypress/e2e/perses/99.coo_rbac_perses_user1.cy.ts index cf335462d..2be12104d 100644 --- a/web/cypress/e2e/perses/99.coo_rbac_perses_user1.cy.ts +++ b/web/cypress/e2e/perses/99.coo_rbac_perses_user1.cy.ts @@ -8,7 +8,7 @@ describe( () => { before(() => { // Setup COO and Perses dashboards (requires admin privileges) - cy.beforeBlockCOO({ + cy.ensureMonitoringConsolePlugin({ dashboards: true, troubleshootingPanel: false, }); @@ -17,7 +17,8 @@ describe( cy.setupPersesRBACandExtraDashboards(); // Clear Cypress session cache and logout - // This is critical because beforeBlockCOO uses cy.session() which caches the login state + // This is critical because ensureMonitoringConsolePlugin uses cy.session() + // which caches login state cy.log('Clearing Cypress session cache to ensure fresh login'); cy.then(() => { Cypress.session.clearAllSavedSessions(); diff --git a/web/cypress/e2e/perses/99.coo_rbac_perses_user2.cy.ts b/web/cypress/e2e/perses/99.coo_rbac_perses_user2.cy.ts index c81d6a492..c66809da1 100644 --- a/web/cypress/e2e/perses/99.coo_rbac_perses_user2.cy.ts +++ b/web/cypress/e2e/perses/99.coo_rbac_perses_user2.cy.ts @@ -30,7 +30,8 @@ describe( // ); // Step 4: Clear Cypress session cache and logout - // This is critical because beforeBlockCOO uses cy.session() which caches the login state + // This is critical because ensureMonitoringConsolePlugin uses cy.session() + // which caches login state cy.log('Clearing Cypress session cache to ensure fresh login'); cy.then(() => { Cypress.session.clearAllSavedSessions(); diff --git a/web/cypress/e2e/perses/99.coo_rbac_perses_user3.cy.ts b/web/cypress/e2e/perses/99.coo_rbac_perses_user3.cy.ts index 2816015a9..9f6a6a9e4 100644 --- a/web/cypress/e2e/perses/99.coo_rbac_perses_user3.cy.ts +++ b/web/cypress/e2e/perses/99.coo_rbac_perses_user3.cy.ts @@ -30,7 +30,8 @@ describe( // ); // Step 4: Clear Cypress session cache and logout - // This is critical because beforeBlockCOO uses cy.session() which caches the login state + // This is critical because ensureMonitoringConsolePlugin uses cy.session() + // which caches login state cy.log('Clearing Cypress session cache to ensure fresh login'); cy.then(() => { Cypress.session.clearAllSavedSessions(); diff --git a/web/cypress/e2e/perses/99.coo_rbac_perses_user4.cy.ts b/web/cypress/e2e/perses/99.coo_rbac_perses_user4.cy.ts index cfb344fc8..40e8493eb 100644 --- a/web/cypress/e2e/perses/99.coo_rbac_perses_user4.cy.ts +++ b/web/cypress/e2e/perses/99.coo_rbac_perses_user4.cy.ts @@ -30,7 +30,8 @@ describe( // ); // Step 4: Clear Cypress session cache and logout - // This is critical because beforeBlockCOO uses cy.session() which caches the login state + // This is critical because ensureMonitoringConsolePlugin uses cy.session() + // which caches login state cy.log('Clearing Cypress session cache to ensure fresh login'); cy.then(() => { Cypress.session.clearAllSavedSessions(); diff --git a/web/cypress/e2e/perses/99.coo_rbac_perses_user5.cy.ts b/web/cypress/e2e/perses/99.coo_rbac_perses_user5.cy.ts index 0fedf98f2..561db2b68 100644 --- a/web/cypress/e2e/perses/99.coo_rbac_perses_user5.cy.ts +++ b/web/cypress/e2e/perses/99.coo_rbac_perses_user5.cy.ts @@ -30,7 +30,8 @@ describe( // ); // Step 4: Clear Cypress session cache and logout - // This is critical because beforeBlockCOO uses cy.session() which caches the login state + // This is critical because ensureMonitoringConsolePlugin uses cy.session() + // which caches login state cy.log('Clearing Cypress session cache to ensure fresh login'); cy.then(() => { Cypress.session.clearAllSavedSessions(); diff --git a/web/cypress/e2e/perses/99.coo_rbac_perses_user6.cy.ts b/web/cypress/e2e/perses/99.coo_rbac_perses_user6.cy.ts index 259dd57e2..5758be8b5 100644 --- a/web/cypress/e2e/perses/99.coo_rbac_perses_user6.cy.ts +++ b/web/cypress/e2e/perses/99.coo_rbac_perses_user6.cy.ts @@ -30,7 +30,8 @@ describe( // ); // Step 4: Clear Cypress session cache and logout - // This is critical because beforeBlockCOO uses cy.session() which caches the login state + // This is critical because ensureMonitoringConsolePlugin uses cy.session() + // which caches login state cy.log('Clearing Cypress session cache to ensure fresh login'); cy.then(() => { Cypress.session.clearAllSavedSessions(); diff --git a/web/cypress/e2e/virtualization/00.coo_ivt.cy.ts b/web/cypress/e2e/virtualization/00.coo_ivt.cy.ts index 7a957f429..ed2204970 100644 --- a/web/cypress/e2e/virtualization/00.coo_ivt.cy.ts +++ b/web/cypress/e2e/virtualization/00.coo_ivt.cy.ts @@ -10,7 +10,7 @@ describe( { tags: ['@alerting', '@metrics', '@coo', '@virtualization', '@slow'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); it('1. Installation: COO and setting up Monitoring Plugin', () => { diff --git a/web/cypress/e2e/virtualization/01.coo_ivt_alerts.cy.ts b/web/cypress/e2e/virtualization/01.coo_ivt_alerts.cy.ts index 1c3994665..8a3d23e28 100644 --- a/web/cypress/e2e/virtualization/01.coo_ivt_alerts.cy.ts +++ b/web/cypress/e2e/virtualization/01.coo_ivt_alerts.cy.ts @@ -1,5 +1,4 @@ import { CustomerPerspectiveName } from '@/shared/constants/perspective'; - import { alerts } from '../../fixtures/monitoring/alert'; import { testAlertsRegression } from '../../support/monitoring/01.reg_alerts.cy'; import { commonPages } from '../../views/common'; @@ -11,7 +10,7 @@ describe( { tags: ['@alerting', '@coo', '@virtualization', '@slow'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); cy.log('Installation: COO and setting up Monitoring Plugin'); }); }, diff --git a/web/cypress/e2e/virtualization/02.coo_ivt_metrics_1.cy.ts b/web/cypress/e2e/virtualization/02.coo_ivt_metrics_1.cy.ts index 7508412c8..303da1645 100644 --- a/web/cypress/e2e/virtualization/02.coo_ivt_metrics_1.cy.ts +++ b/web/cypress/e2e/virtualization/02.coo_ivt_metrics_1.cy.ts @@ -12,7 +12,7 @@ describe( { tags: ['@metrics', '@coo', '@virtualization', '@slow'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); it('1. Installation: COO and setting up Monitoring Plugin', () => { diff --git a/web/cypress/e2e/virtualization/02.coo_ivt_metrics_2.cy.ts b/web/cypress/e2e/virtualization/02.coo_ivt_metrics_2.cy.ts index f89a5e23a..17706035e 100644 --- a/web/cypress/e2e/virtualization/02.coo_ivt_metrics_2.cy.ts +++ b/web/cypress/e2e/virtualization/02.coo_ivt_metrics_2.cy.ts @@ -12,7 +12,7 @@ describe( { tags: ['@coo', '@virtualization', '@slow'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); it('1. Installation: COO and setting up Monitoring Plugin', () => { diff --git a/web/cypress/e2e/virtualization/03.coo_ivt_legacy_dashboards.cy.ts b/web/cypress/e2e/virtualization/03.coo_ivt_legacy_dashboards.cy.ts index aada19972..2338c4d39 100644 --- a/web/cypress/e2e/virtualization/03.coo_ivt_legacy_dashboards.cy.ts +++ b/web/cypress/e2e/virtualization/03.coo_ivt_legacy_dashboards.cy.ts @@ -11,7 +11,7 @@ describe( { tags: ['@legacy-dashboards', '@coo', '@virtualization', '@slow'] }, () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); cy.log('Installation: COO and setting up Monitoring Plugin'); }); }, diff --git a/web/cypress/e2e/virtualization/04.coo_ivt_perses.cy.ts b/web/cypress/e2e/virtualization/04.coo_ivt_perses.cy.ts index 523f25ab6..b5f85646e 100644 --- a/web/cypress/e2e/virtualization/04.coo_ivt_perses.cy.ts +++ b/web/cypress/e2e/virtualization/04.coo_ivt_perses.cy.ts @@ -10,7 +10,7 @@ describe( () => { before(() => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); }); it('1. Installation: COO and setting up Monitoring Plugin', () => { diff --git a/web/cypress/fixtures/coo/update-cha-image.sh b/web/cypress/fixtures/coo/update-cha-image.sh deleted file mode 100755 index 4b57f8cb4..000000000 --- a/web/cypress/fixtures/coo/update-cha-image.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -# Script to patch the cluster-health-analyzer image in COO CSV -# Used by Cypress tests to test custom CHA builds - -echo "--------------------------------" -echo "CHA_IMAGE: ${CHA_IMAGE}" -echo "--------------------------------" - -# Generate a random filename -RANDOM_FILE="/tmp/coo_cha_csv_$(date +%s%N).yaml" - -COO_CSV_NAME=$(oc get csv --kubeconfig "${KUBECONFIG}" --namespace="${MCP_NAMESPACE}" | grep "cluster-observability-operator" | awk '{print $1}') - -if [ -z "${COO_CSV_NAME}" ]; then - echo "Error: Could not find cluster-observability-operator CSV in namespace ${MCP_NAMESPACE}" - exit 1 -fi - -echo "Found COO CSV: ${COO_CSV_NAME}" - -oc get csv "${COO_CSV_NAME}" -n "${MCP_NAMESPACE}" -o yaml > "${RANDOM_FILE}" --kubeconfig "${KUBECONFIG}" - -# Patch the CSV file env vars for cluster-health-analyzer -# Handle both US and UK spellings (analyser/analyzer) for compatibility -sed -i "s#value: .*cluster-health-analy[sz]er.*#value: ${CHA_IMAGE}#g" "${RANDOM_FILE}" - -# Patch the CSV file related images -sed -i "s#^\([[:space:]]*- image:\).*cluster-health-analy[sz]er.*#\1 ${CHA_IMAGE}#g" "${RANDOM_FILE}" - -# Apply the patched CSV resource file -oc replace -f "${RANDOM_FILE}" --kubeconfig "${KUBECONFIG}" - -# Wait for the operator to reconcile the change -sleep 25 - -# Wait for health-analyzer pod to be ready with the new image -OUTPUT=$(oc wait --for=condition=ready pods -l app.kubernetes.io/instance=health-analyzer -n "${MCP_NAMESPACE}" --timeout=120s --kubeconfig "${KUBECONFIG}") -echo "${OUTPUT}" - -echo "--------------------------------" -echo "Health-analyzer pod status:" -echo "--------------------------------" -oc get pods -l app.kubernetes.io/instance=health-analyzer -n "${MCP_NAMESPACE}" -o wide --kubeconfig "${KUBECONFIG}" -echo "--------------------------------" - diff --git a/web/cypress/fixtures/coo/update-mcp-image.sh b/web/cypress/fixtures/coo/update-mcp-image.sh deleted file mode 100755 index 79b20b2f0..000000000 --- a/web/cypress/fixtures/coo/update-mcp-image.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/bash -echo "--------------------------------" -echo "MCP_CONSOLE_IMAGE: ${MCP_CONSOLE_IMAGE}" -echo "--------------------------------" - -# Generate a random filename -RANDOM_FILE="/tmp/coo_monitoring_csv_$(date +%s%N).yaml" - -COO_CSV_NAME=$(oc get csv --kubeconfig "${KUBECONFIG}" --namespace="${MCP_NAMESPACE}" | grep "cluster-observability-operator" | awk '{print $1}' | sort -V | tail -1) - echo "COO_CSV_NAME: ${COO_CSV_NAME}" -if [ -z "${COO_CSV_NAME}" ]; then - echo "ERROR: could not find cluster-observability-operator CSV in namespace ${MCP_NAMESPACE}" - exit 1 -fi - -patch_csv() { - oc get csv "${COO_CSV_NAME}" -n "${MCP_NAMESPACE}" -o yaml > "${RANDOM_FILE}" --kubeconfig "${KUBECONFIG}" - - # Patch the CSV file env vars - sed -i "s#value: .*monitoring-console-plugin.*#value: ${MCP_CONSOLE_IMAGE}#g" "${RANDOM_FILE}" - - # Patch the CSV file related images - sed -i "s#^\([[:space:]]*- image:\).*monitoring-console-plugin.*#\1 ${MCP_CONSOLE_IMAGE}#g" "${RANDOM_FILE}" - - echo "--------------------------------" - echo "COO CSV content after patching (local file)" - echo "--------------------------------" - cat "${RANDOM_FILE}" - echo "--------------------------------" - - oc replace -f "${RANDOM_FILE}" --kubeconfig "${KUBECONFIG}" -} - -# Initial patch -patch_csv - -# Verify the CSV patch was applied and not reverted by OLM. -# OLM may reconcile the CSV and revert the image back to the original. -MAX_CSV_RETRIES=6 -CSV_RETRY_INTERVAL=10 -CSV_PATCHED=false - -for i in $(seq 1 $MAX_CSV_RETRIES); do - sleep $CSV_RETRY_INTERVAL - - LIVE_CSV=$(oc get csv "${COO_CSV_NAME}" -n "${MCP_NAMESPACE}" -o yaml --kubeconfig "${KUBECONFIG}") - - if echo "${LIVE_CSV}" | grep -qF "${MCP_CONSOLE_IMAGE}"; then - echo "CSV patch verified successfully on attempt ${i}" - CSV_PATCHED=true - break - fi - - echo "CSV patch was reverted by OLM (attempt ${i}/${MAX_CSV_RETRIES}). Re-applying..." - patch_csv -done - -if [ "${CSV_PATCHED}" = false ]; then - echo "WARNING: CSV patch could not be stabilized after ${MAX_CSV_RETRIES} attempts" -fi - -echo "--------------------------------" -echo "COO CSV live content after verification loop" -echo "--------------------------------" -oc get csv "${COO_CSV_NAME}" -n "${MCP_NAMESPACE}" -o yaml --kubeconfig "${KUBECONFIG}" -echo "--------------------------------" - -# Wait for the operator to reconcile the change and make sure all the pods are running. -sleep 25 -OUTPUT=$(oc wait --for=condition=ready pods -l app.kubernetes.io/name=observability-operator -n "${MCP_NAMESPACE}" --timeout=60s --kubeconfig "${KUBECONFIG}") -rc=$? -echo "${OUTPUT}" -if [ $rc -ne 0 ]; then - echo "ERROR: oc wait failed with exit code ${rc}" - exit $rc -fi diff --git a/web/cypress/support/commands/auth-commands.ts b/web/cypress/support/commands/auth-commands.ts index f4109fbe4..4c8f4a973 100644 --- a/web/cypress/support/commands/auth-commands.ts +++ b/web/cypress/support/commands/auth-commands.ts @@ -28,7 +28,7 @@ declare global { password: string, oauthurl: string, ): Chainable; - adminCLI(command: string, options?): Chainable; + adminCLI(command: string, options?: Partial): Chainable; executeAndDelete(command: string); validateLogin(): Chainable; relogin(provider: string, username: string, password: string): Chainable; @@ -45,40 +45,36 @@ export const operatorAuthUtils = { cy.adminCLI( `oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`, ); - } else { - cy.adminCLI(`oc project openshift-monitoring`); - cy.adminCLI( - `oc adm policy add-role-to-user monitoring-edit ${Cypress.env( - 'LOGIN_USERNAME', - )} -n openshift-monitoring`, - ); - cy.adminCLI( - 'oc adm policy add-role-to-user monitoring-alertmanager-edit ' + - `--role-namespace openshift-monitoring ${Cypress.env('LOGIN_USERNAME')}`, - ); - cy.adminCLI( - `oc adm policy add-role-to-user view ${Cypress.env( - 'LOGIN_USERNAME', - )} -n openshift-monitoring`, - ); - cy.adminCLI(`oc project default`); - cy.adminCLI( - `oc adm policy add-role-to-user monitoring-edit ${Cypress.env( - 'LOGIN_USERNAME', - )} -n default`, - ); - cy.adminCLI( - 'oc adm policy add-role-to-user monitoring-alertmanager-edit ' + - `--role-namespace default ${Cypress.env('LOGIN_USERNAME')}`, - ); - cy.adminCLI( - `oc adm policy add-role-to-user view ${Cypress.env('LOGIN_USERNAME')} -n default`, - ); + return; } + cy.adminCLI(`oc project openshift-monitoring`); + cy.adminCLI( + `oc adm policy add-role-to-user monitoring-edit ${Cypress.env( + 'LOGIN_USERNAME', + )} -n openshift-monitoring`, + ); + cy.adminCLI( + 'oc adm policy add-role-to-user monitoring-alertmanager-edit ' + + `--role-namespace openshift-monitoring ${Cypress.env('LOGIN_USERNAME')}`, + ); + cy.adminCLI( + `oc adm policy add-role-to-user view ${Cypress.env( + 'LOGIN_USERNAME', + )} -n openshift-monitoring`, + ); + cy.adminCLI(`oc project default`); + cy.adminCLI( + `oc adm policy add-role-to-user monitoring-edit ${Cypress.env('LOGIN_USERNAME')} -n default`, + ); + cy.adminCLI( + 'oc adm policy add-role-to-user monitoring-alertmanager-edit ' + + `--role-namespace default ${Cypress.env('LOGIN_USERNAME')}`, + ); + cy.adminCLI(`oc adm policy add-role-to-user view ${Cypress.env('LOGIN_USERNAME')} -n default`); }, login(useSession = true): void { - cy.exec( + cy.adminCLI( `oc get oauthclient openshift-browser-client -o go-template ` + `--template="{{index .redirectURIs 0}}"`, ).then((result) => { @@ -150,17 +146,6 @@ export const operatorAuthUtils = { return [...baseKey, ...envVars.map((v) => v || '')]; }, - generateMPSessionKey(): 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(): string[] { const baseKey = [ Cypress.env('LOGIN_IDP'), @@ -334,7 +319,7 @@ Cypress.Commands.add('relogin', (provider: string, username: string, password: s cy.uiLogout(); // Get the OAuth URL from the cluster before performing a fresh login. - cy.exec( + cy.adminCLI( `oc get oauthclient openshift-browser-client -o go-template ` + `--template="{{index .redirectURIs 0}}"`, ).then((result) => { diff --git a/web/cypress/support/commands/coo-install-commands.ts b/web/cypress/support/commands/coo-install-commands.ts index 3cef5da00..7088a41c2 100644 --- a/web/cypress/support/commands/coo-install-commands.ts +++ b/web/cypress/support/commands/coo-install-commands.ts @@ -6,7 +6,117 @@ import { CLUSTER_OBSERVABILITY_OPERATOR } from '../operators'; export {}; +type COOInstallationCSV = { + metadata: { name: string }; + status?: { phase?: string }; + spec: { + install: { + spec: { + deployments: Array<{ + name: string; + spec: { + template: { spec: { containers: Array<{ name: string; args?: string[] }> } }; + }; + }>; + }; + }; + }; +}; + +function waitForCOOSubscriptionDeletion(): void { + cy.waitUntil( + () => + cy + .adminCLI( + `oc get subscription ${CLUSTER_OBSERVABILITY_OPERATOR.packageName} -n ` + + `${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, + { failOnNonZeroExit: false }, + ) + .then((result) => result.code !== 0), + { + timeout: readyTimeoutMilliseconds, + interval: 5000, + errorMsg: `Subscription ${CLUSTER_OBSERVABILITY_OPERATOR.packageName} was not deleted.`, + }, + ); +} + export const cooInstallUtils = { + ensureCOOInstalled(): void { + if (Cypress.env('SKIP_COO_INSTALL')) { + cy.log('SKIP_COO_INSTALL is set. Using the pre-provisioned Cluster Observability Operator.'); + return; + } + + let installationIdentity: string; + if (Cypress.env('KONFLUX_COO_BUNDLE_IMAGE')) { + installationIdentity = `bundle:${Cypress.env('KONFLUX_COO_BUNDLE_IMAGE')}`; + } else if (Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')) { + installationIdentity = `bundle:${Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')}`; + } else if (Cypress.env('FBC_STAGE_COO_IMAGE')) { + installationIdentity = `fbc:${Cypress.env('FBC_STAGE_COO_IMAGE')}`; + } else if (Cypress.env('COO_UI_INSTALL')) { + installationIdentity = 'catalog:redhat-operators'; + } else { + throw new Error( + 'No CYPRESS env set for operator installation, check the README for more details.', + ); + } + + cy.adminCLI(`oc get namespace ${CLUSTER_OBSERVABILITY_OPERATOR.namespace} -o json`, { + failOnNonZeroExit: false, + }).then((result) => { + let installedIdentity = ''; + if (result.code === 0) { + installedIdentity = + JSON.parse(result.stdout).metadata.annotations?.[ + 'e2e.monitoring.openshift.io/coo-installation' + ] ?? ''; + } + if (installedIdentity === installationIdentity) { + cy.adminCLI( + `oc get deployment observability-operator -n ` + + `${CLUSTER_OBSERVABILITY_OPERATOR.namespace} ` + + `-o jsonpath='{.status.availableReplicas}'`, + { failOnNonZeroExit: false }, + ).then((deploymentResult) => { + if (deploymentResult.code === 0 && Number(deploymentResult.stdout) > 0) { + cy.log(`Cluster Observability Operator installation matches ${installationIdentity}`); + return; + } + + cy.log('Cluster Observability Operator is unhealthy; reinstalling'); + cooInstallUtils.cleanupCOONamespace(); + cooInstallUtils.installCOO(); + cy.adminCLI( + `oc annotate namespace ${CLUSTER_OBSERVABILITY_OPERATOR.namespace} ` + + `e2e.monitoring.openshift.io/coo-installation="${installationIdentity}" ` + + `--overwrite`, + ); + }); + return; + } + + if (result.code === 0) { + cy.log( + `Cluster Observability Operator installation changed from ${ + installedIdentity || 'unknown' + } to ${installationIdentity}; reinstalling`, + ); + cooInstallUtils.cleanupCOONamespace(); + } else { + cy.log('Cluster Observability Operator is not installed; installing'); + } + + cooInstallUtils.installCOO(); + cy.adminCLI( + `oc annotate namespace ${CLUSTER_OBSERVABILITY_OPERATOR.namespace} ` + + `e2e.monitoring.openshift.io/coo-installation="${installationIdentity}" ` + + `--overwrite`, + ); + }); + }, + installCOO(): void { if (Cypress.env('SKIP_COO_INSTALL')) { cy.log('SKIP_COO_INSTALL is set. Skipping Cluster Observability Operator installation.'); @@ -163,94 +273,93 @@ export const cooInstallUtils = { return; } + const kubeconfig = Cypress.env('KUBECONFIG_PATH'); const ns = CLUSTER_OBSERVABILITY_OPERATOR.namespace; cy.log('Enabling OpenShift mode on bundle-installed COO'); - // Patch the CSV so OLM's source of truth includes the flag. - // Find the correct CSV and deployment index for observability-operator. - cy.adminCLI( - `oc get csv -n ${ns} -o jsonpath=` + - `'{range .items[?(@.status.phase=="Succeeded")]}` + - `{.metadata.name}{"\\n"}{end}'`, - ).then((result) => { - const csvNames = result.stdout.trim().split('\n').filter(Boolean); - const csvName = csvNames.find((name) => name.includes('observability-operator')); - if (!csvName) { - throw new Error( - `No observability-operator CSV found in namespace ${ns}. ` + - `Available CSVs: [${csvNames.join(', ')}]`, + const flag = '--openshift.enabled=true'; + const reconcile = (attempt: number): void => { + cy.adminCLI(`oc get csv -n ${ns} -o json`).then((csvResult) => { + const csvs = JSON.parse(csvResult.stdout).items as COOInstallationCSV[]; + const csv = csvs.filter( + (item) => + item.metadata.name.includes('cluster-observability-operator') && + item.status?.phase === 'Succeeded', ); - } - cy.log(`Found CSV: ${csvName}`); - - cy.adminCLI( - `oc get csv ${csvName} -n ${ns} -o jsonpath=` + - `'{range .spec.install.spec.deployments[*]}` + - `{.name}{"\\n"}{end}'`, - ).then((deploymentsResult) => { - const deploymentNames = deploymentsResult.stdout.trim().split('\n').filter(Boolean); - const opIdx = deploymentNames.indexOf('observability-operator'); - if (opIdx === -1) { - throw new Error( - `observability-operator not found in CSV deployments: [${deploymentNames.join(', ')}]`, - ); + if (csv.length !== 1) { + throw new Error(`Expected one succeeded COO CSV in ${ns}, found ${csv.length}.`); } - cy.log(`Patching CSV ${csvName} deployment[${opIdx}] to add --openshift.enabled=true`); - cy.adminCLI( - `oc patch csv ${csvName} -n ${ns} --type=json ` + - `-p '[{"op":"add","path":"/spec/install/spec/deployments/` + - `${opIdx}/spec/template/spec/containers/0/args/-",` + - `"value":"--openshift.enabled=true"}]'`, - ); - }); - }); - // Step 2: Patch the deployment directly to apply the change immediately. - cy.log('Patching deployment to add --openshift.enabled=true'); - cy.adminCLI( - `oc patch deployment observability-operator -n ${ns} --type=json ` + - `-p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-",` + - `"value":"--openshift.enabled=true"}]'`, - ); + const deploymentIndex = csv[0].spec.install.spec.deployments.findIndex( + (deployment) => deployment.name === 'observability-operator', + ); + const containerIndex = csv[0].spec.install.spec.deployments[ + deploymentIndex + ]?.spec.template.spec.containers.findIndex((container) => container.name === 'operator'); + if (deploymentIndex === -1 || containerIndex === undefined || containerIndex === -1) { + throw new Error('The COO CSV does not define the observability-operator container.'); + } - // Step 3: Wait for the rollout to complete. - cy.adminCLI( - `oc rollout status deployment/observability-operator -n ${ns} ` + `--timeout=120s`, - { timeout: 130000 }, - ); + const csvArgs = + csv[0].spec.install.spec.deployments[deploymentIndex].spec.template.spec.containers[ + containerIndex + ].args ?? []; + cy.adminCLI(`oc get deployment observability-operator -n ${ns} -o json`).then( + (deploymentResult) => { + const deploymentArgs = JSON.parse(deploymentResult.stdout).spec.template.spec + .containers[0].args as string[] | undefined; + if (csvArgs.includes(flag) && deploymentArgs?.includes(flag)) { + cy.log('OpenShift mode is enabled on the COO CSV and deployment.'); + return; + } + if (attempt === 5) { + throw new Error('OpenShift mode did not converge after 5 attempts.'); + } - // Final verification: confirm the running pod actually has the flag. - cy.adminCLI( - `oc get deployment observability-operator -n ${ns} ` + - `-o jsonpath="{.spec.template.spec.containers[0].args}"`, - ).then((result) => { - const args = result.stdout; - cy.log(`Deployment args after rollout: ${args}`); - if (!args.includes('openshift.enabled=true')) { - cy.adminCLI(`oc get csv -n ${ns} -o yaml`, { - failOnNonZeroExit: false, - }).then((csvResult) => { - cy.log(`CSV YAML:\n${csvResult.stdout.substring(0, 3000)}`); - }); - cy.adminCLI(`oc get deployment observability-operator -n ${ns} -o yaml`).then( - (yamlResult) => { - cy.log(`Deployment YAML:\n${yamlResult.stdout}`); + const csvArgsPath = + `/spec/install/spec/deployments/${deploymentIndex}/spec/template/spec/containers/` + + `${containerIndex}/args`; + const deploymentArgsPath = '/spec/template/spec/containers/0/args'; + cy.exec( + 'oc patch csv "$CSV_NAME" -n "$NAMESPACE" --type=json -p "$PATCH" --kubeconfig "$KUBECONFIG"', + { + env: { + CSV_NAME: csv[0].metadata.name, + NAMESPACE: ns, + KUBECONFIG: kubeconfig, + PATCH: JSON.stringify([ + csvArgs.length + ? { op: 'add', path: `${csvArgsPath}/-`, value: flag } + : { op: 'add', path: csvArgsPath, value: [flag] }, + ]), + }, + }, + ); + cy.exec( + 'oc patch deployment observability-operator -n "$NAMESPACE" --type=json -p "$PATCH" --kubeconfig "$KUBECONFIG"', + { + env: { + NAMESPACE: ns, + KUBECONFIG: kubeconfig, + PATCH: JSON.stringify([ + deploymentArgs?.length + ? { op: 'add', path: `${deploymentArgsPath}/-`, value: flag } + : { op: 'add', path: deploymentArgsPath, value: [flag] }, + ]), + }, + }, + ); + cy.adminCLI( + `oc rollout status deployment/observability-operator -n ${ns} ` + `--timeout=120s`, + { timeout: 130000 }, + ); + reconcile(attempt + 1); }, ); - cy.then(() => { - throw new Error( - '--openshift.enabled=true NOT found in deployment args after rollout. ' + - `Actual args: ${args}`, - ); - }); - } - }); + }); + }; - cy.adminCLI(`oc logs -l app.kubernetes.io/name=observability-operator -n ${ns} ` + `--tail=5`, { - failOnNonZeroExit: false, - }).then((result) => { - cy.log(`Operator logs after restart:\n${result.stdout}`); - }); + reconcile(1); }, cleanupCOONamespace(): void { @@ -260,25 +369,6 @@ export const cooInstallUtils = { cy.log('Remove Cluster Observability Operator namespace'); - // For bundle installs, run operator-sdk cleanup first to remove - // CatalogSource, registry pod, and other bundle-specific resources. - // The bundle package name is "observability-operator" - // (not the CLUSTER_OBSERVABILITY_OPERATOR.packageName - // which is "cluster-observability-operator" used for catalog installs). - if (Cypress.env('KONFLUX_COO_BUNDLE_IMAGE') || Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')) { - cy.adminCLI( - `operator-sdk cleanup observability-operator -n ` + - `${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, - { failOnNonZeroExit: false, timeout: 60000 }, - ).then((result) => { - if (result.code === 0) { - cy.log('operator-sdk cleanup completed successfully'); - } else { - cy.log(`operator-sdk cleanup failed (may not exist): ${result.stderr}`); - } - }); - } - cy.adminCLI(`oc get namespace ${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, { timeout: readyTimeoutMilliseconds, failOnNonZeroExit: false, @@ -286,33 +376,48 @@ export const cooInstallUtils = { if (checkResult.code === 0) { cy.log('Namespace exists, proceeding with deletion'); - cy.adminCLI( - `oc delete csv --all -n ${ - CLUSTER_OBSERVABILITY_OPERATOR.namespace - } --ignore-not-found --wait=false`, - { timeout: 30000, failOnNonZeroExit: false }, - ).then((result) => { - if (result.code === 0) { - cy.log(`CSV deletion initiated in ${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`); - } else { - cy.log(`CSV deletion failed or not found: ${result.stderr}`); - } - }); + if (Cypress.env('COO_UI_INSTALL')) { + operatorHubPage.uninstallOperator(CLUSTER_OBSERVABILITY_OPERATOR.operatorName); + waitForCOOSubscriptionDeletion(); + } else { + cy.log('Delete Cluster Observability Operator subscription'); + cy.executeAndDelete( + `oc delete subscription ${CLUSTER_OBSERVABILITY_OPERATOR.packageName} -n ` + + `${CLUSTER_OBSERVABILITY_OPERATOR.namespace} --ignore-not-found --wait=true ` + + `--kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, + ); + waitForCOOSubscriptionDeletion(); - cy.adminCLI( - `oc delete subscription --all -n ${ - CLUSTER_OBSERVABILITY_OPERATOR.namespace - } --ignore-not-found --wait=false`, - { timeout: 30000, failOnNonZeroExit: false }, - ).then((result) => { - if (result.code === 0) { - cy.log( - `Subscription deletion initiated in ${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, - ); - } else { - cy.log(`Subscription deletion failed or not found: ${result.stderr}`); - } - }); + cy.log('Delete Cluster Observability Operator CSV'); + cy.adminCLI( + `oc delete csv -n ${CLUSTER_OBSERVABILITY_OPERATOR.namespace} ` + + `-l operators.coreos.com/${CLUSTER_OBSERVABILITY_OPERATOR.packageName}.` + + `${CLUSTER_OBSERVABILITY_OPERATOR.namespace} --ignore-not-found --wait=false`, + { timeout: readyTimeoutMilliseconds, failOnNonZeroExit: false }, + ); + + cy.log('Delete Cluster Observability Operator resource'); + cy.adminCLI( + `oc delete operator ${CLUSTER_OBSERVABILITY_OPERATOR.packageName}.` + + `${CLUSTER_OBSERVABILITY_OPERATOR.namespace} --ignore-not-found`, + { timeout: readyTimeoutMilliseconds, failOnNonZeroExit: false }, + ); + + cy.log('Delete Cluster Observability OperatorGroup'); + cy.adminCLI( + `oc delete operatorgroup --all -n ${CLUSTER_OBSERVABILITY_OPERATOR.namespace} ` + + `--ignore-not-found --wait=false`, + { timeout: readyTimeoutMilliseconds, failOnNonZeroExit: false }, + ); + } + + if (Cypress.env('KONFLUX_COO_BUNDLE_IMAGE') || Cypress.env('CUSTOM_COO_BUNDLE_IMAGE')) { + cy.adminCLI( + `operator-sdk cleanup observability-operator -n ` + + `${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, + { failOnNonZeroExit: false, timeout: 60000 }, + ); + } cy.adminCLI( `oc delete namespace ${ diff --git a/web/cypress/support/commands/image-patch-commands.ts b/web/cypress/support/commands/image-patch-commands.ts index 7eb355ac3..50e9add38 100644 --- a/web/cypress/support/commands/image-patch-commands.ts +++ b/web/cypress/support/commands/image-patch-commands.ts @@ -4,12 +4,33 @@ import { CLUSTER_MONITORING_OPERATOR, CLUSTER_OBSERVABILITY_OPERATOR } from '../ export {}; +type COOOperatorContainer = { + name: string; + args?: string[]; + env?: Array<{ name: string; value?: string }>; +}; + +type COOCSV = { + metadata: { name: string }; + status?: { phase?: string }; + spec: { + relatedImages?: Array<{ name: string; image: string }>; + install: { + spec: { + deployments: Array<{ + name: string; + spec: { template: { spec: { containers: COOOperatorContainer[] } } }; + }>; + }; + }; + }; +}; + function getImage(resource: string, namespace: string): Cypress.Chainable { return cy - .exec( + .adminCLI( `oc get ${resource} -n ${namespace} ` + - `-o jsonpath='{.spec.template.spec.containers[0].image}' ` + - `--kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, + `-o jsonpath='{.spec.template.spec.containers[0].image}'`, ) .then((result) => result.stdout.trim()); } @@ -18,10 +39,9 @@ function waitForMonitoringPluginImage(namespace: string, expectedImage: string): cy.waitUntil( () => cy - .exec( + .adminCLI( `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')}"`, + `-o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\\n"}{end}'`, { failOnNonZeroExit: false }, ) .then((result) => { @@ -40,10 +60,22 @@ function waitForMonitoringPluginImage(namespace: string, expectedImage: string): ); } +function verifyOperandImage(namespace: string, selector: string, expectedImage: string): void { + waitForPodsReady(selector, namespace, readyTimeoutMilliseconds); + cy.adminCLI( + `oc get pods -l ${selector} -n ${namespace} ` + + `-o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\\n"}{end}'`, + ).then((result) => { + const images = result.stdout.trim().split('\n').filter(Boolean); + expect(images.length).to.be.greaterThan(0); + expect(images.every((image) => image === expectedImage)).to.equal(true); + }); +} + export const imagePatchUtils = { getImage, - setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR: { namespace: string }): void { + setupMonitoringPluginImage(): void { const expectedImage = Cypress.env('MP_IMAGE') as string | undefined; if (expectedImage) { cy.log('Check Monitoring Plugin image'); @@ -68,7 +100,7 @@ export const imagePatchUtils = { env: { MP_IMAGE: expectedImage, KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), - MP_NAMESPACE: `${CLUSTER_MONITORING_OPERATOR.namespace}`, + MP_NAMESPACE: CLUSTER_MONITORING_OPERATOR.namespace, }, timeout: readyTimeoutMilliseconds, }).then((result) => { @@ -93,143 +125,158 @@ export const imagePatchUtils = { } }, - /** - * Generic function to patch a component image in the COO CSV. - */ - patchCOOCSVImage(config: { envVar: string; scriptPath: string; componentName: string }): void { - const imageValue = Cypress.env(config.envVar); - cy.log(`Set ${config.componentName} image in operator CSV`); + reconcileCOOCSVImage(config: { + envVar: string; + componentName: string; + relatedImageNames: string[]; + operatorEnvNames: string[]; + }): void { + const expectedImage = Cypress.env(config.envVar) as string | undefined; + if (!expectedImage) { + cy.log(`${config.envVar} is not set. Skipping ${config.componentName} image reconciliation.`); + return; + } - if (imageValue) { - cy.log(`${config.envVar} is set. The image will be patched in COO operator CSV`); - cy.exec(config.scriptPath, { - env: { - [config.envVar]: imageValue, - KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), - MCP_NAMESPACE: `${CLUSTER_OBSERVABILITY_OPERATOR.namespace}`, - }, - timeout: readyTimeoutMilliseconds, - }).then((result) => { - expect(result.code).to.eq(0); - cy.log(`COO CSV updated successfully with ${config.componentName} image: ${result.stdout}`); - cy.reload(true); + const namespace = CLUSTER_OBSERVABILITY_OPERATOR.namespace; + const kubeconfig = Cypress.env('KUBECONFIG_PATH'); + const reconcile = (attempt: number): void => { + cy.adminCLI(`oc get csv -n ${namespace} -o json`).then((result) => { + const csvs = JSON.parse(result.stdout).items as COOCSV[]; + const csv = csvs.filter((item) => + item.metadata.name.includes('cluster-observability-operator'), + ); + if (csv.length !== 1) { + throw new Error( + `Expected one Cluster Observability Operator CSV in ${namespace}, found ${csv.length}.`, + ); + } + + const relatedImages = csv[0].spec.relatedImages ?? []; + const deploymentIndex = csv[0].spec.install.spec.deployments.findIndex( + (deployment) => deployment.name === 'observability-operator', + ); + const containerIndex = csv[0].spec.install.spec.deployments[ + deploymentIndex + ]?.spec.template.spec.containers.findIndex((container) => container.name === 'operator'); + if (deploymentIndex === -1 || containerIndex === undefined || containerIndex === -1) { + throw new Error('The COO CSV does not define the observability-operator container.'); + } + + const env = + csv[0].spec.install.spec.deployments[deploymentIndex].spec.template.spec.containers[ + containerIndex + ].env ?? []; + const patch = [ + ...config.relatedImageNames.map((name) => { + const index = relatedImages.findIndex((image) => image.name === name); + if (index === -1) { + throw new Error(`The COO CSV does not define related image ${name}.`); + } + return { + op: 'replace', + path: `/spec/relatedImages/${index}/image`, + value: expectedImage, + }; + }), + ...config.operatorEnvNames.map((name) => { + const index = env.findIndex((entry) => entry.name === name); + if (index === -1) { + throw new Error(`The COO CSV does not define operator environment variable ${name}.`); + } + return { + op: 'replace', + path: + `/spec/install/spec/deployments/${deploymentIndex}/spec/template/spec/containers/` + + `${containerIndex}/env/${index}/value`, + value: expectedImage, + }; + }), + ]; + const currentImages = [ + ...config.relatedImageNames.map( + (name) => relatedImages.find((image) => image.name === name)?.image, + ), + ...config.operatorEnvNames.map((name) => env.find((entry) => entry.name === name)?.value), + ]; + if (currentImages.every((image) => image === expectedImage)) { + cy.log(`${config.componentName} image already matches ${expectedImage}`); + return; + } + + if (attempt === 5) { + throw new Error( + `${config.componentName} image did not converge to ${expectedImage} after 5 attempts.`, + ); + } + + cy.exec( + 'oc patch csv "$CSV_NAME" -n "$NAMESPACE" --type=json -p "$PATCH" --kubeconfig "$KUBECONFIG"', + { + env: { + CSV_NAME: csv[0].metadata.name, + NAMESPACE: namespace, + PATCH: JSON.stringify(patch), + KUBECONFIG: kubeconfig, + }, + }, + ); + cy.adminCLI( + `oc rollout status deployment/observability-operator -n ${namespace} --timeout=120s`, + { timeout: 130000 }, + ); + reconcile(attempt + 1); }); - } else { - cy.log(`${config.envVar} is NOT set. Skipping patching the image in COO operator CSV.`); - } + }; + + reconcile(1); }, setupMonitoringConsolePlugin(): void { - imagePatchUtils.patchCOOCSVImage({ + imagePatchUtils.reconcileCOOCSVImage({ envVar: 'MCP_CONSOLE_IMAGE', - scriptPath: './cypress/fixtures/coo/update-mcp-image.sh', componentName: 'Monitoring Console Plugin', + relatedImageNames: ['ui-monitoring', 'ui-monitoring-pf5', 'ui-monitoring-pf6'], + operatorEnvNames: [ + 'RELATED_IMAGE_CONSOLE_MONITORING_PLUGIN', + 'RELATED_IMAGE_CONSOLE_MONITORING_PLUGIN_PF5', + 'RELATED_IMAGE_CONSOLE_MONITORING_PLUGIN_PF6', + ], }); }, setupClusterHealthAnalyzer(): void { - imagePatchUtils.patchCOOCSVImage({ + imagePatchUtils.reconcileCOOCSVImage({ envVar: 'CHA_IMAGE', - scriptPath: './cypress/fixtures/coo/update-cha-image.sh', componentName: 'cluster-health-analyzer', + relatedImageNames: ['cluster-health-analyzer'], + operatorEnvNames: ['RELATED_IMAGE_CLUSTER_HEALTH_ANALYZER'], }); }, - /** - * After the monitoring-console-plugin pod is running, verify it uses the - * expected CI image. If OLM reverted the CSV patch, re-apply it, patch the - * deployment directly, and wait until the pod rolls out with the correct image. - */ verifyMonitoringConsolePluginImage(): void { - const expectedImage = Cypress.env('MCP_CONSOLE_IMAGE'); + const expectedImage = Cypress.env('MCP_CONSOLE_IMAGE') as string | undefined; if (!expectedImage) { return; } - cy.log('Verify monitoring-console-plugin pod image matches expected CI image'); - const kubeconfig = Cypress.env('KUBECONFIG_PATH'); - const maxAttempts = 5; - const ns = CLUSTER_OBSERVABILITY_OPERATOR.namespace; + verifyOperandImage( + CLUSTER_OBSERVABILITY_OPERATOR.namespace, + 'app.kubernetes.io/instance=monitoring', + expectedImage, + ); + }, - if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/.test(ns)) { - throw new Error(`Invalid Kubernetes namespace: ${ns}`); - } - if (!/^[a-zA-Z0-9][a-zA-Z0-9._/:@-]*$/.test(expectedImage)) { - throw new Error(`Invalid container image reference: ${expectedImage}`); + verifyClusterHealthAnalyzerImage(): void { + const expectedImage = Cypress.env('CHA_IMAGE') as string | undefined; + if (!expectedImage) { + return; } - const execEnv = { KUBECONFIG: kubeconfig, NS: ns }; - - const checkAndFix = (attempt: number): void => { - cy.exec( - 'oc get pods -l app.kubernetes.io/instance=monitoring -n "$NS" ' + - "-o jsonpath='{.items[0].spec.containers[0].image}'", - { failOnNonZeroExit: false, env: execEnv }, - ).then((result) => { - const currentImage = result.stdout.replace(/'/g, '').trim(); - cy.log( - `monitoring-console-plugin image check ` + - `(attempt ${attempt}/${maxAttempts}): ${currentImage}`, - ); - - if (currentImage === expectedImage) { - cy.log('monitoring-console-plugin pod image verified successfully'); - return; - } - - if (attempt >= maxAttempts) { - throw new Error( - `monitoring-console-plugin pod image mismatch after ${maxAttempts} attempts.\n` + - `Expected: ${expectedImage}\nActual: ${currentImage}`, - ); - } - - cy.log('Image mismatch detected. Re-patching CSV and deployment...'); - - cy.exec('./cypress/fixtures/coo/update-mcp-image.sh', { - env: { - MCP_CONSOLE_IMAGE: expectedImage, - KUBECONFIG: kubeconfig, - MCP_NAMESPACE: ns, - }, - timeout: readyTimeoutMilliseconds, - failOnNonZeroExit: false, - }); - - cy.exec( - 'oc get deployment -l app.kubernetes.io/instance=monitoring -n "$NS" ' + - "-o jsonpath='{.items[0].metadata.name}'", - { failOnNonZeroExit: false, env: execEnv }, - ).then((deployResult) => { - const deployName = deployResult.stdout.replace(/'/g, '').trim(); - if (deployName && /^[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?$/.test(deployName)) { - cy.log(`Patching deployment/${deployName} image directly`); - cy.exec( - 'oc patch deployment "$DEPLOY_NAME" -n "$NS" --type=json ' + - '-p "[{\\"op\\":\\"replace\\",\\"path\\":\\"/spec/template/spec/containers/0/image\\",' + - '\\"value\\":\\"$EXPECTED_IMAGE\\"}]"', - { - env: { - ...execEnv, - DEPLOY_NAME: deployName, - EXPECTED_IMAGE: expectedImage, - }, - }, - ); - cy.exec('oc rollout status deployment/"$DEPLOY_NAME" -n "$NS" --timeout=120s', { - timeout: 130000, - env: { ...execEnv, DEPLOY_NAME: deployName }, - }); - } - }); - - waitForPodsReady('app.kubernetes.io/instance=monitoring', ns, readyTimeoutMilliseconds); - - checkAndFix(attempt + 1); - }); - }; - - checkAndFix(1); + verifyOperandImage( + CLUSTER_OBSERVABILITY_OPERATOR.namespace, + 'app.kubernetes.io/instance=health-analyzer', + expectedImage, + ); }, revertMonitoringPluginImage(): void { @@ -239,7 +286,7 @@ export const imagePatchUtils = { env: { MP_IMAGE: Cypress.env('MP_IMAGE'), KUBECONFIG: Cypress.env('KUBECONFIG_PATH'), - MP_NAMESPACE: `${CLUSTER_MONITORING_OPERATOR.namespace}`, + MP_NAMESPACE: CLUSTER_MONITORING_OPERATOR.namespace, }, timeout: readyTimeoutMilliseconds, }).then((result) => { diff --git a/web/cypress/support/commands/operator-commands.ts b/web/cypress/support/commands/operator-commands.ts index a3a82e478..7b8bb15e8 100644 --- a/web/cypress/support/commands/operator-commands.ts +++ b/web/cypress/support/commands/operator-commands.ts @@ -23,14 +23,11 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { - ensureMonitoringPlugin(CLUSTER_MONITORING_OPERATOR: { - namespace: string; - operatorName: string; - }); - beforeBlockCOO(options?: COOSetupOptions); + ensureMonitoringPlugin(); + cleanupMP(); + ensureMonitoringConsolePlugin(options?: COOSetupOptions); cleanupCOO(options?: COOSetupOptions); RemoveClusterAdminRole(); - setupCOO(options?: COOSetupOptions); beforeBlockACM(): Chainable; waitForAcmAlertsFiring(alertNames?: string[]): Chainable; closeOnboardingModalIfPresent(): Chainable; @@ -92,7 +89,7 @@ function waitForAcmAlertsFiring(alertNames: string[] = ACM_DEFAULT_TEST_ALERTS): cy.log('Waiting for observability-thanos-rule pods to be Ready'); cy.adminCLI( - `oc rollout status statefulset/observability-thanos-rule ` + `-n ${ns} --timeout=300s`, + `oc rollout status statefulset/observability-thanos-rule` + ` -n ${ns} --timeout=300s`, { failOnNonZeroExit: false, timeout: acmAlertReadyTimeoutMilliseconds }, ).then((result) => { if (result.code !== 0) { @@ -176,19 +173,24 @@ function cleanupUIPlugin(opts: Required): void { // ── Cypress commands ─────────────────────────────────────────────── -Cypress.Commands.add( - 'ensureMonitoringPlugin', - (CLUSTER_MONITORING_OPERATOR: { namespace: string; operatorName: string }) => { - cy.log('Ensure Monitoring Plugin'); - operatorAuthUtils.loginAndAuth(); - imagePatchUtils.setupMonitoringPluginImage(CLUSTER_MONITORING_OPERATOR); - collectDebugInfo({ debugMonitoringPlugin: true }); - cy.task('clearDownloads'); - cy.log('Ensure Monitoring Plugin completed'); - }, -); - -Cypress.Commands.add('beforeBlockCOO', (options?: COOSetupOptions) => { +Cypress.Commands.add('ensureMonitoringPlugin', () => { + cy.log('Ensure Monitoring Plugin'); + operatorAuthUtils.loginAndAuth(); + imagePatchUtils.setupMonitoringPluginImage(); + collectDebugInfo({ debugMonitoringPlugin: true, debugMonitoringConsolePlugin: false }); + cy.task('clearDownloads'); + cy.log('Ensure Monitoring Plugin completed'); +}); + +Cypress.Commands.add('cleanupMP', () => { + if (useSession) { + cy.log('cleanupMP (session)'); + imagePatchUtils.revertMonitoringPluginImage(); + cy.log('cleanupMP (session) completed'); + } +}); + +Cypress.Commands.add('ensureMonitoringConsolePlugin', (options?: COOSetupOptions) => { const opts = { ...DEFAULT_COO_OPTIONS, ...options }; if (useSession) { @@ -202,60 +204,27 @@ Cypress.Commands.add('beforeBlockCOO', (options?: COOSetupOptions) => { cy.session( sessionKey, () => { - cy.log('Before block COO (session)'); - cy.cleanupCOO(opts); operatorAuthUtils.loginAndAuthNoSession(); - cy.setupCOO(opts); - cy.log('Before block COO (session) completed'); }, { cacheAcrossSpecs: true, - validate() { - cy.validateLogin(); - if (opts.dashboards) { - cy.visit('/monitoring/v2/dashboards'); - cy.url().should('include', '/monitoring/v2/dashboards'); - } - }, + validate: () => cy.validateLogin(), }, ); } else { - cy.log('Before block COO (no session)'); - cy.cleanupCOO(opts); operatorAuthUtils.loginAndAuth(); - cy.setupCOO(opts); - cy.log('Before block COO (no session) completed'); } -}); -Cypress.Commands.add('cleanupCOO', (options?: COOSetupOptions) => { - const opts = { ...DEFAULT_COO_OPTIONS, ...options }; - - cy.log('Cleanup COO'); if (Cypress.env('SKIP_ALL_INSTALL')) { - cy.log( - 'SKIP_ALL_INSTALL is set. Skipping COO cleanup and operator verifications (preserves existing setup).', - ); + cy.log('SKIP_ALL_INSTALL is set. Skipping Monitoring Console Plugin reconciliation.'); return; } - if (opts.troubleshootingPanel) { - dashboardsUtils.cleanupTroubleshootingPanel(); - } - cleanupUIPlugin(opts); - imagePatchUtils.revertMonitoringPluginImage(); - cy.log('Cleanup COO completed'); -}); -Cypress.Commands.add('setupCOO', (options?: COOSetupOptions) => { - const opts = { ...DEFAULT_COO_OPTIONS, ...options }; - - if (Cypress.env('SKIP_ALL_INSTALL')) { - cy.log( - 'SKIP_ALL_INSTALL is set. Skipping COO setup and operator verifications (uses existing installation).', - ); - return; - } - cooInstallUtils.installCOO(); + cy.log('Ensure Monitoring Console Plugin'); + cy.adminCLI( + `oc adm policy add-cluster-role-to-user cluster-admin ${Cypress.env('LOGIN_USERNAME')}`, + ); + cooInstallUtils.ensureCOOInstalled(); cooInstallUtils.waitForCOOReady(); cooInstallUtils.enableOpenShiftMode(); imagePatchUtils.setupMonitoringConsolePlugin(); @@ -264,6 +233,9 @@ Cypress.Commands.add('setupCOO', (options?: COOSetupOptions) => { } dashboardsUtils.setupMonitoringUIPlugin(); imagePatchUtils.verifyMonitoringConsolePluginImage(); + if (opts.healthAnalyzer) { + imagePatchUtils.verifyClusterHealthAnalyzerImage(); + } if (opts.dashboards) { dashboardsUtils.setupDashboardsAndPlugins(); } @@ -273,6 +245,25 @@ Cypress.Commands.add('setupCOO', (options?: COOSetupOptions) => { imagePatchUtils.setupMonitoringPluginImage(); removeClusterAdminRole(); collectDebugInfo({ debugMonitoringPlugin: true, debugMonitoringConsolePlugin: true }); + cy.log('Ensure Monitoring Console Plugin completed'); +}); + +Cypress.Commands.add('cleanupCOO', (options?: COOSetupOptions) => { + const opts = { ...DEFAULT_COO_OPTIONS, ...options }; + + cy.log('Cleanup COO'); + if (Cypress.env('SKIP_ALL_INSTALL')) { + cy.log( + 'SKIP_ALL_INSTALL is set. Skipping COO cleanup and operator verifications (preserves existing setup).', + ); + return; + } + if (opts.troubleshootingPanel) { + dashboardsUtils.cleanupTroubleshootingPanel(); + } + cleanupUIPlugin(opts); + imagePatchUtils.revertMonitoringPluginImage(); + cy.log('Cleanup COO completed'); }); Cypress.Commands.add('RemoveClusterAdminRole', () => { @@ -286,7 +277,7 @@ Cypress.Commands.add('waitForAcmAlertsFiring', (alertNames?: string[]) => { }); Cypress.Commands.add('beforeBlockACM', () => { - cy.beforeBlockCOO(); + cy.ensureMonitoringConsolePlugin(); cy.log('=== [Setup] Installing ACM test resources ==='); cy.exec('bash ./cypress/fixtures/coo/acm-install.sh', { env: { KUBECONFIG: Cypress.env('KUBECONFIG_PATH') }, diff --git a/web/cypress/support/commands/traces-logging-commands.ts b/web/cypress/support/commands/traces-logging-commands.ts index f358920d3..a76bb0916 100644 --- a/web/cypress/support/commands/traces-logging-commands.ts +++ b/web/cypress/support/commands/traces-logging-commands.ts @@ -48,7 +48,7 @@ declare global { const useSession = String(Cypress.env('SESSION')).toLowerCase() === 'true'; -const DTP = { +const DISTRIBUTING_TRACING_PLUGIN = { namespace: Cypress.env('COO_NAMESPACE'), packageName: 'cluster-observability-operator', operatorName: 'Cluster Observability Operator', @@ -291,7 +291,7 @@ const tracesUtils = { cy.exec( // eslint-disable-next-line max-len `sleep 15 && oc wait --for=condition=Ready pods --selector=app.kubernetes.io/instance=distributed-tracing -n ${ - DTP.namespace + DISTRIBUTING_TRACING_PLUGIN.namespace } --timeout=60s --kubeconfig "${Cypress.env('KUBECONFIG_PATH')}"`, { timeout: 80000, @@ -300,7 +300,8 @@ const tracesUtils = { ).then((result) => { expect(result.code).to.eq(0); cy.log( - `Distributed Tracing Console plugin pod is now running in namespace: ${DTP.namespace}`, + `Distributed Tracing Console plugin pod is now running in namespace: ` + + `${DISTRIBUTING_TRACING_PLUGIN.namespace}`, ); }); // Check for web console update alert for up to 2 minutes @@ -323,7 +324,11 @@ const tracesUtils = { cy.log('SKIP_COO_INSTALL is set. Skipping Distributed Tracing UI Plugin cleanup.'); return; } - cy.adminCLI(`oc delete ${DTP.config.kind} ${DTP.config.name}`, { failOnNonZeroExit: false }); + cy.adminCLI( + `oc delete ${DISTRIBUTING_TRACING_PLUGIN.config.kind} ` + + `${DISTRIBUTING_TRACING_PLUGIN.config.name}`, + { failOnNonZeroExit: false }, + ); cy.log('Cleanup Distributed Tracing UI Plugin completed'); }, @@ -613,9 +618,8 @@ const loggingUtils = { () => cy .exec( - `oc get pods -n ${CLUSTER_LOGGING_OPERATOR.namespace} -o name ` + - `--kubeconfig ${kubeconfig} ` + - '| grep logging', + `oc get pods -n ${CLUSTER_LOGGING_OPERATOR.namespace} -o name --kubeconfig ` + + `${kubeconfig} | grep logging`, { failOnNonZeroExit: false }, ) .then((result) => result.code === 0 && result.stdout.trim().length > 0), diff --git a/web/cypress/views/operator-hub-page.ts b/web/cypress/views/operator-hub-page.ts index d60e22700..83b7284e5 100644 --- a/web/cypress/views/operator-hub-page.ts +++ b/web/cypress/views/operator-hub-page.ts @@ -17,6 +17,19 @@ export const operatorHubPage = { } cy.get('[data-test="install-operator"]').click(); }, + uninstallOperator: (operatorName) => { + cy.switchPerspective(CustomerPerspectiveName.CorePlatform, 'Administrator'); + cy.visit('/operatorhub/installed-operators'); + cy.get('body').should('be.visible'); + cy.byTestID('name-filter-input').clear().type(`${operatorName}{enter}`); + cy.contains('tr', operatorName, { timeout: 120000 }) + .should('be.visible') + .within(() => { + cy.get('[data-test-id="kebab-button"]').click(); + }); + cy.contains('[role="menuitem"]', 'Uninstall Operator').click(); + cy.contains('button', 'Uninstall').click(); + }, checkOperatorStatus: (csvName, csvStatus) => { cy.get('input[data-test="name-filter-input"]').clear().type(`${csvName}`); cy.get(`[data-test-operator-row="${csvName}"]`, { timeout: 120000 }) From 0b92711f9395d2b4547548b875d8779ddefbe71d Mon Sep 17 00:00:00 2001 From: PeterYurkovich Date: Wed, 16 Sep 2026 12:27:28 -0400 Subject: [PATCH 3/3] feat: add cypress lint command and enforce it on make lint-frontend --- Makefile | 1 + web/cypress/component/labels.cy.tsx | 2 +- web/cypress/support/commands/operator-commands.ts | 4 +++- web/cypress/support/commands/selector-commands.ts | 4 ++-- web/cypress/support/index.ts | 3 ++- web/cypress/support/selectors.ts | 4 ++-- web/cypress/tsconfig.json | 2 +- web/cypress/views/incidents-page.ts | 2 +- web/package.json | 1 + 9 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index e1d59a9c5..a837d7e10 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ i18n-frontend: .PHONY: lint-frontend lint-frontend: cd web && npm run lint + cd web && npm run lint:tsc:cypress .PHONY: install-backend install-backend: diff --git a/web/cypress/component/labels.cy.tsx b/web/cypress/component/labels.cy.tsx index 1a4c0bdcb..0abe977f5 100644 --- a/web/cypress/component/labels.cy.tsx +++ b/web/cypress/component/labels.cy.tsx @@ -1,4 +1,4 @@ -import { Labels } from '@/shared/components/labels'; +import { Labels } from '@/shared/components/Labels'; describe('Labels', () => { it('renders "No labels" when labels is empty', () => { diff --git a/web/cypress/support/commands/operator-commands.ts b/web/cypress/support/commands/operator-commands.ts index 7b8bb15e8..ccb516f1b 100644 --- a/web/cypress/support/commands/operator-commands.ts +++ b/web/cypress/support/commands/operator-commands.ts @@ -208,7 +208,9 @@ Cypress.Commands.add('ensureMonitoringConsolePlugin', (options?: COOSetupOptions }, { cacheAcrossSpecs: true, - validate: () => cy.validateLogin(), + validate: () => { + cy.validateLogin(); + }, }, ); } else { diff --git a/web/cypress/support/commands/selector-commands.ts b/web/cypress/support/commands/selector-commands.ts index 398bf3e97..083d31d4b 100644 --- a/web/cypress/support/commands/selector-commands.ts +++ b/web/cypress/support/commands/selector-commands.ts @@ -31,8 +31,8 @@ declare global { ): Chainable>; byTestSectionHeading(selector: string): Chainable>; byTestOperandLink(selector: string): Chainable>; - byOUIAID(selector: string): Chainable; - byClass(selector: string): Chainable; + byOUIAID(selector: string): Chainable>; + byClass(selector: string): Chainable>; bySemanticElement(element: string, text?: string): Chainable>; byAriaLabel( label: string, diff --git a/web/cypress/support/index.ts b/web/cypress/support/index.ts index 433a46c36..a545f29bc 100644 --- a/web/cypress/support/index.ts +++ b/web/cypress/support/index.ts @@ -16,7 +16,8 @@ import './commands/traces-logging-commands'; export const checkErrors = () => cy.window().then((win) => { - assert.isTrue(!win.windowError, win.windowError); + const { windowError } = win as Cypress.AUTWindow & { windowError?: string }; + assert.isTrue(!windowError, windowError); }); // Ignore benign ResizeObserver errors globally so they don't fail tests diff --git a/web/cypress/support/selectors.ts b/web/cypress/support/selectors.ts index 89cd9858f..eb40124db 100644 --- a/web/cypress/support/selectors.ts +++ b/web/cypress/support/selectors.ts @@ -13,8 +13,8 @@ declare global { selector: string, options?: Partial, ): Chainable; - byOUIAID(selector: string): Chainable; - byClass(selector: string): Chainable; + byOUIAID(selector: string): Chainable>; + byClass(selector: string): Chainable>; byLegacyTestID( selector: string, options?: Partial, diff --git a/web/cypress/tsconfig.json b/web/cypress/tsconfig.json index 7492a6331..0a881268f 100644 --- a/web/cypress/tsconfig.json +++ b/web/cypress/tsconfig.json @@ -5,5 +5,5 @@ "lib": ["es5", "dom"], "types": ["cypress", "node"] }, - "include": ["**/*.ts", "**/*.tsx"] + "include": ["**/*.ts", "**/*.tsx", "../src/index.d.ts"] } diff --git a/web/cypress/views/incidents-page.ts b/web/cypress/views/incidents-page.ts index f4d459e7c..2669506e5 100644 --- a/web/cypress/views/incidents-page.ts +++ b/web/cypress/views/incidents-page.ts @@ -455,7 +455,7 @@ export const incidentsPage = { .incidentIdFilterList() .should('exist') .then(($list) => { - const $items = $list.find(`[data-test^="${prefix}"]`); + const $items = Cypress.$($list).find(`[data-test^="${prefix}"]`); const ids: string[] = []; $items.each((_, el) => { const id = Cypress.$(el).attr('data-test')?.slice(prefix.length); diff --git a/web/package.json b/web/package.json index 544a32ab5..c0e4f5c76 100644 --- a/web/package.json +++ b/web/package.json @@ -19,6 +19,7 @@ "po-to-i18n": "node ./i18n-scripts/po-to-i18n.js", "lint": "eslint ./src ./cypress ./eslint-rules", "lint:tsc": "tsc --noEmit", + "lint:tsc:cypress": "tsc --noEmit --project cypress/tsconfig.json", "type-check": "tsc --noEmit", "lint-fix": "eslint ./src --fix", "pre-commit:check": "npm run lint",