Skip to content

Commit 62898d4

Browse files
DavidRajnohaclaude
andcommitted
fix(tests): suppress Cypress DOM snapshots during search loop to prevent OOM
cy.reload() only frees browser-side DOM memory, but Cypress stores serialized DOM snapshots in Node.js memory for each command (~1-5 MB each). With ~40 commands per search iteration * 15+ iterations, this accumulates ~600 MB+ of snapshots, causing OOM (exit 137) in CI. Adds a _quietSearch flag that suppresses Cypress command logging (and thus DOM snapshot creation) during findIncidentWithAlert's search loop. Key operations use { log: false } via _qLog() helper. Outer cy.log() calls are preserved for CI visibility. CI run: https://prow.ci.openshift.org/view/gs/test-platform-results/pr-logs/pull/openshift_monitoring-plugin/860/pull-ci-openshift-monitoring-plugin-main-e2e-incidents/2041404831268081664 Classifications: TEST_BUG (confidence: high) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f5d235c commit 62898d4

1 file changed

Lines changed: 59 additions & 53 deletions

File tree

web/cypress/views/incidents-page.ts

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import { DataTestIDs } from '../../src/components/data-test';
77
let _findIncidentSearchStart: number | null = null;
88
const _FIND_INCIDENT_HARD_TIMEOUT_MS = 35 * 60 * 1000; // 35 minutes
99

