From 444508a349115b59315084badef5e3ec1b757e9f Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Fri, 6 Oct 2023 21:40:34 +0000 Subject: [PATCH 1/2] [BUG][Fuctional Test] Make setDefaultAbsoluteRange more robust and update doc views tests * add a helper function setInputValueWithRetry for setAbsoluteRange to retry time range setup * update doc_views test to click docTableExpandToggleColumn-0 * update doc_views_link to check new tab Issue Resolve https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5241 Signed-off-by: Anan Zhuang --- CHANGELOG.md | 1 + test/functional/page_objects/time_picker.ts | 35 ++++++++++- .../test_suites/doc_views/doc_views.ts | 2 +- .../doc_views_links/doc_views_links.ts | 61 ++++++++++++++++--- 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 020ce3583eb..56ca0743f81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Data Explorer][Discover] Allow data grid to auto adjust size based on fetched data count ([#5191](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5191)) - [BUG] Fix wrong test due to time conversion ([#5174](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5174)) - [BUG][Data Explorer][Discover] Allow filter and query persist when refresh page or paste url to a new tab ([#5206](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5206)) +- [BUG][Fuctional Test] Make setDefaultAbsoluteRange more robust and update doc views tests ([#5242](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5242)) ### 🚞 Infrastructure diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 05108f787b2..0c48f69c65a 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -52,6 +52,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo const { header } = getPageObjects(['header']); const opensearchDashboardsServer = getService('opensearchDashboardsServer'); const MenuToggle = getService('MenuToggle'); + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const quickSelectTimeMenuToggle = new MenuToggle({ name: 'QuickSelectTime Menu', @@ -130,6 +131,32 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.exists('superDatePickerstartDatePopoverButton'); } + // Helper function to set input value and verify + private async setInputValueWithRetry(testSubjectId: string, value: string) { + const MAX_ATTEMPTS = 3; + + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + // try to set the value + await this.inputValue(testSubjectId, value); + await sleep(500); + + // verify if the value was correctly set + const actualValue = await (await testSubjects.find(testSubjectId)).getAttribute('value'); + if (actualValue === value) { + return; + } + + // if it's the last attempt and value wasn't set correctly, throw an error + if (attempt === MAX_ATTEMPTS - 1) { + throw new Error( + `Failed to set ${testSubjectId} to ${value} after ${MAX_ATTEMPTS} attempts.` + ); + } + + await sleep(500); // wait before retrying + } + } + /** * @param {String} fromTime MMM D, YYYY @ HH:mm:ss.SSS * @param {String} toTime MMM D, YYYY @ HH:mm:ss.SSS @@ -137,13 +164,16 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo public async setAbsoluteRange(fromTime: string, toTime: string) { log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); await this.showStartEndTimes(); + // make sure to close this verify panel + await browser.pressKeys(browser.keys.ESCAPE); + await sleep(500); // set to time await testSubjects.click('superDatePickerendDatePopoverButton'); let panel = await this.getTimePickerPanel(); await testSubjects.click('superDatePickerAbsoluteTab'); await testSubjects.click('superDatePickerAbsoluteDateInput'); - await this.inputValue('superDatePickerAbsoluteDateInput', toTime); + await this.setInputValueWithRetry('superDatePickerAbsoluteDateInput', toTime); await browser.pressKeys(browser.keys.ESCAPE); // close popover because sometimes browser can't find start input // set from time @@ -152,7 +182,8 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo panel = await this.getTimePickerPanel(); await testSubjects.click('superDatePickerAbsoluteTab'); await testSubjects.click('superDatePickerAbsoluteDateInput'); - await this.inputValue('superDatePickerAbsoluteDateInput', fromTime); + await this.setInputValueWithRetry('superDatePickerAbsoluteDateInput', fromTime); + await browser.pressKeys(browser.keys.ESCAPE); const superDatePickerApplyButtonExists = await testSubjects.exists( 'superDatePickerApplyTimeButton' diff --git a/test/plugin_functional/test_suites/doc_views/doc_views.ts b/test/plugin_functional/test_suites/doc_views/doc_views.ts index 4a25bed2525..2cecbc0f01e 100644 --- a/test/plugin_functional/test_suites/doc_views/doc_views.ts +++ b/test/plugin_functional/test_suites/doc_views/doc_views.ts @@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('should show custom doc views', async () => { - await testSubjects.click('docTableExpandToggleColumn'); + await testSubjects.click('docTableExpandToggleColumn-0'); const reactTab = await find.byButtonText('React doc view'); expect(await reactTab.isDisplayed()).to.be(true); }); diff --git a/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts b/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts index 2933e0add11..751083d3cda 100644 --- a/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts +++ b/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts @@ -11,36 +11,81 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const find = getService('find'); const browser = getService('browser'); const PageObjects = getPageObjects(['common', 'discover', 'timePicker']); + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + const waitFor = async (conditionFunction, timeoutMs = 10000, intervalMs = 100) => { + const start = Date.now(); + + let lastError; + while (Date.now() - start < timeoutMs) { + try { + if (await conditionFunction()) { + return; + } + } catch (error) { + lastError = error; + } + + await sleep(intervalMs); + } + + throw new Error( + `waitFor condition did not become true within ${timeoutMs}ms. Last error: ${ + lastError && lastError.message + }` + ); + }; describe('custom doc views links', function () { beforeEach(async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await testSubjects.click('docTableExpandToggleColumn'); + await testSubjects.click('docTableExpandToggleColumn-0'); }); - it('should show href and generateCb doc views link', async () => { + it('should show href and generateCb doc views link and not show generateCbHidden doc views link', async () => { const hrefLink = await find.byLinkText('href doc view link'); const generateCbLink = await find.byLinkText('generateCb doc view link'); - expect(await hrefLink.isDisplayed()).to.be(true); expect(await generateCbLink.isDisplayed()).to.be(true); - }); - - it('should not render generateCbHidden doc views link', async () => { expect(await find.existsByLinkText('generateCbHidden doc view link')).to.eql(false); }); it('should render href doc view link', async () => { const hrefLink = await find.byLinkText('href doc view link'); + const originalTabCount = (await browser.getAllWindowHandles()).length; await hrefLink.click(); - expect(await browser.getCurrentUrl()).to.eql('http://some-url/'); + + // wait until a new tab is opened + await waitFor(async () => (await browser.getAllWindowHandles()).length > originalTabCount); + + // switch to the originalTabCount in case previous tab is not closed in time + await browser.switchTab(originalTabCount); + + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.eql('http://some-url/'); + + // close new tab and switch back to original tab + await browser.closeCurrentWindow(); + await browser.switchTab(0); }); it('should render generateCb doc view link', async () => { const generateCbLink = await find.byLinkText('generateCb doc view link'); + const originalTabCount = (await browser.getAllWindowHandles()).length; await generateCbLink.click(); - expect(await browser.getCurrentUrl()).to.eql('http://some-url/'); + + // wait until a new tab is opened + await waitFor(async () => (await browser.getAllWindowHandles()).length > originalTabCount); + + // switch to the originalTabCount in case previous tab is not closed in time + await browser.switchTab(originalTabCount); + + const currentUrl = await browser.getCurrentUrl(); + expect(currentUrl).to.eql('http://some-url/'); + + // close new tab and switch back to original tab + await browser.closeCurrentWindow(); + await browser.switchTab(0); }); }); } From 8e7834ca8a4edc80ce2e61c10ff8fc720b7c1015 Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Mon, 9 Oct 2023 18:01:09 +0000 Subject: [PATCH 2/2] fix PR comment by making a special time picker method for discover Signed-off-by: Anan Zhuang --- test/functional/page_objects/time_picker.ts | 111 +++++++++++++----- .../test_suites/doc_views/doc_views.ts | 4 +- .../doc_views_links/doc_views_links.ts | 4 +- 3 files changed, 85 insertions(+), 34 deletions(-) diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 0c48f69c65a..4aafc4d4b80 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -131,32 +131,6 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.exists('superDatePickerstartDatePopoverButton'); } - // Helper function to set input value and verify - private async setInputValueWithRetry(testSubjectId: string, value: string) { - const MAX_ATTEMPTS = 3; - - for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { - // try to set the value - await this.inputValue(testSubjectId, value); - await sleep(500); - - // verify if the value was correctly set - const actualValue = await (await testSubjects.find(testSubjectId)).getAttribute('value'); - if (actualValue === value) { - return; - } - - // if it's the last attempt and value wasn't set correctly, throw an error - if (attempt === MAX_ATTEMPTS - 1) { - throw new Error( - `Failed to set ${testSubjectId} to ${value} after ${MAX_ATTEMPTS} attempts.` - ); - } - - await sleep(500); // wait before retrying - } - } - /** * @param {String} fromTime MMM D, YYYY @ HH:mm:ss.SSS * @param {String} toTime MMM D, YYYY @ HH:mm:ss.SSS @@ -164,16 +138,13 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo public async setAbsoluteRange(fromTime: string, toTime: string) { log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); await this.showStartEndTimes(); - // make sure to close this verify panel - await browser.pressKeys(browser.keys.ESCAPE); - await sleep(500); // set to time await testSubjects.click('superDatePickerendDatePopoverButton'); let panel = await this.getTimePickerPanel(); await testSubjects.click('superDatePickerAbsoluteTab'); await testSubjects.click('superDatePickerAbsoluteDateInput'); - await this.setInputValueWithRetry('superDatePickerAbsoluteDateInput', toTime); + await this.inputValue('superDatePickerAbsoluteDateInput', toTime); await browser.pressKeys(browser.keys.ESCAPE); // close popover because sometimes browser can't find start input // set from time @@ -182,8 +153,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo panel = await this.getTimePickerPanel(); await testSubjects.click('superDatePickerAbsoluteTab'); await testSubjects.click('superDatePickerAbsoluteDateInput'); - await this.setInputValueWithRetry('superDatePickerAbsoluteDateInput', fromTime); - await browser.pressKeys(browser.keys.ESCAPE); + await this.inputValue('superDatePickerAbsoluteDateInput', fromTime); const superDatePickerApplyButtonExists = await testSubjects.exists( 'superDatePickerApplyTimeButton' @@ -340,6 +310,83 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo const toTime = 'Apr 13, 2018 @ 00:00:00.000'; await this.setAbsoluteRange(fromTime, toTime); } + + // Helper function to set input value and verify + private async setInputValueWithRetry(testSubjectId: string, value: string) { + const MAX_ATTEMPTS = 3; + + for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + // try to set the value + await this.inputValue(testSubjectId, value); + await sleep(500); + + // verify if the value was correctly set + const actualValue = await (await testSubjects.find(testSubjectId)).getAttribute('value'); + if (actualValue === value) { + return; + } + + // if it's the last attempt and value wasn't set correctly, throw an error + if (attempt === MAX_ATTEMPTS - 1) { + throw new Error( + `Failed to set ${testSubjectId} to ${value} after ${MAX_ATTEMPTS} attempts.` + ); + } + + await sleep(500); // wait before retrying + } + } + + // TODO: This is a temporary method added due to observed issues with panels + // not closing in time and incorrect time settings on Discover page. Once these bugs are resolved + // and the interactions become more reliable, we should consider removing this method and related helper functions. + // Tracking issue: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5241 + /** + * @param {String} fromTime MMM D, YYYY @ HH:mm:ss.SSS + * @param {String} toTime MMM D, YYYY @ HH:mm:ss.SSS + */ + public async setDefaultRangeForDiscover() { + const fromTime = this.defaultStartTime; + const toTime = this.defaultEndTime; + log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); + + await this.showStartEndTimes(); + // make sure to close this verify panel + await browser.pressKeys(browser.keys.ESCAPE); + await sleep(500); + + // set to time + await testSubjects.click('superDatePickerendDatePopoverButton'); + let panel = await this.getTimePickerPanel(); + await testSubjects.click('superDatePickerAbsoluteTab'); + await testSubjects.click('superDatePickerAbsoluteDateInput'); + await this.setInputValueWithRetry('superDatePickerAbsoluteDateInput', toTime); + await browser.pressKeys(browser.keys.ESCAPE); // close popover because sometimes browser can't find start input + + // set from time + await testSubjects.click('superDatePickerstartDatePopoverButton'); + await this.waitPanelIsGone(panel); + panel = await this.getTimePickerPanel(); + await testSubjects.click('superDatePickerAbsoluteTab'); + await testSubjects.click('superDatePickerAbsoluteDateInput'); + await this.setInputValueWithRetry('superDatePickerAbsoluteDateInput', fromTime); + await browser.pressKeys(browser.keys.ESCAPE); + + const superDatePickerApplyButtonExists = await testSubjects.exists( + 'superDatePickerApplyTimeButton' + ); + if (superDatePickerApplyButtonExists) { + // Timepicker is in top nav + // Click super date picker apply button to apply time range + await testSubjects.click('superDatePickerApplyTimeButton'); + } else { + // Timepicker is embedded in query bar + // click query bar submit button to apply time range + await testSubjects.click('querySubmitButton'); + } + await this.waitPanelIsGone(panel); + await header.awaitGlobalLoadingIndicatorHidden(); + } } return new TimePicker(); diff --git a/test/plugin_functional/test_suites/doc_views/doc_views.ts b/test/plugin_functional/test_suites/doc_views/doc_views.ts index 2cecbc0f01e..0944c8feb77 100644 --- a/test/plugin_functional/test_suites/doc_views/doc_views.ts +++ b/test/plugin_functional/test_suites/doc_views/doc_views.ts @@ -39,7 +39,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide describe('custom doc views', function () { before(async () => { await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); + // TODO: change back to setDefaultRange() once we resolve + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5241 + await PageObjects.timePicker.setDefaultRangeForDiscover(); }); it('should show custom doc views', async () => { diff --git a/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts b/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts index 751083d3cda..1b1fee37f80 100644 --- a/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts +++ b/test/plugin_functional/test_suites/doc_views_links/doc_views_links.ts @@ -38,7 +38,9 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide describe('custom doc views links', function () { beforeEach(async () => { await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); + // TODO: change back to setDefaultRange() once we resolve + // https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5241 + await PageObjects.timePicker.setDefaultRangeForDiscover(); await testSubjects.click('docTableExpandToggleColumn-0'); });