mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[kbn/response-ops-alerts-table] set data-test-subj for EuiDataGrid based on loading status (#217230)
## Summary Follow-up to #217153 ### Problem Description In UI tests, there was no reliable way to determine when the Alerts table content had fully loaded before interacting with it. This could lead to flaky tests where interactions occurred before the data was available (rows are not present yet), causing failures or inconsistent results (checking for row with specific content to exist)  Quite often we see tests waiting for global indicator (spinner in the top left corner) to be hidden as a condition for page loading is complete. This is quite unreliable approach and testing tools have no consistent built-in solution: FTR, Cypress or even Playwright - network idle wait is officially marked as [discouraged](https://playwright.dev/docs/api/class-page)). We need to help testing tool to interact with UI components in ready state only. ### Solution To address this issue, I modified a `data-test-subj` property in the `<EuiDataGrid>` component. The property dynamically switches between `alertsTableIsLoading` when data is still loading and `alertsTableIsLoaded `once the content is available. This allows UI tests to wait for precisely `alertsTableIsLoaded` to be in in the DOM before interacting with the table, ensuring more reliable and stable test execution. Passed 10/10 <img width="538" alt="image" src="https://github.com/user-attachments/assets/e44bae5f-4094-4ed2-89f3-74a52cb2be53" />
This commit is contained in:
parent
401b76e5eb
commit
edf8d6d975
13 changed files with 23 additions and 20 deletions
|
@ -358,7 +358,7 @@ export const AlertsDataGrid = typedMemo(
|
|||
ref={dataGridRef}
|
||||
css={rowStyles}
|
||||
aria-label="Alerts table"
|
||||
data-test-subj="alertsTable"
|
||||
data-test-subj={isLoading ? `alertsTableIsLoading` : `alertsTableIsLoaded`}
|
||||
height={height}
|
||||
columns={columnsWithCellActions}
|
||||
columnVisibility={columnVisibility}
|
||||
|
|
|
@ -10,16 +10,14 @@ import { ScoutPage, Locator, expect } from '@kbn/scout';
|
|||
const PAGE_URL = 'security/alerts';
|
||||
|
||||
export class AlertsTablePage {
|
||||
public detectionsAlertsContainer: Locator;
|
||||
public detectionsAlertsWrapper: Locator;
|
||||
public alertRow: Locator;
|
||||
public alertsTableBody: Locator;
|
||||
public alertsTable: Locator;
|
||||
|
||||
constructor(private readonly page: ScoutPage) {
|
||||
this.detectionsAlertsContainer = this.page.testSubj.locator('detectionsAlertsPage');
|
||||
this.detectionsAlertsWrapper = this.page.testSubj.locator('detectionsAlertsPage');
|
||||
this.alertRow = this.page.locator('div.euiDataGridRow');
|
||||
this.alertsTableBody = this.page.testSubj
|
||||
.locator('alertsTable')
|
||||
.locator(`[data-test-subj='euiDataGridBody']`);
|
||||
this.alertsTable = this.page.testSubj.locator('alertsTableIsLoaded'); // Search for loaded Alerts table
|
||||
}
|
||||
|
||||
async navigate() {
|
||||
|
@ -27,7 +25,7 @@ export class AlertsTablePage {
|
|||
}
|
||||
|
||||
async expandAlertDetailsFlyout(ruleName: string) {
|
||||
await this.alertsTableBody.waitFor({ state: 'visible' });
|
||||
await this.alertsTable.waitFor({ state: 'visible' });
|
||||
// Filter alert by unique rule name
|
||||
const row = this.alertRow.filter({ hasText: ruleName });
|
||||
await expect(
|
||||
|
@ -37,4 +35,9 @@ export class AlertsTablePage {
|
|||
|
||||
return row.locator(`[data-test-subj='expand-event']`).click();
|
||||
}
|
||||
|
||||
async waitForDetectionsAlertsWrapper() {
|
||||
// Increased timeout to 20 seconds because this page sometimes takes longer to load
|
||||
return this.detectionsAlertsWrapper.waitFor({ state: 'visible', timeout: 20_000 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ describe('Isolate command', { tags: ['@ess', '@serverless', '@brokenInServerless
|
|||
loadPage(APP_ALERTS_PATH);
|
||||
closeAllToasts();
|
||||
|
||||
cy.getByTestSubj('alertsTable').within(() => {
|
||||
cy.getByTestSubj('alertsTableIsLoaded').within(() => {
|
||||
cy.getByTestSubj('expand-event')
|
||||
.first()
|
||||
.within(() => {
|
||||
|
|
|
@ -30,7 +30,7 @@ export const getAlertsTableRows = (timeout?: number): Cypress.Chainable<JQuery<H
|
|||
clickAlertListRefreshButton();
|
||||
|
||||
return cy
|
||||
.getByTestSubj('alertsTable')
|
||||
.getByTestSubj('alertsTableIsLoaded')
|
||||
.find<HTMLDivElement>('.euiDataGridRow')
|
||||
.then(($rowsFound) => {
|
||||
$rows = $rowsFound;
|
||||
|
|
|
@ -28,7 +28,7 @@ spaceTest.describe('Expandable flyout state sync', { tag: ['@ess', '@svlSecurity
|
|||
const urlBeforeAlertDetails = page.url();
|
||||
expect(urlBeforeAlertDetails).not.toContain(RIGHT);
|
||||
|
||||
await pageObjects.alertsTablePage.detectionsAlertsContainer.waitFor({ state: 'visible' });
|
||||
await pageObjects.alertsTablePage.waitForDetectionsAlertsWrapper();
|
||||
await pageObjects.alertsTablePage.expandAlertDetailsFlyout(ruleName);
|
||||
|
||||
const urlAfterAlertDetails = page.url();
|
||||
|
@ -38,7 +38,7 @@ spaceTest.describe('Expandable flyout state sync', { tag: ['@ess', '@svlSecurity
|
|||
await expect(headerTitle).toHaveText(ruleName);
|
||||
|
||||
await page.reload();
|
||||
await pageObjects.alertsTablePage.detectionsAlertsContainer.waitFor({ state: 'visible' });
|
||||
await pageObjects.alertsTablePage.waitForDetectionsAlertsWrapper();
|
||||
|
||||
const urlAfterReload = page.url();
|
||||
expect(urlAfterReload).toContain(RIGHT);
|
||||
|
|
|
@ -64,7 +64,7 @@ export const testSubjectIds = {
|
|||
GRAPH_ACTIONS_TOGGLE_SEARCH_ID: 'cloudSecurityGraphGraphInvestigationToggleSearch',
|
||||
GRAPH_ACTIONS_INVESTIGATE_IN_TIMELINE_ID:
|
||||
'cloudSecurityGraphGraphInvestigationInvestigateInTimeline',
|
||||
ALERT_TABLE_ROW_CSS_SELECTOR: '[data-test-subj="alertsTable"] .euiDataGridRow',
|
||||
ALERT_TABLE_ROW_CSS_SELECTOR: '[data-test-subj="alertsTableIsLoaded"] .euiDataGridRow',
|
||||
SETUP_TECHNOLOGY_SELECTOR: 'setup-technology-selector',
|
||||
DIRECT_ACCESS_KEYS: 'direct_access_keys',
|
||||
SETUP_TECHNOLOGY_SELECTOR_AGENTLESS_RADIO: 'setup-technology-agentless-radio',
|
||||
|
|
|
@ -21,7 +21,7 @@ const DATE_WITH_DATA = {
|
|||
|
||||
const ALERTS_FLYOUT_SELECTOR = 'alertsFlyout';
|
||||
const FILTER_FOR_VALUE_BUTTON_SELECTOR = 'filterForValue';
|
||||
const ALERTS_TABLE_WITH_DATA_SELECTOR = 'alertsTable';
|
||||
const ALERTS_TABLE_WITH_DATA_SELECTOR = 'alertsTableIsLoaded';
|
||||
const ALERTS_TABLE_NO_DATA_SELECTOR = 'alertsTableEmptyState';
|
||||
const ALERTS_TABLE_ERROR_PROMPT_SELECTOR = 'alertsTableErrorPrompt';
|
||||
const ALERTS_TABLE_ACTIONS_MENU_SELECTOR = 'alertsTableActionsMenu';
|
||||
|
|
|
@ -22,7 +22,7 @@ const ALERTS_TITLE = 'Alerts';
|
|||
const ALERTS_ACCORDION_SELECTOR = `accordion-${ALERTS_TITLE}`;
|
||||
const ALERTS_SECTION_BUTTON_CSS_SELECTOR = `[data-test-subj=${ALERTS_ACCORDION_SELECTOR}] button.euiAccordion__button`;
|
||||
const ALERTS_TABLE_NO_DATA_SELECTOR = 'alertsTableEmptyState';
|
||||
const ALERTS_TABLE_WITH_DATA_SELECTOR = 'alertsTable';
|
||||
const ALERTS_TABLE_WITH_DATA_SELECTOR = 'alertsTableIsLoaded';
|
||||
const ALERTS_TABLE_LOADING_SELECTOR = 'internalAlertsPageLoading';
|
||||
|
||||
export function ObservabilityOverviewCommonProvider({
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('Alert Table API calls', { tags: ['@ess', '@serverless'] }, () => {
|
|||
});
|
||||
|
||||
it('should call `api/lists/index` only once', () => {
|
||||
cy.get('[data-test-subj="alertsTable"]').then(() => {
|
||||
cy.get('[data-test-subj="alertsTableIsLoaded"]').then(() => {
|
||||
expect(callCount, 'number of times lists index api is called').to.equal(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,7 +38,7 @@ export const GET_DATA_GRID_HEADER_CELL_ACTION_GROUP = (fieldName: string) => {
|
|||
};
|
||||
|
||||
export const DATA_GRID_FULL_SCREEN =
|
||||
'[data-test-subj="alertsTable"] [data-test-subj="dataGridFullScreenButton"]';
|
||||
'[data-test-subj="alertsTableIsLoaded"] [data-test-subj="dataGridFullScreenButton"]';
|
||||
|
||||
export const DATA_GRID_FIELD_SORT_BTN = '[data-test-subj="dataGridColumnSortingButton"]';
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ export const NEW_TERMS_FIELDS_DETAILS = 'Fields';
|
|||
export const NEW_TERMS_HISTORY_WINDOW_DETAILS = 'History Window Size';
|
||||
|
||||
export const FIELDS_BROWSER_BTN =
|
||||
'[data-test-subj="alertsTable"] [data-test-subj="show-field-browser"]';
|
||||
'[data-test-subj="alertsTableIsLoaded"] [data-test-subj="show-field-browser"]';
|
||||
|
||||
export const LAST_EXECUTION_STATUS_REFRESH_BUTTON =
|
||||
'[data-test-subj="ruleLastExecutionStatusRefreshButton"]';
|
||||
|
|
|
@ -489,7 +489,7 @@ export const sumAlertCountFromAlertCountTable = (callback?: (sumOfEachRow: numbe
|
|||
};
|
||||
|
||||
export const selectFirstPageAlerts = () => {
|
||||
const ALERTS_DATA_GRID = '[data-test-subj="alertsTable"]';
|
||||
const ALERTS_DATA_GRID = '[data-test-subj="alertsTableIsLoaded"]';
|
||||
cy.get(ALERTS_DATA_GRID).find(SELECT_ALL_VISIBLE_ALERTS).scrollIntoView();
|
||||
cy.get(ALERTS_DATA_GRID).find(SELECT_ALL_VISIBLE_ALERTS).click({ force: true });
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
|
||||
import { FtrService } from '../../../functional/ftr_provider_context';
|
||||
|
||||
const ALERT_TABLE_ROW_CSS_SELECTOR = '[data-test-subj="alertsTable"] .euiDataGridRow';
|
||||
const ALERT_TABLE_ROW_CSS_SELECTOR = '[data-test-subj="alertsTableIsLoaded"] .euiDataGridRow';
|
||||
|
||||
export class DetectionsPageObject extends FtrService {
|
||||
private readonly find = this.ctx.getService('find');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue