Skip to content

Commit 5dd4b0b

Browse files
[APM] Migrate and fix alert error count tests (#247373)
## Summary Closes #238521 & #244847 This PR migrates the alerts error count tests from the legacy cypress framework to the updated Scout framework supported by playwright. A key point in the migration is this test step ```typescript await test.step('wait for the rule to be executed', async () => { const foundResponse = await apiServices.alerting.rules.find({ search: RULE_NAME, search_fields: ['name'], per_page: 1, page: 1, }); const alert = foundResponse.data.data.find((obj: any) => obj.name === RULE_NAME); expect(alert).toBeDefined(); await apiServices.alerting.rules.runSoon(alert!.id); await apiServices.alerting.waiting.waitForNextExecution(alert!.id); }); ``` As per the comment added in #244847 this step addresses the flakiness we were running into in the cypress version of these tests. This step will ensure a rule execution was triggered for the rule created during the test, in turn ensuring that b y the time the UI test gets to the alert tab there is an alert to display and the test doesn't timeout awaiting selector visibility ### Checklist - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed --------- Co-authored-by: Dzmitry Lemechko <dzmitry.lemechko@elastic.co>
1 parent 25c67f4 commit 5dd4b0b

File tree

15 files changed

+318
-210
lines changed

15 files changed

+318
-210
lines changed

src/platform/packages/shared/kbn-scout/src/playwright/eui_components/data_grid.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export class EuiDataGridWrapper {
5555
this.popoverPanel = this.page.locator('div[data-popover-panel="true"]');
5656
}
5757

58-
private async ensureGridVisible(): Promise<void> {
58+
async ensureGridVisible(): Promise<void> {
5959
await this.dataGridWrapper.waitFor({ state: 'visible' });
6060
}
6161

src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/alerting/index.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export const getAlertingApiHelper = (
413413
});
414414
},
415415

416-
getExecutionLog: async (ruleId: string, spaceId?: string) => {
416+
getExecutionLog: async (ruleId: string, spaceId?: string, dateStart = new Date()) => {
417417
return await measurePerformanceAsync(
418418
log,
419419
`alertingApi.rules.getExecutionLog [${ruleId}]`,
@@ -422,6 +422,7 @@ export const getAlertingApiHelper = (
422422
method: 'GET',
423423
path: `${buildSpacePath(spaceId)}/internal/alerting/rule/${ruleId}/_execution_log`,
424424
retries: 3,
425+
query: { date_start: dateStart.toISOString() },
425426
});
426427
return response.data;
427428
}
@@ -650,7 +651,12 @@ export const getAlertingApiHelper = (
650651
);
651652
},
652653

