Skip to content

Commit a351aca

Browse files
committed
test: Initial Incident Detection e2e Tests
- incidents-page.ts includes cypher selectors for operation on the incident tab - 01.incidents.cy.ts tests that incidents are loaded, the individual tests interact with the elements on the page - index.ts includes catching 'ResizeObserver loop limit' exception, which is otherwise triggered by the incident page
1 parent 9a7a190 commit a351aca

3 files changed

Lines changed: 175 additions & 7 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
2+
import { commonPages } from '../../views/common';
3+
import { incidentsPage } from '../../views/incidents-page';
4+
5+
// Set constants for the operators that need to be installed for tests.
6+
const MCP = {
7+
namespace: 'openshift-cluster-observability-operator',
8+
packageName: 'cluster-observability-operator',
9+
operatorName: 'Cluster Observability Operator',
10+
config: {
11+
kind: 'UIPlugin',
12+
name: 'monitoring',
13+
},
14+
};
15+
16+
const MP = {
17+
namespace: 'openshift-monitoring',
18+
operatorName: 'Cluster Monitoring Operator',
19+
};
20+
21+
const ALERTNAME = 'Watchdog';
22+
const NAMESPACE = 'openshift-monitoring';
23+
const SEVERITY = 'Critical';
24+
const ALERT_DESC = 'This is an alert meant to ensure that the entire alerting pipeline is functional. This alert is always firing, therefore it should always be firing in Alertmanager and always fire against a receiver. There are integrations with various notification mechanisms that send a notification when this alert is not firing. For example the "DeadMansSnitch" integration in PagerDuty.'
25+
const ALERT_SUMMARY = 'An alert that should always be firing to certify that Alertmanager is working properly.'
26+
describe('Incidents', () => {
27+
before(() => {
28+
cy.beforeBlockCOO(MCP, MP);
29+
// TODO: Inject alerts into the database so the behavior is deterministic
30+
});
31+
32+
after(() => {
33+
cy.afterBlockCOO(MCP, MP);
34+
});
35+
36+
beforeEach(() => {
37+
38+
cy.log('Navigate to Observe → Incidents');
39+
incidentsPage.goTo();
40+
commonPages.titleShouldHaveText('Incidents');
41+
});
42+
43+
it('renders toolbar and toggle charts button', () => {
44+
incidentsPage.elements.toolbar().should('be.visible');
45+
incidentsPage.elements.toggleChartsButton().should('be.visible');
46+
});
47+
48+
it('sets days filter to 3 days and updates the URL', () => {
49+
incidentsPage.setDays('3 days');
50+
incidentsPage.elements.daysSelect().should('contain.text', '3 days');
51+
cy.url().should('match', /[?&]days=3\+days/);
52+
});
53+
54+
it('toggles Critical filter and updates the URL', () => {
55+
incidentsPage.clearAllFilters();
56+
incidentsPage.toggleFilter('Critical');
57+
cy.url().should('match', /incidentFilters=.*Critical/);
58+
});
59+
60+
it('shows charts and alerts empty state initially', () => {
61+
incidentsPage.elements.incidentsChartTitle().should('be.visible');
62+
incidentsPage.elements.alertsChartTitle().should('be.visible');
63+
incidentsPage.elements.alertsChartEmptyState().should('exist');
64+
});
65+
66+
it('selecting an incident via chart shows alerts and adds groupId to URL', () => {
67+
incidentsPage.clearAllFilters();
68+
cy.pause();
69+
incidentsPage.selectIncidentByBarIndex(0);
70+
cy.pause();
71+
cy.url().should('match', /[?&]groupId=/);
72+
cy.pause();
73+
incidentsPage.elements.alertsChartSvg().find('path').should('exist');
74+
cy.pause();
75+
});
76+
77+
it('shows alerts table and allows expanding a row', () => {
78+
cy.pause();
79+
incidentsPage.elements.alertsTable().should('exist');
80+
incidentsPage.expandRow(0);
81+
cy.pause();
82+
});
83+
});

web/cypress/support/index.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ export const checkErrors = () =>
77
assert.isTrue(!win.windowError, win.windowError);
88
});
99

