From 54b5286f5ab251ee04f47b4e0290a13ae07c6022 Mon Sep 17 00:00:00 2001 From: Dmitry Lemeshko Date: Mon, 18 Nov 2019 20:08:22 +0100 Subject: [PATCH] [page_objects/common_page] convert to ts (#50771) * [page_objects/common_page] convert to ts * fix lint errors * descrease navigation timeouts * use template literal for log messages --- .../lib/config/config.ts | 4 +- .../{common_page.js => common_page.ts} | 364 +++++++++--------- test/functional/page_objects/index.ts | 1 - .../feature_controls/canvas_security.ts | 4 +- .../saved_objects_management_security.ts | 6 +- 5 files changed, 182 insertions(+), 197 deletions(-) rename test/functional/page_objects/{common_page.js => common_page.ts} (53%) diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index 03eb048a125bb..ad9247523797a 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -58,7 +58,7 @@ export class Config { this[$values] = value; } - public has(key: string) { + public has(key: string | string[]) { function recursiveHasCheck( remainingPath: string[], values: Record, @@ -109,7 +109,7 @@ export class Config { return recursiveHasCheck(path, this[$values], schema); } - public get(key: string, defaultValue?: any) { + public get(key: string | string[], defaultValue?: any) { if (!this.has(key)) { throw new Error(`Unknown config key "${key}"`); } diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.ts similarity index 53% rename from test/functional/page_objects/common_page.js rename to test/functional/page_objects/common_page.ts index 5320ba978cbe9..19a7bd25db9c6 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.ts @@ -19,10 +19,13 @@ import { delay } from 'bluebird'; import expect from '@kbn/expect'; +// @ts-ignore import fetch from 'node-fetch'; +import { FtrProviderContext } from '../ftr_provider_context'; +// @ts-ignore not TS yet import getUrl from '../../../src/test_utils/get_url'; -export function CommonPageProvider({ getService, getPageObjects }) { +export function CommonPageProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const config = getService('config'); const browser = getService('browser'); @@ -30,19 +33,22 @@ export function CommonPageProvider({ getService, getPageObjects }) { const find = getService('find'); const globalNav = getService('globalNav'); const testSubjects = getService('testSubjects'); - const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['shield']); const defaultTryTimeout = config.get('timeouts.try'); const defaultFindTimeout = config.get('timeouts.find'); class CommonPage { - - static async navigateToUrlAndHandleAlert(url, shouldAcceptAlert) { + /** + * Navigates the browser window to provided URL + * @param url URL + * @param shouldAcceptAlert pass 'true' if browser alert should be accepted + */ + private static async navigateToUrlAndHandleAlert(url: string, shouldAcceptAlert: boolean) { log.debug('Navigate to: ' + url); try { await browser.get(url); - } catch(navigationError) { + } catch (navigationError) { log.debug('Error navigating to url'); const alert = await browser.getAlert(); if (alert && alert.accept) { @@ -50,7 +56,7 @@ export function CommonPageProvider({ getService, getPageObjects }) { log.debug('Should accept alert'); try { await alert.accept(); - } catch(alertException) { + } catch (alertException) { log.debug('Error accepting alert'); throw alertException; } @@ -64,63 +70,85 @@ export function CommonPageProvider({ getService, getPageObjects }) { } } - getHostPort() { + /** + * Returns Kibana host URL + */ + public getHostPort() { return getUrl.baseUrl(config.get('servers.kibana')); } - getEsHostPort() { + /** + * Returns ES host URL + */ + public getEsHostPort() { return getUrl.baseUrl(config.get('servers.elasticsearch')); } /** - * @param {string} appName As defined in the apps config - * @param {string} subUrl The route after the hash (#) + * Logins to Kibana as default user and navigates to provided app + * @param appUrl Kibana URL */ - async navigateToUrl(appName, subUrl, { - basePath = '', - ensureCurrentUrl = true, - shouldLoginIfPrompted = true, - shouldAcceptAlert = true - } = {}) { - // we onlt use the pathname from the appConfig and use the subUrl as the hash - const appConfig = { - pathname: `${basePath}${config.get(['apps', appName]).pathname}`, - hash: `/${appName}/${subUrl}`, - }; - - const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig); - - await retry.try(async () => { - await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert); - const currentUrl = shouldLoginIfPrompted ? await this.loginIfPrompted(appUrl) : await browser.getCurrentUrl(); + private async loginIfPrompted(appUrl: string) { + let currentUrl = await browser.getCurrentUrl(); + log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`); + await find.byCssSelector('[data-test-subj="kibanaChrome"]', 6 * defaultFindTimeout); // 60 sec waiting + const loginPage = currentUrl.includes('/login'); + const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout'); - if (ensureCurrentUrl && !currentUrl.includes(appUrl)) { - throw new Error(`expected ${currentUrl}.includes(${appUrl})`); - } - }); + if (loginPage && !wantedLoginPage) { + log.debug( + `Found login page. Logging in with username = ${config.get('servers.kibana.username')}` + ); + await PageObjects.shield.login( + config.get('servers.kibana.username'), + config.get('servers.kibana.password') + ); + await find.byCssSelector( + '[data-test-subj="kibanaChrome"] nav:not(.ng-hide)', + 2 * defaultFindTimeout + ); + await browser.get(appUrl); + currentUrl = await browser.getCurrentUrl(); + log.debug(`Finished login process currentUrl = ${currentUrl}`); + } + return currentUrl; } /** - * @param {string} appName As defined in the apps config - * @param {string} hash The route after the hash (#) + * Navigates browser using the pathname from the appConfig and subUrl as the hash + * @param appName As defined in the apps config, e.g. 'home' + * @param subUrl The route after the hash (#), e.g. 'tutorial_directory/sampleData' + * @param args additional arguments */ - async navigateToActualUrl(appName, hash, { - basePath = '', - ensureCurrentUrl = true, - shouldLoginIfPrompted = true - } = {}) { - // we only use the apps config to get the application path + public async navigateToUrl( + appName: string, + subUrl?: string, + { + basePath = '', + ensureCurrentUrl = true, + shouldLoginIfPrompted = true, + shouldAcceptAlert = true, + useActualUrl = false, + } = {} + ) { const appConfig = { pathname: `${basePath}${config.get(['apps', appName]).pathname}`, - hash, + hash: useActualUrl ? subUrl : `/${appName}/${subUrl}`, }; const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig); + await retry.try(async () => { - log.debug(`navigateToActualUrl ${appUrl}`); - await browser.get(appUrl); + if (useActualUrl) { + log.debug(`navigateToActualUrl ${appUrl}`); + await browser.get(appUrl); + } else { + await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert); + } - const currentUrl = shouldLoginIfPrompted ? await this.loginIfPrompted(appUrl) : await browser.getCurrentUrl(); + const currentUrl = shouldLoginIfPrompted + ? await this.loginIfPrompted(appUrl) + : await browser.getCurrentUrl(); if (ensureCurrentUrl && !currentUrl.includes(appUrl)) { throw new Error(`expected ${currentUrl}.includes(${appUrl})`); @@ -128,32 +156,42 @@ export function CommonPageProvider({ getService, getPageObjects }) { }); } - - async loginIfPrompted(appUrl) { - let currentUrl = await browser.getCurrentUrl(); - log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`); - await find.byCssSelector('[data-test-subj="kibanaChrome"]', defaultTryTimeout * 2); - const loginPage = currentUrl.includes('/login'); - const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout'); - - if (loginPage && !wantedLoginPage) { - log.debug(`Found login page. Logging in with username = ${config.get('servers.kibana.username')}`); - await PageObjects.shield.login( - config.get('servers.kibana.username'), - config.get('servers.kibana.password') - ); - await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide)', 20000); - await browser.get(appUrl); - currentUrl = await browser.getCurrentUrl(); - log.debug(`Finished login process currentUrl = ${currentUrl}`); - } - return currentUrl; + /** + * Navigates browser using only the pathname from the appConfig + * @param appName As defined in the apps config, e.g. 'kibana' + * @param hash The route after the hash (#), e.g. 'management/kibana/settings' + * @param args additional arguments + */ + async navigateToActualUrl( + appName: string, + hash?: string, + { + basePath = '', + ensureCurrentUrl = true, + shouldLoginIfPrompted = true, + shouldAcceptAlert = true, + } = {} + ) { + await this.navigateToUrl(appName, hash, { + basePath, + ensureCurrentUrl, + shouldLoginIfPrompted, + shouldAcceptAlert, + useActualUrl: true, + }); } - navigateToApp(appName, { basePath = '', shouldLoginIfPrompted = true, shouldAcceptAlert = true, hash = '' } = {}) { - const self = this; + async sleep(sleepMilliseconds: number) { + log.debug(`... sleep(${sleepMilliseconds}) start`); + await delay(sleepMilliseconds); + log.debug(`... sleep(${sleepMilliseconds}) end`); + } - let appUrl; + async navigateToApp( + appName: string, + { basePath = '', shouldLoginIfPrompted = true, shouldAcceptAlert = true, hash = '' } = {} + ) { + let appUrl: string; if (config.has(['apps', appName])) { // Legacy applications const appConfig = config.get(['apps', appName]); @@ -164,116 +202,73 @@ export function CommonPageProvider({ getService, getPageObjects }) { } else { appUrl = getUrl.noAuth(config.get('servers.kibana'), { pathname: `${basePath}/app/${appName}`, - hash + hash, }); } log.debug('navigating to ' + appName + ' url: ' + appUrl); - function navigateTo(url) { - return retry.try(function () { + await retry.tryForTime(defaultTryTimeout * 2, async () => { + let lastUrl = await retry.try(async () => { // since we're using hash URLs, always reload first to force re-render - return kibanaServer.uiSettings.getDefaultIndex() - .then(async function () { - return await CommonPage.navigateToUrlAndHandleAlert(url, shouldAcceptAlert); - }) - .then(function () { - return self.sleep(700); - }) - .then(function () { - log.debug('returned from get, calling refresh'); - return browser.refresh(); - }) - .then(async function () { - const currentUrl = shouldLoginIfPrompted ? await self.loginIfPrompted(appUrl) : browser.getCurrentUrl(); - - if (currentUrl.includes('app/kibana')) { - await testSubjects.find('kibanaChrome'); - } - }) - .then(async function () { - - const currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//'); - const maxAdditionalLengthOnNavUrl = 230; - // On several test failures at the end of the TileMap test we try to navigate back to - // Visualize so we can create the next Vertical Bar Chart, but we can see from the - // logging and the screenshot that it's still on the TileMap page. Why didn't the "get" - // with a new timestamped URL go? I thought that sleep(700) between the get and the - // refresh would solve the problem but didn't seem to always work. - // So this hack fails the navSuccessful check if the currentUrl doesn't match the - // appUrl plus up to 230 other chars. - // Navigating to Settings when there is a default index pattern has a URL length of 196 - // (from debug output). Some other tabs may also be long. But a rather simple configured - // visualization is about 1000 chars long. So at least we catch that case. - - // Browsers don't show the ':port' if it's 80 or 443 so we have to - // remove that part so we can get a match in the tests. - const navSuccessful = new RegExp(appUrl.replace(':80/', '/').replace(':443/', '/') - + '.{0,' + maxAdditionalLengthOnNavUrl + '}$') - .test(currentUrl); - - if (!navSuccessful) { - const msg = 'App failed to load: ' + appName + - ' in ' + defaultFindTimeout + 'ms' + - ' appUrl = ' + appUrl + - ' currentUrl = ' + currentUrl; - log.debug(msg); - throw new Error(msg); - } - - return currentUrl; - }); + await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert); + await this.sleep(700); + log.debug('returned from get, calling refresh'); + await browser.refresh(); + let currentUrl = shouldLoginIfPrompted + ? await this.loginIfPrompted(appUrl) + : await browser.getCurrentUrl(); + + if (currentUrl.includes('app/kibana')) { + await testSubjects.find('kibanaChrome'); + } + + currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//'); + const maxAdditionalLengthOnNavUrl = 230; + + // On several test failures at the end of the TileMap test we try to navigate back to + // Visualize so we can create the next Vertical Bar Chart, but we can see from the + // logging and the screenshot that it's still on the TileMap page. Why didn't the "get" + // with a new timestamped URL go? I thought that sleep(700) between the get and the + // refresh would solve the problem but didn't seem to always work. + // So this hack fails the navSuccessful check if the currentUrl doesn't match the + // appUrl plus up to 230 other chars. + // Navigating to Settings when there is a default index pattern has a URL length of 196 + // (from debug output). Some other tabs may also be long. But a rather simple configured + // visualization is about 1000 chars long. So at least we catch that case. + + // Browsers don't show the ':port' if it's 80 or 443 so we have to + // remove that part so we can get a match in the tests. + const navSuccessful = new RegExp( + appUrl.replace(':80/', '/').replace(':443/', '/') + + `.{0,${maxAdditionalLengthOnNavUrl}}$` + ).test(currentUrl); + + if (!navSuccessful) { + const msg = `App failed to load: ${appName} in ${defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`; + log.debug(msg); + throw new Error(msg); + } + return currentUrl; }); - } - return retry.tryForTime(defaultTryTimeout * 3, () => { - return navigateTo(appUrl) - .then(function (currentUrl) { - let lastUrl = currentUrl; - return retry.try(function () { - // give the app time to update the URL - return self.sleep(501) - .then(function () { - return browser.getCurrentUrl(); - }) - .then(function (currentUrl) { - log.debug('in navigateTo url = ' + currentUrl); - if (lastUrl !== currentUrl) { - lastUrl = currentUrl; - throw new Error('URL changed, waiting for it to settle'); - } - }); - }); - }) - .then(async () => { - if (appName === 'status_page') return; - if (await testSubjects.exists('statusPageContainer')) { - throw new Error('Navigation ended up at the status page.'); - } - }); + await retry.try(async () => { + await this.sleep(501); + const currentUrl = await browser.getCurrentUrl(); + log.debug('in navigateTo url = ' + currentUrl); + if (lastUrl !== currentUrl) { + lastUrl = currentUrl; + throw new Error('URL changed, waiting for it to settle'); + } + }); + if (appName === 'status_page') return; + if (await testSubjects.exists('statusPageContainer')) { + throw new Error('Navigation ended up at the status page.'); + } }); } - async sleep(sleepMilliseconds) { - log.debug('... sleep(' + sleepMilliseconds + ') start'); - await delay(sleepMilliseconds); - log.debug('... sleep(' + sleepMilliseconds + ') end'); - } - - createErrorHandler(testObj) { - const testName = (testObj.parent) ? [testObj.parent.name, testObj.name].join('_') : testObj.name; - return error => { - const now = Date.now(); - const fileName = `failure_${now}_${testName}`; - - return this.saveScreenshot(fileName, true) - .then(function () { - throw error; - }); - }; - } - - async waitUntilUrlIncludes(path) { + async waitUntilUrlIncludes(path: string) { await retry.try(async () => { const url = await browser.getCurrentUrl(); if (!url.includes(path)) { @@ -288,13 +283,10 @@ export function CommonPageProvider({ getService, getPageObjects }) { return { title: await element.getAttribute('data-title'), - description: await element.getAttribute('data-description') + description: await element.getAttribute('data-description'), }; } - /** - * Makes sure the modal overlay is not showing, tries a few times in case it is in the process of hiding. - */ async ensureModalOverlayHidden() { return retry.try(async () => { const shown = await testSubjects.exists('confirmModalTitleText'); @@ -316,9 +308,11 @@ export function CommonPageProvider({ getService, getPageObjects }) { await browser.pressKeys(browser.keys.ENTER); } - // pass in true if your test will show multiple modals - // in succession - async clickCancelOnModal(overlayWillStay = false) { + /** + * Clicks cancel button on modal + * @param overlayWillStay pass in true if your test will show multiple modals in succession + */ + async clickCancelOnModal(overlayWillStay = true) { log.debug('Clicking modal cancel'); await testSubjects.click('confirmModalCancelButton'); if (!overlayWillStay) { @@ -326,19 +320,17 @@ export function CommonPageProvider({ getService, getPageObjects }) { } } - async expectConfirmModalOpenState(state) { - if (typeof state !== 'boolean') { - throw new Error('pass true or false to expectConfirmModalOpenState()'); - } - + async expectConfirmModalOpenState(state: boolean) { log.debug(`expectConfirmModalOpenState(${state})`); - // we use retry here instead of a simple .exists() check because the modal // fades in/out, which takes time, and we really only care that at some point // the modal is either open or closed await retry.try(async () => { const actualState = await testSubjects.exists('confirmModalCancelButton'); - expect(actualState).to.be(state); + expect(actualState).to.equal( + state, + state ? 'Confirm modal should be present' : 'Confirm modal should be hidden' + ); }); } @@ -364,16 +356,10 @@ export function CommonPageProvider({ getService, getPageObjects }) { } async closeToast() { - let toast; - await retry.try(async () => { - toast = await find.byCssSelector('.euiToast'); - if (!toast) { - throw new Error('Toast is not visible yet'); - } - }); + const toast = await find.byCssSelector('.euiToast', 2 * defaultFindTimeout); await toast.moveMouseTo(); const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText(); - log.debug(title); + log.debug(`Toast title: ${title}`); await find.clickByCssSelector('.euiToast__closeButton'); return title; } @@ -392,7 +378,7 @@ export function CommonPageProvider({ getService, getPageObjects }) { } async getBodyText() { - if (await find.existsByCssSelector('a[id=rawdata-tab]', 10000)) { + if (await find.existsByCssSelector('a[id=rawdata-tab]', defaultFindTimeout)) { // Firefox has 3 tabs and requires navigation to see Raw output await find.clickByCssSelector('a[id=rawdata-tab]'); } @@ -418,7 +404,7 @@ export function CommonPageProvider({ getService, getPageObjects }) { method: 'get', headers: { 'Content-Type': 'application/json', - 'Authorization': 'Basic ' + Buffer.from(username + ':' + password).toString('base64') + Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'), }, }); return response.status !== 200; diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 84562990191d1..5a104c8d17bf2 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore not TS yet import { CommonPageProvider } from './common_page'; // @ts-ignore not TS yet import { ConsolePageProvider } from './console_page'; diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index a55ada12b4749..e9e1436241308 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -86,7 +86,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it(`allows a workpad to be created`, async () => { await PageObjects.common.navigateToActualUrl('canvas', 'workpad/create', { ensureCurrentUrl: true, - showLoginIfPrompted: false, + shouldLoginIfPrompted: false, }); await PageObjects.canvas.expectAddElementButton(); @@ -98,7 +98,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { 'workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31', { ensureCurrentUrl: true, - showLoginIfPrompted: false, + shouldLoginIfPrompted: false, } ); diff --git a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts index 692dc589cc157..619a85c616b2e 100644 --- a/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts +++ b/x-pack/test/functional/apps/saved_objects_management/feature_controls/saved_objects_management_security.ts @@ -115,7 +115,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { 'kibana', '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { - loginIfPrompted: false, + shouldLoginIfPrompted: false, } ); }); @@ -233,7 +233,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { 'kibana', '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { - loginIfPrompted: false, + shouldLoginIfPrompted: false, } ); await testSubjects.existOrFail('savedObjectsEdit'); @@ -316,7 +316,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { 'kibana', '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed', { - loginIfPrompted: false, + shouldLoginIfPrompted: false, ensureCurrentUrl: false, } );