diff --git a/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts b/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts new file mode 100644 index 00000000..6b142e04 --- /dev/null +++ b/web/cypress/e2e/incidents/regression/01.reg_filtering.cy.ts @@ -0,0 +1,151 @@ +/* +Regression test for incidents filtering functionality. + +This test uses the comprehensive filtering test scenarios to verify +that filtering works correctly across various incident types and severities. +Uses elements defined in incidents-page.ts for all interactions. + +Verifies: OU-727 +*/ + +import { incidentsPage } from '../../../views/incidents-page'; + +const MCP = { + namespace: 'openshift-cluster-observability-operator', + packageName: 'cluster-observability-operator', + operatorName: 'Cluster Observability Operator', + config: { + kind: 'UIPlugin', + name: 'monitoring', + }, +}; + +const MP = { + namespace: 'openshift-monitoring', + operatorName: 'Cluster Monitoring Operator', +}; + +describe('Regression: Incidents Filtering', () => { + + before(() => { + cy.beforeBlockCOO(MCP, MP); + }); + + beforeEach(() => { + cy.log('Navigate to Observe → Incidents'); + incidentsPage.goTo(); + cy.log('Setting up comprehensive filtering test scenarios'); + cy.mockIncidentFixture('incident-scenarios/7-comprehensive-filtering-test-scenarios.yaml'); + }); + + it('1. Severity filtering - Critical, Warning, Info', () => { + cy.log('1.1 Clear all filters and ensure all incidents are loaded'); + incidentsPage.clearAllFilters(); + + incidentsPage.setDays('1 day'); + incidentsPage.setDays('7 days'); + + incidentsPage.elements.incidentsChartContainer().should('be.visible'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 12); + cy.log('All 12 incidents from comprehensive filtering scenarios are loaded'); + + + cy.log('1.2 Active filters: Critical'); + incidentsPage.toggleFilter('Critical'); + + incidentsPage.elements.severityFilterChip().should('be.visible'); + incidentsPage.elements.incidentsChartContainer().should('be.visible'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 7); + + cy.log('Verified: 7 incidents shown'); + + + cy.log('1.3 Active filters: Warning'); + incidentsPage.toggleFilter('Critical'); // Deselect Critical + incidentsPage.toggleFilter('Warning'); // Select Warning + incidentsPage.elements.severityFilterChip().should('be.visible'); + incidentsPage.elements.incidentsChartContainer().should('be.visible'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 5); + + cy.log('Verified: 5 incidents shown'); + + + cy.log('1.4 Active filters: Informative'); + incidentsPage.toggleFilter('Warning'); // Deselect Warning + incidentsPage.toggleFilter('Informative'); // Select Info + incidentsPage.elements.severityFilterChip().should('be.visible'); + incidentsPage.elements.incidentsChartContainer().should('be.visible'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 3); + + cy.log('Verified: 3 incidents shown'); + + + cy.log('1.6 Test severity filter combinations'); + cy.log('1.6.1 Active filters: Critical + Informative'); + incidentsPage.toggleFilter('Critical'); // Add Critical to existing Info + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 9); + cy.log('Verified: 9 incidents shown'); + + + cy.log('1.6.2 Active filters: Warning + Informative'); + incidentsPage.toggleFilter('Critical'); // Deselect Critical + incidentsPage.toggleFilter('Warning'); // Add Warning to existing Info + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 7); + cy.log('Verified: 7 incidents shown'); + + + cy.log('1.6.3 Active filters: Critical + Warning + Informative'); + incidentsPage.toggleFilter('Critical'); // Add Critical to existing Info + Warning + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 12); + cy.log('Verified: 12 incidents shown'); + + + cy.log('1.7 Test state filter combinations - remove all severity filters and focus on Info + state'); + cy.log('1.7.1 Active filters: Informative + Resolved'); + incidentsPage.toggleFilter('Critical'); // Deselect Critical + incidentsPage.toggleFilter('Warning'); // Deselect Warning + // Now we have only Informative selected + incidentsPage.toggleFilter('Resolved'); // Add Resolved state filter + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 1); + cy.log('Verified: 1 incident shown'); + + + cy.log('1.7.2 Active filters: Informative + Firing'); + incidentsPage.toggleFilter('Resolved'); // Deselect Resolved + incidentsPage.toggleFilter('Firing'); // Select Firing + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 2); + cy.log('Verified: 2 incidents shown'); + + + incidentsPage.clearAllFilters(); + }); + + it('2. Chart interaction with active filters', () => { + cy.log('Setting up filters for chart interaction testing'); + incidentsPage.clearAllFilters(); + + // Set up a specific filter state for testing + incidentsPage.toggleFilter('Informative'); + incidentsPage.toggleFilter('Firing'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 2); + cy.log('Setup complete: Informative + Firing filters active, 2 incidents shown'); + + cy.log('2.1 Select incident bar while filters are active'); + incidentsPage.selectIncidentByBarIndex(0); + incidentsPage.elements.incidentsTable().should('be.visible'); + cy.log('Incident table displayed after bar selection'); + + cy.log('2.2 Verify just selected incident is shown in the chart'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 1); + cy.log('Verified: 1 incident shown'); + + cy.log('2.3 Deselect incident bar and verify filter persistence'); + incidentsPage.deselectIncidentByBar(); + incidentsPage.elements.incidentsTable().should('not.exist'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 2); + cy.log('Filters persisted after deselection: 2 incidents still shown'); + + incidentsPage.clearAllFilters(); + }); + +}); 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 new file mode 100644 index 00000000..436a7662 --- /dev/null +++ b/web/cypress/e2e/incidents/regression/02.reg_ui_charts_comprehensive.cy.ts @@ -0,0 +1,433 @@ +/* +Regression test for Charts UI bugs (Section 2 of TESTING_CHECKLIST.md) + +This test loads comprehensive test data covering: +- 2.1: Tooltip Positioning Issues +- 2.2: Bar Sorting & Visibility Issues +- 2.3: Date/Time Display Issues + +Test data: 12 incidents with 1-6 alerts each, varying durations (10m to 8h), +alert names (4 to 180+ chars), multi-component and multi-severity scenarios. +*/ + +import { incidentsPage } from '../../../views/incidents-page'; + +const ARROW_HEIGHT = 12; +const ALLOWED_MARGIN = 8; + +interface RectLike { + top: number; + bottom: number; + left: number; + right: number; + width: number; + height: number; +} + +function verifyTooltipPositioning( + tooltipRect: RectLike, + barRect: RectLike, + context: string, + win?: Window +) { + cy.log(`${context}: Bar rect: top=${barRect.top}, bottom=${barRect.bottom}`); + cy.log(`${context}: Tooltip rect: top=${tooltipRect.top}, bottom=${tooltipRect.bottom}`); + + expect(tooltipRect.top, `${context}: tooltip top should be above bar`).to.be.lessThan(barRect.top); + expect(tooltipRect.top, `${context}: tooltip should be in viewport`).to.be.greaterThan(0); + expect(tooltipRect.bottom + ARROW_HEIGHT, `${context}: tooltip bottom with arrow`).to.be.lessThan(barRect.top); + expect(tooltipRect.bottom + ARROW_HEIGHT + ALLOWED_MARGIN, `${context}: tooltip arrow should be near bar top`).to.be.greaterThan(barRect.top); + + if (win) { + expect(tooltipRect.right, `${context}: tooltip should not overflow viewport width`).to.be.lessThan(win.innerWidth); + expect(tooltipRect.bottom, `${context}: tooltip should not overflow viewport`).to.be.lessThan(win.innerHeight); + } +} + +const MCP = { + namespace: 'openshift-cluster-observability-operator', + packageName: 'cluster-observability-operator', + operatorName: 'Cluster Observability Operator', + config: { + kind: 'UIPlugin', + name: 'monitoring', + }, +}; + +const MP = { + namespace: 'openshift-monitoring', + operatorName: 'Cluster Monitoring Operator', +}; + +describe('Regression: Charts UI - Comprehensive', () => { + + before(() => { + cy.beforeBlockCOO(MCP, MP); + + }); + + beforeEach(() => { + incidentsPage.goTo(); + cy.mockIncidentFixture('incident-scenarios/12-charts-ui-comprehensive.yaml'); + }); + + describe('Section 2.1: Tooltip Positioning', () => { + + it('Tooltip positioning and content validation', () => { + cy.log('Setup: Clear filters and verify all incidents loaded'); + incidentsPage.clearAllFilters(); + incidentsPage.setDays('7 days'); + incidentsPage.elements.incidentsChartContainer().should('be.visible'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 14); + + // Chart order: Index 0 = newest (top), Index 13 = oldest (bottom) + // 0: network-firing-short-002 (10m) + // 13: version-short-name-001 (8h) + + cy.log('1.2 Test top incident (newest) tooltip positioning'); + incidentsPage.getIncidentBarRect(0).then((barRect) => { + incidentsPage.hoverOverIncidentBar(0); + incidentsPage.elements.tooltip().then(($tooltip) => { + verifyTooltipPositioning($tooltip[0].getBoundingClientRect(), barRect, 'Top incident'); + }); + }); + cy.log('Verified: Top incident tooltip appears above bar without overlapping'); + + cy.log('1.3 Test middle incident tooltip positioning'); + incidentsPage.getIncidentBarRect(7).then((barRect) => { + incidentsPage.hoverOverIncidentBar(7); + incidentsPage.elements.tooltip().then(($tooltip) => { + verifyTooltipPositioning($tooltip[0].getBoundingClientRect(), barRect, 'Middle incident'); + }); + }); + cy.log('Verified: Middle incident tooltip appears above bar without overlapping'); + + cy.log('1.4 Test bottom incident (oldest) tooltip positioning'); + incidentsPage.getIncidentBarRect(13).then((barRect) => { + incidentsPage.hoverOverIncidentBar(13); + cy.window().then((win) => { + incidentsPage.elements.tooltip().then(($tooltip) => { + verifyTooltipPositioning($tooltip[0].getBoundingClientRect(), barRect, 'Bottom incident', win); + }); + }); + }); + cy.log('Verified: Bottom incident tooltip appears above bar and stays within viewport'); + + cy.log('2-4: Multi-incident verification (single traversal optimization)'); + cy.log('3.1 Firing vs resolved incident tooltips'); + cy.log('3.2 Find and verify firing incident (network-firing-short-002)'); + incidentsPage.hoverOverIncidentBar(0); + incidentsPage.elements.tooltip() + .invoke('text') + .then((text) => { + expect(text).to.contain('network-firing-short-002'); + expect(text).to.match(/End.*---/); + }); + cy.log('Verified: Firing incident shows --- for end time'); + + let foundMultiComponent = false; + let foundResolved = false; + let foundLongName = false; + + incidentsPage.elements.incidentsChartBarsGroups().each(($group, index) => { + const groupId = $group.attr('data-test'); + + if (!foundMultiComponent || !foundResolved || !foundLongName) { + incidentsPage.hoverOverIncidentBar(index); + incidentsPage.elements.tooltip().invoke('text').then((text) => { + + if (!foundMultiComponent && text.includes('network-three-alerts-001')) { + cy.log('2.1 Multi-component tooltip content'); + cy.log(`Found network-three-alerts-001 at index ${index}`); + cy.log('2.3 Verify tooltip shows all 3 components'); + expect(text).to.contain('network'); + expect(text).to.contain('compute'); + expect(text).to.contain('storage'); + cy.log('Verified: Multi-component tooltip displays all components'); + foundMultiComponent = true; + } + + if (!foundResolved && text.includes('network-resolved-short-001')) { + cy.log('3.3 Find and verify resolved incident (network-resolved-short-001)'); + cy.log(`Found network-resolved-short-001 at index ${index}`); + expect(text).to.contain('Start'); + expect(text).to.contain('End'); + expect(text).to.not.match(/End.*---/); + cy.log('Verified: Resolved incident shows actual end time'); + foundResolved = true; + } + + if (!foundLongName && text.includes('others-very-long-name-001')) { + cy.log('4.1 Long alert name tooltip handling'); + cy.log(`Found others-very-long-name-001 at index ${index}`); + cy.log('4.2 Verify tooltip with long name stays within viewport'); + cy.window().then((win) => { + incidentsPage.elements.tooltip().then(($tooltip) => { + const tooltipRect = $tooltip[0].getBoundingClientRect(); + expect(tooltipRect.right).to.be.lessThan(win.innerWidth); + expect(tooltipRect.bottom).to.be.lessThan(win.innerHeight); + expect(tooltipRect.left).to.be.greaterThan(0); + expect(tooltipRect.top).to.be.greaterThan(0); + }); + }); + cy.log('Verified: Tooltip with 180+ char alert name stays within viewport'); + foundLongName = true; + } + }); + } + }); + + cy.log('5.1 Alert chart tooltip positioning'); + cy.log('5.2 Find and select incident with 6 alerts (etcd-six-alerts-001)'); + + incidentsPage.elements.incidentsChartBarsGroups().each(($group, index) => { + const groupId = $group.attr('data-test'); + if (groupId && groupId.includes('etcd-six-alerts-001')) { + cy.log(`Found etcd-six-alerts-001 at index ${index}`); + incidentsPage.selectIncidentByBarIndex(index); + + cy.log('5.2 Verify alerts chart displays alerts'); + incidentsPage.elements.alertsChartCard().should('be.visible'); + incidentsPage.elements.alertsChartBarsGroups() + .should('have.length.greaterThan', 0); + + cy.log('5.3 Test tooltip positioning for all alert bars'); + incidentsPage.elements.alertsChartBarsPaths() + .its('length') + .then((alertCount) => { + cy.log(`Found ${alertCount} alert bars in chart`); + + for (let i = 0; i < alertCount; i++) { + if (i > 2) { + // Expected failure for the latter alerts at this time + break; + } + incidentsPage.getAlertBarRect(i).then((barRect) => { + incidentsPage.hoverOverAlertBar(i); + cy.window().then((win) => { + incidentsPage.elements.alertsChartTooltip().first().then(($tooltip) => { + verifyTooltipPositioning($tooltip[0].getBoundingClientRect(), barRect, `Alert ${i}`, win); + }); + }); + }); + } + }); + cy.log('Verified: All alert tooltips appear correctly above their bars'); + + return false; + } + }); + }); + }); + + describe('Section 2.2: Bar Sorting & Visibility', () => { + + it('Bar sorting, visibility, and filtering', () => { + cy.log('Setup: Clear filters and verify all incidents loaded'); + incidentsPage.clearAllFilters(); + incidentsPage.setDays('7 days'); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 14); + + cy.log('1.2 Verify newest incident is at top (index 0)'); + incidentsPage.hoverOverIncidentBar(0); + + incidentsPage.elements.tooltip() + .invoke('text') + .should('contain', 'network-firing-short-002'); + + cy.log('1.3 Verify oldest incident is at bottom (index 13)'); + incidentsPage.hoverOverIncidentBar(13); + + incidentsPage.elements.tooltip() + .invoke('text') + .should('contain', 'version-short-name-001'); + + cy.log('Verified: Incidents are sorted chronologically with newest at top, oldest at bottom'); + + cy.log('2.1 Short duration incidents have visible bars'); + cy.log('2.2 Check network-firing-short-002 (10 min duration, index 0)'); + incidentsPage.getIncidentBarRect(0).then((barRect) => { + expect(barRect.width).to.be.greaterThan(0); + expect(barRect.height).to.be.greaterThan(0); + }); + + incidentsPage.elements.incidentsChartBarsGroups() + .eq(0) + .find('path[role="presentation"]') + .then(($paths) => { + const visiblePath = $paths.filter((i, el) => { + const fillOpacity = Cypress.$(el).css('fill-opacity') || Cypress.$(el).attr('fill-opacity'); + return parseFloat(fillOpacity || '0') > 0; + }).first(); + + expect(visiblePath.length).to.be.greaterThan(0); + }); + cy.log('Verified: Short duration firing incident has visible bar and is not transparent'); + + cy.log('2.3 Find and check network-resolved-short-001 (10 min duration)'); + incidentsPage.elements.incidentsChartBarsGroups().each(($group, index) => { + const groupId = $group.attr('data-test'); + if (groupId && groupId.includes('network-resolved-short-001')) { + cy.log(`Found network-resolved-short-001 at index ${index}`); + + incidentsPage.getIncidentBarRect(index).then((barRect) => { + expect(barRect.width).to.be.greaterThan(0); + expect(barRect.height).to.be.greaterThan(0); + }); + + cy.wrap($group) + .find('path[role="presentation"]') + .then(($paths) => { + const visiblePath = $paths.filter((i, el) => { + const fillOpacity = Cypress.$(el).css('fill-opacity') || Cypress.$(el).attr('fill-opacity'); + return parseFloat(fillOpacity || '0') > 0; + }).first(); + + expect(visiblePath.length).to.be.greaterThan(0); + }); + cy.log('Verified: Short duration resolved incident has visible bar and is not transparent'); + + return false; + } + }); + + cy.log('3.1 Filtered bars maintain uniform Y-axis spacing'); + + const verifyUniformSpacing = (positions: number[], context: string, maxAllowedDeviation = 2) => { + const spacings: number[] = []; + for (let i = 0; i < positions.length - 1; i++) { + spacings.push(positions[i + 1] - positions[i]); + } + + const avgSpacing = spacings.reduce((a, b) => a + b, 0) / spacings.length; + const maxDeviation = Math.max(...spacings.map(s => Math.abs(s - avgSpacing))); + + cy.log(`${context}: ${positions.length} bars, avg spacing: ${avgSpacing.toFixed(2)}px, max deviation: ${maxDeviation.toFixed(2)}px`); + expect(maxDeviation, `${context}: spacing should be uniform`).to.be.lessThan(maxAllowedDeviation); + }; + + cy.log('3.2 Verify uniform spacing before filtering'); + const barPositionsBefore: number[] = []; + incidentsPage.elements.incidentsChartBarsGroups().each(($group) => { + const rect = $group[0].getBoundingClientRect(); + barPositionsBefore.push(rect.top); + }).then(() => { + verifyUniformSpacing(barPositionsBefore, 'Before filter'); + }); + + cy.log('3.3 Apply Critical filter'); + incidentsPage.toggleFilter('Critical'); + incidentsPage.elements.severityFilterChip().should('be.visible'); + + cy.log('3.4 Verify uniform spacing after filtering'); + const barPositionsAfter: number[] = []; + incidentsPage.elements.incidentsChartBarsGroups().each(($group) => { + const rect = $group[0].getBoundingClientRect(); + barPositionsAfter.push(rect.top); + }).then(() => { + verifyUniformSpacing(barPositionsAfter, 'After filter'); + }); + + cy.log('Verified: Critical filter applied and visible bars maintain uniform spacing without gaps'); + }); + }); + + describe('Section 2.3: Date/Time Display', () => { + + it('Date and time display validation', () => { + cy.log('Setup: Clear filters'); + incidentsPage.clearAllFilters(); + + cy.log('1.2 Hover over firing incident and verify end shows ---'); + incidentsPage.hoverOverIncidentBar(0); + + incidentsPage.elements.tooltip() + .invoke('text') + .then((text) => { + expect(text).to.contain('network-firing-short-002'); + expect(text).to.contain('Start'); + expect(text).to.match(/End.*---/); + }); + cy.log('Verified: Firing incident shows start time and --- for end'); + + cy.log('1.3 Find and verify resolved incident shows both start and end times'); + incidentsPage.elements.incidentsChartBarsGroups().each(($group, index) => { + const groupId = $group.attr('data-test'); + if (groupId && groupId.includes('network-resolved-short-001')) { + cy.log(`Found network-resolved-short-001 at index ${index}`); + incidentsPage.hoverOverIncidentBar(index); + + incidentsPage.elements.tooltip() + .invoke('text') + .then((text) => { + expect(text).to.contain('Start'); + expect(text).to.contain('End'); + expect(text).to.not.match(/End.*---/); + }); + cy.log('Verified: Resolved incident shows both start and end times as timestamps'); + + return false; + } + }); + + cy.log('2.1 Multi-severity incident segments'); + cy.log('2.2 Find monitoring-gradual-alerts-001 incident'); + incidentsPage.elements.incidentsChartBarsGroups().each(($group, index) => { + const groupId = $group.attr('data-test'); + if (groupId && groupId.includes('monitoring-gradual-alerts-001')) { + cy.log(`Found monitoring-gradual-alerts-001 at index ${index}`); + + cy.log('2.3 Verify bar has multiple severity segments'); + cy.wrap($group) + .find('path[role="presentation"]') + .should('have.length.greaterThan', 1); + cy.log('Verified: Multi-severity incident has multiple colored segments'); + + return false; + } + }); + + cy.log('3.1 Date format validation'); + incidentsPage.hoverOverIncidentBar(0); + + cy.log('3.2 Verify tooltip contains formatted timestamps'); + incidentsPage.elements.tooltip() + .invoke('text') + .then((text) => { + expect(text).to.match(/\d{1,2}:\d{2}/); + }); + cy.log('Verified: Tooltips display formatted date/time'); + + cy.log('4.1 Alert-level time verification in table'); + incidentsPage.selectIncidentByBarIndex(13); + + cy.log('4.2 Expand all rows to see alert details'); + incidentsPage.elements.incidentsTable().should('be.visible'); + + cy.log('4.3 Get alert information'); + incidentsPage.getSelectedIncidentAlerts().then((alerts) => { + expect(alerts.length).to.be.greaterThan(0); + + cy.log('4.4 Verify each alert has start time'); + alerts.forEach((alert, index) => { + alert.getStartCell().invoke('text').then((startText) => { + expect(startText.trim()).to.not.be.empty; + expect(startText.trim()).to.not.equal('-'); + cy.log(`Alert ${index + 1} start time: ${startText.trim()}`); + }); + }); + + cy.log('4.5 Verify firing alerts show --- or Firing for end time'); + alerts.forEach((alert, index) => { + alert.getEndCell().invoke('text').then((endText) => { + expect(endText.trim()).to.not.be.empty; + cy.log(`Alert ${index + 1} end time: ${endText.trim()}`); + }); + }); + }); + cy.log('Verified: Alert times in table display correctly with valid timestamps'); + }); + }); + +}); + 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 new file mode 100644 index 00000000..01be6ba8 --- /dev/null +++ b/web/cypress/e2e/incidents/regression/03.reg_api_calls.cy.ts @@ -0,0 +1,128 @@ +/* +Regression test for Silences Not Applied Correctly (Section 3.2) + +BUG: Silences were being matched by name only, not by name + namespace + severity. +This test verifies that silence matching uses: alertname + namespace + severity. + +While targeting the bug, it verifies the basic Silences Implementation. + +Verifies: OU-1020, OU-706 +*/ + +import { incidentsPage } from '../../../views/incidents-page'; + +const MCP = { + namespace: 'openshift-cluster-observability-operator', + packageName: 'cluster-observability-operator', + operatorName: 'Cluster Observability Operator', + config: { + kind: 'UIPlugin', + name: 'monitoring', + }, +}; + +const MP = { + namespace: 'openshift-monitoring', + operatorName: 'Cluster Monitoring Operator', +}; + +describe('Regression: Silences Not Applied Correctly', () => { + + before(() => { + cy.beforeBlockCOO(MCP, MP); + }); + + beforeEach(() => { + cy.log('Navigate to Observe → Incidents'); + incidentsPage.goTo(); + cy.log('Setting up silenced alerts mixed scenario'); + cy.mockIncidentFixture('incident-scenarios/9-silenced-alerts-mixed-scenario.yaml'); + }); + + it('Silence matching verification flow - opacity and tooltip indicators', () => { + const selectIncidentAndWaitForAlerts = (incidentId: string, expectedAlertCount?: number) => { + incidentsPage.elements.incidentsChartBar(incidentId).click(); + cy.wait(2000); + incidentsPage.elements.alertsChartContainer().should('be.visible'); + incidentsPage.elements.alertsChartCard().should('be.visible'); + + if (expectedAlertCount !== undefined) { + incidentsPage.elements.alertsChartBarsVisiblePaths().should('have.length', expectedAlertCount); + } + }; + + const verifyAlertOpacity = (alertIndex: number, expectedOpacity: number) => { + incidentsPage.elements.alertsChartBarsVisiblePaths() + .eq(alertIndex) + .then(($bar) => { + cy.log('Bar: ' + $bar); + cy.log('Fill-opacity: ' + $bar.css('fill-opacity')); + const opacity = parseFloat($bar.css('fill-opacity') || '1'); + expect(opacity).to.equal(expectedOpacity); + }); + }; + + const verifyAlertTooltip = (alertIndex: number, expectedTexts: string[], shouldBeSilenced: boolean) => { + incidentsPage.hoverOverAlertBar(alertIndex); + const tooltip = incidentsPage.elements.alertsChartTooltip().should('be.visible'); + expectedTexts.forEach(text => { + tooltip.should('contain.text', text); + }); + tooltip.should(shouldBeSilenced ? 'contain.text' : 'not.contain.text', '(silenced)'); + }; + + cy.log('1.1 Verify all incidents loaded'); + incidentsPage.clearAllFilters(); + incidentsPage.elements.incidentsChartBarsGroups().should('have.length.greaterThan', 0); + + cy.log('1.2 Select silenced alert incident (PAIR-2-storage-SILENCED)'); + selectIncidentAndWaitForAlerts('PAIR-2-shared-alert-name-storage-SILENCED', 1); + + cy.log('1.4 Hover over silenced alert and verify tooltip shows (silenced)'); + verifyAlertTooltip(0, ['SyntheticSharedFiring002'], true); + cy.log('Verified: Silenced alert has opacity 0.3 and tooltip shows (silenced)'); + + + cy.log('1.3 Hover over silenced alert and verify tooltip shows (silenced)'); + verifyAlertTooltip(0, ['SyntheticSharedFiring002'], true); + cy.log('Verified: Silenced alert has opacity 0.3 and tooltip shows (silenced)'); + + + cy.log('2.0 Deselect incident'); + incidentsPage.deselectIncidentByBar(); + + cy.log('2.1 Select non-silenced alert incident with same alert name (PAIR-2-network-UNSILENCED)'); + selectIncidentAndWaitForAlerts('PAIR-2-shared-alert-name-network-UNSILENCED', 1); + + cy.log('2.2 Verify non-silenced alert has full opacity 1.0'); + verifyAlertOpacity(0, 1.0); + + cy.log('2.3 Hover over non-silenced alert and verify tooltip does not show (silenced)'); + verifyAlertTooltip(0, ['SyntheticSharedFiring002'], false); + cy.log('Verified: Non-silenced alert has opacity 1.0 and tooltip does not show (silenced)'); + + + cy.log('3.0 Deselect incident'); + incidentsPage.deselectIncidentByBar(); + + cy.log('3.1 Select incident with both silenced and non-silenced alerts (CASE-3)'); + selectIncidentAndWaitForAlerts('CASE-3-shared-alert-single-incident-storage-network-SILENCED-UNSILENCED', 2); + + cy.log('3.2 Verify first alert (storage namespace / silenced) has opacity 0.3'); + verifyAlertOpacity(0, 0.3); + + cy.log('3.3 Verify second alert (network namespace / non-silenced) has opacity 1.0'); + verifyAlertOpacity(1, 1.0); + + cy.log('3.4 Hover over first alert and verify tooltip shows (silenced) with storage'); + verifyAlertTooltip(0, ['storage'], true); + + cy.log('3.5 Hover over second alert and verify tooltip shows network without (silenced)'); + verifyAlertTooltip(1, ['network'], false); + + cy.log('Verified: Same alert name with different namespaces handled correctly'); + cy.log('Verified: Silence matching uses alertname + namespace + severity'); + }); +}); + + 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 8f9017fc..ed5d22be 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 @@ -132,6 +132,7 @@ describe('Regression: Redux State Management', () => { dropdown.listElement().should('be.visible'); deselection.action(); + cy.wait(2000) dropdown.listElement().should('not.exist'); diff --git a/web/cypress/fixtures/incident-scenarios/12-charts-ui-comprehensive.yaml b/web/cypress/fixtures/incident-scenarios/12-charts-ui-comprehensive.yaml new file mode 100644 index 00000000..3a48e7b7 --- /dev/null +++ b/web/cypress/fixtures/incident-scenarios/12-charts-ui-comprehensive.yaml @@ -0,0 +1,331 @@ +name: "Charts UI Comprehensive Test Scenarios" +description: "Combined scenarios for testing tooltip positioning, bar visibility, short duration alerts, varying alert counts (1-6 alerts), and alert name lengths (short to extremely long). Tests Section 2 of TESTING_CHECKLIST.md comprehensively." +incidents: + # SHORT DURATION INCIDENTS (Section 2.2 - Bar Visibility, Section 2.3 - Date/Time Display) + # Resolved incident with 5 alerts of varying durations (0-10 minutes) + - id: "network-resolved-short-001" + component: "network" + layer: "core" + timeline: + start: "50m" + end: "40m" + alerts: + - name: "NetworkInterfaceDown0023" + namespace: "openshift-network" + severity: "critical" + firing: false + timeline: + start: "50m" + end: "40m" + - name: "NetworkLatencyHigh0023" + namespace: "openshift-network" + severity: "warning" + firing: false + timeline: + start: "47m" + end: "40m" + - name: "NetworkBandwidthUtilizationHigh0023" + namespace: "openshift-network" + severity: "warning" + firing: false + timeline: + start: "45m" + end: "40m" + - name: "NetworkConnectionPoolExhausted0023" + namespace: "openshift-network" + severity: "info" + firing: false + timeline: + start: "42m" + end: "40m" + - name: "NetworkDNSResolutionSlow0023" + namespace: "openshift-network" + severity: "info" + firing: false + timeline: + start: "41m" + end: "40m" + + # Firing incident with 5 alerts of short durations + - id: "network-firing-short-002" + component: "network" + layer: "core" + timeline: + start: "10m" + alerts: + - name: "NetworkFirewallRuleBlocking0023" + namespace: "openshift-network" + severity: "critical" + firing: true + timeline: + start: "10m" + - name: "NetworkPacketLossHigh0023" + namespace: "openshift-network" + severity: "warning" + firing: true + timeline: + start: "8m" + - name: "NetworkRouteTableCorrupted0023" + namespace: "openshift-network" + severity: "warning" + firing: true + timeline: + start: "5m" + - name: "NetworkSecurityPolicyViolation0023" + namespace: "openshift-network" + severity: "info" + firing: true + timeline: + start: "3m" + - name: "NetworkLoadBalancerUnhealthy0023" + namespace: "openshift-network" + severity: "info" + firing: true + timeline: + start: "0m" + + # VARYING ALERT COUNTS (Section 2.1 - Tooltip Positioning) + # 1 alert - bottom of chart (oldest) - Section 2.1 tooltip positioning + - id: "version-short-name-001" + component: "version" + layer: "core" + timeline: + start: "8h" + alerts: + - name: "Ver001" + namespace: "openshift-cluster-version" + severity: "info" + firing: true + + # 1 alert - middle position + - id: "monitoring-one-alert-001" + component: "monitoring" + layer: "core" + timeline: + start: "7h" + alerts: + - name: "Short001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + + # 2 alerts + - id: "storage-two-alerts-001" + component: "storage" + layer: "core" + timeline: + start: "6h30m" + alerts: + - name: "DiskPressure001" + namespace: "openshift-storage" + severity: "warning" + firing: true + - name: "VolumeSpaceLow001" + namespace: "openshift-storage" + severity: "warning" + firing: true + + # 3 alerts - multi-component incident (Section 2.1 tooltip content) + - id: "network-three-alerts-001" + component: "network" + layer: "core" + timeline: + start: "6h" + alerts: + - name: "NetworkLatencyHigh001" + namespace: "openshift-network" + severity: "warning" + firing: true + component: "network" + - name: "PacketDropRateElevated001" + namespace: "openshift-network" + severity: "warning" + firing: true + component: "compute" + - name: "ConnectionPoolExhausted001" + namespace: "openshift-network" + severity: "warning" + firing: true + component: "storage" + + # 4 alerts - multi-severity (Section 2.3 - Multi-severity segments) + - id: "compute-four-alerts-001" + component: "compute" + layer: "core" + timeline: + start: "5h" + alerts: + - name: "NodeMemoryPressureDetected001" + namespace: "openshift-monitoring" + severity: "critical" + firing: true + - name: "CPUUtilizationThresholdExceeded001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + - name: "DiskIOSaturation001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + - name: "NetworkBandwidthLimited001" + namespace: "openshift-monitoring" + severity: "info" + firing: true + + # 5 alerts - complex timeline + - id: "api-server-five-alerts-001" + component: "api-server" + layer: "core" + timeline: + start: "4h" + alerts: + - name: "APIServerRequestLatencyAboveThresholdForExtendedDuration001" + namespace: "openshift-monitoring" + severity: "critical" + firing: true + - name: "APIServerCertificateExpirationWarning001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + - name: "APIServerConnectionPoolUtilizationHigh001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + - name: "APIServerRateLimitingActive001" + namespace: "openshift-monitoring" + severity: "info" + firing: true + - name: "APIServerHealthcheckInfo001" + namespace: "openshift-monitoring" + severity: "info" + firing: true + + # 6 alerts - extreme name lengths (Section 2.1 - long names) + - id: "etcd-six-alerts-001" + component: "etcd" + layer: "core" + timeline: + start: "3h" + alerts: + - name: "S001" + namespace: "openshift-etcd" + severity: "critical" + firing: true + - name: "EtcdBackupOperationFailureDetectedWithRetryAttempts001" + namespace: "openshift-etcd" + severity: "critical" + firing: true + - name: "EtcdDatabaseSizeApproachingConfiguredLimitRequiringCompaction001" + namespace: "openshift-etcd" + severity: "warning" + firing: true + - name: "EtcdLeaderElectionTimeExceedingNormalThresholdIndicatingPotentialNetworkIssues001" + namespace: "openshift-etcd" + severity: "warning" + firing: true + - name: "EtcdMemberCommunicationLatencyHigherThanExpectedBaselinePerformanceMetrics001" + namespace: "openshift-etcd" + severity: "info" + firing: true + - name: "EtcdClusterHealthMonitoringSystemReportingNormalOperatingConditionsWithAllMembersRespondingWithinAcceptableTimeframesAndNoOutstandingIssuesDetectedDuringRoutineMaintenanceChecks001" + namespace: "openshift-etcd" + severity: "info" + firing: true + + # ALERT NAME LENGTH VARIATIONS (Section 2.1 - Tooltip content with varying lengths) + - id: "monitoring-medium-name-001" + component: "monitoring" + layer: "core" + timeline: + start: "2h30m" + alerts: + - name: "PrometheusTargetScrapeDurationExceeded001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + + - id: "storage-long-name-001" + component: "storage" + layer: "core" + timeline: + start: "2h" + alerts: + - name: "PersistentVolumeClaimPendingDueToInsufficientStorageCapacityAvailableInCluster001" + namespace: "openshift-storage" + severity: "critical" + firing: true + + - id: "storage-long-component-name-001" + component: "storage-very-very-long-component-name" + layer: "core" + timeline: + start: "1h30m" + alerts: + - name: "StandardAlert001" + namespace: "openshift-storage-very-very-long-namespace-name" + severity: "critical" + firing: true + + - id: "others-very-long-name-001" + component: "Others" + layer: "Others" + timeline: + start: "1h" + alerts: + - name: "CustomApplicationDatabaseConnectionPoolExhaustedRequiringImmediateAttentionFromOperationsTeamToInvestigateAndResolveUnderlyingInfrastructureLimitationsOrConfigurationIssues001" + namespace: "custom-application-namespace" + severity: "critical" + firing: true + + # GRADUAL ESCALATION (Section 2.1 - Alerts chart tooltip positioning, Section 2.3 - Multi-severity segments) + # Tests tooltip positioning for multiple bars in alerts chart + - id: "monitoring-gradual-alerts-001" + component: "monitoring" + layer: "core" + timeline: + start: "6h" + severityChanges: + - time: "6h" + severity: "info" + - time: "4h" + severity: "warning" + - time: "2h" + severity: "critical" + alerts: + - name: "PrometheusRuleEvaluationSlowFirst001" + namespace: "openshift-monitoring" + severity: "info" + firing: true + timeline: + start: "6h" + - name: "PrometheusQueryLatencyIncreasingSecond001" + namespace: "openshift-monitoring" + severity: "info" + firing: true + timeline: + start: "5h" + - name: "AlertmanagerConfigReloadFailedThird001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + timeline: + start: "4h" + - name: "PrometheusTargetScrapeFailuresFourth001" + namespace: "openshift-monitoring" + severity: "warning" + firing: true + timeline: + start: "3h" + - name: "AlertmanagerClusterFailedToSendNotificationsFifth001" + namespace: "openshift-monitoring" + severity: "critical" + firing: true + timeline: + start: "2h" + - name: "PrometheusRemoteWriteDesiredShardsSixth001" + namespace: "openshift-monitoring" + severity: "critical" + firing: true + timeline: + start: "1h" + diff --git a/web/cypress/views/incidents-page.ts b/web/cypress/views/incidents-page.ts index 793c0c08..a2d622d2 100644 --- a/web/cypress/views/incidents-page.ts +++ b/web/cypress/views/incidents-page.ts @@ -92,6 +92,24 @@ export const incidentsPage = { alertsChartCard: () => cy.byTestID(DataTestIDs.AlertsChart.Card), alertsChartContainer: () => cy.byTestID(DataTestIDs.AlertsChart.ChartContainer), alertsChartSvg: () => incidentsPage.elements.alertsChartCard().find('svg'), + alertsChartBarsGroups: () => incidentsPage.elements.alertsChartSvg().find('g[role="presentation"]'), + alertsChartBarsPaths: () => incidentsPage.elements.alertsChartSvg().find('path[role="presentation"]'), + alertsChartBarsVisiblePaths: () => { + return cy.get('body').then($body => { + const exists = $body.find('g[role="presentation"][data-test*="alerts-chart-bar-"]').length > 0; + if (exists) { + return cy.get('g[role="presentation"][data-test*="alerts-chart-bar-"]') + .find('path[role="presentation"]') + .filter((index, element) => { + const fillOpacity = Cypress.$(element).css('fill-opacity') || Cypress.$(element).attr('fill-opacity'); + return parseFloat(fillOpacity || '0') > 0; + }); + } else { + cy.log('Alert chart bars were not found. Test continues.'); + return cy.wrap([]); + } + }); + }, alertsChartEmptyState: () => cy.byTestID(DataTestIDs.AlertsChart.EmptyState), // Tables and data @@ -121,6 +139,13 @@ export const incidentsPage = { // Days select options daysSelectList: () => cy.byTestID(DataTestIDs.IncidentsPage.DaysSelectList), daysSelectOption: (days: string) => cy.byTestID(`${DataTestIDs.IncidentsPage.DaysSelectOption}-${days.replace(' ', '-')}`), + + // Tooltips (custom Victory chart tooltips) + tooltip: () => cy.get('.incidents__tooltip'), + tooltipWrap: () => cy.get('.incidents__tooltip-wrap'), + tooltipArrow: () => cy.get('.incidents__tooltip-arrow'), + + alertsChartTooltip: () => incidentsPage.elements.alertsChartCard().find('.incidents__tooltip'), }, @@ -244,6 +269,95 @@ export const incidentsPage = { .click({ force: true }); }, + waitForTooltip: () => { + cy.log('incidentsPage.waitForTooltip'); + return incidentsPage.elements.tooltip().should('be.visible'); + }, + + hoverOverIncidentBar: (index: number) => { + cy.log(`incidentsPage.hoverOverIncidentBar: ${index}`); + incidentsPage.elements.incidentsChartBarsGroups() + .eq(index) + .find('path[role="presentation"]') + .then(($paths) => { + const visiblePath = $paths.filter((i, el) => { + const fillOpacity = Cypress.$(el).css('fill-opacity') || Cypress.$(el).attr('fill-opacity'); + return parseFloat(fillOpacity || '0') > 0; + }).first(); + + if (visiblePath.length > 0) { + const rect = visiblePath[0].getBoundingClientRect(); + const x = rect.left + rect.width / 2; + const y = rect.top + rect.height / 2; + + cy.log(`Hovering at coordinates: x=${x}, y=${y}`); + + incidentsPage.elements.incidentsChartSvg() + .first() + .trigger('mousemove', { clientX: x, clientY: y, force: true }) + .wait(100); + } else { + throw new Error(`No visible paths found in bar group ${index}`); + } + }); + return incidentsPage.waitForTooltip(); + }, + + getIncidentBarRect: (index: number) => { + cy.log(`incidentsPage.getIncidentBarRect: ${index}`); + return incidentsPage.elements.incidentsChartBarsGroups() + .eq(index) + .then(($group) => { + const rect = $group[0].getBoundingClientRect(); + return cy.wrap({ + top: rect.top, + bottom: rect.bottom, + left: rect.left, + right: rect.right, + width: rect.width, + height: rect.height, + x: rect.x, + y: rect.y + }); + }); + }, + + getAlertBarRect: (index: number) => { + cy.log(`incidentsPage.getAlertBarRect: ${index}`); + return incidentsPage.elements.alertsChartBarsPaths() + .eq(index) + .then(($bar) => { + const rect = $bar[0].getBoundingClientRect(); + return cy.wrap({ + top: rect.top, + bottom: rect.bottom, + left: rect.left, + right: rect.right, + width: rect.width, + height: rect.height, + x: rect.x, + y: rect.y + }); + }); + }, + + hoverOverAlertBar: (index: number) => { + cy.log(`incidentsPage.hoverOverAlertBar: ${index}`); + incidentsPage.elements.alertsChartBarsPaths() + .eq(index) + .then(($bar) => { + const rect = $bar[0].getBoundingClientRect(); + const x = rect.left + rect.width / 2; + const y = rect.top + rect.height / 2; + + incidentsPage.elements.alertsChartSvg() + .first() + .trigger('mousemove', { clientX: x, clientY: y, force: true }) + .wait(100); + }); + return incidentsPage.waitForTooltip(); + }, + // Constants for search configuration SEARCH_CONFIG: { CHART_LOAD_WAIT: 1000, @@ -424,6 +538,8 @@ export const incidentsPage = { */ getSelectedIncidentAlerts: () => { cy.log('incidentsPage.getSelectedIncidentAlerts: Collecting alert information from selected incident'); + + incidentsPage.elements.incidentsDetailsTable().should('exist'); return incidentsPage.elements.incidentsTableRows() .then(($rows) => { diff --git a/web/src/components/Incidents/AlertsChart/AlertsChart.tsx b/web/src/components/Incidents/AlertsChart/AlertsChart.tsx index b1737714..917eede6 100644 --- a/web/src/components/Incidents/AlertsChart/AlertsChart.tsx +++ b/web/src/components/Incidents/AlertsChart/AlertsChart.tsx @@ -215,13 +215,14 @@ const AlertsChart = ({ theme }: { theme: 'light' | 'dark' }) => { } /> - + {chartData.map((bar, index) => { return ( //we have several arrays and for each array we make a ChartBar datum.fill, diff --git a/web/src/components/data-test.ts b/web/src/components/data-test.ts index 0b8223cd..12ff1cad 100644 --- a/web/src/components/data-test.ts +++ b/web/src/components/data-test.ts @@ -116,6 +116,7 @@ export const DataTestIDs = { Title: 'alerts-chart-title', EmptyState: 'alerts-chart-empty-state', ChartContainer: 'alerts-chart-container', + ChartBar: 'alerts-chart-bar', }, // Incidents Table Test IDs