10-
Cypress.on('uncaught:exception', (err, runnable) => {
11-
// returning false here prevents Cypress from failing the test
12-
// on a JavaScript exception
13-
if (err.message.includes('ResizeObserver loop completed with undelivered notifications')) {
14-
return false
15-
}
16-
});
10+
11+
// Ignore benign ResizeObserver errors globally so they don't fail tests
12+
// See: https://docs.cypress.io/api/cypress-api/catalog-of-events#Uncaught-Exceptions
13+
Cypress.on('uncaught:exception', (err) => {
14+
const message = err?.message || String(err || '');
15+
if (
16+
message.includes('ResizeObserver loop limit exceeded') ||
17+
message.includes('ResizeObserver loop completed with undelivered notifications') ||
18+
message.includes('ResizeObserver')
19+
) {
20+
return false;
21+
}
22+
// allow other errors to fail the test
23+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { nav } from './nav';
2+
3+
export const incidentsPage = {
4+
goTo: () => {
5+
cy.log('incidentsPage.goTo');
6+
nav.sidenav.clickNavLink(['Observe', 'Incidents']);
7+
},
8+
9+
setDays: (value: '1 day' | '3 days' | '7 days' | '15 days') => {
10+
cy.log('incidentsPage.setDays');
11+
incidentsPage.elements.daysSelect().click();
12+
cy.contains(value).should('be.visible').click();
13+
},
14+
15+
toggleFilter: (name: 'Critical' | 'Warning' | 'Informative' | 'Firing' | 'Resolved') => {
16+
cy.log('incidentsPage.toggleFilter');
17+
incidentsPage.elements.filtersSelect().click();
18+
cy.contains('label', name)
19+
.find('input[type="checkbox"]')
20+
.click({ force: true });
21+
incidentsPage.elements.filtersSelect().click();
22+
},
23+
24+
clearAllFilters: () => {
25+
cy.log('incidentsPage.clearAllFilters');
26+
incidentsPage.elements.clearAllFiltersButton().click({ force: true });
27+
},
28+
29+
toggleCharts: () => {
30+
cy.log('incidentsPage.toggleCharts');
31+
incidentsPage.elements.toggleChartsButton().click();
32+
},
33+
34+
selectIncidentByBarIndex: (index = 0) => {
35+
cy.log('incidentsPage.selectIncidentByBarIndex');
36+
incidentsPage.elements.incidentsChartCard()
37+
.find('path[role="presentation"]')
38+
.eq(index)
39+
.click({ force: true });
40+
},
41+
42+
expandRow: (rowIndex = 0) => {
43+
cy.log('incidentsPage.expandRow');
44+
incidentsPage.elements.alertsTable()
45+
.find('tbody')
46+
.eq(rowIndex)
47+
.within(() => {
48+
cy.get('[aria-label="Details"], button[aria-expanded], button.pf-m-plain')
49+
.first()
50+
.click({ force: true });
51+
});
52+
},
53+
54+
// Centralized element selectors - all selectors defined in one place
55+
elements: {
56+
// Page structure
57+
toolbar: () => cy.get('#toolbar-with-filter'),
58+
59+
// Controls and filters
60+
daysSelect: () => cy.get('button[data-ouia-component-id="OUIA-Generated-MenuToggle-4"]'),
61+
filtersSelect: () => cy.get('button[data-ouia-component-id="OUIA-Generated-MenuToggle-5"]'),
62+
clearAllFiltersButton: () => cy.contains('button', 'Clear all filters'),
63+
toggleChartsButton: () => cy.contains('button', /Hide graph|Show graph/),
64+
65+
// Charts and visualizations
66+
incidentsChartTitle: () => cy.contains('Incidents Timeline'),
67+
incidentsChartCard: () => cy.contains('Incidents Timeline').closest('.pf-v6-c-card'),
68+
incidentsChartSvg: () => incidentsPage.elements.incidentsChartCard().find('svg'),
69+
70+
alertsChartTitle: () => cy.contains('Alerts Timeline'),
71+
alertsChartCard: () => cy.contains('Alerts Timeline').closest('.pf-v6-c-card'),
72+
alertsChartSvg: () => incidentsPage.elements.alertsChartCard().find('svg'),
73+
alertsChartEmptyState: () => cy.contains('Select an incident in the chart above to see alerts.').closest('.pf-v6-c-card'),
74+
75+
// Tables and data
76+
alertsTable: () => cy.get('[aria-label="alerts-table"]'),
77+
},
78+
};

0 commit comments

Comments
 (0)