653-
waitForNextExecution: async (ruleId: string, spaceId?: string, timeoutMs: number = 30000) => {
654+
waitForNextExecution: async (
655+
ruleId: string,
656+
spaceId?: string,
657+
timeoutMs: number = 30000,
658+
dateStart: Date = new Date()
659+
) => {
654660
return await measurePerformanceAsync(
655661
log,
656662
`alertingApi.waiting.waitForNextExecution [${ruleId}]`,
@@ -660,6 +666,7 @@ export const getAlertingApiHelper = (
660666
method: 'GET',
661667
path: `${buildSpacePath(spaceId)}/internal/alerting/rule/${ruleId}/_execution_log`,
662668
retries: 3,
669+
query: { date_start: dateStart.toISOString() },
663670
});
664671
const initialLogData = initialLog.data as any;
665672
const initialCount = initialLogData.total;
@@ -671,7 +678,8 @@ export const getAlertingApiHelper = (
671678
path: `${buildSpacePath(
672679
spaceId
673680
)}/internal/alerting/rule/${ruleId}/_execution_log`,
674-
retries: 1, // Lower retries for frequent polling operations
681+
retries: 1, // Lower retries for frequent polling operations,
682+
query: { date_start: dateStart.toISOString() },
675683
});
676684
const logData = executionLog.data as any;
677685
return logData.total;

x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/e2e/alerts/error_count.cy.ts

Lines changed: 0 additions & 105 deletions
This file was deleted.

x-pack/solutions/observability/plugins/apm/ftr_e2e/cypress/e2e/alerts/generate_data.ts

Lines changed: 0 additions & 50 deletions
This file was deleted.

x-pack/solutions/observability/plugins/apm/public/components/alerting/utils/fields.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ export function IsAboveField({
221221
onChange: (value: number) => void;
222222
step?: number;
223223
}) {
224-
const [thresholdPopoverOpen, serThresholdPopoverOpen] = useState(false);
224+
const [thresholdPopoverOpen, setThresholdPopoverOpen] = useState(false);
225225
const [isAboveValue, setIsAboveValue] = useState(String(value));
226226

227227
return (
@@ -230,7 +230,7 @@ export function IsAboveField({
230230
anchorPosition={'downLeft'}
231231
ownFocus
232232
closePopover={() => {
233-
serThresholdPopoverOpen(false);
233+
setThresholdPopoverOpen(false);
234234
}}
235235
button={
236236
<EuiExpression
@@ -241,8 +241,9 @@ export function IsAboveField({
241241
isInvalid={!isNumeric(isAboveValue)}
242242
isActive={thresholdPopoverOpen}
243243
onClick={() => {
244-
serThresholdPopoverOpen(true);
244+
setThresholdPopoverOpen(true);
245245
}}
246+
data-test-subj="apmIsAboveExpression"
246247
/>
247248
}
248249
>

x-pack/solutions/observability/plugins/apm/test/scout/ui/fixtures/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import type {
1313
BrowserAuthFixture,
1414
} from '@kbn/scout-oblt';
1515
import { test as base, createLazyPageObject } from '@kbn/scout-oblt';
16-
import { AlertsTab } from './page_objects/alerts_tab';
1716
import { ServiceMapPage } from './page_objects/service_map';
1817
import { ServiceInventoryPage } from './page_objects/service_inventory';
1918
import { StorageExplorerPage } from './page_objects/storage_explorer';
@@ -32,6 +31,7 @@ import { ServiceDetailsPage } from './page_objects/service_details/service_detai
3231
import { DependenciesInventoryPage } from './page_objects/dependencies_inventory';
3332
import { APM_ROLES } from './constants';
3433
import { DependencyDetailsPage } from './page_objects/dependency_details/dependency_details';
34+
import { AlertsControls } from './page_objects/alerts_controls/alerts_controls';
3535

3636
export interface ApmBrowserAuthFixture extends BrowserAuthFixture {
3737
loginAsApmAllPrivilegesWithoutWriteSettings: () => Promise<void>;
@@ -41,7 +41,6 @@ export interface ApmBrowserAuthFixture extends BrowserAuthFixture {
4141

4242
export interface ExtendedScoutTestFixtures extends ObltTestFixtures {
4343
pageObjects: ObltPageObjects & {
44-
alertsTab: AlertsTab;
4544
serviceMapPage: ServiceMapPage;
4645
serviceInventoryPage: ServiceInventoryPage;
4746
storageExplorerPage: StorageExplorerPage;
@@ -59,6 +58,7 @@ export interface ExtendedScoutTestFixtures extends ObltTestFixtures {
5958
serviceDetailsPage: ServiceDetailsPage;
6059
dependenciesInventoryPage: DependenciesInventoryPage;
6160
dependencyDetailsPage: DependencyDetailsPage;
61+
alertsControls: AlertsControls;
6262
};
6363
browserAuth: ApmBrowserAuthFixture;
6464
}
@@ -78,7 +78,6 @@ export const test = base.extend<ExtendedScoutTestFixtures, ObltWorkerFixtures>({
7878
) => {
7979
const extendedPageObjects = {
8080
...pageObjects,
81-
alertsTab: createLazyPageObject(AlertsTab, page, kbnUrl),
8281
serviceMapPage: createLazyPageObject(ServiceMapPage, page, kbnUrl),
8382
serviceInventoryPage: createLazyPageObject(ServiceInventoryPage, page, kbnUrl),
8483
storageExplorerPage: createLazyPageObject(StorageExplorerPage, page, kbnUrl),
@@ -96,6 +95,7 @@ export const test = base.extend<ExtendedScoutTestFixtures, ObltWorkerFixtures>({
9695
serviceDetailsPage: createLazyPageObject(ServiceDetailsPage, page, kbnUrl),
9796
dependenciesInventoryPage: createLazyPageObject(DependenciesInventoryPage, page, kbnUrl),
9897
dependencyDetailsPage: createLazyPageObject(DependencyDetailsPage, page, kbnUrl),
98+
alertsControls: createLazyPageObject(AlertsControls, page),
9999
};
100100

101101
await use(extendedPageObjects);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { Locator, ScoutPage } from '@kbn/scout-oblt';
9+
10+
type AddRuleFlyoutStep = 'definition' | 'actions' | 'details';
11+
12+
export class AddRuleFlyout {
13+
public readonly flyout: Locator;
14+
public readonly saveButton: Locator;
15+
16+
public readonly isAboveExpression: Locator;
17+
public readonly isAboveInput: Locator;
18+
19+
public readonly scheduleValueInput: Locator;
20+
public readonly scheduleUnitSelect: Locator;
21+
22+
constructor(private readonly page: ScoutPage) {
23+
this.flyout = this.page.getByRole('dialog');
24+
this.saveButton = this.flyout.getByTestId('ruleFlyoutFooterSaveButton');
25+
26+
this.isAboveExpression = this.flyout.getByTestId('apmIsAboveExpression');
27+
this.isAboveInput = this.flyout.getByTestId('apmIsAboveFieldFieldNumber');
28+
29+
this.scheduleValueInput = this.flyout.getByTestId('ruleScheduleNumberInput');
30+
this.scheduleUnitSelect = this.flyout.getByTestId('ruleScheduleUnitInput');
31+
}
32+
33+
public async waitForErrorCountToLoad() {
34+
await this.flyout.getByRole('heading', { name: 'Error count threshold' }).waitFor();
35+
}
36+
37+
public async fillIsAbove(amount: number) {
38+
await this.isAboveExpression.click();
39+
await this.isAboveInput.fill(amount.toString());
40+
}
41+
42+
public async fillRuleSchedule(value: number, unit: 's' | 'm' | 'h' | 'd') {
43+
await this.scheduleValueInput.fill(value.toString());
44+
await this.scheduleUnitSelect.selectOption(unit);
45+
}
46+
47+
public async jumpToStep(step: AddRuleFlyoutStep) {
48+
await this.page.getByTestId(`ruleFormStep-${step}`).click();
49+
}
50+
51+
public async saveRule(opts: { saveEmptyActions?: boolean } = {}) {
52+
await this.saveButton.click();
53+
54+
if (opts.saveEmptyActions) {
55+
await this.page.getByTestId('confirmModalConfirmButton').click();
56+
}
57+
58+
await this.flyout.waitFor({ state: 'hidden' });
59+
}
60+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { Locator, ScoutPage } from '@kbn/scout-oblt';
9+
import { AddRuleFlyout } from './add_rule_flyout';
10+
11+
export class AlertsControls {
12+
public readonly addRuleFlyout: AddRuleFlyout;
13+
14+
public readonly button: Locator;
15+
public readonly contextMenu: Locator;
16+
17+
public readonly errorCountItem: Locator;
18+
public readonly manageRulesItem: Locator;
19+
20+
constructor(private readonly page: ScoutPage) {
21+
this.addRuleFlyout = new AddRuleFlyout(this.page);
22+
23+
this.button = this.page.getByTestId('apmAlertAndRulesHeaderLink');
24+
this.contextMenu = this.page.getByRole('dialog');
25+
26+
this.errorCountItem = this.contextMenu.getByTestId('apmAlertsMenuItemErrorCount');
27+
this.manageRulesItem = this.contextMenu.getByTestId('apmAlertsMenuItemManageRules');
28+
}
29+
30+
public async openContextMenu() {
31+
await this.button.click();
32+
await this.contextMenu.getByTestId('contextMenuPanelTitle').getByText('Alerts').waitFor();
33+
}
34+
35+
public async openErrorCountFlyout() {
36+
await this.errorCountItem.click();
37+
await this.addRuleFlyout.waitForErrorCountToLoad();
38+
}
39+
40+
public async goToManageRules() {
41+
await this.manageRulesItem.click();
42+
}
43+
}

0 commit comments

Comments
 (0)