mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ftr/obs/alerts] refactor to avoid stale-element errors (#140427)
* [ftr/obs/alerts] refactor to avoid stale-element errors
* Revert "skip failing test suite (#140248)"
This reverts commit b0b9b585fb
.
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
01d28511e3
commit
d42e0e1166
6 changed files with 77 additions and 68 deletions
|
@ -10,7 +10,6 @@ import { WebDriver, WebElement, By, until } from 'selenium-webdriver';
|
|||
|
||||
import { Browsers } from '../remote/browsers';
|
||||
import { FtrService, FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { retryOnStale } from './retry_on_stale';
|
||||
import { WebElementWrapper } from '../lib/web_element_wrapper';
|
||||
import { TimeoutOpt } from './types';
|
||||
|
||||
|
@ -18,6 +17,7 @@ export class FindService extends FtrService {
|
|||
private readonly log = this.ctx.getService('log');
|
||||
private readonly config = this.ctx.getService('config');
|
||||
private readonly retry = this.ctx.getService('retry');
|
||||
private readonly retryOnStale = this.ctx.getService('retryOnStale');
|
||||
|
||||
private readonly WAIT_FOR_EXISTS_TIME = this.config.get('timeouts.waitForExists');
|
||||
private readonly POLLING_TIME = 500;
|
||||
|
@ -290,7 +290,7 @@ export class FindService extends FtrService {
|
|||
public async clickByCssSelectorWhenNotDisabled(selector: string, opts?: TimeoutOpt) {
|
||||
const timeout = opts?.timeout ?? this.defaultFindTimeout;
|
||||
|
||||
await retryOnStale(this.log, async () => {
|
||||
await this.retryOnStale(async () => {
|
||||
this.log.debug(`Find.clickByCssSelectorWhenNotDisabled(${selector}, timeout=${timeout})`);
|
||||
|
||||
const element = await this.byCssSelector(selector);
|
||||
|
|
|
@ -14,3 +14,4 @@ export { PngService } from './png';
|
|||
export { ScreenshotsService } from './screenshots';
|
||||
export { SnapshotsService } from './snapshots';
|
||||
export { TestSubjects } from './test_subjects';
|
||||
export { RetryOnStaleProvider } from './retry_on_stale';
|
||||
|
|
|
@ -6,30 +6,44 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const MAX_ATTEMPTS = 10;
|
||||
|
||||
const isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null;
|
||||
const errMsg = (err: unknown) => (isObj(err) && typeof err.message === 'string' ? err.message : '');
|
||||
|
||||
export async function retryOnStale<T>(log: ToolingLog, fn: () => Promise<T>): Promise<T> {
|
||||
let attempt = 0;
|
||||
while (true) {
|
||||
attempt += 1;
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
if (errMsg(error).includes('stale element reference')) {
|
||||
if (attempt >= MAX_ATTEMPTS) {
|
||||
throw new Error(`retryOnStale ran out of attempts after ${attempt} tries`);
|
||||
export function RetryOnStaleProvider({ getService }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
|
||||
async function retryOnStale<T>(fn: () => Promise<T>): Promise<T> {
|
||||
let attempt = 0;
|
||||
while (true) {
|
||||
attempt += 1;
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
if (errMsg(error).includes('stale element reference')) {
|
||||
if (attempt >= MAX_ATTEMPTS) {
|
||||
throw new Error(`retryOnStale ran out of attempts after ${attempt} tries`);
|
||||
}
|
||||
|
||||
log.warning('stale element exception caught, retrying');
|
||||
continue;
|
||||
}
|
||||
|
||||
log.warning('stale element exception caught, retrying');
|
||||
continue;
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
retryOnStale.wrap = <Args extends any[], Result>(fn: (...args: Args) => Promise<Result>) => {
|
||||
return async (...args: Args) => {
|
||||
return await retryOnStale(async () => {
|
||||
return await fn(...args);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return retryOnStale;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
ScreenshotsService,
|
||||
SnapshotsService,
|
||||
TestSubjects,
|
||||
RetryOnStaleProvider,
|
||||
} from './common';
|
||||
import { ComboBoxService } from './combo_box';
|
||||
import {
|
||||
|
@ -88,4 +89,5 @@ export const services = {
|
|||
managementMenu: ManagementMenuService,
|
||||
monacoEditor: MonacoEditorService,
|
||||
menuToggle: MenuToggleService,
|
||||
retryOnStale: RetryOnStaleProvider,
|
||||
};
|
||||
|
|
|
@ -36,6 +36,7 @@ export function ObservabilityAlertsCommonProvider({
|
|||
const retry = getService('retry');
|
||||
const toasts = getService('toasts');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const retryOnStale = getService('retryOnStale');
|
||||
|
||||
const navigateToTimeWithData = async () => {
|
||||
return await pageObjects.common.navigateToUrlWithBrowserHistory(
|
||||
|
@ -108,14 +109,14 @@ export function ObservabilityAlertsCommonProvider({
|
|||
return await find.allByCssSelector('.euiDataGridRowCell input[type="checkbox"]:enabled');
|
||||
};
|
||||
|
||||
const getTableCellsInRows = async () => {
|
||||
const getTableCellsInRows = retryOnStale.wrap(async () => {
|
||||
const columnHeaders = await getTableColumnHeaders();
|
||||
if (columnHeaders.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
const cells = await getTableCells();
|
||||
return chunk(cells, columnHeaders.length);
|
||||
};
|
||||
});
|
||||
|
||||
const getTableOrFail = async () => {
|
||||
return await testSubjects.existOrFail(ALERTS_TABLE_CONTAINER_SELECTOR);
|
||||
|
@ -134,37 +135,28 @@ export function ObservabilityAlertsCommonProvider({
|
|||
return await testSubjects.find('queryInput');
|
||||
};
|
||||
|
||||
const getQuerySubmitButton = async () => {
|
||||
return await testSubjects.find('querySubmitButton');
|
||||
};
|
||||
|
||||
const clearQueryBar = async () => {
|
||||
const clearQueryBar = retryOnStale.wrap(async () => {
|
||||
return await (await getQueryBar()).clearValueWithKeyboard();
|
||||
};
|
||||
});
|
||||
|
||||
const typeInQueryBar = async (query: string) => {
|
||||
const typeInQueryBar = retryOnStale.wrap(async (query: string) => {
|
||||
return await (await getQueryBar()).type(query);
|
||||
};
|
||||
});
|
||||
|
||||
const submitQuery = async (query: string) => {
|
||||
await typeInQueryBar(query);
|
||||
return await (await getQuerySubmitButton()).click();
|
||||
await testSubjects.click('querySubmitButton');
|
||||
};
|
||||
|
||||
// Flyout
|
||||
const getViewAlertDetailsFlyoutButton = async () => {
|
||||
const openAlertsFlyout = retryOnStale.wrap(async () => {
|
||||
await openActionsMenuForRow(0);
|
||||
|
||||
return await testSubjects.find('viewAlertDetailsFlyout');
|
||||
};
|
||||
|
||||
const openAlertsFlyout = async () => {
|
||||
await (await getViewAlertDetailsFlyoutButton()).click();
|
||||
await testSubjects.click('viewAlertDetailsFlyout');
|
||||
await retry.waitFor(
|
||||
'flyout open',
|
||||
async () => await testSubjects.exists(ALERTS_FLYOUT_SELECTOR, { timeout: 2500 })
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const getAlertsFlyout = async () => {
|
||||
return await testSubjects.find(ALERTS_FLYOUT_SELECTOR);
|
||||
|
@ -190,15 +182,19 @@ export function ObservabilityAlertsCommonProvider({
|
|||
return await testSubjects.existOrFail('viewRuleDetailsFlyout');
|
||||
};
|
||||
|
||||
const getAlertsFlyoutDescriptionListTitles = async (): Promise<WebElementWrapper[]> => {
|
||||
const flyout = await getAlertsFlyout();
|
||||
return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListTitle', flyout);
|
||||
};
|
||||
const getAlertsFlyoutDescriptionListTitles = retryOnStale.wrap(
|
||||
async (): Promise<WebElementWrapper[]> => {
|
||||
const flyout = await getAlertsFlyout();
|
||||
return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListTitle', flyout);
|
||||
}
|
||||
);
|
||||
|
||||
const getAlertsFlyoutDescriptionListDescriptions = async (): Promise<WebElementWrapper[]> => {
|
||||
const flyout = await getAlertsFlyout();
|
||||
return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListDescription', flyout);
|
||||
};
|
||||
const getAlertsFlyoutDescriptionListDescriptions = retryOnStale.wrap(
|
||||
async (): Promise<WebElementWrapper[]> => {
|
||||
const flyout = await getAlertsFlyout();
|
||||
return await testSubjects.findAllDescendant('alertsFlyoutDescriptionListDescription', flyout);
|
||||
}
|
||||
);
|
||||
|
||||
// Cell actions
|
||||
|
||||
|
@ -210,17 +206,19 @@ export function ObservabilityAlertsCommonProvider({
|
|||
return await testSubjects.find(FILTER_FOR_VALUE_BUTTON_SELECTOR);
|
||||
};
|
||||
|
||||
const openActionsMenuForRow = async (rowIndex: number) => {
|
||||
const openActionsMenuForRow = retryOnStale.wrap(async (rowIndex: number) => {
|
||||
const actionsOverflowButton = await getActionsButtonByIndex(rowIndex);
|
||||
await actionsOverflowButton.click();
|
||||
};
|
||||
});
|
||||
|
||||
const viewRuleDetailsButtonClick = async () => {
|
||||
return await (await testSubjects.find(VIEW_RULE_DETAILS_SELECTOR)).click();
|
||||
await testSubjects.click(VIEW_RULE_DETAILS_SELECTOR);
|
||||
};
|
||||
|
||||
const viewRuleDetailsLinkClick = async () => {
|
||||
return await (await testSubjects.find(VIEW_RULE_DETAILS_FLYOUT_SELECTOR)).click();
|
||||
await testSubjects.click(VIEW_RULE_DETAILS_FLYOUT_SELECTOR);
|
||||
};
|
||||
|
||||
// Workflow status
|
||||
const setWorkflowStatusForRow = async (rowIndex: number, workflowStatus: WorkflowStatus) => {
|
||||
await openActionsMenuForRow(rowIndex);
|
||||
|
@ -236,17 +234,14 @@ export function ObservabilityAlertsCommonProvider({
|
|||
await toasts.dismissAllToasts();
|
||||
};
|
||||
|
||||
const setWorkflowStatusFilter = async (workflowStatus: WorkflowStatus) => {
|
||||
const buttonGroupButton = await testSubjects.find(
|
||||
`workflowStatusFilterButton-${workflowStatus}`
|
||||
);
|
||||
await buttonGroupButton.click();
|
||||
};
|
||||
const setWorkflowStatusFilter = retryOnStale.wrap(async (workflowStatus: WorkflowStatus) => {
|
||||
await testSubjects.click(`workflowStatusFilterButton-${workflowStatus}`);
|
||||
});
|
||||
|
||||
const getWorkflowStatusFilterValue = async () => {
|
||||
const getWorkflowStatusFilterValue = retryOnStale.wrap(async () => {
|
||||
const selectedWorkflowStatusButton = await find.byClassName('euiButtonGroupButton-isSelected');
|
||||
return await selectedWorkflowStatusButton.getVisibleText();
|
||||
};
|
||||
});
|
||||
|
||||
// Alert status
|
||||
const setAlertStatusFilter = async (alertStatus?: AlertStatus) => {
|
||||
|
@ -257,8 +252,8 @@ export function ObservabilityAlertsCommonProvider({
|
|||
if (alertStatus === ALERT_STATUS_RECOVERED) {
|
||||
buttonSubject = 'alert-status-filter-recovered-button';
|
||||
}
|
||||
const buttonGroupButton = await testSubjects.find(buttonSubject);
|
||||
await buttonGroupButton.click();
|
||||
|
||||
await testSubjects.click(buttonSubject);
|
||||
};
|
||||
|
||||
const alertDataIsBeingLoaded = async () => {
|
||||
|
@ -277,14 +272,12 @@ export function ObservabilityAlertsCommonProvider({
|
|||
const isAbsoluteRange = await testSubjects.exists('superDatePickerstartDatePopoverButton');
|
||||
|
||||
if (isAbsoluteRange) {
|
||||
const startButton = await testSubjects.find('superDatePickerstartDatePopoverButton');
|
||||
const endButton = await testSubjects.find('superDatePickerendDatePopoverButton');
|
||||
return `${await startButton.getVisibleText()} - ${await endButton.getVisibleText()}`;
|
||||
const startText = await testSubjects.getVisibleText('superDatePickerstartDatePopoverButton');
|
||||
const endText = await testSubjects.getVisibleText('superDatePickerendDatePopoverButton');
|
||||
return `${startText} - ${endText}`;
|
||||
}
|
||||
|
||||
const datePickerButton = await testSubjects.find('superDatePickerShowDatesButton');
|
||||
const buttonText = await datePickerButton.getVisibleText();
|
||||
return buttonText;
|
||||
return await testSubjects.getVisibleText('superDatePickerShowDatesButton');
|
||||
};
|
||||
|
||||
const getActionsButtonByIndex = async (index: number) => {
|
||||
|
@ -294,14 +287,14 @@ export function ObservabilityAlertsCommonProvider({
|
|||
return actionsOverflowButtons[index] || null;
|
||||
};
|
||||
|
||||
const getRuleStatValue = async (testSubj: string) => {
|
||||
const getRuleStatValue = retryOnStale.wrap(async (testSubj: string) => {
|
||||
const stat = await testSubjects.find(testSubj);
|
||||
const title = await stat.findByCssSelector('.euiStat__title');
|
||||
const count = await title.getVisibleText();
|
||||
const value = Number.parseInt(count, 10);
|
||||
expect(Number.isNaN(value)).to.be(false);
|
||||
return value;
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
getQueryBar,
|
||||
|
|
|
@ -20,8 +20,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const esArchiver = getService('esArchiver');
|
||||
const find = getService('find');
|
||||
|
||||
// Failing: See https://github.com/elastic/kibana/issues/140248
|
||||
describe.skip('Observability alerts', function () {
|
||||
describe('Observability alerts', function () {
|
||||
this.tags('includeFirefox');
|
||||
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue