diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 3ac6c83e61f14..272fc5a7914be 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -35,12 +35,18 @@ export type CommonlyUsed = export function TimePickerProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); - const retry = getService('retry'); const find = getService('find'); const browser = getService('browser'); const testSubjects = getService('testSubjects'); const { header } = getPageObjects(['header']); const kibanaServer = getService('kibanaServer'); + const MenuToggle = getService('MenuToggle'); + + const quickSelectTimeMenuToggle = new MenuToggle({ + name: 'QuickSelectTime Menu', + menuTestSubject: 'superDatePickerQuickMenu', + toggleButtonTestSubject: 'superDatePickerToggleQuickMenuButton', + }); class TimePicker { defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000'; @@ -158,34 +164,8 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo return await find.existsByCssSelector('.euiDatePickerRange--readOnly'); } - public async isQuickSelectMenuOpen() { - return await testSubjects.exists('superDatePickerQuickMenu'); - } - - public async openQuickSelectTimeMenu() { - log.debug('openQuickSelectTimeMenu'); - const isMenuOpen = await this.isQuickSelectMenuOpen(); - if (!isMenuOpen) { - log.debug('opening quick select menu'); - await retry.try(async () => { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - }); - } - } - - public async closeQuickSelectTimeMenu() { - log.debug('closeQuickSelectTimeMenu'); - const isMenuOpen = await this.isQuickSelectMenuOpen(); - if (isMenuOpen) { - log.debug('closing quick select menu'); - await retry.try(async () => { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - }); - } - } - public async getRefreshConfig(keepQuickSelectOpen = false) { - await this.openQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.open(); const interval = await testSubjects.getAttribute( 'superDatePickerRefreshIntervalInput', 'value' @@ -207,7 +187,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo 'superDatePickerToggleRefreshButton' ); if (!keepQuickSelectOpen) { - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } return { @@ -270,26 +250,26 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo } public async startAutoRefresh(intervalS = 3) { - await this.openQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.open(); await this.inputValue('superDatePickerRefreshIntervalInput', intervalS.toString()); const refreshConfig = await this.getRefreshConfig(true); if (refreshConfig.isPaused) { log.debug('start auto refresh'); await testSubjects.click('superDatePickerToggleRefreshButton'); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async pauseAutoRefresh() { log.debug('pauseAutoRefresh'); const refreshConfig = await this.getRefreshConfig(true); + if (!refreshConfig.isPaused) { log.debug('pause auto refresh'); await testSubjects.click('superDatePickerToggleRefreshButton'); - await this.closeQuickSelectTimeMenu(); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async resumeAutoRefresh() { @@ -300,7 +280,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.click('superDatePickerToggleRefreshButton'); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async setHistoricalDataRange() { diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index 56c5e6acd5ad1..e464eaf943627 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -167,10 +167,30 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { }); } - public async getAttribute(selector: string, attribute: string): Promise { - return await retry.try(async () => { - log.debug(`TestSubjects.getAttribute(${selector}, ${attribute})`); - const element = await this.find(selector); + public async getAttribute( + selector: string, + attribute: string, + options?: + | number + | { + findTimeout?: number; + tryTimeout?: number; + } + ): Promise { + const findTimeout = + (typeof options === 'number' ? options : options?.findTimeout) ?? + config.get('timeouts.find'); + + const tryTimeout = + (typeof options !== 'number' ? options?.tryTimeout : undefined) ?? + config.get('timeouts.try'); + + log.debug( + `TestSubjects.getAttribute(${selector}, ${attribute}, tryTimeout=${tryTimeout}, findTimeout=${findTimeout})` + ); + + return await retry.tryForTime(tryTimeout, async () => { + const element = await this.find(selector, findTimeout); return await element.getAttribute(attribute); }); } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 057ae0bd13b6e..2c71fd8ef8f55 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -57,6 +57,7 @@ import { import { ListingTableProvider } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; +import { MenuToggleProvider } from './menu_toggle'; export const services = { ...commonServiceProviders, @@ -93,4 +94,5 @@ export const services = { elasticChart: ElasticChartProvider, supertest: KibanaSupertestProvider, managementMenu: ManagementMenuProvider, + MenuToggle: MenuToggleProvider, }; diff --git a/test/functional/services/menu_toggle.ts b/test/functional/services/menu_toggle.ts new file mode 100644 index 0000000000000..073aa846f47a8 --- /dev/null +++ b/test/functional/services/menu_toggle.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function MenuToggleProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + interface Options { + name: string; + menuTestSubject: string; + toggleButtonTestSubject: string; + } + + return class MenuToggle { + private readonly name: string; + private readonly menuTestSubject: string; + private readonly toggleButtonTestSubject: string; + + constructor(options: Options) { + this.name = options.name; + this.menuTestSubject = options.menuTestSubject; + this.toggleButtonTestSubject = options.toggleButtonTestSubject; + } + + async open() { + await this.setState(true); + } + + async close() { + await this.setState(false); + } + + private async setState(expectedState: boolean) { + log.debug( + `setting menu open state [name=${this.name}] [state=${expectedState ? 'open' : 'closed'}]` + ); + + await retry.try(async () => { + // if the menu is clearly in the expected state already, bail out quickly if so + const isOpen = await testSubjects.exists(this.menuTestSubject, { timeout: 1000 }); + if (isOpen === expectedState) { + return; + } + + // toggle the view state by clicking the button + await testSubjects.click(this.toggleButtonTestSubject); + + if (expectedState === true) { + // wait for up to 10 seconds for the menu to show up, otherwise fail and retry + await testSubjects.existOrFail(this.menuTestSubject, { timeout: 10000 }); + } else { + // wait for the form to hide, otherwise fail and retry + await testSubjects.waitForDeleted(this.menuTestSubject); + } + }); + } + }; +} diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js index 3c935b385cb39..0c8a208e92ece 100644 --- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js @@ -15,6 +15,7 @@ export default function ({ getPageObjects, getService }) { const inspector = getService('inspector'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); + const retry = getService('retry'); describe('embed in dashboard', () => { before(async () => { @@ -50,9 +51,11 @@ export default function ({ getPageObjects, getService }) { it('should populate inspector with requests for map embeddable', async () => { await dashboardPanelActions.openInspectorByTitle('join example'); - const joinExampleRequestNames = await inspector.getRequestNames(); + await retry.try(async () => { + const joinExampleRequestNames = await inspector.getRequestNames(); + expect(joinExampleRequestNames).to.equal('geo_shapes*,meta_for_geo_shapes*.shape_name'); + }); await inspector.close(); - expect(joinExampleRequestNames).to.equal('geo_shapes*,meta_for_geo_shapes*.shape_name'); await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example'); const gridExampleRequestNames = await inspector.getRequestNames(); diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 247e743458817..3b461d4536b02 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -20,6 +20,13 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte const comboBox = getService('comboBox'); const renderable = getService('renderable'); const browser = getService('browser'); + const MenuToggle = getService('MenuToggle'); + + const setViewPopoverToggle = new MenuToggle({ + name: 'SetView Popover', + menuTestSubject: 'mapSetViewForm', + toggleButtonTestSubject: 'toggleSetViewVisibilityButton', + }); function escapeLayerName(layerName: string) { return layerName.split(' ').join('_'); @@ -234,41 +241,11 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte return buttons.length; } - async isSetViewPopoverOpen() { - return await testSubjects.exists('mapSetViewForm', { timeout: 500 }); - } - - async openSetViewPopover() { - const isOpen = await this.isSetViewPopoverOpen(); - if (!isOpen) { - await retry.try(async () => { - await testSubjects.click('toggleSetViewVisibilityButton'); - const isOpenAfterClick = await this.isSetViewPopoverOpen(); - if (!isOpenAfterClick) { - throw new Error('set view popover not opened'); - } - }); - } - } - - async closeSetViewPopover() { - const isOpen = await this.isSetViewPopoverOpen(); - if (isOpen) { - await retry.try(async () => { - await testSubjects.click('toggleSetViewVisibilityButton'); - const isOpenAfterClick = await this.isSetViewPopoverOpen(); - if (isOpenAfterClick) { - throw new Error('set view popover not closed'); - } - }); - } - } - async setView(lat: number, lon: number, zoom: number) { log.debug( `Set view lat: ${lat.toString()}, lon: ${lon.toString()}, zoom: ${zoom.toString()}` ); - await this.openSetViewPopover(); + await setViewPopoverToggle.open(); await testSubjects.setValue('latitudeInput', lat.toString()); await testSubjects.setValue('longitudeInput', lon.toString()); await testSubjects.setValue('zoomInput', zoom.toString()); @@ -278,11 +255,20 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte async getView() { log.debug('Get view'); - await this.openSetViewPopover(); - const lat = await testSubjects.getAttribute('latitudeInput', 'value'); - const lon = await testSubjects.getAttribute('longitudeInput', 'value'); - const zoom = await testSubjects.getAttribute('zoomInput', 'value'); - await this.closeSetViewPopover(); + await setViewPopoverToggle.open(); + // this method is regularly called within a retry, so we need to reduce the timeouts + // of the retries done within the getAttribute method in order to ensure that they fail + // early enough to retry getView() + const getAttributeOptions = { + tryTimeout: 5000, + findTimeout: 1000, + }; + + const lat = await testSubjects.getAttribute('latitudeInput', 'value', getAttributeOptions); + const lon = await testSubjects.getAttribute('longitudeInput', 'value', getAttributeOptions); + const zoom = await testSubjects.getAttribute('zoomInput', 'value', getAttributeOptions); + + await setViewPopoverToggle.close(); return { lat: parseFloat(lat), lon: parseFloat(lon),