10+
// When true, search methods suppress Cypress command logging to prevent
11+
// DOM snapshot accumulation that causes OOM (exit 137) in CI containers.
12+
// Toggled by findIncidentWithAlert during its retry loop.
13+
let _quietSearch = false;
14+
const _qLog = (): { log: false } | Record<string, never> => _quietSearch ? { log: false } : {};
15+
1016
export const incidentsPage = {
1117

1218
// Centralized element selectors - all selectors defined in one place
@@ -60,22 +66,22 @@ export const incidentsPage = {
6066
incidentsChartBars: () => cy.byTestID(DataTestIDs.IncidentsChart.ChartBars),
6167
incidentsChartBar: (groupId: string) => cy.byTestID(`${DataTestIDs.IncidentsChart.ChartBar}-${groupId}`),
6268
incidentsChartBarsVisiblePaths: () => {
63-
return cy.get('body').then($body => {
69+
return cy.get('body', _qLog()).then($body => {
6470
// There is a delay between the element being rendered and the paths being visible.
6571
// The case when no paths are visible is valid, so we can not use should or conditional testing semantics.
66-
cy.wait(500);
72+
cy.wait(500, _qLog());
6773
// We need to use the $body as both cases when the element is there or not are valid.
6874
const exists = $body.find('g[role="presentation"][data-test*="incidents-chart-bar-"]').length > 0;
6975
if (exists) {
70-
return cy.get('g[role="presentation"][data-test*="incidents-chart-bar-"]')
76+
return cy.get('g[role="presentation"][data-test*="incidents-chart-bar-"]', _qLog())
7177
.find('path[role="presentation"]')
7278
.filter((index, element) => {
7379
const fillOpacity = Cypress.$(element).css('fill-opacity') || Cypress.$(element).attr('fill-opacity');
7480
return parseFloat(fillOpacity || '0') > 0;
7581
});
7682
} else {
77-
cy.log('Chart bars were not found. Test continues.');
78-
return cy.wrap([]);
83+
if (!_quietSearch) cy.log('Chart bars were not found. Test continues.');
84+
return cy.wrap([], _qLog());
7985
}
8086
});
8187
},
@@ -320,35 +326,35 @@ export const incidentsPage = {
320326
* @returns Promise that resolves when the incidents table is visible
321327
*/
322328
selectIncidentByBarIndex: (index = 0) => {
323-
cy.log(`incidentsPage.selectIncidentByBarIndex: ${index} (clicking visible path elements)`);
324-
329+
if (!_quietSearch) cy.log(`incidentsPage.selectIncidentByBarIndex: ${index} (clicking visible path elements)`);
330+
325331
return incidentsPage.elements.incidentsChartBarsVisiblePaths()
326332
.should('have.length.greaterThan', index)
327333
.then(($paths) => {
328334
if (index >= $paths.length) {
329335
throw new Error(`Index ${index} exceeds available paths (${$paths.length})`);
330336
}
331-
332-
return cy.wrap($paths.eq(index))
333-
.click({ force: true });
337+
338+
return cy.wrap($paths.eq(index), _qLog())
339+
.click({ force: true, ..._qLog() });
334340
})
335341
.then(() => {
336-
cy.wait(2000);
342+
cy.wait(2000, _qLog());
337343
return incidentsPage.elements.incidentsTable()
338344
.scrollIntoView()
339345
.should('exist');
340346
});
341347
},
342348

343349
deselectIncidentByBar: () => {
344-
cy.log('incidentsPage.deselectIncidentByBar');
350+
if (!_quietSearch) cy.log('incidentsPage.deselectIncidentByBar');
345351
return incidentsPage.elements.incidentsChartBarsVisiblePaths()
346352
.then(($paths) => {
347353
if ($paths.length === 0) {
348354
throw new Error('No paths found in incidents chart');
349355
}
350-
return cy.wrap($paths.eq(0))
351-
.click({ force: true });
356+
return cy.wrap($paths.eq(0), _qLog())
357+
.click({ force: true, ..._qLog() });
352358
})
353359
.then(() => {
354360
return incidentsPage.elements.incidentsTable()
@@ -403,9 +409,9 @@ export const incidentsPage = {
403409
},
404410

405411
expandRow: (rowIndex = 0) => {
406-
cy.log('incidentsPage.expandRow');
412+
if (!_quietSearch) cy.log('incidentsPage.expandRow');
407413
incidentsPage.elements.incidentsTableExpandButton(rowIndex)
408-
.click({ force: true });
414+
.click({ force: true, ..._qLog() });
409415
},
410416

411417
waitForTooltip: () => {
@@ -555,15 +561,13 @@ export const incidentsPage = {
555561
},
556562

557563
prepareIncidentsPageForSearch: () => {
558-
cy.log('incidentsPage.prepareIncidentsPageForSearch: Setting up page...');
559-
// Force a hard page reload to release DOM memory from previous search iterations.
560-
// Without this, repeated searches within waitUntil accumulate Cypress command
561-
// snapshots and browser DOM nodes, causing OOM (exit 137) in CI containers.
564+
if (!_quietSearch) cy.log('incidentsPage.prepareIncidentsPageForSearch: Setting up page...');
565+
// Force a hard page reload to release browser DOM memory from previous search iterations.
562566
cy.reload({ log: false });
563567
incidentsPage.goTo();
564568
incidentsPage.setDays(incidentsPage.SEARCH_CONFIG.DEFAULT_DAYS);
565569
incidentsPage.elements.incidentsChartContainer().should('be.visible');
566-
cy.wait(incidentsPage.SEARCH_CONFIG.CHART_LOAD_WAIT);
570+
cy.wait(incidentsPage.SEARCH_CONFIG.CHART_LOAD_WAIT, _qLog());
567571
},
568572

569573
/**
@@ -577,10 +581,9 @@ export const incidentsPage = {
577581
return incidentsPage.elements.incidentsTable().invoke('text').then((text) => {
578582
if (String(text).includes(alertName)) {
579583
cy.log(`Found alert "${alertName}" in incident ${incidentIndex + 1} table content`);
580-
cy.log(text);
581-
return cy.wrap(true);
584+
return cy.wrap(true, _qLog());
582585
}
583-
return cy.wrap(false);
586+
return cy.wrap(false, _qLog());
584587
});
585588
},
586589

@@ -596,18 +599,18 @@ export const incidentsPage = {
596599
*/
597600
checkComponentInIncident: (alertName: string, incidentIndex: number, totalRows: number, currentRowIndex: number = 0): Cypress.Chainable<boolean> => {
598601
if (currentRowIndex >= totalRows) {
599-
cy.log(`Checked all ${totalRows} rows in incident ${incidentIndex + 1}, alert not found`);
600-
return cy.wrap(false);
602+
if (!_quietSearch) cy.log(`Checked all ${totalRows} rows in incident ${incidentIndex + 1}, alert not found`);
603+
return cy.wrap(false, _qLog());
601604
}
602-
603-
cy.log(`Expanding and checking row ${currentRowIndex} in incident ${incidentIndex + 1}`);
605+
606+
if (!_quietSearch) cy.log(`Expanding and checking row ${currentRowIndex} in incident ${incidentIndex + 1}`);
604607
incidentsPage.expandRow(currentRowIndex);
605-
608+
606609
return incidentsPage.checkComponentRowInIncidentTableForAlert(alertName, incidentIndex)
607610
.then((found) => {
608611
if (found) {
609612
cy.log(`Found alert "${alertName}" in expanded row ${currentRowIndex} of incident ${incidentIndex + 1}`);
610-
return cy.wrap(true);
613+
return cy.wrap(true, _qLog());
611614
}
612615
return incidentsPage.checkComponentInIncident(alertName, incidentIndex, totalRows, currentRowIndex + 1);
613616
});
@@ -622,24 +625,24 @@ export const incidentsPage = {
622625
* @returns Promise resolving to true if alert is found anywhere in the incident
623626
*/
624627
searchAllComponentsInIncident: (alertName: string, incidentIndex: number): Cypress.Chainable<boolean> => {
625-
cy.log(`incidentsPage.searchAllRowsInIncident: Checking all rows in incident ${incidentIndex + 1} for alert "${alertName}"`);
626-
628+
if (!_quietSearch) cy.log(`incidentsPage.searchAllRowsInIncident: Checking all rows in incident ${incidentIndex + 1} for alert "${alertName}"`);
629+
627630
return incidentsPage.checkComponentRowInIncidentTableForAlert(alertName, incidentIndex)
628631
.then((foundInMain) => {
629632
if (foundInMain) {
630-
return cy.wrap(true);
633+
return cy.wrap(true, _qLog());
631634
}
632-
635+
633636
return incidentsPage.elements.incidentsTable()
634637
.find('tbody[data-test*="incidents-table-row-"]')
635638
.then(($rows) => {
636639
const totalRows = $rows.length;
637640
if (totalRows === 0) {
638-
cy.log(`No rows found in incident ${incidentIndex + 1}`);
639-
return cy.wrap(false);
641+
if (!_quietSearch) cy.log(`No rows found in incident ${incidentIndex + 1}`);
642+
return cy.wrap(false, _qLog());
640643
}
641-
642-
cy.log(`Found ${totalRows} incident rows to check in incident ${incidentIndex + 1}`);
644+
645+
if (!_quietSearch) cy.log(`Found ${totalRows} incident rows to check in incident ${incidentIndex + 1}`);
643646
return incidentsPage.checkComponentInIncident(alertName, incidentIndex, totalRows);
644647
});
645648
});
@@ -654,10 +657,10 @@ export const incidentsPage = {
654657
* @returns Promise resolving to true if alert is found in the incident
655658
*/
656659
searchForAlertInIncident: (alertName: string, incidentIndex: number): Cypress.Chainable<boolean> => {
657-
cy.log(`incidentsPage.searchForAlertInIncident: Checking incident ${incidentIndex + 1} for alert "${alertName}"`);
658-
660+
if (!_quietSearch) cy.log(`incidentsPage.searchForAlertInIncident: Checking incident ${incidentIndex + 1} for alert "${alertName}"`);
661+
659662
return cy
660-
.wrap(null)
663+
.wrap(null, _qLog())
661664
.then(() => {
662665
incidentsPage.selectIncidentByBarIndex(incidentIndex);
663666
return null;
@@ -674,25 +677,21 @@ export const incidentsPage = {
674677
* @returns Promise resolving to true if alert is found in any incident
675678
*/
676679
traverseAllIncidentsBars: (alertName: string, totalIncidents: number): Cypress.Chainable<boolean> => {
677-
cy.log(`incidentsPage.searchAllIncidents: Searching ${totalIncidents} incidents for alert "${alertName}"`);
678-
680+
if (!_quietSearch) cy.log(`incidentsPage.searchAllIncidents: Searching ${totalIncidents} incidents for alert "${alertName}"`);
681+
679682
const searchNextIncidentBar = (currentIndex: number): Cypress.Chainable<boolean> => {
680683
if (currentIndex >= totalIncidents) {
681-
cy.log(`Checked all ${totalIncidents} incidents, alert "${alertName}" not found`);
682-
return cy.wrap(false);
684+
if (!_quietSearch) cy.log(`Checked all ${totalIncidents} incidents, alert "${alertName}" not found`);
685+
return cy.wrap(false, _qLog());
683686
}
684687

685688
return incidentsPage.searchForAlertInIncident(alertName, currentIndex)
686689
.then((found) => {
687690
if (found) {
688-
return cy.wrap(true);
691+
return cy.wrap(true, _qLog());
689692
}
690693
incidentsPage.deselectIncidentByBar();
691-
// Wait for the incident to be deselected
692-
// Quick workaround, could be improved by waiting for the number of paths to change, but it
693-
// does not has to if 1 initially. The check for the alert table non existance is already implemented,
694-
// but there seems to be a short delay between the alert table closing and new bars rendering.
695-
cy.wait(500)
694+
cy.wait(500, _qLog());
696695
return searchNextIncidentBar(currentIndex + 1);
697696
});
698697
};
@@ -716,28 +715,35 @@ export const incidentsPage = {
716715
const elapsed = Date.now() - _findIncidentSearchStart;
717716
if (elapsed > _FIND_INCIDENT_HARD_TIMEOUT_MS) {
718717
_findIncidentSearchStart = null;
718+
_quietSearch = false;
719719
throw new Error(
720720
`findIncidentWithAlert: hard timeout after ${Math.round(elapsed / 60000)} minutes searching for "${alertName}"`
721721
);
722722
}
723723

724724
cy.log(`incidentsPage.findIncidentWithAlert: Starting search for alert "${alertName}"`);
725725

726+
// Enable quiet mode to suppress Cypress DOM snapshots during the search.
727+
// Each snapshot stores a serialized copy of the DOM (~1-5 MB). Without this,
728+
// ~40 snapshots per search iteration * 15+ iterations = OOM in CI containers.
729+
_quietSearch = true;
730+
726731
incidentsPage.prepareIncidentsPageForSearch();
727732

728733
return incidentsPage.elements.incidentsChartBarsVisiblePaths()
729734
.then(($paths) => {
730735
const totalPaths = $paths.length;
731736
if (totalPaths === 0) {
732737
cy.log('No visible incident bar paths found in chart');
733-
return cy.wrap(false);
738+
return cy.wrap(false, { log: false });
734739
}
735740

736741
return incidentsPage.traverseAllIncidentsBars(alertName, totalPaths);
737742
})
738743
.then((found: boolean) => {
744+
_quietSearch = false;
739745
if (found) {
740-
_findIncidentSearchStart = null; // Reset on success
746+
_findIncidentSearchStart = null;
741747
}
742748
return found;
743749
});

0 commit comments

Comments
 (0)