Skip to content
This repository has been archived by the owner on Mar 31, 2024. It is now read-only.

Commit

Permalink
[ftr/testSubjects/clickOnEnabled] retry on stale-element exceptions (e…
Browse files Browse the repository at this point in the history
…lastic#139964)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
Spencer and kibanamachine authored Sep 6, 2022
1 parent 06a3e1d commit 5adda1f
Show file tree
Hide file tree
Showing 45 changed files with 206 additions and 103 deletions.
4 changes: 2 additions & 2 deletions test/functional/apps/discover/group2/_data_grid_doc_table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
log.debug(`expanded document id: ${expandDocId}`);

await dataGrid.clickRowToggle();
await find.clickByCssSelectorWhenNotDisabled('#kbn_doc_viewer_tab_1');
await find.clickByCssSelectorWhenNotDisabledWithoutRetry('#kbn_doc_viewer_tab_1');

await retry.waitForWithTimeout(
'document id in flyout matching the expanded document id',
Expand Down Expand Up @@ -140,7 +140,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
log.debug(`expanded document id: ${expandDocId}`);

await dataGrid.clickRowToggle();
await find.clickByCssSelectorWhenNotDisabled('#kbn_doc_viewer_tab_1');
await find.clickByCssSelectorWhenNotDisabledWithoutRetry('#kbn_doc_viewer_tab_1');

await retry.waitForWithTimeout(
'document id in flyout matching the expanded document id',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('interval errors', () => {
before(async () => {
// to trigger displaying of error messages
await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton');
await testSubjects.clickWhenNotDisabledWithoutRetry('visualizeEditorRenderButton');
// this will avoid issues with the play tooltip covering the interval field
await testSubjects.scrollIntoView('advancedParams-2');
});
Expand Down Expand Up @@ -520,7 +520,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visEditor.setInterval('Millisecond');

// Apply interval
await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton');
await testSubjects.clickWhenNotDisabledWithoutRetry('visualizeEditorRenderButton');

const isHelperScaledLabelExists = await find.existsByCssSelector(
'[data-test-subj="currentlyScaledText"]'
Expand All @@ -537,7 +537,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {

it('should update scaled label text after custom interval is set and time range is changed', async () => {
await PageObjects.visEditor.setInterval('10s', { type: 'custom' });
await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton');
await testSubjects.clickWhenNotDisabledWithoutRetry('visualizeEditorRenderButton');
const isHelperScaledLabelExists = await find.existsByCssSelector(
'[data-test-subj="currentlyScaledText"]'
);
Expand Down
2 changes: 1 addition & 1 deletion test/functional/page_objects/discover_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ export class DiscoverPageObject extends FtrService {
public async clickViewModeFieldStatsButton() {
await this.retry.tryForTime(2 * 1000, async () => {
await this.testSubjects.existOrFail('dscViewModeFieldStatsButton');
await this.testSubjects.clickWhenNotDisabled('dscViewModeFieldStatsButton');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('dscViewModeFieldStatsButton');
await this.testSubjects.existOrFail('dscFieldStatsEmbeddedContent');
});
}
Expand Down
10 changes: 6 additions & 4 deletions test/functional/page_objects/settings_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export class SettingsPageObject extends FtrService {
}

async clearFieldTypeFilter(type: string) {
await this.testSubjects.clickWhenNotDisabled('indexedFieldTypeFilterDropdown');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('indexedFieldTypeFilterDropdown');
await this.retry.try(async () => {
await this.testSubjects.existOrFail('indexedFieldTypeFilterDropdown-popover');
});
Expand All @@ -319,7 +319,7 @@ export class SettingsPageObject extends FtrService {
}

async setFieldTypeFilter(type: string) {
await this.testSubjects.clickWhenNotDisabled('indexedFieldTypeFilterDropdown');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('indexedFieldTypeFilterDropdown');
await this.testSubjects.existOrFail('indexedFieldTypeFilterDropdown-popover');
await this.testSubjects.existOrFail(`indexedFieldTypeFilterDropdown-option-${type}`);
await this.testSubjects.click(`indexedFieldTypeFilterDropdown-option-${type}`);
Expand All @@ -328,7 +328,7 @@ export class SettingsPageObject extends FtrService {
}

async clearScriptedFieldLanguageFilter(type: string) {
await this.testSubjects.clickWhenNotDisabled('scriptedFieldLanguageFilterDropdown');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('scriptedFieldLanguageFilterDropdown');
await this.retry.try(async () => {
await this.testSubjects.existOrFail('scriptedFieldLanguageFilterDropdown-popover');
});
Expand All @@ -344,7 +344,9 @@ export class SettingsPageObject extends FtrService {

async setScriptedFieldLanguageFilter(language: string) {
await this.retry.try(async () => {
await this.testSubjects.clickWhenNotDisabled('scriptedFieldLanguageFilterDropdown');
await this.testSubjects.clickWhenNotDisabledWithoutRetry(
'scriptedFieldLanguageFilterDropdown'
);
return await this.find.byCssSelector('div.euiPopover__panel[data-popover-open]');
});
await this.testSubjects.existOrFail('scriptedFieldLanguageFilterDropdown-popover');
Expand Down
2 changes: 1 addition & 1 deletion test/functional/page_objects/visual_builder_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ export class VisualBuilderPageObject extends FtrService {
}

public async applyChanges() {
await this.testSubjects.clickWhenNotDisabled('applyBtn');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('applyBtn');
}

/**
Expand Down
4 changes: 2 additions & 2 deletions test/functional/page_objects/visualize_editor_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class VisualizeEditorPageObject extends FtrService {
}

public async inputControlSubmit() {
await this.testSubjects.clickWhenNotDisabled('inputControlSubmitBtn');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('inputControlSubmitBtn');
await this.visChart.waitForVisualizationRenderingStabilized();
}

Expand All @@ -70,7 +70,7 @@ export class VisualizeEditorPageObject extends FtrService {

const prevRenderingCount = await this.visChart.getVisualizationRenderingCount();
this.log.debug(`Before Rendering count ${prevRenderingCount}`);
await this.testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('visualizeEditorRenderButton');
await this.visChart.waitForRenderingCount(prevRenderingCount + 1);
}

Expand Down
27 changes: 23 additions & 4 deletions test/functional/services/common/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ 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';

export class FindService extends FtrService {
private readonly log = this.ctx.getService('log');
Expand Down Expand Up @@ -285,16 +287,33 @@ export class FindService extends FtrService {
}, timeout);
}

public async clickByCssSelectorWhenNotDisabled(
public async clickByCssSelectorWhenNotDisabled(selector: string, opts?: TimeoutOpt) {
const timeout = opts?.timeout ?? this.defaultFindTimeout;

await retryOnStale(this.log, async () => {
this.log.debug(`Find.clickByCssSelectorWhenNotDisabled(${selector}, timeout=${timeout})`);

const element = await this.byCssSelector(selector);
await element.moveMouseTo();
await this.driver.wait(until.elementIsEnabled(element._webElement), timeout);
await element.click();
});
}

public async clickByCssSelectorWhenNotDisabledWithoutRetry(
selector: string,
{ timeout } = { timeout: this.defaultFindTimeout }
opts?: TimeoutOpt
): Promise<void> {
this.log.debug(`Find.clickByCssSelectorWhenNotDisabled('${selector}') with timeout=${timeout}`);
const timeout = opts?.timeout ?? this.defaultFindTimeout;

this.log.debug(
`Find.clickByCssSelectorWhenNotDisabledWithoutRetry(${selector}, timeout=${timeout})`
);

// Don't wrap this code in a retry, or stale element checks may get caught here and the element
// will never be re-grabbed. Let errors bubble, but continue checking for disabled property until
// it's gone.
const element = await this.byCssSelector(selector, timeout);
const element = await this.byCssSelector(selector);
await element.moveMouseTo();
await this.driver.wait(until.elementIsEnabled(element._webElement), timeout);
await element.click();
Expand Down
35 changes: 35 additions & 0 deletions test/functional/services/common/retry_on_stale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ToolingLog } from '@kbn/tooling-log';

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`);
}

log.warning('stale element exception caught, retrying');
continue;
}

throw error;
}
}
}
32 changes: 26 additions & 6 deletions test/functional/services/common/test_subjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import testSubjSelector from '@kbn/test-subj-selector';
import { WebElementWrapper } from '../lib/web_element_wrapper';
import { FtrService } from '../../ftr_provider_context';
import { TimeoutOpt } from './types';

interface ExistsOptions {
timeout?: number;
Expand Down Expand Up @@ -111,14 +112,33 @@ export class TestSubjects extends FtrService {
await input.type(text);
}

public async clickWhenNotDisabled(
/**
* Clicks on the element identified by the testSubject selector. If the retries
* automatically on "stale element" errors unlike clickWhenNotDisabledWithoutRetry.
* `opts.timeout` defaults to the 'timeouts.find' config, which defaults to 10 seconds
*/
public async clickWhenNotDisabled(selector: string, opts?: TimeoutOpt) {
this.log.debug(`TestSubjects.clickWhenNotDisabled(${selector})`);
await this.findService.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), opts);
}

/**
* Clicks on the element identified by the testSubject selector. Somewhat surprisingly,
* this method allows `stale element` errors to propogate, which is why it was renamed
* from `clickWhenNotDisabled()` and that method was re-implemented to be more consistent
* with the rest of the functions in this service.
*
* `opts.timeout` defaults to the 'timeouts.find' config, which defaults to 10 seconds
*/
public async clickWhenNotDisabledWithoutRetry(
selector: string,
{ timeout = this.FIND_TIME }: { timeout?: number } = {}
opts?: TimeoutOpt
): Promise<void> {
this.log.debug(`TestSubjects.clickWhenNotDisabled(${selector})`);
await this.findService.clickByCssSelectorWhenNotDisabled(testSubjSelector(selector), {
timeout,
});
this.log.debug(`TestSubjects.clickWhenNotDisabledWithoutRetry(${selector})`);
await this.findService.clickByCssSelectorWhenNotDisabledWithoutRetry(
testSubjSelector(selector),
opts
);
}

public async click(
Expand Down
11 changes: 11 additions & 0 deletions test/functional/services/common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export interface TimeoutOpt {
timeout?: number;
}
4 changes: 2 additions & 2 deletions test/functional/services/dashboard/panel_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class DashboardPanelActionsService extends FtrService {
await this.openContextMenu();
const isActionVisible = await this.testSubjects.exists(EDIT_PANEL_DATA_TEST_SUBJ);
if (!isActionVisible) await this.clickContextMenuMoreItem();
await this.testSubjects.clickWhenNotDisabled(EDIT_PANEL_DATA_TEST_SUBJ);
await this.testSubjects.clickWhenNotDisabledWithoutRetry(EDIT_PANEL_DATA_TEST_SUBJ);
await this.header.waitUntilLoadingHasFinished();
await this.common.waitForTopNavToBeVisible();
}
Expand All @@ -92,7 +92,7 @@ export class DashboardPanelActionsService extends FtrService {
} else {
await this.openContextMenu();
}
await this.testSubjects.clickWhenNotDisabled(EDIT_PANEL_DATA_TEST_SUBJ);
await this.testSubjects.clickWhenNotDisabledWithoutRetry(EDIT_PANEL_DATA_TEST_SUBJ);
}

async clickExpandPanelToggle() {
Expand Down
8 changes: 6 additions & 2 deletions test/functional/services/field_editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,18 @@ export class FieldEditorService extends FtrService {
public async confirmSave() {
await this.retry.try(async () => {
await this.testSubjects.setValue('saveModalConfirmText', 'change');
await this.testSubjects.clickWhenNotDisabled('confirmModalConfirmButton', { timeout: 1000 });
await this.testSubjects.clickWhenNotDisabledWithoutRetry('confirmModalConfirmButton', {
timeout: 1000,
});
});
}

public async confirmDelete() {
await this.retry.try(async () => {
await this.testSubjects.setValue('deleteModalConfirmText', 'remove');
await this.testSubjects.clickWhenNotDisabled('confirmModalConfirmButton', { timeout: 1000 });
await this.testSubjects.clickWhenNotDisabledWithoutRetry('confirmModalConfirmButton', {
timeout: 1000,
});
});
}
}
2 changes: 1 addition & 1 deletion test/functional/services/filter_bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export class FilterBarService extends FtrService {
}
}

await this.testSubjects.clickWhenNotDisabled('saveFilter');
await this.testSubjects.clickWhenNotDisabledWithoutRetry('saveFilter');
});
await this.header.awaitGlobalLoadingIndicatorHidden();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('should start search, save session, restore session using "restore" button', async () => {
await comboBox.setCustom('dataViewSelector', 'logstash-*');
await comboBox.setCustom('searchMetricField', 'bytes');
await testSubjects.clickWhenNotDisabled('startSearch');
await testSubjects.clickWhenNotDisabledWithoutRetry('startSearch');
await testSubjects.find('searchResults-1');
await searchSessions.expectState('completed');
await searchSessions.save();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});

it('navigates to Discover app on action click carrying over pie slice filter', async () => {
await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ);
await testSubjects.clickWhenNotDisabledWithoutRetry(ACTION_TEST_SUBJ);
await discover.waitForDiscoverAppOnScreen();
await filterBar.hasFilter('memory', '160,000 to 200,000');
const filterCount = await filterBar.getFilterCount();
Expand Down Expand Up @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});

it('navigates to Discover on click carrying over brushed time range', async () => {
await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ);
await testSubjects.clickWhenNotDisabledWithoutRetry(ACTION_TEST_SUBJ);
await discover.waitForDiscoverAppOnScreen();
const newTimeRangeDurationHours = await timePicker.getTimeDurationInHours();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});

it('navigates to Discover app to index pattern of the panel on action click', async () => {
await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ);
await testSubjects.clickWhenNotDisabledWithoutRetry(ACTION_TEST_SUBJ);
await discover.waitForDiscoverAppOnScreen();

const el = await testSubjects.find('discover-dataView-switch-link');
Expand All @@ -87,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboard.saveDashboard('Dashboard with Pie Chart');

await panelActions.openContextMenu();
await testSubjects.clickWhenNotDisabled(ACTION_TEST_SUBJ);
await testSubjects.clickWhenNotDisabledWithoutRetry(ACTION_TEST_SUBJ);
await discover.waitForDiscoverAppOnScreen();

const text = await timePicker.getShowDatesButtonText();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
);

// check the JSON tab
await find.clickByCssSelectorWhenNotDisabled('#kbn_doc_viewer_tab_1');
await find.clickByCssSelectorWhenNotDisabledWithoutRetry('#kbn_doc_viewer_tab_1');
await retry.waitForWithTimeout(
'index in flyout JSON tab is matching the logstash index',
5000,
Expand Down
Loading

0 comments on commit 5adda1f

Please sign in to comment.