Skip to content

Commit 0cb566d

Browse files
Claude Agentclaude
andcommitted
fix(tests): use bar groups for incident selection, add warm-up to BVT
- selectIncidentByBarIndex/deselectIncidentByBar: use incidentsChartBarsGroups() (one element per incident) instead of incidentsChartBarsVisiblePaths() (flattened paths). Fixes incorrect incident selection/deselection with multi-severity incidents. - findIncidentWithAlert: count bar groups instead of flattened paths - deselectIncidentByBar: accept index parameter to deselect the correct incident instead of always clicking the first bar - 01.incidents.cy.ts: add warmUpForPlugin() to before() hook — same plugin loading race fix as reg/02-05 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2e6f654 commit 0cb566d

2 files changed

Lines changed: 69 additions & 49 deletions

File tree

web/cypress/e2e/incidents/01.incidents.cy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const MP = {
2727
describe('BVT: Incidents - UI', { tags: ['@smoke', '@incidents'] }, () => {
2828
before(() => {
2929
cy.beforeBlockCOO(MCP, MP, { dashboards: false, troubleshootingPanel: false });
30+
incidentsPage.warmUpForPlugin();
3031
cy.mockIncidentFixture(
3132
'incident-scenarios/1-single-incident-firing-critical-and-warning-alerts.yaml',
3233
);

web/cypress/views/incidents-page.ts

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ const _resetSearchState = () => {
1818
_quietSearch = false;
1919
};
2020

21+
// jQuery selector for the Incidents tab — covers both PatternFly v6 and
22+
// legacy Console horizontal nav markup. Used by goTo()/warmUpForPlugin()
23+
// to poll for plugin registration without Cypress command overhead.
24+
const _INCIDENTS_TAB_SELECTOR =
25+
'.pf-v6-c-tabs__item:contains("Incidents"), ' +
26+
'.co-m-horizontal-nav__menu-item:contains("Incidents")';
27+
28+
// Selector for bar group containers in the incidents chart (one per incident).
29+
const _BAR_GROUP_SELECTOR = 'g[role="presentation"][data-test*="incidents-chart-bar-"]';
30+
31+
// Filter predicate for visible path segments (fill-opacity > 0).
32+
// Multi-severity incidents have placeholder paths with zero opacity.
33+
const _isVisiblePath = (_: number, el: HTMLElement) => {
34+
const opacity = Cypress.$(el).css('fill-opacity') || Cypress.$(el).attr('fill-opacity');
35+
return parseFloat(opacity || '0') > 0;
36+
};
37+
2138
export const incidentsPage = {
2239
// Centralized element selectors - all selectors defined in one place
2340
elements: {
@@ -93,11 +110,10 @@ export const incidentsPage = {
93110
// or conditional testing semantics.
94111
cy.wait(500, _qLog());
95112
// We need to use the $body as both cases when the element is there or not are valid.
96-
const exists =
97-
$body.find('g[role="presentation"][data-test*="incidents-chart-bar-"]').length > 0;
113+
const exists = $body.find(_BAR_GROUP_SELECTOR).length > 0;
98114
if (exists) {
99115
return cy
100-
.get('g[role="presentation"][data-test*="incidents-chart-bar-"]', _qLog())
116+
.get(_BAR_GROUP_SELECTOR, _qLog())
101117
.find('path[role="presentation"]')
102118
.filter((index, element) => {
103119
const fillOpacity =
@@ -123,9 +139,7 @@ export const incidentsPage = {
123139
});
124140
},
125141
incidentsChartBarsGroups: () =>
126-
cy
127-
.byTestID(DataTestIDs.IncidentsChart.ChartBars)
128-
.find('g[role="presentation"][data-test*="incidents-chart-bar-"]'),
142+
cy.byTestID(DataTestIDs.IncidentsChart.ChartBars).find(_BAR_GROUP_SELECTOR),
129143
incidentsChartSvg: () => incidentsPage.elements.incidentsChartCard().find('svg'),
130144

131145
alertsChartTitle: () => cy.byTestID(DataTestIDs.AlertsChart.Title),
@@ -212,8 +226,11 @@ export const incidentsPage = {
212226
},
213227

214228
goTo: () => {
215-
cy.log('incidentsPage.goTo');
229+
if (!_quietSearch) cy.log('incidentsPage.goTo');
216230
nav.sidenav.clickNavLink(['Observe', 'Alerting']);
231+
// Wait for the Incidents tab to be registered by the dynamic plugin.
232+
// After session restore the plugin may need up to 2 min to re-register.
233+
incidentsPage.waitForIncidentsTab();
217234
nav.tabs.switchTab('Incidents');
218235
incidentsPage.elements.daysSelectToggle().should('be.visible');
219236
},
@@ -228,19 +245,20 @@ export const incidentsPage = {
228245
// Wait up to 3 minutes for the Incidents tab to appear. Uses synchronous jQuery check
229246
// inside cy.waitUntil() to avoid the 80s default command timeout, then uses
230247
// nav.tabs.switchTab() which correctly clicks the button element (not the li wrapper).
231-
cy.waitUntil(
232-
() =>
233-
Cypress.$(
234-
'.pf-v6-c-tabs__item:contains("Incidents"), .co-m-horizontal-nav__menu-item:contains("Incidents")',
235-
).length > 0,
236-
{
237-
interval: 3000,
238-
timeout: 180000,
239-
errorMsg: 'Incidents tab not registered within 3 minutes',
240-
},
241-
);
248+
incidentsPage.waitForIncidentsTab();
242249
nav.tabs.switchTab('Incidents');
243-
cy.get('[data-test="incidents-days-select-toggle"]', { timeout: 180000 }).should('be.visible');
250+
incidentsPage.elements.daysSelectToggle().should('be.visible');
251+
},
252+
253+
// Polls for the Incidents tab to appear in the horizontal nav using a
254+
// synchronous jQuery check (no Cypress command overhead / DOM snapshots).
255+
// Shared by goTo() and warmUpForPlugin().
256+
waitForIncidentsTab: () => {
257+
cy.waitUntil(() => Cypress.$(_INCIDENTS_TAB_SELECTOR).length > 0, {
258+
interval: 2000,
259+
timeout: 180000,
260+
errorMsg: 'Incidents tab not registered within 3 minutes',
261+
});
244262
},
245263

246264
setDays: (value: '1 day' | '3 days' | '7 days' | '15 days') => {
@@ -399,42 +417,39 @@ export const incidentsPage = {
399417
},
400418

401419
/**
402-
* Selects an incident from the chart by clicking on a bar at the specified index.
403-
* BUG: Problems with multi-severity incidents (multiple paths in a single incident bar)
420+
* Selects an incident from the chart by clicking on a bar group at the
421+
* specified index. Uses bar groups (one per incident) instead of flattened
422+
* paths to correctly handle multi-severity incidents.
404423
*
405424
* @param index - Zero-based index of the incident bar to click (default: 0)
406425
* @returns Promise that resolves when the incidents table is visible
407426
*/
408427
selectIncidentByBarIndex: (index = 0) => {
409-
if (!_quietSearch)
410-
cy.log(`incidentsPage.selectIncidentByBarIndex: ${index} (clicking visible path elements)`);
428+
if (!_quietSearch) cy.log(`incidentsPage.selectIncidentByBarIndex: ${index}`);
411429

412430
return incidentsPage.elements
413-
.incidentsChartBarsVisiblePaths()
431+
.incidentsChartBarsGroups()
414432
.should('have.length.greaterThan', index)
415-
.then(($paths) => {
416-
if (index >= $paths.length) {
417-
throw new Error(`Index ${index} exceeds available paths (${$paths.length})`);
418-
}
419-
420-
return cy.wrap($paths.eq(index), _qLog()).click({ force: true, ..._qLog() });
421-
})
433+
.eq(index)
434+
.find('path[role="presentation"]')
435+
.filter(_isVisiblePath)
436+
.first()
437+
.click({ force: true, ..._qLog() })
422438
.then(() => {
423439
cy.wait(2000, _qLog());
424440
return incidentsPage.elements.incidentsTable().scrollIntoView().should('exist');
425441
});
426442
},
427443

428-
deselectIncidentByBar: () => {
444+
deselectIncidentByBar: (index = 0) => {
429445
if (!_quietSearch) cy.log('incidentsPage.deselectIncidentByBar');
430446
return incidentsPage.elements
431-
.incidentsChartBarsVisiblePaths()
432-
.then(($paths) => {
433-
if ($paths.length === 0) {
434-
throw new Error('No paths found in incidents chart');
435-
}
436-
return cy.wrap($paths.eq(0), _qLog()).click({ force: true, ..._qLog() });
437-
})
447+
.incidentsChartBarsGroups()
448+
.eq(index)
449+
.find('path[role="presentation"]')
450+
.filter(_isVisiblePath)
451+
.first()
452+
.click({ force: true, ..._qLog() })
438453
.then(() => {
439454
return incidentsPage.elements.incidentsTable().should('not.exist');
440455
});
@@ -653,8 +668,10 @@ export const incidentsPage = {
653668

654669
prepareIncidentsPageForSearch: () => {
655670
if (!_quietSearch) cy.log('incidentsPage.prepareIncidentsPageForSearch: Setting up page...');
656-
// Force a hard page reload to release browser DOM memory from previous search iterations.
657-
cy.reload({ log: false });
671+
// Use SPA navigation instead of cy.reload() — the Incidents component is a
672+
// dynamic plugin chunk, and cy.reload() causes the Console to re-resolve all
673+
// plugins from scratch, which silently fails in headless CI (blank page).
674+
// OOM is handled by _quietSearch suppressing DOM snapshots, not by reload.
658675
incidentsPage.goTo();
659676
incidentsPage.setDays(incidentsPage.SEARCH_CONFIG.DEFAULT_DAYS);
660677
incidentsPage.elements.incidentsChartContainer().should('be.visible');
@@ -821,7 +838,7 @@ export const incidentsPage = {
821838
if (found) {
822839
return cy.wrap(true, _qLog());
823840
}
824-
incidentsPage.deselectIncidentByBar();
841+
incidentsPage.deselectIncidentByBar(currentIndex);
825842
cy.wait(500, _qLog());
826843
return searchNextIncidentBar(currentIndex + 1);
827844
});
@@ -859,16 +876,18 @@ export const incidentsPage = {
859876

860877
incidentsPage.prepareIncidentsPageForSearch();
861878

862-
return incidentsPage.elements
863-
.incidentsChartBarsVisiblePaths()
864-
.then(($paths) => {
865-
const totalPaths = $paths.length;
866-
if (totalPaths === 0) {
867-
cy.log('No visible incident bar paths found in chart');
879+
// Check for bar groups without asserting existence — an empty chart is
880+
// valid (e.g. when mocking empty incidents or before detection fires).
881+
return cy
882+
.get('body', _qLog())
883+
.then(($body) => {
884+
const totalIncidents = $body.find(_BAR_GROUP_SELECTOR).length;
885+
if (totalIncidents === 0) {
886+
if (!_quietSearch) cy.log('No incident bar groups found in chart');
868887
return cy.wrap(false, { log: false });
869888
}
870889

871-
return incidentsPage.traverseAllIncidentsBars(alertName, totalPaths);
890+
return incidentsPage.traverseAllIncidentsBars(alertName, totalIncidents);
872891
})
873892
.then((found: boolean) => {
874893
if (found) {

0 commit comments

Comments
 (0)