diff --git a/packages/kbn-test/src/functional_test_runner/public_types.ts b/packages/kbn-test/src/functional_test_runner/public_types.ts index 4a30744c09b516..d94f61e23b8b80 100644 --- a/packages/kbn-test/src/functional_test_runner/public_types.ts +++ b/packages/kbn-test/src/functional_test_runner/public_types.ts @@ -74,6 +74,12 @@ export interface GenericFtrProviderContext< getService(serviceName: 'failureMetadata'): FailureMetadata; getService(serviceName: T): ServiceMap[T]; + /** + * Get the instance of a page object + * @param pageObjectName + */ + getPageObject(pageObjectName: K): PageObjectMap[K]; + /** * Get a map of PageObjects * @param pageObjects diff --git a/test/accessibility/ftr_provider_context.d.ts b/test/accessibility/ftr_provider_context.ts similarity index 78% rename from test/accessibility/ftr_provider_context.d.ts rename to test/accessibility/ftr_provider_context.ts index 4c827393e1ef3b..a1a29f50b77611 100644 --- a/test/accessibility/ftr_provider_context.d.ts +++ b/test/accessibility/ftr_provider_context.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; import { pageObjects } from './page_objects'; import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/test/accessibility/services/a11y/a11y.ts b/test/accessibility/services/a11y/a11y.ts index ea205e8121eba0..4b01b0dd3b9531 100644 --- a/test/accessibility/services/a11y/a11y.ts +++ b/test/accessibility/services/a11y/a11y.ts @@ -9,7 +9,7 @@ import chalk from 'chalk'; import testSubjectToCss from '@kbn/test-subj-selector'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrService } from '../../ftr_provider_context'; import { AxeReport, printResult } from './axe_report'; // @ts-ignore JS that is run in browser as is import { analyzeWithAxe, analyzeWithAxeWithClient } from './analyze_with_axe'; @@ -33,86 +33,84 @@ export const normalizeResult = (report: any) => { return report.result as false | AxeReport; }; -export function A11yProvider({ getService }: FtrProviderContext) { - const browser = getService('browser'); - const Wd = getService('__webdriver__'); - - /** - * Accessibility testing service using the Axe (https://www.deque.com/axe/) - * toolset to validate a11y rules similar to ESLint. In order to test against - * the rules we must load up the UI and feed a full HTML snapshot into Axe. - */ - return new (class Accessibility { - public async testAppSnapshot(options: TestOptions = {}) { - const context = this.getAxeContext(true, options.excludeTestSubj); - const report = await this.captureAxeReport(context); - await this.testAxeReport(report); - } +/** + * Accessibility testing service using the Axe (https://www.deque.com/axe/) + * toolset to validate a11y rules similar to ESLint. In order to test against + * the rules we must load up the UI and feed a full HTML snapshot into Axe. + */ +export class AccessibilityService extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly Wd = this.ctx.getService('__webdriver__'); + + public async testAppSnapshot(options: TestOptions = {}) { + const context = this.getAxeContext(true, options.excludeTestSubj); + const report = await this.captureAxeReport(context); + this.assertValidAxeReport(report); + } - public async testGlobalSnapshot(options: TestOptions = {}) { - const context = this.getAxeContext(false, options.excludeTestSubj); - const report = await this.captureAxeReport(context); - await this.testAxeReport(report); - } + public async testGlobalSnapshot(options: TestOptions = {}) { + const context = this.getAxeContext(false, options.excludeTestSubj); + const report = await this.captureAxeReport(context); + this.assertValidAxeReport(report); + } - private getAxeContext(global: boolean, excludeTestSubj?: string | string[]): AxeContext { - return { - include: global ? undefined : [testSubjectToCss('appA11yRoot')], - exclude: ([] as string[]) - .concat(excludeTestSubj || []) - .map((ts) => [testSubjectToCss(ts)]) - .concat([['[role="graphics-document"][aria-roledescription="visualization"]']]), - }; - } + private getAxeContext(global: boolean, excludeTestSubj?: string | string[]): AxeContext { + return { + include: global ? undefined : [testSubjectToCss('appA11yRoot')], + exclude: ([] as string[]) + .concat(excludeTestSubj || []) + .map((ts) => [testSubjectToCss(ts)]) + .concat([['[role="graphics-document"][aria-roledescription="visualization"]']]), + }; + } - private testAxeReport(report: AxeReport) { - const errorMsgs = []; + private assertValidAxeReport(report: AxeReport) { + const errorMsgs = []; - for (const result of report.violations) { - errorMsgs.push(printResult(chalk.red('VIOLATION'), result)); - } + for (const result of report.violations) { + errorMsgs.push(printResult(chalk.red('VIOLATION'), result)); + } - if (errorMsgs.length) { - throw new Error(`a11y report:\n${errorMsgs.join('\n')}`); - } + if (errorMsgs.length) { + throw new Error(`a11y report:\n${errorMsgs.join('\n')}`); } + } - private async captureAxeReport(context: AxeContext): Promise { - const axeOptions = { - reporter: 'v2', - runOnly: ['wcag2a', 'wcag2aa'], - rules: { - 'color-contrast': { - enabled: false, // disabled because we have too many failures - }, - bypass: { - enabled: false, // disabled because it's too flaky - }, + private async captureAxeReport(context: AxeContext): Promise { + const axeOptions = { + reporter: 'v2', + runOnly: ['wcag2a', 'wcag2aa'], + rules: { + 'color-contrast': { + enabled: false, // disabled because we have too many failures }, - }; - - await (Wd.driver.manage() as any).setTimeouts({ - ...(await (Wd.driver.manage() as any).getTimeouts()), - script: 600000, - }); + bypass: { + enabled: false, // disabled because it's too flaky + }, + }, + }; - const report = normalizeResult( - await browser.executeAsync(analyzeWithAxe, context, axeOptions) - ); + await this.Wd.driver.manage().setTimeouts({ + ...(await this.Wd.driver.manage().getTimeouts()), + script: 600000, + }); - if (report !== false) { - return report; - } + const report = normalizeResult( + await this.browser.executeAsync(analyzeWithAxe, context, axeOptions) + ); - const withClientReport = normalizeResult( - await browser.executeAsync(analyzeWithAxeWithClient, context, axeOptions) - ); + if (report !== false) { + return report; + } - if (withClientReport === false) { - throw new Error('Attempted to analyze with axe but failed to load axe client'); - } + const withClientReport = normalizeResult( + await this.browser.executeAsync(analyzeWithAxeWithClient, context, axeOptions) + ); - return withClientReport; + if (withClientReport === false) { + throw new Error('Attempted to analyze with axe but failed to load axe client'); } - })(); + + return withClientReport; + } } diff --git a/test/accessibility/services/a11y/index.ts b/test/accessibility/services/a11y/index.ts index 79912dd99d326f..642b170c4e0771 100644 --- a/test/accessibility/services/a11y/index.ts +++ b/test/accessibility/services/a11y/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { A11yProvider } from './a11y'; +export * from './a11y'; diff --git a/test/accessibility/services/index.ts b/test/accessibility/services/index.ts index ec3bf534590b34..ef5674d4011fbb 100644 --- a/test/accessibility/services/index.ts +++ b/test/accessibility/services/index.ts @@ -7,9 +7,9 @@ */ import { services as kibanaFunctionalServices } from '../../functional/services'; -import { A11yProvider } from './a11y'; +import { AccessibilityService } from './a11y'; export const services = { ...kibanaFunctionalServices, - a11y: A11yProvider, + a11y: AccessibilityService, }; diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index bc60b8ce5f19c2..49d56d6f437847 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -11,470 +11,465 @@ import expect from '@kbn/expect'; // @ts-ignore import fetch from 'node-fetch'; import { getUrl } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function CommonPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const config = getService('config'); - const browser = getService('browser'); - const retry = getService('retry'); - const find = getService('find'); - const globalNav = getService('globalNav'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['login']); - - const defaultTryTimeout = config.get('timeouts.try'); - const defaultFindTimeout = config.get('timeouts.find'); - - interface NavigateProps { - appConfig: {}; - ensureCurrentUrl: boolean; - shouldLoginIfPrompted: boolean; - useActualUrl: boolean; - insertTimestamp: boolean; - } - - class CommonPage { - /** - * Logins to Kibana as default user and navigates to provided app - * @param appUrl Kibana URL - */ - private async loginIfPrompted(appUrl: string, insertTimestamp: boolean) { - // Disable the welcome screen. This is relevant for environments - // which don't allow to use the yml setting, e.g. cloud production. - // It is done here so it applies to logins but also to a login re-use. - await browser.setLocalStorageItem('home:welcome:show', 'false'); - - let currentUrl = await browser.getCurrentUrl(); - log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`); - await testSubjects.find('kibanaChrome', 6 * defaultFindTimeout); // 60 sec waiting - const loginPage = currentUrl.includes('/login'); - const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout'); - - if (loginPage && !wantedLoginPage) { - log.debug('Found login page'); - if (config.get('security.disableTestUser')) { - await PageObjects.login.login( - config.get('servers.kibana.username'), - config.get('servers.kibana.password') - ); - } else { - await PageObjects.login.login('test_user', 'changeme'); - } - - await find.byCssSelector( - '[data-test-subj="kibanaChrome"] nav:not(.ng-hide)', - 6 * defaultFindTimeout +import { FtrService } from '../ftr_provider_context'; + +interface NavigateProps { + appConfig: {}; + ensureCurrentUrl: boolean; + shouldLoginIfPrompted: boolean; + useActualUrl: boolean; + insertTimestamp: boolean; +} +export class CommonPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly config = this.ctx.getService('config'); + private readonly browser = this.ctx.getService('browser'); + private readonly retry = this.ctx.getService('retry'); + private readonly find = this.ctx.getService('find'); + private readonly globalNav = this.ctx.getService('globalNav'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly loginPage = this.ctx.getPageObject('login'); + + private readonly defaultTryTimeout = this.config.get('timeouts.try'); + private readonly defaultFindTimeout = this.config.get('timeouts.find'); + + /** + * Logins to Kibana as default user and navigates to provided app + * @param appUrl Kibana URL + */ + private async loginIfPrompted(appUrl: string, insertTimestamp: boolean) { + // Disable the welcome screen. This is relevant for environments + // which don't allow to use the yml setting, e.g. cloud production. + // It is done here so it applies to logins but also to a login re-use. + await this.browser.setLocalStorageItem('home:welcome:show', 'false'); + + let currentUrl = await this.browser.getCurrentUrl(); + this.log.debug(`currentUrl = ${currentUrl}\n appUrl = ${appUrl}`); + await this.testSubjects.find('kibanaChrome', 6 * this.defaultFindTimeout); // 60 sec waiting + const loginPage = currentUrl.includes('/login'); + const wantedLoginPage = appUrl.includes('/login') || appUrl.includes('/logout'); + + if (loginPage && !wantedLoginPage) { + this.log.debug('Found login page'); + if (this.config.get('security.disableTestUser')) { + await this.loginPage.login( + this.config.get('servers.kibana.username'), + this.config.get('servers.kibana.password') ); - await browser.get(appUrl, insertTimestamp); - currentUrl = await browser.getCurrentUrl(); - log.debug(`Finished login process currentUrl = ${currentUrl}`); + } else { + await this.loginPage.login('test_user', 'changeme'); } - return currentUrl; - } - private async navigate(navigateProps: NavigateProps) { - const { - appConfig, - ensureCurrentUrl, - shouldLoginIfPrompted, - useActualUrl, - insertTimestamp, - } = navigateProps; - const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig); - - await retry.try(async () => { - if (useActualUrl) { - log.debug(`navigateToActualUrl ${appUrl}`); - await browser.get(appUrl); - } else { - log.debug(`navigateToUrl ${appUrl}`); - await browser.get(appUrl, insertTimestamp); - } + await this.find.byCssSelector( + '[data-test-subj="kibanaChrome"] nav:not(.ng-hide)', + 6 * this.defaultFindTimeout + ); + await this.browser.get(appUrl, insertTimestamp); + currentUrl = await this.browser.getCurrentUrl(); + this.log.debug(`Finished login process currentUrl = ${currentUrl}`); + } + return currentUrl; + } - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); + private async navigate(navigateProps: NavigateProps) { + const { + appConfig, + ensureCurrentUrl, + shouldLoginIfPrompted, + useActualUrl, + insertTimestamp, + } = navigateProps; + const appUrl = getUrl.noAuth(this.config.get('servers.kibana'), appConfig); + + await this.retry.try(async () => { + if (useActualUrl) { + this.log.debug(`navigateToActualUrl ${appUrl}`); + await this.browser.get(appUrl); + } else { + this.log.debug(`navigateToUrl ${appUrl}`); + await this.browser.get(appUrl, insertTimestamp); + } - const currentUrl = shouldLoginIfPrompted - ? await this.loginIfPrompted(appUrl, insertTimestamp) - : await browser.getCurrentUrl(); + // accept alert if it pops up + const alert = await this.browser.getAlert(); + await alert?.accept(); - if (ensureCurrentUrl && !currentUrl.includes(appUrl)) { - throw new Error(`expected ${currentUrl}.includes(${appUrl})`); - } - }); - } + const currentUrl = shouldLoginIfPrompted + ? await this.loginIfPrompted(appUrl, insertTimestamp) + : await this.browser.getCurrentUrl(); - /** - * 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 - */ - public async navigateToUrl( - appName: string, - subUrl?: string, - { - basePath = '', - ensureCurrentUrl = true, - shouldLoginIfPrompted = true, - useActualUrl = false, - insertTimestamp = true, - shouldUseHashForSubUrl = true, - } = {} - ) { - const appConfig: { pathname: string; hash?: string } = { - pathname: `${basePath}${config.get(['apps', appName]).pathname}`, - }; - - if (shouldUseHashForSubUrl) { - appConfig.hash = useActualUrl ? subUrl : `/${appName}/${subUrl}`; - } else { - appConfig.pathname += `/${subUrl}`; + if (ensureCurrentUrl && !currentUrl.includes(appUrl)) { + throw new Error(`expected ${currentUrl}.includes(${appUrl})`); } + }); + } - await this.navigate({ - appConfig, - ensureCurrentUrl, - shouldLoginIfPrompted, - useActualUrl, - insertTimestamp, - }); + /** + * 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 + */ + public async navigateToUrl( + appName: string, + subUrl?: string, + { + basePath = '', + ensureCurrentUrl = true, + shouldLoginIfPrompted = true, + useActualUrl = false, + insertTimestamp = true, + shouldUseHashForSubUrl = true, + } = {} + ) { + const appConfig: { pathname: string; hash?: string } = { + pathname: `${basePath}${this.config.get(['apps', appName]).pathname}`, + }; + + if (shouldUseHashForSubUrl) { + appConfig.hash = useActualUrl ? subUrl : `/${appName}/${subUrl}`; + } else { + appConfig.pathname += `/${subUrl}`; } - /** - * Navigates browser using the pathname from the appConfig and subUrl as the extended path. - * This was added to be able to test an application that uses browser history over hash history. - * @param appName As defined in the apps config, e.g. 'home' - * @param subUrl The route after the appUrl, e.g. '/tutorial_directory/sampleData' - * @param args additional arguments - */ - public async navigateToUrlWithBrowserHistory( - appName: string, - subUrl?: string, - search?: string, - { - basePath = '', - ensureCurrentUrl = true, - shouldLoginIfPrompted = true, - useActualUrl = true, - insertTimestamp = true, - } = {} - ) { - const appConfig = { - // subUrl following the basePath, assumes no hashes. Ex: 'app/endpoint/management' - pathname: `${basePath}${config.get(['apps', appName]).pathname}${subUrl}`, - search, - }; - - await this.navigate({ - appConfig, - ensureCurrentUrl, - shouldLoginIfPrompted, - useActualUrl, - insertTimestamp, - }); - } + await this.navigate({ + appConfig, + ensureCurrentUrl, + shouldLoginIfPrompted, + useActualUrl, + insertTimestamp, + }); + } - /** - * 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 } = {} - ) { - await this.navigateToUrl(appName, hash, { - basePath, - ensureCurrentUrl, - shouldLoginIfPrompted, - useActualUrl: true, - }); - } + /** + * Navigates browser using the pathname from the appConfig and subUrl as the extended path. + * This was added to be able to test an application that uses browser history over hash history. + * @param appName As defined in the apps config, e.g. 'home' + * @param subUrl The route after the appUrl, e.g. '/tutorial_directory/sampleData' + * @param args additional arguments + */ + public async navigateToUrlWithBrowserHistory( + appName: string, + subUrl?: string, + search?: string, + { + basePath = '', + ensureCurrentUrl = true, + shouldLoginIfPrompted = true, + useActualUrl = true, + insertTimestamp = true, + } = {} + ) { + const appConfig = { + // subUrl following the basePath, assumes no hashes. Ex: 'app/endpoint/management' + pathname: `${basePath}${this.config.get(['apps', appName]).pathname}${subUrl}`, + search, + }; + + await this.navigate({ + appConfig, + ensureCurrentUrl, + shouldLoginIfPrompted, + useActualUrl, + insertTimestamp, + }); + } - async sleep(sleepMilliseconds: number) { - log.debug(`... sleep(${sleepMilliseconds}) start`); - await delay(sleepMilliseconds); - log.debug(`... sleep(${sleepMilliseconds}) end`); - } + /** + * 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 } = {} + ) { + await this.navigateToUrl(appName, hash, { + basePath, + ensureCurrentUrl, + shouldLoginIfPrompted, + useActualUrl: true, + }); + } - async navigateToApp( - appName: string, - { basePath = '', shouldLoginIfPrompted = true, hash = '', insertTimestamp = true } = {} - ) { - let appUrl: string; - if (config.has(['apps', appName])) { - // Legacy applications - const appConfig = config.get(['apps', appName]); - appUrl = getUrl.noAuth(config.get('servers.kibana'), { - pathname: `${basePath}${appConfig.pathname}`, - hash: hash || appConfig.hash, - }); - } else { - appUrl = getUrl.noAuth(config.get('servers.kibana'), { - pathname: `${basePath}/app/${appName}`, - hash, - }); - } + async sleep(sleepMilliseconds: number) { + this.log.debug(`... sleep(${sleepMilliseconds}) start`); + await delay(sleepMilliseconds); + this.log.debug(`... sleep(${sleepMilliseconds}) end`); + } - log.debug('navigating to ' + appName + ' url: ' + appUrl); - - 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 - log.debug('navigate to: ' + appUrl); - await browser.get(appUrl, insertTimestamp); - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); - await this.sleep(700); - log.debug('returned from get, calling refresh'); - await browser.refresh(); - let currentUrl = shouldLoginIfPrompted - ? await this.loginIfPrompted(appUrl, insertTimestamp) - : await browser.getCurrentUrl(); - - if (currentUrl.includes('app/kibana')) { - await testSubjects.find('kibanaChrome'); - } - - currentUrl = (await browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//'); - - const navSuccessful = currentUrl - .replace(':80/', '/') - .replace(':443/', '/') - .startsWith(appUrl); - - 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 retry.tryForTime(defaultFindTimeout, 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'); - } - }); + async navigateToApp( + appName: string, + { basePath = '', shouldLoginIfPrompted = true, hash = '', insertTimestamp = true } = {} + ) { + let appUrl: string; + if (this.config.has(['apps', appName])) { + // Legacy applications + const appConfig = this.config.get(['apps', appName]); + appUrl = getUrl.noAuth(this.config.get('servers.kibana'), { + pathname: `${basePath}${appConfig.pathname}`, + hash: hash || appConfig.hash, + }); + } else { + appUrl = getUrl.noAuth(this.config.get('servers.kibana'), { + pathname: `${basePath}/app/${appName}`, + hash, }); } - async waitUntilUrlIncludes(path: string) { - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - if (!url.includes(path)) { - throw new Error('Url not found'); + this.log.debug('navigating to ' + appName + ' url: ' + appUrl); + + await this.retry.tryForTime(this.defaultTryTimeout * 2, async () => { + let lastUrl = await this.retry.try(async () => { + // since we're using hash URLs, always reload first to force re-render + this.log.debug('navigate to: ' + appUrl); + await this.browser.get(appUrl, insertTimestamp); + // accept alert if it pops up + const alert = await this.browser.getAlert(); + await alert?.accept(); + await this.sleep(700); + this.log.debug('returned from get, calling refresh'); + await this.browser.refresh(); + let currentUrl = shouldLoginIfPrompted + ? await this.loginIfPrompted(appUrl, insertTimestamp) + : await this.browser.getCurrentUrl(); + + if (currentUrl.includes('app/kibana')) { + await this.testSubjects.find('kibanaChrome'); } - }); - } - async getSharedItemTitleAndDescription() { - const cssSelector = '[data-shared-item][data-title][data-description]'; - const element = await find.byCssSelector(cssSelector); + currentUrl = (await this.browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//'); - return { - title: await element.getAttribute('data-title'), - description: await element.getAttribute('data-description'), - }; - } + const navSuccessful = currentUrl + .replace(':80/', '/') + .replace(':443/', '/') + .startsWith(appUrl); - async getSharedItemContainers() { - const cssSelector = '[data-shared-items-container]'; - return find.allByCssSelector(cssSelector); - } + if (!navSuccessful) { + const msg = `App failed to load: ${appName} in ${this.defaultFindTimeout}ms appUrl=${appUrl} currentUrl=${currentUrl}`; + this.log.debug(msg); + throw new Error(msg); + } + return currentUrl; + }); - async ensureModalOverlayHidden() { - return retry.try(async () => { - const shown = await testSubjects.exists('confirmModalTitleText'); - if (shown) { - throw new Error('Modal overlay is showing'); + await this.retry.tryForTime(this.defaultFindTimeout, async () => { + await this.sleep(501); + const currentUrl = await this.browser.getCurrentUrl(); + this.log.debug('in navigateTo url = ' + currentUrl); + if (lastUrl !== currentUrl) { + lastUrl = currentUrl; + throw new Error('URL changed, waiting for it to settle'); } }); - } + }); + } - async clickConfirmOnModal(ensureHidden = true) { - log.debug('Clicking modal confirm'); - // make sure this data-test-subj 'confirmModalTitleText' exists because we're going to wait for it to be gone later - await testSubjects.exists('confirmModalTitleText'); - await testSubjects.click('confirmModalConfirmButton'); - if (ensureHidden) { - await this.ensureModalOverlayHidden(); + async waitUntilUrlIncludes(path: string) { + await this.retry.try(async () => { + const url = await this.browser.getCurrentUrl(); + if (!url.includes(path)) { + throw new Error('Url not found'); } - } + }); + } - async pressEnterKey() { - await browser.pressKeys(browser.keys.ENTER); - } + async getSharedItemTitleAndDescription() { + const cssSelector = '[data-shared-item][data-title][data-description]'; + const element = await this.find.byCssSelector(cssSelector); - async pressTabKey() { - await browser.pressKeys(browser.keys.TAB); - } + return { + title: await element.getAttribute('data-title'), + description: await element.getAttribute('data-description'), + }; + } - // Pause the browser at a certain place for debugging - // Not meant for usage in CI, only for dev-usage - async pause() { - return browser.pause(); - } + async getSharedItemContainers() { + const cssSelector = '[data-shared-items-container]'; + return this.find.allByCssSelector(cssSelector); + } - /** - * 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) { - await this.ensureModalOverlayHidden(); + async ensureModalOverlayHidden() { + return this.retry.try(async () => { + const shown = await this.testSubjects.exists('confirmModalTitleText'); + if (shown) { + throw new Error('Modal overlay is showing'); } - } + }); + } - 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.equal( - state, - state ? 'Confirm modal should be present' : 'Confirm modal should be hidden' - ); - }); + async clickConfirmOnModal(ensureHidden = true) { + this.log.debug('Clicking modal confirm'); + // make sure this data-test-subj 'confirmModalTitleText' exists because we're going to wait for it to be gone later + await this.testSubjects.exists('confirmModalTitleText'); + await this.testSubjects.click('confirmModalConfirmButton'); + if (ensureHidden) { + await this.ensureModalOverlayHidden(); } + } - async isChromeVisible() { - const globalNavShown = await globalNav.exists(); - return globalNavShown; - } + async pressEnterKey() { + await this.browser.pressKeys(this.browser.keys.ENTER); + } - async isChromeHidden() { - const globalNavShown = await globalNav.exists(); - return !globalNavShown; - } + async pressTabKey() { + await this.browser.pressKeys(this.browser.keys.TAB); + } - async waitForTopNavToBeVisible() { - await retry.try(async () => { - const isNavVisible = await testSubjects.exists('top-nav'); - if (!isNavVisible) { - throw new Error('Local nav not visible yet'); - } - }); + // Pause the browser at a certain place for debugging + // Not meant for usage in CI, only for dev-usage + async pause() { + return this.browser.pause(); + } + + /** + * Clicks cancel button on modal + * @param overlayWillStay pass in true if your test will show multiple modals in succession + */ + async clickCancelOnModal(overlayWillStay = true) { + this.log.debug('Clicking modal cancel'); + await this.testSubjects.click('confirmModalCancelButton'); + if (!overlayWillStay) { + await this.ensureModalOverlayHidden(); } + } - async closeToast() { - const toast = await find.byCssSelector('.euiToast', 6 * defaultFindTimeout); - await toast.moveMouseTo(); - const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText(); + async expectConfirmModalOpenState(state: boolean) { + this.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 this.retry.try(async () => { + const actualState = await this.testSubjects.exists('confirmModalCancelButton'); + expect(actualState).to.equal( + state, + state ? 'Confirm modal should be present' : 'Confirm modal should be hidden' + ); + }); + } - await find.clickByCssSelector('.euiToast__closeButton'); - return title; - } + async isChromeVisible() { + const globalNavShown = await this.globalNav.exists(); + return globalNavShown; + } - async closeToastIfExists() { - const toastShown = await find.existsByCssSelector('.euiToast'); - if (toastShown) { - try { - await find.clickByCssSelector('.euiToast__closeButton'); - } catch (err) { - // ignore errors, toast clear themselves after timeout - } - } - } + async isChromeHidden() { + const globalNavShown = await this.globalNav.exists(); + return !globalNavShown; + } - async clearAllToasts() { - const toasts = await find.allByCssSelector('.euiToast'); - for (const toastElement of toasts) { - try { - await toastElement.moveMouseTo(); - const closeBtn = await toastElement.findByCssSelector('.euiToast__closeButton'); - await closeBtn.click(); - } catch (err) { - // ignore errors, toast clear themselves after timeout - } + async waitForTopNavToBeVisible() { + await this.retry.try(async () => { + const isNavVisible = await this.testSubjects.exists('top-nav'); + if (!isNavVisible) { + throw new Error('Local nav not visible yet'); } - } + }); + } - async getJsonBodyText() { - 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]'); - } - const msgElements = await find.allByCssSelector('body pre'); - if (msgElements.length > 0) { - return await msgElements[0].getVisibleText(); - } else { - // Sometimes Firefox renders Timelion page without tabs and with div#json - const jsonElement = await find.byCssSelector('body div#json'); - return await jsonElement.getVisibleText(); + async closeToast() { + const toast = await this.find.byCssSelector('.euiToast', 6 * this.defaultFindTimeout); + await toast.moveMouseTo(); + const title = await (await this.find.byCssSelector('.euiToastHeader__title')).getVisibleText(); + + await this.find.clickByCssSelector('.euiToast__closeButton'); + return title; + } + + async closeToastIfExists() { + const toastShown = await this.find.existsByCssSelector('.euiToast'); + if (toastShown) { + try { + await this.find.clickByCssSelector('.euiToast__closeButton'); + } catch (err) { + // ignore errors, toast clear themselves after timeout } } + } - async getBodyText() { - const body = await find.byCssSelector('body'); - return await body.getVisibleText(); + async clearAllToasts() { + const toasts = await this.find.allByCssSelector('.euiToast'); + for (const toastElement of toasts) { + try { + await toastElement.moveMouseTo(); + const closeBtn = await toastElement.findByCssSelector('.euiToast__closeButton'); + await closeBtn.click(); + } catch (err) { + // ignore errors, toast clear themselves after timeout + } } + } - async waitForSaveModalToClose() { - log.debug('Waiting for save modal to close'); - await retry.try(async () => { - if (await testSubjects.exists('savedObjectSaveModal')) { - throw new Error('save modal still open'); - } - }); + async getJsonBodyText() { + if (await this.find.existsByCssSelector('a[id=rawdata-tab]', this.defaultFindTimeout)) { + // Firefox has 3 tabs and requires navigation to see Raw output + await this.find.clickByCssSelector('a[id=rawdata-tab]'); } - - async setFileInputPath(path: string) { - log.debug(`Setting the path '${path}' on the file input`); - const input = await find.byCssSelector('.euiFilePicker__input'); - await input.type(path); + const msgElements = await this.find.allByCssSelector('body pre'); + if (msgElements.length > 0) { + return await msgElements[0].getVisibleText(); + } else { + // Sometimes Firefox renders Timelion page without tabs and with div#json + const jsonElement = await this.find.byCssSelector('body div#json'); + return await jsonElement.getVisibleText(); } + } - async scrollKibanaBodyTop() { - await browser.setScrollToById('kibana-body', 0, 0); - } + async getBodyText() { + const body = await this.find.byCssSelector('body'); + return await body.getVisibleText(); + } - /** - * Dismiss Banner if available. - */ - async dismissBanner() { - if (await testSubjects.exists('global-banner-item')) { - const button = await find.byButtonText('Dismiss'); - await button.click(); + async waitForSaveModalToClose() { + this.log.debug('Waiting for save modal to close'); + await this.retry.try(async () => { + if (await this.testSubjects.exists('savedObjectSaveModal')) { + throw new Error('save modal still open'); } - } + }); + } - /** - * Get visible text of the Welcome Banner - */ - async getWelcomeText() { - return await testSubjects.getVisibleText('global-banner-item'); - } + async setFileInputPath(path: string) { + this.log.debug(`Setting the path '${path}' on the file input`); + const input = await this.find.byCssSelector('.euiFilePicker__input'); + await input.type(path); + } + + async scrollKibanaBodyTop() { + await this.browser.setScrollToById('kibana-body', 0, 0); + } - /** - * Clicks on an element, and validates that the desired effect has taken place - * by confirming the existence of a validator - */ - async clickAndValidate( - clickTarget: string, - validator: string, - isValidatorCssString: boolean = false, - topOffset?: number - ) { - await testSubjects.click(clickTarget, undefined, topOffset); - const validate = isValidatorCssString ? find.byCssSelector : testSubjects.exists; - await validate(validator); + /** + * Dismiss Banner if available. + */ + async dismissBanner() { + if (await this.testSubjects.exists('global-banner-item')) { + const button = await this.find.byButtonText('Dismiss'); + await button.click(); } } - return new CommonPage(); + /** + * Get visible text of the Welcome Banner + */ + async getWelcomeText() { + return await this.testSubjects.getVisibleText('global-banner-item'); + } + + /** + * Clicks on an element, and validates that the desired effect has taken place + * by confirming the existence of a validator + */ + async clickAndValidate( + clickTarget: string, + validator: string, + isValidatorCssString: boolean = false, + topOffset?: number + ) { + await this.testSubjects.click(clickTarget, undefined, topOffset); + const validate = isValidatorCssString ? this.find.byCssSelector : this.testSubjects.exists; + await validate(validator); + } } diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 6fb554e6d34a0c..77c87f6066e854 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -7,102 +7,98 @@ */ import { Key } from 'selenium-webdriver'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; -export function ConsolePageProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const find = getService('find'); +export class ConsolePageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly find = this.ctx.getService('find'); - class ConsolePage { - public async getVisibleTextFromAceEditor(editor: WebElementWrapper) { - const lines = await editor.findAllByClassName('ace_line_group'); - const linesText = await Promise.all(lines.map(async (line) => await line.getVisibleText())); - return linesText.join('\n'); - } + public async getVisibleTextFromAceEditor(editor: WebElementWrapper) { + const lines = await editor.findAllByClassName('ace_line_group'); + const linesText = await Promise.all(lines.map(async (line) => await line.getVisibleText())); + return linesText.join('\n'); + } - public async getRequestEditor() { - return await testSubjects.find('request-editor'); - } + public async getRequestEditor() { + return await this.testSubjects.find('request-editor'); + } - public async getRequest() { - const requestEditor = await this.getRequestEditor(); - return await this.getVisibleTextFromAceEditor(requestEditor); - } + public async getRequest() { + const requestEditor = await this.getRequestEditor(); + return await this.getVisibleTextFromAceEditor(requestEditor); + } - public async getResponse() { - const responseEditor = await testSubjects.find('response-editor'); - return await this.getVisibleTextFromAceEditor(responseEditor); - } + public async getResponse() { + const responseEditor = await this.testSubjects.find('response-editor'); + return await this.getVisibleTextFromAceEditor(responseEditor); + } - public async clickPlay() { - await testSubjects.click('sendRequestButton'); - } + public async clickPlay() { + await this.testSubjects.click('sendRequestButton'); + } - public async collapseHelp() { - await testSubjects.click('help-close-button'); - } + public async collapseHelp() { + await this.testSubjects.click('help-close-button'); + } - public async openSettings() { - await testSubjects.click('consoleSettingsButton'); - } + public async openSettings() { + await this.testSubjects.click('consoleSettingsButton'); + } - public async setFontSizeSetting(newSize: number) { - await this.openSettings(); + public async setFontSizeSetting(newSize: number) { + await this.openSettings(); - // while the settings form opens/loads this may fail, so retry for a while - await retry.try(async () => { - const fontSizeInput = await testSubjects.find('setting-font-size-input'); - await fontSizeInput.clearValue({ withJS: true }); - await fontSizeInput.click(); - await fontSizeInput.type(String(newSize)); - }); + // while the settings form opens/loads this may fail, so retry for a while + await this.retry.try(async () => { + const fontSizeInput = await this.testSubjects.find('setting-font-size-input'); + await fontSizeInput.clearValue({ withJS: true }); + await fontSizeInput.click(); + await fontSizeInput.type(String(newSize)); + }); - await testSubjects.click('settings-save-button'); - } + await this.testSubjects.click('settings-save-button'); + } - public async getFontSize(editor: WebElementWrapper) { - const aceLine = await editor.findByClassName('ace_line'); - return await aceLine.getComputedStyle('font-size'); - } + public async getFontSize(editor: WebElementWrapper) { + const aceLine = await editor.findByClassName('ace_line'); + return await aceLine.getComputedStyle('font-size'); + } - public async getRequestFontSize() { - return await this.getFontSize(await this.getRequestEditor()); - } + public async getRequestFontSize() { + return await this.getFontSize(await this.getRequestEditor()); + } - public async getEditor() { - return testSubjects.find('console-application'); - } + public async getEditor() { + return this.testSubjects.find('console-application'); + } - public async dismissTutorial() { - try { - const closeButton = await testSubjects.find('help-close-button'); - await closeButton.click(); - } catch (e) { - // Ignore because it is probably not there. - } + public async dismissTutorial() { + try { + const closeButton = await this.testSubjects.find('help-close-button'); + await closeButton.click(); + } catch (e) { + // Ignore because it is probably not there. } + } - public async promptAutocomplete() { - // This focusses the cursor on the bottom of the text area - const editor = await this.getEditor(); - const content = await editor.findByCssSelector('.ace_content'); - await content.click(); - const textArea = await testSubjects.find('console-textarea'); - // There should be autocomplete for this on all license levels - await textArea.pressKeys('\nGET s'); - await textArea.pressKeys([Key.CONTROL, Key.SPACE]); - } + public async promptAutocomplete() { + // This focusses the cursor on the bottom of the text area + const editor = await this.getEditor(); + const content = await editor.findByCssSelector('.ace_content'); + await content.click(); + const textArea = await this.testSubjects.find('console-textarea'); + // There should be autocomplete for this on all license levels + await textArea.pressKeys('\nGET s'); + await textArea.pressKeys([Key.CONTROL, Key.SPACE]); + } - public async hasAutocompleter(): Promise { - try { - return Boolean(await find.byCssSelector('.ace_autocomplete')); - } catch (e) { - return false; - } + public async hasAutocompleter(): Promise { + try { + return Boolean(await this.find.byCssSelector('.ace_autocomplete')); + } catch (e) { + return false; } } - - return new ConsolePage(); } diff --git a/test/functional/page_objects/context_page.ts b/test/functional/page_objects/context_page.ts index b758423d9346dd..05ea89cb65b3d0 100644 --- a/test/functional/page_objects/context_page.ts +++ b/test/functional/page_objects/context_page.ts @@ -8,93 +8,91 @@ import rison from 'rison-node'; import { getUrl } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; const DEFAULT_INITIAL_STATE = { columns: ['@message'], }; -export function ContextPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const config = getService('config'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['header', 'common']); - const log = getService('log'); +export class ContextPageObject extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly config = this.ctx.getService('config'); + private readonly retry = this.ctx.getService('retry'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly header = this.ctx.getPageObject('header'); + private readonly common = this.ctx.getPageObject('common'); + private readonly log = this.ctx.getService('log'); - class ContextPage { - public async navigateTo(indexPattern: string, anchorId: string, overrideInitialState = {}) { - const initialState = rison.encode({ - ...DEFAULT_INITIAL_STATE, - ...overrideInitialState, - }); - const appUrl = getUrl.noAuth(config.get('servers.kibana'), { - ...config.get('apps.context'), - hash: `${config.get('apps.context.hash')}/${indexPattern}/${anchorId}?_a=${initialState}`, - }); + public async navigateTo(indexPattern: string, anchorId: string, overrideInitialState = {}) { + const initialState = rison.encode({ + ...DEFAULT_INITIAL_STATE, + ...overrideInitialState, + }); + const contextHash = this.config.get('apps.context.hash'); + const appUrl = getUrl.noAuth(this.config.get('servers.kibana'), { + ...this.config.get('apps.context'), + hash: `${contextHash}/${indexPattern}/${anchorId}?_a=${initialState}`, + }); - log.debug(`browser.get(${appUrl})`); + this.log.debug(`browser.get(${appUrl})`); - await browser.get(appUrl); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - await this.waitUntilContextLoadingHasFinished(); - // For lack of a better way, using a sleep to ensure page is loaded before proceeding - await PageObjects.common.sleep(1000); - } - - public async getPredecessorCountPicker() { - return await testSubjects.find('predecessorsCountPicker'); - } + await this.browser.get(appUrl); + await this.header.awaitGlobalLoadingIndicatorHidden(); + await this.waitUntilContextLoadingHasFinished(); + // For lack of a better way, using a sleep to ensure page is loaded before proceeding + await this.common.sleep(1000); + } - public async getSuccessorCountPicker() { - return await testSubjects.find('successorsCountPicker'); - } + public async getPredecessorCountPicker() { + return await this.testSubjects.find('predecessorsCountPicker'); + } - public async getPredecessorLoadMoreButton() { - return await testSubjects.find('predecessorsLoadMoreButton'); - } + public async getSuccessorCountPicker() { + return await this.testSubjects.find('successorsCountPicker'); + } - public async getSuccessorLoadMoreButton() { - return await testSubjects.find('successorsLoadMoreButton'); - } + public async getPredecessorLoadMoreButton() { + return await this.testSubjects.find('predecessorsLoadMoreButton'); + } - public async clickPredecessorLoadMoreButton() { - log.debug('Click Predecessor Load More Button'); - await retry.try(async () => { - const predecessorButton = await this.getPredecessorLoadMoreButton(); - await predecessorButton.click(); - }); - await this.waitUntilContextLoadingHasFinished(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async getSuccessorLoadMoreButton() { + return await this.testSubjects.find('successorsLoadMoreButton'); + } - public async clickSuccessorLoadMoreButton() { - log.debug('Click Successor Load More Button'); - await retry.try(async () => { - const sucessorButton = await this.getSuccessorLoadMoreButton(); - await sucessorButton.click(); - }); - await this.waitUntilContextLoadingHasFinished(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async clickPredecessorLoadMoreButton() { + this.log.debug('Click Predecessor Load More Button'); + await this.retry.try(async () => { + const predecessorButton = await this.getPredecessorLoadMoreButton(); + await predecessorButton.click(); + }); + await this.waitUntilContextLoadingHasFinished(); + await this.header.waitUntilLoadingHasFinished(); + } - public async waitUntilContextLoadingHasFinished() { - return await retry.try(async () => { - const successorLoadMoreButton = await this.getSuccessorLoadMoreButton(); - const predecessorLoadMoreButton = await this.getPredecessorLoadMoreButton(); - if ( - !( - (await successorLoadMoreButton.isEnabled()) && - (await successorLoadMoreButton.isDisplayed()) && - (await predecessorLoadMoreButton.isEnabled()) && - (await predecessorLoadMoreButton.isDisplayed()) - ) - ) { - throw new Error('loading context rows'); - } - }); - } + public async clickSuccessorLoadMoreButton() { + this.log.debug('Click Successor Load More Button'); + await this.retry.try(async () => { + const sucessorButton = await this.getSuccessorLoadMoreButton(); + await sucessorButton.click(); + }); + await this.waitUntilContextLoadingHasFinished(); + await this.header.waitUntilLoadingHasFinished(); } - return new ContextPage(); + public async waitUntilContextLoadingHasFinished() { + return await this.retry.try(async () => { + const successorLoadMoreButton = await this.getSuccessorLoadMoreButton(); + const predecessorLoadMoreButton = await this.getPredecessorLoadMoreButton(); + if ( + !( + (await successorLoadMoreButton.isEnabled()) && + (await successorLoadMoreButton.isDisplayed()) && + (await predecessorLoadMoreButton.isEnabled()) && + (await predecessorLoadMoreButton.isDisplayed()) + ) + ) { + throw new Error('loading context rows'); + } + }); + } } diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index ba75ab75cc6e89..194f0936274e5e 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -9,668 +9,667 @@ export const PIE_CHART_VIS_NAME = 'Visualization PieChart'; export const AREA_CHART_VIS_NAME = 'Visualization漢字 AreaChart'; export const LINE_CHART_VIS_NAME = 'Visualization漢字 LineChart'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function DashboardPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const find = getService('find'); - const retry = getService('retry'); - const browser = getService('browser'); - const globalNav = getService('globalNav'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const renderable = getService('renderable'); - const listingTable = getService('listingTable'); - const elasticChart = getService('elasticChart'); - const PageObjects = getPageObjects(['common', 'header', 'visualize', 'discover']); - - interface SaveDashboardOptions { - /** - * @default true - */ - waitDialogIsClosed?: boolean; - exitFromEditMode?: boolean; - needsConfirm?: boolean; - storeTimeWithDashboard?: boolean; - saveAsNew?: boolean; - tags?: string[]; - } - - class DashboardPage { - async initTests({ kibanaIndex = 'dashboard/legacy', defaultIndex = 'logstash-*' } = {}) { - log.debug('load kibana index with visualizations and log data'); - await esArchiver.load(kibanaIndex); - await kibanaServer.uiSettings.replace({ defaultIndex }); - await PageObjects.common.navigateToApp('dashboard'); - } - - public async preserveCrossAppState() { - const url = await browser.getCurrentUrl(); - await browser.get(url, false); - await PageObjects.header.waitUntilLoadingHasFinished(); - } +import { FtrService } from '../ftr_provider_context'; + +interface SaveDashboardOptions { + /** + * @default true + */ + waitDialogIsClosed?: boolean; + exitFromEditMode?: boolean; + needsConfirm?: boolean; + storeTimeWithDashboard?: boolean; + saveAsNew?: boolean; + tags?: string[]; +} - public async clickFullScreenMode() { - log.debug(`clickFullScreenMode`); - await testSubjects.click('dashboardFullScreenMode'); - await testSubjects.exists('exitFullScreenModeLogo'); - await this.waitForRenderComplete(); - } +export class DashboardPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + private readonly globalNav = this.ctx.getService('globalNav'); + private readonly esArchiver = this.ctx.getService('esArchiver'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly dashboardAddPanel = this.ctx.getService('dashboardAddPanel'); + private readonly renderable = this.ctx.getService('renderable'); + private readonly listingTable = this.ctx.getService('listingTable'); + private readonly elasticChart = this.ctx.getService('elasticChart'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly visualize = this.ctx.getPageObject('visualize'); + private readonly discover = this.ctx.getPageObject('discover'); + + async initTests({ kibanaIndex = 'dashboard/legacy', defaultIndex = 'logstash-*' } = {}) { + this.log.debug('load kibana index with visualizations and log data'); + await this.esArchiver.load(kibanaIndex); + await this.kibanaServer.uiSettings.replace({ defaultIndex }); + await this.common.navigateToApp('dashboard'); + } - public async exitFullScreenMode() { - log.debug(`exitFullScreenMode`); - const logoButton = await this.getExitFullScreenLogoButton(); - await logoButton.moveMouseTo(); - await this.clickExitFullScreenTextButton(); - } + public async preserveCrossAppState() { + const url = await this.browser.getCurrentUrl(); + await this.browser.get(url, false); + await this.header.waitUntilLoadingHasFinished(); + } - public async fullScreenModeMenuItemExists() { - return await testSubjects.exists('dashboardFullScreenMode'); - } + public async clickFullScreenMode() { + this.log.debug(`clickFullScreenMode`); + await this.testSubjects.click('dashboardFullScreenMode'); + await this.testSubjects.exists('exitFullScreenModeLogo'); + await this.waitForRenderComplete(); + } - public async exitFullScreenTextButtonExists() { - return await testSubjects.exists('exitFullScreenModeText'); - } + public async exitFullScreenMode() { + this.log.debug(`exitFullScreenMode`); + const logoButton = await this.getExitFullScreenLogoButton(); + await logoButton.moveMouseTo(); + await this.clickExitFullScreenTextButton(); + } - public async getExitFullScreenTextButton() { - return await testSubjects.find('exitFullScreenModeText'); - } + public async fullScreenModeMenuItemExists() { + return await this.testSubjects.exists('dashboardFullScreenMode'); + } - public async exitFullScreenLogoButtonExists() { - return await testSubjects.exists('exitFullScreenModeLogo'); - } + public async exitFullScreenTextButtonExists() { + return await this.testSubjects.exists('exitFullScreenModeText'); + } - public async getExitFullScreenLogoButton() { - return await testSubjects.find('exitFullScreenModeLogo'); - } + public async getExitFullScreenTextButton() { + return await this.testSubjects.find('exitFullScreenModeText'); + } - public async clickExitFullScreenLogoButton() { - await testSubjects.click('exitFullScreenModeLogo'); - await this.waitForRenderComplete(); - } + public async exitFullScreenLogoButtonExists() { + return await this.testSubjects.exists('exitFullScreenModeLogo'); + } - public async clickExitFullScreenTextButton() { - await testSubjects.click('exitFullScreenModeText'); - await this.waitForRenderComplete(); - } + public async getExitFullScreenLogoButton() { + return await this.testSubjects.find('exitFullScreenModeLogo'); + } - public async getDashboardIdFromCurrentUrl() { - const currentUrl = await browser.getCurrentUrl(); - const id = this.getDashboardIdFromUrl(currentUrl); + public async clickExitFullScreenLogoButton() { + await this.testSubjects.click('exitFullScreenModeLogo'); + await this.waitForRenderComplete(); + } - log.debug(`Dashboard id extracted from ${currentUrl} is ${id}`); + public async clickExitFullScreenTextButton() { + await this.testSubjects.click('exitFullScreenModeText'); + await this.waitForRenderComplete(); + } - return id; - } + public async getDashboardIdFromCurrentUrl() { + const currentUrl = await this.browser.getCurrentUrl(); + const id = this.getDashboardIdFromUrl(currentUrl); - public getDashboardIdFromUrl(url: string) { - const urlSubstring = '#/view/'; - const startOfIdIndex = url.indexOf(urlSubstring) + urlSubstring.length; - const endIndex = url.indexOf('?'); - const id = url.substring(startOfIdIndex, endIndex < 0 ? url.length : endIndex); - return id; - } + this.log.debug(`Dashboard id extracted from ${currentUrl} is ${id}`); - public async expectUnsavedChangesListingExists(title: string) { - log.debug(`Expect Unsaved Changes Listing Exists for `, title); - await testSubjects.existOrFail(`edit-unsaved-${title.split(' ').join('-')}`); - } + return id; + } - public async expectUnsavedChangesDoesNotExist(title: string) { - log.debug(`Expect Unsaved Changes Listing Does Not Exist for `, title); - await testSubjects.missingOrFail(`edit-unsaved-${title.split(' ').join('-')}`); - } + public getDashboardIdFromUrl(url: string) { + const urlSubstring = '#/view/'; + const startOfIdIndex = url.indexOf(urlSubstring) + urlSubstring.length; + const endIndex = url.indexOf('?'); + const id = url.substring(startOfIdIndex, endIndex < 0 ? url.length : endIndex); + return id; + } - public async clickUnsavedChangesContinueEditing(title: string) { - log.debug(`Click Unsaved Changes Continue Editing `, title); - await testSubjects.existOrFail(`edit-unsaved-${title.split(' ').join('-')}`); - await testSubjects.click(`edit-unsaved-${title.split(' ').join('-')}`); - } + public async expectUnsavedChangesListingExists(title: string) { + this.log.debug(`Expect Unsaved Changes Listing Exists for `, title); + await this.testSubjects.existOrFail(`edit-unsaved-${title.split(' ').join('-')}`); + } - public async clickUnsavedChangesDiscard(title: string, confirmDiscard = true) { - log.debug(`Click Unsaved Changes Discard for `, title); - await testSubjects.existOrFail(`discard-unsaved-${title.split(' ').join('-')}`); - await testSubjects.click(`discard-unsaved-${title.split(' ').join('-')}`); - if (confirmDiscard) { - await PageObjects.common.clickConfirmOnModal(); - } else { - await PageObjects.common.clickCancelOnModal(); - } - } + public async expectUnsavedChangesDoesNotExist(title: string) { + this.log.debug(`Expect Unsaved Changes Listing Does Not Exist for `, title); + await this.testSubjects.missingOrFail(`edit-unsaved-${title.split(' ').join('-')}`); + } - /** - * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). - * @returns {Promise} - */ - public async onDashboardLandingPage() { - log.debug(`onDashboardLandingPage`); - return await listingTable.onListingPage('dashboard'); - } + public async clickUnsavedChangesContinueEditing(title: string) { + this.log.debug(`Click Unsaved Changes Continue Editing `, title); + await this.testSubjects.existOrFail(`edit-unsaved-${title.split(' ').join('-')}`); + await this.testSubjects.click(`edit-unsaved-${title.split(' ').join('-')}`); + } - public async expectExistsDashboardLandingPage() { - log.debug(`expectExistsDashboardLandingPage`); - await testSubjects.existOrFail('dashboardLandingPage'); + public async clickUnsavedChangesDiscard(title: string, confirmDiscard = true) { + this.log.debug(`Click Unsaved Changes Discard for `, title); + await this.testSubjects.existOrFail(`discard-unsaved-${title.split(' ').join('-')}`); + await this.testSubjects.click(`discard-unsaved-${title.split(' ').join('-')}`); + if (confirmDiscard) { + await this.common.clickConfirmOnModal(); + } else { + await this.common.clickCancelOnModal(); } + } - public async clickDashboardBreadcrumbLink() { - log.debug('clickDashboardBreadcrumbLink'); - await testSubjects.click('breadcrumb dashboardListingBreadcrumb first'); - } + /** + * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). + * @returns {Promise} + */ + public async onDashboardLandingPage() { + this.log.debug(`onDashboardLandingPage`); + return await this.listingTable.onListingPage('dashboard'); + } - public async expectOnDashboard(dashboardTitle: string) { - await retry.waitFor( - 'last breadcrumb to have dashboard title', - async () => (await globalNav.getLastBreadcrumb()) === dashboardTitle - ); - } + public async expectExistsDashboardLandingPage() { + this.log.debug(`expectExistsDashboardLandingPage`); + await this.testSubjects.existOrFail('dashboardLandingPage'); + } - public async gotoDashboardLandingPage(ignorePageLeaveWarning = true) { - log.debug('gotoDashboardLandingPage'); - const onPage = await this.onDashboardLandingPage(); - if (!onPage) { - await this.clickDashboardBreadcrumbLink(); - await retry.try(async () => { - const warning = await testSubjects.exists('confirmModalTitleText'); - if (warning) { - await testSubjects.click( - ignorePageLeaveWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' - ); - } - }); - await this.expectExistsDashboardLandingPage(); - } - } + public async clickDashboardBreadcrumbLink() { + this.log.debug('clickDashboardBreadcrumbLink'); + await this.testSubjects.click('breadcrumb dashboardListingBreadcrumb first'); + } - public async clickClone() { - log.debug('Clicking clone'); - await testSubjects.click('dashboardClone'); - } + public async expectOnDashboard(dashboardTitle: string) { + await this.retry.waitFor( + 'last breadcrumb to have dashboard title', + async () => (await this.globalNav.getLastBreadcrumb()) === dashboardTitle + ); + } - public async getCloneTitle() { - return await testSubjects.getAttribute('clonedDashboardTitle', 'value'); + public async gotoDashboardLandingPage(ignorePageLeaveWarning = true) { + this.log.debug('gotoDashboardLandingPage'); + const onPage = await this.onDashboardLandingPage(); + if (!onPage) { + await this.clickDashboardBreadcrumbLink(); + await this.retry.try(async () => { + const warning = await this.testSubjects.exists('confirmModalTitleText'); + if (warning) { + await this.testSubjects.click( + ignorePageLeaveWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' + ); + } + }); + await this.expectExistsDashboardLandingPage(); } + } - public async confirmClone() { - log.debug('Confirming clone'); - await testSubjects.click('cloneConfirmButton'); - } + public async clickClone() { + this.log.debug('Clicking clone'); + await this.testSubjects.click('dashboardClone'); + } - public async cancelClone() { - log.debug('Canceling clone'); - await testSubjects.click('cloneCancelButton'); - } + public async getCloneTitle() { + return await this.testSubjects.getAttribute('clonedDashboardTitle', 'value'); + } - public async setClonedDashboardTitle(title: string) { - await testSubjects.setValue('clonedDashboardTitle', title); - } + public async confirmClone() { + this.log.debug('Confirming clone'); + await this.testSubjects.click('cloneConfirmButton'); + } - /** - * Asserts that the duplicate title warning is either displayed or not displayed. - * @param { displayed: boolean } - */ - public async expectDuplicateTitleWarningDisplayed({ displayed = true }) { - if (displayed) { - await testSubjects.existOrFail('titleDupicateWarnMsg'); - } else { - await testSubjects.missingOrFail('titleDupicateWarnMsg'); - } - } + public async cancelClone() { + this.log.debug('Canceling clone'); + await this.testSubjects.click('cloneCancelButton'); + } - /** - * Asserts that the toolbar pagination (count and arrows) is either displayed or not displayed. + public async setClonedDashboardTitle(title: string) { + await this.testSubjects.setValue('clonedDashboardTitle', title); + } - */ - public async expectToolbarPaginationDisplayed() { - const isLegacyDefault = PageObjects.discover.useLegacyTable(); - if (isLegacyDefault) { - const subjects = ['btnPrevPage', 'btnNextPage', 'toolBarPagerText']; - await Promise.all(subjects.map(async (subj) => await testSubjects.existOrFail(subj))); - } else { - const subjects = ['pagination-button-previous', 'pagination-button-next']; + /** + * Asserts that the duplicate title warning is either displayed or not displayed. + * @param { displayed: boolean } + */ + public async expectDuplicateTitleWarningDisplayed({ displayed = true }) { + if (displayed) { + await this.testSubjects.existOrFail('titleDupicateWarnMsg'); + } else { + await this.testSubjects.missingOrFail('titleDupicateWarnMsg'); + } + } - await Promise.all(subjects.map(async (subj) => await testSubjects.existOrFail(subj))); - const paginationListExists = await find.existsByCssSelector('.euiPagination__list'); - if (!paginationListExists) { - throw new Error(`expected discover data grid pagination list to exist`); - } + /** + * Asserts that the toolbar pagination (count and arrows) is either displayed or not displayed. + + */ + public async expectToolbarPaginationDisplayed() { + const isLegacyDefault = this.discover.useLegacyTable(); + if (isLegacyDefault) { + const subjects = ['btnPrevPage', 'btnNextPage', 'toolBarPagerText']; + await Promise.all(subjects.map(async (subj) => await this.testSubjects.existOrFail(subj))); + } else { + const subjects = ['pagination-button-previous', 'pagination-button-next']; + + await Promise.all(subjects.map(async (subj) => await this.testSubjects.existOrFail(subj))); + const paginationListExists = await this.find.existsByCssSelector('.euiPagination__list'); + if (!paginationListExists) { + throw new Error(`expected discover data grid pagination list to exist`); } } + } - public async switchToEditMode() { - log.debug('Switching to edit mode'); - await testSubjects.click('dashboardEditMode'); - // wait until the count of dashboard panels equals the count of toggle menu icons - await retry.waitFor('in edit mode', async () => { - const panels = await testSubjects.findAll('embeddablePanel', 2500); - const menuIcons = await testSubjects.findAll('embeddablePanelToggleMenuIcon', 2500); - return panels.length === menuIcons.length; - }); - } + public async switchToEditMode() { + this.log.debug('Switching to edit mode'); + await this.testSubjects.click('dashboardEditMode'); + // wait until the count of dashboard panels equals the count of toggle menu icons + await this.retry.waitFor('in edit mode', async () => { + const panels = await this.testSubjects.findAll('embeddablePanel', 2500); + const menuIcons = await this.testSubjects.findAll('embeddablePanelToggleMenuIcon', 2500); + return panels.length === menuIcons.length; + }); + } - public async getIsInViewMode() { - log.debug('getIsInViewMode'); - return await testSubjects.exists('dashboardEditMode'); - } + public async getIsInViewMode() { + this.log.debug('getIsInViewMode'); + return await this.testSubjects.exists('dashboardEditMode'); + } - public async clickCancelOutOfEditMode(accept = true) { - log.debug('clickCancelOutOfEditMode'); - await testSubjects.click('dashboardViewOnlyMode'); - if (accept) { - const confirmation = await testSubjects.exists('dashboardDiscardConfirmKeep'); - if (confirmation) { - await testSubjects.click('dashboardDiscardConfirmKeep'); - } + public async clickCancelOutOfEditMode(accept = true) { + this.log.debug('clickCancelOutOfEditMode'); + await this.testSubjects.click('dashboardViewOnlyMode'); + if (accept) { + const confirmation = await this.testSubjects.exists('dashboardDiscardConfirmKeep'); + if (confirmation) { + await this.testSubjects.click('dashboardDiscardConfirmKeep'); } } + } - public async clickDiscardChanges(accept = true) { - log.debug('clickDiscardChanges'); - await testSubjects.click('dashboardViewOnlyMode'); - if (accept) { - const confirmation = await testSubjects.exists('dashboardDiscardConfirmDiscard'); - if (confirmation) { - await testSubjects.click('dashboardDiscardConfirmDiscard'); - } + public async clickDiscardChanges(accept = true) { + this.log.debug('clickDiscardChanges'); + await this.testSubjects.click('dashboardViewOnlyMode'); + if (accept) { + const confirmation = await this.testSubjects.exists('dashboardDiscardConfirmDiscard'); + if (confirmation) { + await this.testSubjects.click('dashboardDiscardConfirmDiscard'); } } + } - public async clickQuickSave() { - await this.expectQuickSaveButtonEnabled(); - log.debug('clickQuickSave'); - await testSubjects.click('dashboardQuickSaveMenuItem'); - } - - public async clickNewDashboard(continueEditing = false) { - await listingTable.clickNewButton('createDashboardPromptButton'); - if (await testSubjects.exists('dashboardCreateConfirm')) { - if (continueEditing) { - await testSubjects.click('dashboardCreateConfirmContinue'); - } else { - await testSubjects.click('dashboardCreateConfirmStartOver'); - } - } - // make sure the dashboard page is shown - await this.waitForRenderComplete(); - } + public async clickQuickSave() { + await this.expectQuickSaveButtonEnabled(); + this.log.debug('clickQuickSave'); + await this.testSubjects.click('dashboardQuickSaveMenuItem'); + } - public async clickNewDashboardExpectWarning(continueEditing = false) { - await listingTable.clickNewButton('createDashboardPromptButton'); - await testSubjects.existOrFail('dashboardCreateConfirm'); + public async clickNewDashboard(continueEditing = false) { + await this.listingTable.clickNewButton('createDashboardPromptButton'); + if (await this.testSubjects.exists('dashboardCreateConfirm')) { if (continueEditing) { - await testSubjects.click('dashboardCreateConfirmContinue'); + await this.testSubjects.click('dashboardCreateConfirmContinue'); } else { - await testSubjects.click('dashboardCreateConfirmStartOver'); + await this.testSubjects.click('dashboardCreateConfirmStartOver'); } - // make sure the dashboard page is shown - await this.waitForRenderComplete(); - } - - public async clickCreateDashboardPrompt() { - await testSubjects.click('createDashboardPromptButton'); } + // make sure the dashboard page is shown + await this.waitForRenderComplete(); + } - public async getCreateDashboardPromptExists() { - return await testSubjects.exists('createDashboardPromptButton'); + public async clickNewDashboardExpectWarning(continueEditing = false) { + await this.listingTable.clickNewButton('createDashboardPromptButton'); + await this.testSubjects.existOrFail('dashboardCreateConfirm'); + if (continueEditing) { + await this.testSubjects.click('dashboardCreateConfirmContinue'); + } else { + await this.testSubjects.click('dashboardCreateConfirmStartOver'); } + // make sure the dashboard page is shown + await this.waitForRenderComplete(); + } - public async isOptionsOpen() { - log.debug('isOptionsOpen'); - return await testSubjects.exists('dashboardOptionsMenu'); - } + public async clickCreateDashboardPrompt() { + await this.testSubjects.click('createDashboardPromptButton'); + } - public async openOptions() { - log.debug('openOptions'); - const isOpen = await this.isOptionsOpen(); - if (!isOpen) { - return await testSubjects.click('dashboardOptionsButton'); - } - } + public async getCreateDashboardPromptExists() { + return await this.testSubjects.exists('createDashboardPromptButton'); + } - // avoids any 'Object with id x not found' errors when switching tests. - public async clearSavedObjectsFromAppLinks() { - await PageObjects.header.clickVisualize(); - await PageObjects.visualize.gotoLandingPage(); - await PageObjects.header.clickDashboard(); - await this.gotoDashboardLandingPage(); - } + public async isOptionsOpen() { + this.log.debug('isOptionsOpen'); + return await this.testSubjects.exists('dashboardOptionsMenu'); + } - public async isMarginsOn() { - log.debug('isMarginsOn'); - await this.openOptions(); - return await testSubjects.getAttribute('dashboardMarginsCheckbox', 'checked'); + public async openOptions() { + this.log.debug('openOptions'); + const isOpen = await this.isOptionsOpen(); + if (!isOpen) { + return await this.testSubjects.click('dashboardOptionsButton'); } + } - public async useMargins(on = true) { - await this.openOptions(); - const isMarginsOn = await this.isMarginsOn(); - if (isMarginsOn !== 'on') { - return await testSubjects.click('dashboardMarginsCheckbox'); - } - } + // avoids any 'Object with id x not found' errors when switching tests. + public async clearSavedObjectsFromAppLinks() { + await this.header.clickVisualize(); + await this.visualize.gotoLandingPage(); + await this.header.clickDashboard(); + await this.gotoDashboardLandingPage(); + } - public async isColorSyncOn() { - log.debug('isColorSyncOn'); - await this.openOptions(); - return await testSubjects.getAttribute('dashboardSyncColorsCheckbox', 'checked'); - } + public async isMarginsOn() { + this.log.debug('isMarginsOn'); + await this.openOptions(); + return await this.testSubjects.getAttribute('dashboardMarginsCheckbox', 'checked'); + } - public async useColorSync(on = true) { - await this.openOptions(); - const isColorSyncOn = await this.isColorSyncOn(); - if (isColorSyncOn !== 'on') { - return await testSubjects.click('dashboardSyncColorsCheckbox'); - } + public async useMargins(on = true) { + await this.openOptions(); + const isMarginsOn = await this.isMarginsOn(); + if (isMarginsOn !== 'on') { + return await this.testSubjects.click('dashboardMarginsCheckbox'); } + } - public async gotoDashboardEditMode(dashboardName: string) { - await this.loadSavedDashboard(dashboardName); - await this.switchToEditMode(); - } + public async isColorSyncOn() { + this.log.debug('isColorSyncOn'); + await this.openOptions(); + return await this.testSubjects.getAttribute('dashboardSyncColorsCheckbox', 'checked'); + } - public async renameDashboard(dashboardName: string) { - log.debug(`Naming dashboard ` + dashboardName); - await testSubjects.click('dashboardRenameButton'); - await testSubjects.setValue('savedObjectTitle', dashboardName); + public async useColorSync(on = true) { + await this.openOptions(); + const isColorSyncOn = await this.isColorSyncOn(); + if (isColorSyncOn !== 'on') { + return await this.testSubjects.click('dashboardSyncColorsCheckbox'); } + } - /** - * Save the current dashboard with the specified name and options and - * verify that the save was successful, close the toast and return the - * toast message - * - * @param dashboardName {String} - * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, needsConfirm: false, waitDialogIsClosed: boolean }} - */ - public async saveDashboard( - dashboardName: string, - saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true, exitFromEditMode: true } - ) { - await retry.try(async () => { - await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions); - - if (saveOptions.needsConfirm) { - await this.ensureDuplicateTitleCallout(); - await this.clickSave(); - } + public async gotoDashboardEditMode(dashboardName: string) { + await this.loadSavedDashboard(dashboardName); + await this.switchToEditMode(); + } - // Confirm that the Dashboard has actually been saved - await testSubjects.existOrFail('saveDashboardSuccess'); - }); - const message = await PageObjects.common.closeToast(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.waitForSaveModalToClose(); + public async renameDashboard(dashboardName: string) { + this.log.debug(`Naming dashboard ` + dashboardName); + await this.testSubjects.click('dashboardRenameButton'); + await this.testSubjects.setValue('savedObjectTitle', dashboardName); + } - const isInViewMode = await testSubjects.exists('dashboardEditMode'); - if (saveOptions.exitFromEditMode && !isInViewMode) { - await this.clickCancelOutOfEditMode(); + /** + * Save the current dashboard with the specified name and options and + * verify that the save was successful, close the toast and return the + * toast message + * + * @param dashboardName {String} + * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, needsConfirm: false, waitDialogIsClosed: boolean }} + */ + public async saveDashboard( + dashboardName: string, + saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true, exitFromEditMode: true } + ) { + await this.retry.try(async () => { + await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions); + + if (saveOptions.needsConfirm) { + await this.ensureDuplicateTitleCallout(); + await this.clickSave(); } - await PageObjects.header.waitUntilLoadingHasFinished(); - return message; - } + // Confirm that the Dashboard has actually been saved + await this.testSubjects.existOrFail('saveDashboardSuccess'); + }); + const message = await this.common.closeToast(); + await this.header.waitUntilLoadingHasFinished(); + await this.common.waitForSaveModalToClose(); - public async cancelSave() { - log.debug('Canceling save'); - await testSubjects.click('saveCancelButton'); + const isInViewMode = await this.testSubjects.exists('dashboardEditMode'); + if (saveOptions.exitFromEditMode && !isInViewMode) { + await this.clickCancelOutOfEditMode(); } + await this.header.waitUntilLoadingHasFinished(); - public async clickSave() { - log.debug('DashboardPage.clickSave'); - await testSubjects.click('confirmSaveSavedObjectButton'); - } + return message; + } - /** - * - * @param dashboardTitle {String} - * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, waitDialogIsClosed: boolean}} - */ - public async enterDashboardTitleAndClickSave( - dashboardTitle: string, - saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true } - ) { - await testSubjects.click('dashboardSaveMenuItem'); - const modalDialog = await testSubjects.find('savedObjectSaveModal'); - - log.debug('entering new title'); - await testSubjects.setValue('savedObjectTitle', dashboardTitle); - - if (saveOptions.storeTimeWithDashboard !== undefined) { - await this.setStoreTimeWithDashboard(saveOptions.storeTimeWithDashboard); - } + public async cancelSave() { + this.log.debug('Canceling save'); + await this.testSubjects.click('saveCancelButton'); + } - const saveAsNewCheckboxExists = await testSubjects.exists('saveAsNewCheckbox'); - if (saveAsNewCheckboxExists) { - await this.setSaveAsNewCheckBox(Boolean(saveOptions.saveAsNew)); - } + public async clickSave() { + this.log.debug('DashboardPage.clickSave'); + await this.testSubjects.click('confirmSaveSavedObjectButton'); + } - if (saveOptions.tags) { - await this.selectDashboardTags(saveOptions.tags); - } + /** + * + * @param dashboardTitle {String} + * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, waitDialogIsClosed: boolean}} + */ + public async enterDashboardTitleAndClickSave( + dashboardTitle: string, + saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true } + ) { + await this.testSubjects.click('dashboardSaveMenuItem'); + const modalDialog = await this.testSubjects.find('savedObjectSaveModal'); - await this.clickSave(); - if (saveOptions.waitDialogIsClosed) { - await testSubjects.waitForDeleted(modalDialog); - } + this.log.debug('entering new title'); + await this.testSubjects.setValue('savedObjectTitle', dashboardTitle); + + if (saveOptions.storeTimeWithDashboard !== undefined) { + await this.setStoreTimeWithDashboard(saveOptions.storeTimeWithDashboard); } - public async ensureDuplicateTitleCallout() { - await testSubjects.existOrFail('titleDupicateWarnMsg'); + const saveAsNewCheckboxExists = await this.testSubjects.exists('saveAsNewCheckbox'); + if (saveAsNewCheckboxExists) { + await this.setSaveAsNewCheckBox(Boolean(saveOptions.saveAsNew)); } - public async selectDashboardTags(tagNames: string[]) { - await testSubjects.click('savedObjectTagSelector'); - for (const tagName of tagNames) { - await testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); - } - await testSubjects.click('savedObjectTitle'); + if (saveOptions.tags) { + await this.selectDashboardTags(saveOptions.tags); } - /** - * @param dashboardTitle {String} - */ - public async enterDashboardTitleAndPressEnter(dashboardTitle: string) { - await testSubjects.click('dashboardSaveMenuItem'); - const modalDialog = await testSubjects.find('savedObjectSaveModal'); + await this.clickSave(); + if (saveOptions.waitDialogIsClosed) { + await this.testSubjects.waitForDeleted(modalDialog); + } + } - log.debug('entering new title'); - await testSubjects.setValue('savedObjectTitle', dashboardTitle); + public async ensureDuplicateTitleCallout() { + await this.testSubjects.existOrFail('titleDupicateWarnMsg'); + } - await PageObjects.common.pressEnterKey(); - await testSubjects.waitForDeleted(modalDialog); + public async selectDashboardTags(tagNames: string[]) { + await this.testSubjects.click('savedObjectTagSelector'); + for (const tagName of tagNames) { + await this.testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); } + await this.testSubjects.click('savedObjectTitle'); + } - // use the search filter box to narrow the results down to a single - // entry, or at least to a single page of results - public async loadSavedDashboard(dashboardName: string) { - log.debug(`Load Saved Dashboard ${dashboardName}`); + /** + * @param dashboardTitle {String} + */ + public async enterDashboardTitleAndPressEnter(dashboardTitle: string) { + await this.testSubjects.click('dashboardSaveMenuItem'); + const modalDialog = await this.testSubjects.find('savedObjectSaveModal'); - await this.gotoDashboardLandingPage(); + this.log.debug('entering new title'); + await this.testSubjects.setValue('savedObjectTitle', dashboardTitle); - await listingTable.searchForItemWithName(dashboardName); - await retry.try(async () => { - await listingTable.clickItemLink('dashboard', dashboardName); - await PageObjects.header.waitUntilLoadingHasFinished(); - // check Dashboard landing page is not present - await testSubjects.missingOrFail('dashboardLandingPage', { timeout: 10000 }); - }); - } + await this.common.pressEnterKey(); + await this.testSubjects.waitForDeleted(modalDialog); + } - public async getPanelTitles() { - log.debug('in getPanelTitles'); - const titleObjects = await testSubjects.findAll('dashboardPanelTitle'); - return await Promise.all(titleObjects.map(async (title) => await title.getVisibleText())); - } + // use the search filter box to narrow the results down to a single + // entry, or at least to a single page of results + public async loadSavedDashboard(dashboardName: string) { + this.log.debug(`Load Saved Dashboard ${dashboardName}`); - public async getPanelDimensions() { - const panels = await find.allByCssSelector('.react-grid-item'); // These are gridster-defined elements and classes - return await Promise.all( - panels.map(async (panel) => { - const size = await panel.getSize(); - return { - width: size.width, - height: size.height, - }; - }) - ); - } + await this.gotoDashboardLandingPage(); - public async getPanelCount() { - log.debug('getPanelCount'); - const panels = await testSubjects.findAll('embeddablePanel'); - return panels.length; - } + await this.listingTable.searchForItemWithName(dashboardName); + await this.retry.try(async () => { + await this.listingTable.clickItemLink('dashboard', dashboardName); + await this.header.waitUntilLoadingHasFinished(); + // check Dashboard landing page is not present + await this.testSubjects.missingOrFail('dashboardLandingPage', { timeout: 10000 }); + }); + } - public getTestVisualizations() { - return [ - { name: PIE_CHART_VIS_NAME, description: 'PieChart' }, - { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, - { name: AREA_CHART_VIS_NAME, description: 'AreaChart' }, - { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, - { name: LINE_CHART_VIS_NAME, description: 'LineChart' }, - { name: 'Visualization TileMap', description: 'TileMap' }, - { name: 'Visualization MetricChart', description: 'MetricChart' }, - ]; - } + public async getPanelTitles() { + this.log.debug('in getPanelTitles'); + const titleObjects = await this.testSubjects.findAll('dashboardPanelTitle'); + return await Promise.all(titleObjects.map(async (title) => await title.getVisibleText())); + } - public getTestVisualizationNames() { - return this.getTestVisualizations().map((visualization) => visualization.name); - } + public async getPanelDimensions() { + const panels = await this.find.allByCssSelector('.react-grid-item'); // These are gridster-defined elements and classes + return await Promise.all( + panels.map(async (panel) => { + const size = await panel.getSize(); + return { + width: size.width, + height: size.height, + }; + }) + ); + } - public getTestVisualizationDescriptions() { - return this.getTestVisualizations().map((visualization) => visualization.description); - } + public async getPanelCount() { + this.log.debug('getPanelCount'); + const panels = await this.testSubjects.findAll('embeddablePanel'); + return panels.length; + } - public async getDashboardPanels() { - return await testSubjects.findAll('embeddablePanel'); - } + public getTestVisualizations() { + return [ + { name: PIE_CHART_VIS_NAME, description: 'PieChart' }, + { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, + { name: AREA_CHART_VIS_NAME, description: 'AreaChart' }, + { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, + { name: LINE_CHART_VIS_NAME, description: 'LineChart' }, + { name: 'Visualization TileMap', description: 'TileMap' }, + { name: 'Visualization MetricChart', description: 'MetricChart' }, + ]; + } - public async addVisualizations(visualizations: string[]) { - await dashboardAddPanel.addVisualizations(visualizations); - } + public getTestVisualizationNames() { + return this.getTestVisualizations().map((visualization) => visualization.name); + } - public async setSaveAsNewCheckBox(checked: boolean) { - log.debug('saveAsNewCheckbox: ' + checked); - let saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); - const isAlreadyChecked = (await saveAsNewCheckbox.getAttribute('aria-checked')) === 'true'; - if (isAlreadyChecked !== checked) { - log.debug('Flipping save as new checkbox'); - saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); - await retry.try(() => saveAsNewCheckbox.click()); - } - } + public getTestVisualizationDescriptions() { + return this.getTestVisualizations().map((visualization) => visualization.description); + } - public async setStoreTimeWithDashboard(checked: boolean) { - log.debug('Storing time with dashboard: ' + checked); - let storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); - const isAlreadyChecked = (await storeTimeCheckbox.getAttribute('aria-checked')) === 'true'; - if (isAlreadyChecked !== checked) { - log.debug('Flipping store time checkbox'); - storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); - await retry.try(() => storeTimeCheckbox.click()); - } - } + public async getDashboardPanels() { + return await this.testSubjects.findAll('embeddablePanel'); + } - public async getSharedItemsCount() { - log.debug('in getSharedItemsCount'); - const attributeName = 'data-shared-items-count'; - const element = await find.byCssSelector(`[${attributeName}]`); - if (element) { - return await element.getAttribute(attributeName); - } + public async addVisualizations(visualizations: string[]) { + await this.dashboardAddPanel.addVisualizations(visualizations); + } - throw new Error('no element'); + public async setSaveAsNewCheckBox(checked: boolean) { + this.log.debug('saveAsNewCheckbox: ' + checked); + let saveAsNewCheckbox = await this.testSubjects.find('saveAsNewCheckbox'); + const isAlreadyChecked = (await saveAsNewCheckbox.getAttribute('aria-checked')) === 'true'; + if (isAlreadyChecked !== checked) { + this.log.debug('Flipping save as new checkbox'); + saveAsNewCheckbox = await this.testSubjects.find('saveAsNewCheckbox'); + await this.retry.try(() => saveAsNewCheckbox.click()); } + } - public async waitForRenderComplete() { - log.debug('waitForRenderComplete'); - const count = await this.getSharedItemsCount(); - // eslint-disable-next-line radix - await renderable.waitForRender(parseInt(count)); + public async setStoreTimeWithDashboard(checked: boolean) { + this.log.debug('Storing time with dashboard: ' + checked); + let storeTimeCheckbox = await this.testSubjects.find('storeTimeWithDashboard'); + const isAlreadyChecked = (await storeTimeCheckbox.getAttribute('aria-checked')) === 'true'; + if (isAlreadyChecked !== checked) { + this.log.debug('Flipping store time checkbox'); + storeTimeCheckbox = await this.testSubjects.find('storeTimeWithDashboard'); + await this.retry.try(() => storeTimeCheckbox.click()); } + } - public async getSharedContainerData() { - log.debug('getSharedContainerData'); - const sharedContainer = await find.byCssSelector('[data-shared-items-container]'); - return { - title: await sharedContainer.getAttribute('data-title'), - description: await sharedContainer.getAttribute('data-description'), - count: await sharedContainer.getAttribute('data-shared-items-count'), - }; + public async getSharedItemsCount() { + this.log.debug('in getSharedItemsCount'); + const attributeName = 'data-shared-items-count'; + const element = await this.find.byCssSelector(`[${attributeName}]`); + if (element) { + return await element.getAttribute(attributeName); } - public async getPanelSharedItemData() { - log.debug('in getPanelSharedItemData'); - const sharedItemscontainer = await find.byCssSelector('[data-shared-items-count]'); - const $ = await sharedItemscontainer.parseDomContent(); - return $('[data-shared-item]') - .toArray() - .map((item) => { - return { - title: $(item).attr('data-title'), - description: $(item).attr('data-description'), - }; - }); - } + throw new Error('no element'); + } - public async checkHideTitle() { - log.debug('ensure that you can click on hide title checkbox'); - await this.openOptions(); - return await testSubjects.click('dashboardPanelTitlesCheckbox'); - } + public async waitForRenderComplete() { + this.log.debug('waitForRenderComplete'); + const count = await this.getSharedItemsCount(); + // eslint-disable-next-line radix + await this.renderable.waitForRender(parseInt(count)); + } - public async expectMissingSaveOption() { - await testSubjects.missingOrFail('dashboardSaveMenuItem'); - } + public async getSharedContainerData() { + this.log.debug('getSharedContainerData'); + const sharedContainer = await this.find.byCssSelector('[data-shared-items-container]'); + return { + title: await sharedContainer.getAttribute('data-title'), + description: await sharedContainer.getAttribute('data-description'), + count: await sharedContainer.getAttribute('data-shared-items-count'), + }; + } - public async expectMissingQuickSaveOption() { - await testSubjects.missingOrFail('dashboardQuickSaveMenuItem'); - } - public async expectExistsQuickSaveOption() { - await testSubjects.existOrFail('dashboardQuickSaveMenuItem'); - } + public async getPanelSharedItemData() { + this.log.debug('in getPanelSharedItemData'); + const sharedItemscontainer = await this.find.byCssSelector('[data-shared-items-count]'); + const $ = await sharedItemscontainer.parseDomContent(); + return $('[data-shared-item]') + .toArray() + .map((item) => { + return { + title: $(item).attr('data-title'), + description: $(item).attr('data-description'), + }; + }); + } - public async expectQuickSaveButtonEnabled() { - log.debug('expectQuickSaveButtonEnabled'); - const quickSaveButton = await testSubjects.find('dashboardQuickSaveMenuItem'); - const isDisabled = await quickSaveButton.getAttribute('disabled'); - if (isDisabled) { - throw new Error('Quick save button disabled'); - } - } + public async checkHideTitle() { + this.log.debug('ensure that you can click on hide title checkbox'); + await this.openOptions(); + return await this.testSubjects.click('dashboardPanelTitlesCheckbox'); + } - public async getNotLoadedVisualizations(vizList: string[]) { - const checkList = []; - for (const name of vizList) { - const isPresent = await testSubjects.exists( - `embeddablePanelHeading-${name.replace(/\s+/g, '')}`, - { timeout: 10000 } - ); - checkList.push({ name, isPresent }); - } + public async expectMissingSaveOption() { + await this.testSubjects.missingOrFail('dashboardSaveMenuItem'); + } + + public async expectMissingQuickSaveOption() { + await this.testSubjects.missingOrFail('dashboardQuickSaveMenuItem'); + } + public async expectExistsQuickSaveOption() { + await this.testSubjects.existOrFail('dashboardQuickSaveMenuItem'); + } - return checkList.filter((viz) => viz.isPresent === false).map((viz) => viz.name); + public async expectQuickSaveButtonEnabled() { + this.log.debug('expectQuickSaveButtonEnabled'); + const quickSaveButton = await this.testSubjects.find('dashboardQuickSaveMenuItem'); + const isDisabled = await quickSaveButton.getAttribute('disabled'); + if (isDisabled) { + throw new Error('Quick save button disabled'); } + } - public async getPanelDrilldownCount(panelIndex = 0): Promise { - log.debug('getPanelDrilldownCount'); - const panel = (await this.getDashboardPanels())[panelIndex]; - try { - const count = await panel.findByTestSubject( - 'embeddablePanelNotification-ACTION_PANEL_NOTIFICATIONS' - ); - return Number.parseInt(await count.getVisibleText(), 10); - } catch (e) { - // if not found then this is 0 (we don't show badge with 0) - return 0; - } + public async getNotLoadedVisualizations(vizList: string[]) { + const checkList = []; + for (const name of vizList) { + const isPresent = await this.testSubjects.exists( + `embeddablePanelHeading-${name.replace(/\s+/g, '')}`, + { timeout: 10000 } + ); + checkList.push({ name, isPresent }); } - public async getPanelChartDebugState(panelIndex: number) { - return await elasticChart.getChartDebugData(undefined, panelIndex); + return checkList.filter((viz) => viz.isPresent === false).map((viz) => viz.name); + } + + public async getPanelDrilldownCount(panelIndex = 0): Promise { + this.log.debug('getPanelDrilldownCount'); + const panel = (await this.getDashboardPanels())[panelIndex]; + try { + const count = await panel.findByTestSubject( + 'embeddablePanelNotification-ACTION_PANEL_NOTIFICATIONS' + ); + return Number.parseInt(await count.getVisibleText(), 10); + } catch (e) { + // if not found then this is 0 (we don't show badge with 0) + return 0; } } - return new DashboardPage(); + public async getPanelChartDebugState(panelIndex: number) { + return await this.elasticChart.getChartDebugData(undefined, panelIndex); + } } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 436d22d659aec6..41c4441a1c95de 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -6,511 +6,510 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; - -export function DiscoverPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const flyout = getService('flyout'); - const { header } = getPageObjects(['header']); - const browser = getService('browser'); - const globalNav = getService('globalNav'); - const elasticChart = getService('elasticChart'); - const docTable = getService('docTable'); - const config = getService('config'); - const defaultFindTimeout = config.get('timeouts.find'); - const dataGrid = getService('dataGrid'); - const kibanaServer = getService('kibanaServer'); - - class DiscoverPage { - public async getChartTimespan() { - const el = await find.byCssSelector('[data-test-subj="discoverIntervalDateRange"]'); - return await el.getVisibleText(); - } +import { FtrService } from '../ftr_provider_context'; + +export class DiscoverPageObject extends FtrService { + private readonly retry = this.ctx.getService('retry'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly flyout = this.ctx.getService('flyout'); + private readonly header = this.ctx.getPageObject('header'); + private readonly browser = this.ctx.getService('browser'); + private readonly globalNav = this.ctx.getService('globalNav'); + private readonly elasticChart = this.ctx.getService('elasticChart'); + private readonly docTable = this.ctx.getService('docTable'); + private readonly config = this.ctx.getService('config'); + private readonly dataGrid = this.ctx.getService('dataGrid'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + + private readonly defaultFindTimeout = this.config.get('timeouts.find'); + + public async getChartTimespan() { + const el = await this.find.byCssSelector('[data-test-subj="discoverIntervalDateRange"]'); + return await el.getVisibleText(); + } - public async getDocTable() { - const isLegacyDefault = await this.useLegacyTable(); - if (isLegacyDefault) { - return docTable; - } else { - return dataGrid; - } + public async getDocTable() { + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + return this.docTable; + } else { + return this.dataGrid; } + } - public async findFieldByName(name: string) { - const fieldSearch = await testSubjects.find('fieldFilterSearchInput'); - await fieldSearch.type(name); - } + public async findFieldByName(name: string) { + const fieldSearch = await this.testSubjects.find('fieldFilterSearchInput'); + await fieldSearch.type(name); + } - public async clearFieldSearchInput() { - const fieldSearch = await testSubjects.find('fieldFilterSearchInput'); - await fieldSearch.clearValue(); - } + public async clearFieldSearchInput() { + const fieldSearch = await this.testSubjects.find('fieldFilterSearchInput'); + await fieldSearch.clearValue(); + } - public async saveSearch(searchName: string) { - await this.clickSaveSearchButton(); - // preventing an occasional flakiness when the saved object wasn't set and the form can't be submitted - await retry.waitFor( - `saved search title is set to ${searchName} and save button is clickable`, - async () => { - const saveButton = await testSubjects.find('confirmSaveSavedObjectButton'); - await testSubjects.setValue('savedObjectTitle', searchName); - return (await saveButton.getAttribute('disabled')) !== 'true'; - } - ); - await testSubjects.click('confirmSaveSavedObjectButton'); - await header.waitUntilLoadingHasFinished(); - // LeeDr - this additional checking for the saved search name was an attempt - // to cause this method to wait for the reloading of the page to complete so - // that the next action wouldn't have to retry. But it doesn't really solve - // that issue. But it does typically take about 3 retries to - // complete with the expected searchName. - await retry.waitFor(`saved search was persisted with name ${searchName}`, async () => { - return (await this.getCurrentQueryName()) === searchName; - }); - } + public async saveSearch(searchName: string) { + await this.clickSaveSearchButton(); + // preventing an occasional flakiness when the saved object wasn't set and the form can't be submitted + await this.retry.waitFor( + `saved search title is set to ${searchName} and save button is clickable`, + async () => { + const saveButton = await this.testSubjects.find('confirmSaveSavedObjectButton'); + await this.testSubjects.setValue('savedObjectTitle', searchName); + return (await saveButton.getAttribute('disabled')) !== 'true'; + } + ); + await this.testSubjects.click('confirmSaveSavedObjectButton'); + await this.header.waitUntilLoadingHasFinished(); + // LeeDr - this additional checking for the saved search name was an attempt + // to cause this method to wait for the reloading of the page to complete so + // that the next action wouldn't have to retry. But it doesn't really solve + // that issue. But it does typically take about 3 retries to + // complete with the expected searchName. + await this.retry.waitFor(`saved search was persisted with name ${searchName}`, async () => { + return (await this.getCurrentQueryName()) === searchName; + }); + } - public async inputSavedSearchTitle(searchName: string) { - await testSubjects.setValue('savedObjectTitle', searchName); - } + public async inputSavedSearchTitle(searchName: string) { + await this.testSubjects.setValue('savedObjectTitle', searchName); + } - public async clickConfirmSavedSearch() { - await testSubjects.click('confirmSaveSavedObjectButton'); - } + public async clickConfirmSavedSearch() { + await this.testSubjects.click('confirmSaveSavedObjectButton'); + } - public async openAddFilterPanel() { - await testSubjects.click('addFilter'); - } + public async openAddFilterPanel() { + await this.testSubjects.click('addFilter'); + } - public async waitUntilSearchingHasFinished() { - await testSubjects.missingOrFail('loadingSpinner', { timeout: defaultFindTimeout * 10 }); + public async waitUntilSearchingHasFinished() { + await this.testSubjects.missingOrFail('loadingSpinner', { + timeout: this.defaultFindTimeout * 10, + }); + } + + public async getColumnHeaders() { + const isLegacy = await this.useLegacyTable(); + if (isLegacy) { + return await this.docTable.getHeaderFields('embeddedSavedSearchDocTable'); } + const table = await this.getDocTable(); + return await table.getHeaderFields(); + } - public async getColumnHeaders() { - const isLegacy = await this.useLegacyTable(); - if (isLegacy) { - return await docTable.getHeaderFields('embeddedSavedSearchDocTable'); - } - const table = await this.getDocTable(); - return await table.getHeaderFields(); + public async openLoadSavedSearchPanel() { + let isOpen = await this.testSubjects.exists('loadSearchForm'); + if (isOpen) { + return; } - public async openLoadSavedSearchPanel() { - let isOpen = await testSubjects.exists('loadSearchForm'); - if (isOpen) { - return; - } + // We need this try loop here because previous actions in Discover like + // saving a search cause reloading of the page and the "Open" menu item goes stale. + await this.retry.waitFor('saved search panel is opened', async () => { + await this.clickLoadSavedSearchButton(); + await this.header.waitUntilLoadingHasFinished(); + isOpen = await this.testSubjects.exists('loadSearchForm'); + return isOpen === true; + }); + } - // We need this try loop here because previous actions in Discover like - // saving a search cause reloading of the page and the "Open" menu item goes stale. - await retry.waitFor('saved search panel is opened', async () => { - await this.clickLoadSavedSearchButton(); - await header.waitUntilLoadingHasFinished(); - isOpen = await testSubjects.exists('loadSearchForm'); - return isOpen === true; - }); - } + public async closeLoadSaveSearchPanel() { + await this.flyout.ensureClosed('loadSearchForm'); + } - public async closeLoadSaveSearchPanel() { - await flyout.ensureClosed('loadSearchForm'); - } + public async hasSavedSearch(searchName: string) { + const searchLink = await this.find.byButtonText(searchName); + return await searchLink.isDisplayed(); + } - public async hasSavedSearch(searchName: string) { - const searchLink = await find.byButtonText(searchName); - return await searchLink.isDisplayed(); - } + public async loadSavedSearch(searchName: string) { + await this.openLoadSavedSearchPanel(); + await this.testSubjects.click(`savedObjectTitle${searchName.split(' ').join('-')}`); + await this.header.waitUntilLoadingHasFinished(); + } - public async loadSavedSearch(searchName: string) { - await this.openLoadSavedSearchPanel(); - await testSubjects.click(`savedObjectTitle${searchName.split(' ').join('-')}`); - await header.waitUntilLoadingHasFinished(); - } + public async clickNewSearchButton() { + await this.testSubjects.click('discoverNewButton'); + await this.header.waitUntilLoadingHasFinished(); + } - public async clickNewSearchButton() { - await testSubjects.click('discoverNewButton'); - await header.waitUntilLoadingHasFinished(); - } + public async clickSaveSearchButton() { + await this.testSubjects.click('discoverSaveButton'); + } - public async clickSaveSearchButton() { - await testSubjects.click('discoverSaveButton'); - } + public async clickLoadSavedSearchButton() { + await this.testSubjects.moveMouseTo('discoverOpenButton'); + await this.testSubjects.click('discoverOpenButton'); + } - public async clickLoadSavedSearchButton() { - await testSubjects.moveMouseTo('discoverOpenButton'); - await testSubjects.click('discoverOpenButton'); - } + public async clickResetSavedSearchButton() { + await this.testSubjects.moveMouseTo('resetSavedSearch'); + await this.testSubjects.click('resetSavedSearch'); + await this.header.waitUntilLoadingHasFinished(); + } - public async clickResetSavedSearchButton() { - await testSubjects.moveMouseTo('resetSavedSearch'); - await testSubjects.click('resetSavedSearch'); - await header.waitUntilLoadingHasFinished(); - } + public async closeLoadSavedSearchPanel() { + await this.testSubjects.click('euiFlyoutCloseButton'); + } - public async closeLoadSavedSearchPanel() { - await testSubjects.click('euiFlyoutCloseButton'); - } + public async clickHistogramBar() { + await this.elasticChart.waitForRenderComplete(); + const el = await this.elasticChart.getCanvas(); - public async clickHistogramBar() { - await elasticChart.waitForRenderComplete(); - const el = await elasticChart.getCanvas(); + await this.browser.getActions().move({ x: 0, y: 0, origin: el._webElement }).click().perform(); + } - await browser.getActions().move({ x: 0, y: 0, origin: el._webElement }).click().perform(); - } + public async brushHistogram() { + await this.elasticChart.waitForRenderComplete(); + const el = await this.elasticChart.getCanvas(); - public async brushHistogram() { - await elasticChart.waitForRenderComplete(); - const el = await elasticChart.getCanvas(); + await this.browser.dragAndDrop( + { location: el, offset: { x: -300, y: 20 } }, + { location: el, offset: { x: -100, y: 30 } } + ); + } - await browser.dragAndDrop( - { location: el, offset: { x: -300, y: 20 } }, - { location: el, offset: { x: -100, y: 30 } } - ); - } + public async getCurrentQueryName() { + return await this.globalNav.getLastBreadcrumb(); + } - public async getCurrentQueryName() { - return await globalNav.getLastBreadcrumb(); - } + public async getChartInterval() { + const selectedValue = await this.testSubjects.getAttribute('discoverIntervalSelect', 'value'); + const selectedOption = await this.find.byCssSelector(`option[value="${selectedValue}"]`); + return selectedOption.getVisibleText(); + } - public async getChartInterval() { - const selectedValue = await testSubjects.getAttribute('discoverIntervalSelect', 'value'); - const selectedOption = await find.byCssSelector(`option[value="${selectedValue}"]`); - return selectedOption.getVisibleText(); - } + public async getChartIntervalWarningIcon() { + await this.header.waitUntilLoadingHasFinished(); + return await this.find.existsByCssSelector('.euiToolTipAnchor'); + } - public async getChartIntervalWarningIcon() { - await header.waitUntilLoadingHasFinished(); - return await find.existsByCssSelector('.euiToolTipAnchor'); - } + public async setChartInterval(interval: string) { + const optionElement = await this.find.byCssSelector(`option[label="${interval}"]`, 5000); + await optionElement.click(); + return await this.header.waitUntilLoadingHasFinished(); + } - public async setChartInterval(interval: string) { - const optionElement = await find.byCssSelector(`option[label="${interval}"]`, 5000); - await optionElement.click(); - return await header.waitUntilLoadingHasFinished(); - } + public async getHitCount() { + await this.header.waitUntilLoadingHasFinished(); + return await this.testSubjects.getVisibleText('discoverQueryHits'); + } - public async getHitCount() { - await header.waitUntilLoadingHasFinished(); - return await testSubjects.getVisibleText('discoverQueryHits'); - } + public async getDocHeader() { + const table = await this.getDocTable(); + const docHeader = await table.getHeaders(); + return docHeader.join(); + } - public async getDocHeader() { - const table = await this.getDocTable(); - const docHeader = await table.getHeaders(); - return docHeader.join(); - } + public async getDocTableRows() { + await this.header.waitUntilLoadingHasFinished(); + const table = await this.getDocTable(); + return await table.getBodyRows(); + } - public async getDocTableRows() { - await header.waitUntilLoadingHasFinished(); - const table = await this.getDocTable(); - return await table.getBodyRows(); - } + public async useLegacyTable() { + return (await this.kibanaServer.uiSettings.get('doc_table:legacy')) !== false; + } - public async useLegacyTable() { - return (await kibanaServer.uiSettings.get('doc_table:legacy')) !== false; + public async getDocTableIndex(index: number) { + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + const row = await this.find.byCssSelector(`tr.kbnDocTable__row:nth-child(${index})`); + return await row.getVisibleText(); } - public async getDocTableIndex(index: number) { - const isLegacyDefault = await this.useLegacyTable(); - if (isLegacyDefault) { - const row = await find.byCssSelector(`tr.kbnDocTable__row:nth-child(${index})`); - return await row.getVisibleText(); - } + const row = await this.dataGrid.getRow({ rowIndex: index - 1 }); + const result = await Promise.all(row.map(async (cell) => await cell.getVisibleText())); + // Remove control columns + return result.slice(2).join(' '); + } - const row = await dataGrid.getRow({ rowIndex: index - 1 }); - const result = await Promise.all(row.map(async (cell) => await cell.getVisibleText())); - // Remove control columns - return result.slice(2).join(' '); - } + public async getDocTableIndexLegacy(index: number) { + const row = await this.find.byCssSelector(`tr.kbnDocTable__row:nth-child(${index})`); + return await row.getVisibleText(); + } - public async getDocTableIndexLegacy(index: number) { - const row = await find.byCssSelector(`tr.kbnDocTable__row:nth-child(${index})`); - return await row.getVisibleText(); + public async getDocTableField(index: number, cellIdx: number = -1) { + const isLegacyDefault = await this.useLegacyTable(); + const usedDefaultCellIdx = isLegacyDefault ? 0 : 2; + const usedCellIdx = cellIdx === -1 ? usedDefaultCellIdx : cellIdx; + if (isLegacyDefault) { + const fields = await this.find.allByCssSelector( + `tr.kbnDocTable__row:nth-child(${index}) [data-test-subj='docTableField']` + ); + return await fields[usedCellIdx].getVisibleText(); } + const row = await this.dataGrid.getRow({ rowIndex: index - 1 }); + const result = await Promise.all(row.map(async (cell) => await cell.getVisibleText())); + return result[usedCellIdx]; + } - public async getDocTableField(index: number, cellIdx: number = -1) { - const isLegacyDefault = await this.useLegacyTable(); - const usedDefaultCellIdx = isLegacyDefault ? 0 : 2; - const usedCellIdx = cellIdx === -1 ? usedDefaultCellIdx : cellIdx; - if (isLegacyDefault) { - const fields = await find.allByCssSelector( - `tr.kbnDocTable__row:nth-child(${index}) [data-test-subj='docTableField']` - ); - return await fields[usedCellIdx].getVisibleText(); - } - const row = await dataGrid.getRow({ rowIndex: index - 1 }); - const result = await Promise.all(row.map(async (cell) => await cell.getVisibleText())); - return result[usedCellIdx]; - } + public async skipToEndOfDocTable() { + // add the focus to the button to make it appear + const skipButton = await this.testSubjects.find('discoverSkipTableButton'); + // force focus on it, to make it interactable + skipButton.focus(); + // now click it! + return skipButton.click(); + } - public async skipToEndOfDocTable() { - // add the focus to the button to make it appear - const skipButton = await testSubjects.find('discoverSkipTableButton'); - // force focus on it, to make it interactable - skipButton.focus(); - // now click it! - return skipButton.click(); - } + /** + * When scrolling down the legacy table there's a link to scroll up + * So this is done by this function + */ + public async backToTop() { + const skipButton = await this.testSubjects.find('discoverBackToTop'); + return skipButton.click(); + } - /** - * When scrolling down the legacy table there's a link to scroll up - * So this is done by this function - */ - public async backToTop() { - const skipButton = await testSubjects.find('discoverBackToTop'); - return skipButton.click(); - } + public async getDocTableFooter() { + return await this.testSubjects.find('discoverDocTableFooter'); + } - public async getDocTableFooter() { - return await testSubjects.find('discoverDocTableFooter'); + public async clickDocSortDown() { + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + await this.find.clickByCssSelector('.fa-sort-down'); + } else { + await this.dataGrid.clickDocSortAsc(); } + } - public async clickDocSortDown() { - const isLegacyDefault = await this.useLegacyTable(); - if (isLegacyDefault) { - await find.clickByCssSelector('.fa-sort-down'); - } else { - await dataGrid.clickDocSortAsc(); - } + public async clickDocSortUp() { + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + await this.find.clickByCssSelector('.fa-sort-up'); + } else { + await this.dataGrid.clickDocSortDesc(); } + } - public async clickDocSortUp() { - const isLegacyDefault = await this.useLegacyTable(); - if (isLegacyDefault) { - await find.clickByCssSelector('.fa-sort-up'); - } else { - await dataGrid.clickDocSortDesc(); - } - } + public async isShowingDocViewer() { + return await this.testSubjects.exists('kbnDocViewer'); + } - public async isShowingDocViewer() { - return await testSubjects.exists('kbnDocViewer'); - } + public async getMarks() { + const table = await this.docTable.getTable(); + const marks = await table.findAllByTagName('mark'); + return await Promise.all(marks.map((mark) => mark.getVisibleText())); + } - public async getMarks() { - const table = await docTable.getTable(); - const marks = await table.findAllByTagName('mark'); - return await Promise.all(marks.map((mark) => mark.getVisibleText())); - } + public async toggleSidebarCollapse() { + return await this.testSubjects.click('collapseSideBarButton'); + } - public async toggleSidebarCollapse() { - return await testSubjects.click('collapseSideBarButton'); - } + public async getAllFieldNames() { + const sidebar = await this.testSubjects.find('discover-sidebar'); + const $ = await sidebar.parseDomContent(); + return $('.dscSidebarField__name') + .toArray() + .map((field) => $(field).text()); + } - public async getAllFieldNames() { - const sidebar = await testSubjects.find('discover-sidebar'); - const $ = await sidebar.parseDomContent(); - return $('.dscSidebarField__name') - .toArray() - .map((field) => $(field).text()); - } + public async editField(field: string) { + await this.retry.try(async () => { + await this.testSubjects.click(`field-${field}`); + await this.testSubjects.click(`discoverFieldListPanelEdit-${field}`); + await this.find.byClassName('indexPatternFieldEditor__form'); + }); + } - public async editField(field: string) { - await retry.try(async () => { - await testSubjects.click(`field-${field}`); - await testSubjects.click(`discoverFieldListPanelEdit-${field}`); - await find.byClassName('indexPatternFieldEditor__form'); - }); - } + public async removeField(field: string) { + await this.testSubjects.click(`field-${field}`); + await this.testSubjects.click(`discoverFieldListPanelDelete-${field}`); + await this.testSubjects.existOrFail('runtimeFieldDeleteConfirmModal'); + } - public async removeField(field: string) { - await testSubjects.click(`field-${field}`); - await testSubjects.click(`discoverFieldListPanelDelete-${field}`); - await testSubjects.existOrFail('runtimeFieldDeleteConfirmModal'); - } + public async clickIndexPatternActions() { + await this.retry.try(async () => { + await this.testSubjects.click('discoverIndexPatternActions'); + await this.testSubjects.existOrFail('discover-addRuntimeField-popover'); + }); + } - public async clickIndexPatternActions() { - await retry.try(async () => { - await testSubjects.click('discoverIndexPatternActions'); - await testSubjects.existOrFail('discover-addRuntimeField-popover'); - }); - } + public async clickAddNewField() { + await this.retry.try(async () => { + await this.testSubjects.click('indexPattern-add-field'); + await this.find.byClassName('indexPatternFieldEditor__form'); + }); + } - public async clickAddNewField() { - await retry.try(async () => { - await testSubjects.click('indexPattern-add-field'); - await find.byClassName('indexPatternFieldEditor__form'); - }); - } + public async hasNoResults() { + return await this.testSubjects.exists('discoverNoResults'); + } - public async hasNoResults() { - return await testSubjects.exists('discoverNoResults'); - } + public async hasNoResultsTimepicker() { + return await this.testSubjects.exists('discoverNoResultsTimefilter'); + } - public async hasNoResultsTimepicker() { - return await testSubjects.exists('discoverNoResultsTimefilter'); - } + public async clickFieldListItem(field: string) { + return await this.testSubjects.click(`field-${field}`); + } - public async clickFieldListItem(field: string) { - return await testSubjects.click(`field-${field}`); + public async clickFieldSort(field: string, text = 'Sort New-Old') { + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + return await this.testSubjects.click(`docTableHeaderFieldSort_${field}`); } + return await this.dataGrid.clickDocSortAsc(field, text); + } - public async clickFieldSort(field: string, text = 'Sort New-Old') { - const isLegacyDefault = await this.useLegacyTable(); - if (isLegacyDefault) { - return await testSubjects.click(`docTableHeaderFieldSort_${field}`); - } - return await dataGrid.clickDocSortAsc(field, text); - } + public async clickFieldListItemToggle(field: string) { + await this.testSubjects.moveMouseTo(`field-${field}`); + await this.testSubjects.click(`fieldToggle-${field}`); + } - public async clickFieldListItemToggle(field: string) { - await testSubjects.moveMouseTo(`field-${field}`); - await testSubjects.click(`fieldToggle-${field}`); - } + public async clickFieldListItemAdd(field: string) { + // a filter check may make sense here, but it should be properly handled to make + // it work with the _score and _source fields as well + await this.clickFieldListItemToggle(field); + } - public async clickFieldListItemAdd(field: string) { - // a filter check may make sense here, but it should be properly handled to make - // it work with the _score and _source fields as well - await this.clickFieldListItemToggle(field); + public async clickFieldListItemRemove(field: string) { + if (!(await this.testSubjects.exists('fieldList-selected'))) { + return; } - - public async clickFieldListItemRemove(field: string) { - if (!(await testSubjects.exists('fieldList-selected'))) { - return; - } - const selectedList = await testSubjects.find('fieldList-selected'); - if (await testSubjects.descendantExists(`field-${field}`, selectedList)) { - await this.clickFieldListItemToggle(field); - } + const selectedList = await this.testSubjects.find('fieldList-selected'); + if (await this.testSubjects.descendantExists(`field-${field}`, selectedList)) { + await this.clickFieldListItemToggle(field); } + } - public async clickFieldListItemVisualize(fieldName: string) { - const field = await testSubjects.find(`field-${fieldName}-showDetails`); - const isActive = await field.elementHasClass('dscSidebarItem--active'); + public async clickFieldListItemVisualize(fieldName: string) { + const field = await this.testSubjects.find(`field-${fieldName}-showDetails`); + const isActive = await field.elementHasClass('dscSidebarItem--active'); - if (!isActive) { - // expand the field to show the "Visualize" button - await field.click(); - } - - await testSubjects.click(`fieldVisualize-${fieldName}`); + if (!isActive) { + // expand the field to show the "Visualize" button + await field.click(); } - public async expectFieldListItemVisualize(field: string) { - await testSubjects.existOrFail(`fieldVisualize-${field}`); - } + await this.testSubjects.click(`fieldVisualize-${fieldName}`); + } - public async expectMissingFieldListItemVisualize(field: string) { - await testSubjects.missingOrFail(`fieldVisualize-${field}`); - } + public async expectFieldListItemVisualize(field: string) { + await this.testSubjects.existOrFail(`fieldVisualize-${field}`); + } - public async clickFieldListPlusFilter(field: string, value: string) { - const plusFilterTestSubj = `plus-${field}-${value}`; - if (!(await testSubjects.exists(plusFilterTestSubj))) { - // field has to be open - await this.clickFieldListItem(field); - } - // testSubjects.find doesn't handle spaces in the data-test-subj value - await testSubjects.click(plusFilterTestSubj); - await header.waitUntilLoadingHasFinished(); - } + public async expectMissingFieldListItemVisualize(field: string) { + await this.testSubjects.missingOrFail(`fieldVisualize-${field}`); + } - public async clickFieldListMinusFilter(field: string, value: string) { - // this method requires the field details to be open from clickFieldListItem() - // testSubjects.find doesn't handle spaces in the data-test-subj value - await testSubjects.click(`minus-${field}-${value}`); - await header.waitUntilLoadingHasFinished(); + public async clickFieldListPlusFilter(field: string, value: string) { + const plusFilterTestSubj = `plus-${field}-${value}`; + if (!(await this.testSubjects.exists(plusFilterTestSubj))) { + // field has to be open + await this.clickFieldListItem(field); } + // this.testSubjects.find doesn't handle spaces in the data-test-subj value + await this.testSubjects.click(plusFilterTestSubj); + await this.header.waitUntilLoadingHasFinished(); + } - public async selectIndexPattern(indexPattern: string) { - await testSubjects.click('indexPattern-switch-link'); - await find.setValue('[data-test-subj="indexPattern-switcher"] input', indexPattern); - await find.clickByCssSelector( - `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]` - ); - await header.waitUntilLoadingHasFinished(); - } + public async clickFieldListMinusFilter(field: string, value: string) { + // this method requires the field details to be open from clickFieldListItem() + // this.testSubjects.find doesn't handle spaces in the data-test-subj value + await this.testSubjects.click(`minus-${field}-${value}`); + await this.header.waitUntilLoadingHasFinished(); + } - public async removeHeaderColumn(name: string) { - const isLegacyDefault = await this.useLegacyTable(); - if (isLegacyDefault) { - await testSubjects.moveMouseTo(`docTableHeader-${name}`); - await testSubjects.click(`docTableRemoveHeader-${name}`); - } else { - await dataGrid.clickRemoveColumn(name); - } - } + public async selectIndexPattern(indexPattern: string) { + await this.testSubjects.click('indexPattern-switch-link'); + await this.find.setValue('[data-test-subj="indexPattern-switcher"] input', indexPattern); + await this.find.clickByCssSelector( + `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]` + ); + await this.header.waitUntilLoadingHasFinished(); + } - public async openSidebarFieldFilter() { - await testSubjects.click('toggleFieldFilterButton'); - await testSubjects.existOrFail('filterSelectionPanel'); + public async removeHeaderColumn(name: string) { + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + await this.testSubjects.moveMouseTo(`docTableHeader-${name}`); + await this.testSubjects.click(`docTableRemoveHeader-${name}`); + } else { + await this.dataGrid.clickRemoveColumn(name); } + } - public async closeSidebarFieldFilter() { - await testSubjects.click('toggleFieldFilterButton'); - await testSubjects.missingOrFail('filterSelectionPanel'); - } + public async openSidebarFieldFilter() { + await this.testSubjects.click('toggleFieldFilterButton'); + await this.testSubjects.existOrFail('filterSelectionPanel'); + } - public async waitForChartLoadingComplete(renderCount: number) { - await elasticChart.waitForRenderingCount(renderCount, 'discoverChart'); - } + public async closeSidebarFieldFilter() { + await this.testSubjects.click('toggleFieldFilterButton'); + await this.testSubjects.missingOrFail('filterSelectionPanel'); + } - public async waitForDocTableLoadingComplete() { - await testSubjects.waitForAttributeToChange( - 'discoverDocTable', - 'data-render-complete', - 'true' - ); - } - public async getNrOfFetches() { - const el = await find.byCssSelector('[data-fetch-counter]'); - const nr = await el.getAttribute('data-fetch-counter'); - return Number(nr); - } + public async waitForChartLoadingComplete(renderCount: number) { + await this.elasticChart.waitForRenderingCount(renderCount, 'discoverChart'); + } - /** - * Check if Discover app is currently rendered on the screen. - */ - public async isDiscoverAppOnScreen(): Promise { - const result = await find.allByCssSelector('discover-app'); - return result.length === 1; - } + public async waitForDocTableLoadingComplete() { + await this.testSubjects.waitForAttributeToChange( + 'discoverDocTable', + 'data-render-complete', + 'true' + ); + } + public async getNrOfFetches() { + const el = await this.find.byCssSelector('[data-fetch-counter]'); + const nr = await el.getAttribute('data-fetch-counter'); + return Number(nr); + } - /** - * Wait until Discover app is rendered on the screen. - */ - public async waitForDiscoverAppOnScreen() { - await retry.waitFor('Discover app on screen', async () => { - return await this.isDiscoverAppOnScreen(); - }); - } + /** + * Check if Discover app is currently rendered on the screen. + */ + public async isDiscoverAppOnScreen(): Promise { + const result = await this.find.allByCssSelector('discover-app'); + return result.length === 1; + } - public async showAllFilterActions() { - await testSubjects.click('showFilterActions'); - } + /** + * Wait until Discover app is rendered on the screen. + */ + public async waitForDiscoverAppOnScreen() { + await this.retry.waitFor('Discover app on screen', async () => { + return await this.isDiscoverAppOnScreen(); + }); + } - public async clickSavedQueriesPopOver() { - await testSubjects.click('saved-query-management-popover-button'); - } + public async showAllFilterActions() { + await this.testSubjects.click('showFilterActions'); + } - public async clickCurrentSavedQuery() { - await testSubjects.click('saved-query-management-save-button'); - } + public async clickSavedQueriesPopOver() { + await this.testSubjects.click('saved-query-management-popover-button'); + } - public async setSaveQueryFormTitle(savedQueryName: string) { - await testSubjects.setValue('saveQueryFormTitle', savedQueryName); - } + public async clickCurrentSavedQuery() { + await this.testSubjects.click('saved-query-management-save-button'); + } - public async toggleIncludeFilters() { - await testSubjects.click('saveQueryFormIncludeFiltersOption'); - } + public async setSaveQueryFormTitle(savedQueryName: string) { + await this.testSubjects.setValue('saveQueryFormTitle', savedQueryName); + } - public async saveCurrentSavedQuery() { - await testSubjects.click('savedQueryFormSaveButton'); - } + public async toggleIncludeFilters() { + await this.testSubjects.click('saveQueryFormIncludeFiltersOption'); + } - public async deleteSavedQuery() { - await testSubjects.click('delete-saved-query-TEST-button'); - } + public async saveCurrentSavedQuery() { + await this.testSubjects.click('savedQueryFormSaveButton'); + } - public async confirmDeletionOfSavedQuery() { - await testSubjects.click('confirmModalConfirmButton'); - } + public async deleteSavedQuery() { + await this.testSubjects.click('delete-saved-query-TEST-button'); + } - public async clearSavedQuery() { - await testSubjects.click('saved-query-management-clear-button'); - } + public async confirmDeletionOfSavedQuery() { + await this.testSubjects.click('confirmModalConfirmButton'); } - return new DiscoverPage(); + public async clearSavedQuery() { + await this.testSubjects.click('saved-query-management-clear-button'); + } } diff --git a/test/functional/page_objects/error_page.ts b/test/functional/page_objects/error_page.ts index 99c17c632720a1..b0166e3753dd55 100644 --- a/test/functional/page_objects/error_page.ts +++ b/test/functional/page_objects/error_page.ts @@ -7,28 +7,24 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function ErrorPageProvider({ getPageObjects }: FtrProviderContext) { - const { common } = getPageObjects(['common']); +export class ErrorPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); - class ErrorPage { - public async expectForbidden() { - const messageText = await common.getBodyText(); - expect(messageText).to.contain('You do not have permission to access the requested page'); - } - - public async expectNotFound() { - const messageText = await common.getJsonBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); - } + public async expectForbidden() { + const messageText = await this.common.getBodyText(); + expect(messageText).to.contain('You do not have permission to access the requested page'); } - return new ErrorPage(); + public async expectNotFound() { + const messageText = await this.common.getJsonBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) + ); + } } diff --git a/test/functional/page_objects/header_page.ts b/test/functional/page_objects/header_page.ts index c5a796a1eb13be..8597a4b4ee2fbb 100644 --- a/test/functional/page_objects/header_page.ts +++ b/test/functional/page_objects/header_page.ts @@ -6,92 +6,88 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const config = getService('config'); - const log = getService('log'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const appsMenu = getService('appsMenu'); - const PageObjects = getPageObjects(['common']); +export class HeaderPageObject extends FtrService { + private readonly config = this.ctx.getService('config'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly appsMenu = this.ctx.getService('appsMenu'); + private readonly common = this.ctx.getPageObject('common'); - const defaultFindTimeout = config.get('timeouts.find'); + private readonly defaultFindTimeout = this.config.get('timeouts.find'); - class HeaderPage { - public async clickDiscover(ignoreAppLeaveWarning = false) { - await appsMenu.clickLink('Discover', { category: 'kibana' }); - await this.onAppLeaveWarning(ignoreAppLeaveWarning); - await PageObjects.common.waitForTopNavToBeVisible(); - await this.awaitGlobalLoadingIndicatorHidden(); - } + public async clickDiscover(ignoreAppLeaveWarning = false) { + await this.appsMenu.clickLink('Discover', { category: 'kibana' }); + await this.onAppLeaveWarning(ignoreAppLeaveWarning); + await this.common.waitForTopNavToBeVisible(); + await this.awaitGlobalLoadingIndicatorHidden(); + } - public async clickVisualize(ignoreAppLeaveWarning = false) { - await appsMenu.clickLink('Visualize Library', { category: 'kibana' }); - await this.onAppLeaveWarning(ignoreAppLeaveWarning); - await this.awaitGlobalLoadingIndicatorHidden(); - await retry.waitFor('Visualize app to be loaded', async () => { - const isNavVisible = await testSubjects.exists('top-nav'); - return isNavVisible; - }); - } + public async clickVisualize(ignoreAppLeaveWarning = false) { + await this.appsMenu.clickLink('Visualize Library', { category: 'kibana' }); + await this.onAppLeaveWarning(ignoreAppLeaveWarning); + await this.awaitGlobalLoadingIndicatorHidden(); + await this.retry.waitFor('Visualize app to be loaded', async () => { + const isNavVisible = await this.testSubjects.exists('top-nav'); + return isNavVisible; + }); + } - public async clickDashboard() { - await appsMenu.clickLink('Dashboard', { category: 'kibana' }); - await retry.waitFor('dashboard app to be loaded', async () => { - const isNavVisible = await testSubjects.exists('top-nav'); - const isLandingPageVisible = await testSubjects.exists('dashboardLandingPage'); - return isNavVisible || isLandingPageVisible; - }); - await this.awaitGlobalLoadingIndicatorHidden(); - } + public async clickDashboard() { + await this.appsMenu.clickLink('Dashboard', { category: 'kibana' }); + await this.retry.waitFor('dashboard app to be loaded', async () => { + const isNavVisible = await this.testSubjects.exists('top-nav'); + const isLandingPageVisible = await this.testSubjects.exists('dashboardLandingPage'); + return isNavVisible || isLandingPageVisible; + }); + await this.awaitGlobalLoadingIndicatorHidden(); + } - public async clickStackManagement() { - await appsMenu.clickLink('Stack Management', { category: 'management' }); - await this.awaitGlobalLoadingIndicatorHidden(); - } + public async clickStackManagement() { + await this.appsMenu.clickLink('Stack Management', { category: 'management' }); + await this.awaitGlobalLoadingIndicatorHidden(); + } - public async waitUntilLoadingHasFinished() { - try { - await this.isGlobalLoadingIndicatorVisible(); - } catch (exception) { - if (exception.name === 'ElementNotVisible') { - // selenium might just have been too slow to catch it - } else { - throw exception; - } + public async waitUntilLoadingHasFinished() { + try { + await this.isGlobalLoadingIndicatorVisible(); + } catch (exception) { + if (exception.name === 'ElementNotVisible') { + // selenium might just have been too slow to catch it + } else { + throw exception; } - await this.awaitGlobalLoadingIndicatorHidden(); - } - - public async isGlobalLoadingIndicatorVisible() { - log.debug('isGlobalLoadingIndicatorVisible'); - return await testSubjects.exists('globalLoadingIndicator', { timeout: 1500 }); } + await this.awaitGlobalLoadingIndicatorHidden(); + } - public async awaitGlobalLoadingIndicatorHidden() { - await testSubjects.existOrFail('globalLoadingIndicator-hidden', { - allowHidden: true, - timeout: defaultFindTimeout * 10, - }); - } + public async isGlobalLoadingIndicatorVisible() { + this.log.debug('isGlobalLoadingIndicatorVisible'); + return await this.testSubjects.exists('globalLoadingIndicator', { timeout: 1500 }); + } - public async awaitKibanaChrome() { - log.debug('awaitKibanaChrome'); - await testSubjects.find('kibanaChrome', defaultFindTimeout * 10); - } + public async awaitGlobalLoadingIndicatorHidden() { + await this.testSubjects.existOrFail('globalLoadingIndicator-hidden', { + allowHidden: true, + timeout: this.defaultFindTimeout * 10, + }); + } - public async onAppLeaveWarning(ignoreWarning = false) { - await retry.try(async () => { - const warning = await testSubjects.exists('confirmModalTitleText'); - if (warning) { - await testSubjects.click( - ignoreWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' - ); - } - }); - } + public async awaitKibanaChrome() { + this.log.debug('awaitKibanaChrome'); + await this.testSubjects.find('kibanaChrome', this.defaultFindTimeout * 10); } - return new HeaderPage(); + public async onAppLeaveWarning(ignoreWarning = false) { + await this.retry.try(async () => { + const warning = await this.testSubjects.exists('confirmModalTitleText'); + if (warning) { + await this.testSubjects.click( + ignoreWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' + ); + } + }); + } } diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index f03f74ef8c61d9..33de6a33c50f54 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -6,138 +6,134 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function HomePageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const find = getService('find'); - const PageObjects = getPageObjects(['common']); +export class HomePageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly find = this.ctx.getService('find'); + private readonly common = this.ctx.getPageObject('common'); - class HomePage { - async clickSynopsis(title: string) { - await testSubjects.click(`homeSynopsisLink${title}`); - } - - async doesSynopsisExist(title: string) { - return await testSubjects.exists(`homeSynopsisLink${title}`); - } + async clickSynopsis(title: string) { + await this.testSubjects.click(`homeSynopsisLink${title}`); + } - async doesSampleDataSetExist(id: string) { - return await testSubjects.exists(`sampleDataSetCard${id}`); - } + async doesSynopsisExist(title: string) { + return await this.testSubjects.exists(`homeSynopsisLink${title}`); + } - async isSampleDataSetInstalled(id: string) { - return !(await testSubjects.exists(`addSampleDataSet${id}`)); - } + async doesSampleDataSetExist(id: string) { + return await this.testSubjects.exists(`sampleDataSetCard${id}`); + } - async getVisibileSolutions() { - const solutionPanels = await testSubjects.findAll('~homSolutionPanel', 2000); - const panelAttributes = await Promise.all( - solutionPanels.map((panel) => panel.getAttribute('data-test-subj')) - ); - return panelAttributes.map((attributeValue) => attributeValue.split('homSolutionPanel_')[1]); - } + async isSampleDataSetInstalled(id: string) { + return !(await this.testSubjects.exists(`addSampleDataSet${id}`)); + } - async addSampleDataSet(id: string) { - const isInstalled = await this.isSampleDataSetInstalled(id); - if (!isInstalled) { - await testSubjects.click(`addSampleDataSet${id}`); - await this._waitForSampleDataLoadingAction(id); - } - } + async getVisibileSolutions() { + const solutionPanels = await this.testSubjects.findAll('~homSolutionPanel', 2000); + const panelAttributes = await Promise.all( + solutionPanels.map((panel) => panel.getAttribute('data-test-subj')) + ); + return panelAttributes.map((attributeValue) => attributeValue.split('homSolutionPanel_')[1]); + } - async removeSampleDataSet(id: string) { - // looks like overkill but we're hitting flaky cases where we click but it doesn't remove - await testSubjects.waitForEnabled(`removeSampleDataSet${id}`); - // https://github.com/elastic/kibana/issues/65949 - // Even after waiting for the "Remove" button to be enabled we still have failures - // where it appears the click just didn't work. - await PageObjects.common.sleep(1010); - await testSubjects.click(`removeSampleDataSet${id}`); + async addSampleDataSet(id: string) { + const isInstalled = await this.isSampleDataSetInstalled(id); + if (!isInstalled) { + await this.testSubjects.click(`addSampleDataSet${id}`); await this._waitForSampleDataLoadingAction(id); } + } - // loading action is either uninstall and install - async _waitForSampleDataLoadingAction(id: string) { - const sampleDataCard = await testSubjects.find(`sampleDataSetCard${id}`); - await retry.try(async () => { - // waitForDeletedByCssSelector needs to be inside retry because it will timeout at least once - // before action is complete - await sampleDataCard.waitForDeletedByCssSelector('.euiLoadingSpinner'); - }); - } + async removeSampleDataSet(id: string) { + // looks like overkill but we're hitting flaky cases where we click but it doesn't remove + await this.testSubjects.waitForEnabled(`removeSampleDataSet${id}`); + // https://github.com/elastic/kibana/issues/65949 + // Even after waiting for the "Remove" button to be enabled we still have failures + // where it appears the click just didn't work. + await this.common.sleep(1010); + await this.testSubjects.click(`removeSampleDataSet${id}`); + await this._waitForSampleDataLoadingAction(id); + } - async launchSampleDashboard(id: string) { - await this.launchSampleDataSet(id); - await find.clickByLinkText('Dashboard'); - } + // loading action is either uninstall and install + async _waitForSampleDataLoadingAction(id: string) { + const sampleDataCard = await this.testSubjects.find(`sampleDataSetCard${id}`); + await this.retry.try(async () => { + // waitForDeletedByCssSelector needs to be inside retry because it will timeout at least once + // before action is complete + await sampleDataCard.waitForDeletedByCssSelector('.euiLoadingSpinner'); + }); + } - async launchSampleDataSet(id: string) { - await this.addSampleDataSet(id); - await testSubjects.click(`launchSampleDataSet${id}`); - } + async launchSampleDashboard(id: string) { + await this.launchSampleDataSet(id); + await this.find.clickByLinkText('Dashboard'); + } - async clickAllKibanaPlugins() { - await testSubjects.click('allPlugins'); - } + async launchSampleDataSet(id: string) { + await this.addSampleDataSet(id); + await this.testSubjects.click(`launchSampleDataSet${id}`); + } - async clickVisualizeExplorePlugins() { - await testSubjects.click('tab-data'); - } + async clickAllKibanaPlugins() { + await this.testSubjects.click('allPlugins'); + } - async clickAdminPlugin() { - await testSubjects.click('tab-admin'); - } + async clickVisualizeExplorePlugins() { + await this.testSubjects.click('tab-data'); + } - async clickOnConsole() { - await this.clickSynopsis('console'); - } - async clickOnLogo() { - await testSubjects.click('logo'); - } + async clickAdminPlugin() { + await this.testSubjects.click('tab-admin'); + } - async clickOnAddData() { - await this.clickSynopsis('home_tutorial_directory'); - } + async clickOnConsole() { + await this.clickSynopsis('console'); + } + async clickOnLogo() { + await this.testSubjects.click('logo'); + } - // clicks on Active MQ logs - async clickOnLogsTutorial() { - await this.clickSynopsis('activemqlogs'); - } + async clickOnAddData() { + await this.clickSynopsis('home_tutorial_directory'); + } - // clicks on cloud tutorial link - async clickOnCloudTutorial() { - await testSubjects.click('onCloudTutorial'); - } + // clicks on Active MQ logs + async clickOnLogsTutorial() { + await this.clickSynopsis('activemqlogs'); + } - // click on side nav toggle button to see all of side nav - async clickOnToggleNavButton() { - await testSubjects.click('toggleNavButton'); - } + // clicks on cloud tutorial link + async clickOnCloudTutorial() { + await this.testSubjects.click('onCloudTutorial'); + } - // collapse the observability side nav details - async collapseObservabibilitySideNav() { - await testSubjects.click('collapsibleNavGroup-observability'); - } + // click on side nav toggle button to see all of side nav + async clickOnToggleNavButton() { + await this.testSubjects.click('toggleNavButton'); + } - // dock the side nav - async dockTheSideNav() { - await testSubjects.click('collapsible-nav-lock'); - } + // collapse the observability side nav details + async collapseObservabibilitySideNav() { + await this.testSubjects.click('collapsibleNavGroup-observability'); + } - async loadSavedObjects() { - await retry.try(async () => { - await testSubjects.click('loadSavedObjects'); - const successMsgExists = await testSubjects.exists('loadSavedObjects_success', { - timeout: 5000, - }); - if (!successMsgExists) { - throw new Error('Failed to load saved objects'); - } - }); - } + // dock the side nav + async dockTheSideNav() { + await this.testSubjects.click('collapsible-nav-lock'); } - return new HomePage(); + async loadSavedObjects() { + await this.retry.try(async () => { + await this.testSubjects.click('loadSavedObjects'); + const successMsgExists = await this.testSubjects.exists('loadSavedObjects_success', { + timeout: 5000, + }); + if (!successMsgExists) { + throw new Error('Failed to load saved objects'); + } + }); + } } diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 413e0aef1444b5..7c06344c1a1ad5 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -6,54 +6,54 @@ * Side Public License, v 1. */ -import { CommonPageProvider } from './common_page'; -import { ConsolePageProvider } from './console_page'; -import { ContextPageProvider } from './context_page'; -import { DashboardPageProvider } from './dashboard_page'; -import { DiscoverPageProvider } from './discover_page'; -import { ErrorPageProvider } from './error_page'; -import { HeaderPageProvider } from './header_page'; -import { HomePageProvider } from './home_page'; -import { NewsfeedPageProvider } from './newsfeed_page'; -import { SettingsPageProvider } from './settings_page'; -import { SharePageProvider } from './share_page'; -import { LoginPageProvider } from './login_page'; -import { TimePickerProvider } from './time_picker'; -import { TimelionPageProvider } from './timelion_page'; -import { VisualBuilderPageProvider } from './visual_builder_page'; -import { VisualizePageProvider } from './visualize_page'; -import { VisualizeEditorPageProvider } from './visualize_editor_page'; -import { VisualizeChartPageProvider } from './visualize_chart_page'; -import { TileMapPageProvider } from './tile_map_page'; -import { TimeToVisualizePageProvider } from './time_to_visualize_page'; -import { TagCloudPageProvider } from './tag_cloud_page'; -import { VegaChartPageProvider } from './vega_chart_page'; -import { SavedObjectsPageProvider } from './management/saved_objects_page'; -import { LegacyDataTableVisProvider } from './legacy/data_table_vis'; +import { CommonPageObject } from './common_page'; +import { ConsolePageObject } from './console_page'; +import { ContextPageObject } from './context_page'; +import { DashboardPageObject } from './dashboard_page'; +import { DiscoverPageObject } from './discover_page'; +import { ErrorPageObject } from './error_page'; +import { HeaderPageObject } from './header_page'; +import { HomePageObject } from './home_page'; +import { NewsfeedPageObject } from './newsfeed_page'; +import { SettingsPageObject } from './settings_page'; +import { SharePageObject } from './share_page'; +import { LoginPageObject } from './login_page'; +import { TimePickerPageObject } from './time_picker'; +import { TimelionPageObject } from './timelion_page'; +import { VisualBuilderPageObject } from './visual_builder_page'; +import { VisualizePageObject } from './visualize_page'; +import { VisualizeEditorPageObject } from './visualize_editor_page'; +import { VisualizeChartPageObject } from './visualize_chart_page'; +import { TileMapPageObject } from './tile_map_page'; +import { TimeToVisualizePageObject } from './time_to_visualize_page'; +import { TagCloudPageObject } from './tag_cloud_page'; +import { VegaChartPageObject } from './vega_chart_page'; +import { SavedObjectsPageObject } from './management/saved_objects_page'; +import { LegacyDataTableVisPageObject } from './legacy/data_table_vis'; export const pageObjects = { - common: CommonPageProvider, - console: ConsolePageProvider, - context: ContextPageProvider, - dashboard: DashboardPageProvider, - discover: DiscoverPageProvider, - error: ErrorPageProvider, - header: HeaderPageProvider, - home: HomePageProvider, - newsfeed: NewsfeedPageProvider, - settings: SettingsPageProvider, - share: SharePageProvider, - legacyDataTableVis: LegacyDataTableVisProvider, - login: LoginPageProvider, - timelion: TimelionPageProvider, - timePicker: TimePickerProvider, - visualBuilder: VisualBuilderPageProvider, - visualize: VisualizePageProvider, - visEditor: VisualizeEditorPageProvider, - visChart: VisualizeChartPageProvider, - tileMap: TileMapPageProvider, - timeToVisualize: TimeToVisualizePageProvider, - tagCloud: TagCloudPageProvider, - vegaChart: VegaChartPageProvider, - savedObjects: SavedObjectsPageProvider, + common: CommonPageObject, + console: ConsolePageObject, + context: ContextPageObject, + dashboard: DashboardPageObject, + discover: DiscoverPageObject, + error: ErrorPageObject, + header: HeaderPageObject, + home: HomePageObject, + newsfeed: NewsfeedPageObject, + settings: SettingsPageObject, + share: SharePageObject, + legacyDataTableVis: LegacyDataTableVisPageObject, + login: LoginPageObject, + timelion: TimelionPageObject, + timePicker: TimePickerPageObject, + visualBuilder: VisualBuilderPageObject, + visualize: VisualizePageObject, + visEditor: VisualizeEditorPageObject, + visChart: VisualizeChartPageObject, + tileMap: TileMapPageObject, + timeToVisualize: TimeToVisualizePageObject, + tagCloud: TagCloudPageObject, + vegaChart: VegaChartPageObject, + savedObjects: SavedObjectsPageObject, }; diff --git a/test/functional/page_objects/legacy/data_table_vis.ts b/test/functional/page_objects/legacy/data_table_vis.ts index ef787263f2ab9d..122409f28de900 100644 --- a/test/functional/page_objects/legacy/data_table_vis.ts +++ b/test/functional/page_objects/legacy/data_table_vis.ts @@ -6,80 +6,79 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from 'test/functional/ftr_provider_context'; -import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; +import { FtrService } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../../services/lib/web_element_wrapper'; -export function LegacyDataTableVisProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); +export class LegacyDataTableVisPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); - class LegacyDataTableVis { - /** - * Converts the table data into nested array - * [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] - * @param element table - */ - private async getDataFromElement(element: WebElementWrapper): Promise { - const $ = await element.parseDomContent(); - return $('tr') - .toArray() - .map((row) => - $(row) - .find('td') - .toArray() - .map((cell) => - $(cell) - .text() - .replace(/ /g, '') - .trim() - ) - ); - } - - public async getTableVisContent({ stripEmptyRows = true } = {}) { - return await retry.try(async () => { - const container = await testSubjects.find('tableVis'); - const allTables = await testSubjects.findAllDescendant('paginated-table-body', container); + /** + * Converts the table data into nested array + * [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] + * @param element table + */ + private async getDataFromElement(element: WebElementWrapper): Promise { + const $ = await element.parseDomContent(); + return $('tr') + .toArray() + .map((row) => + $(row) + .find('td') + .toArray() + .map((cell) => + $(cell) + .text() + .replace(/ /g, '') + .trim() + ) + ); + } - if (allTables.length === 0) { - return []; - } + public async getTableVisContent({ stripEmptyRows = true } = {}) { + return await this.retry.try(async () => { + const container = await this.testSubjects.find('tableVis'); + const allTables = await this.testSubjects.findAllDescendant( + 'paginated-table-body', + container + ); - const allData = await Promise.all( - allTables.map(async (t) => { - let data = await this.getDataFromElement(t); - if (stripEmptyRows) { - data = data.filter( - (row) => row.length > 0 && row.some((cell) => cell.trim().length > 0) - ); - } - return data; - }) - ); + if (allTables.length === 0) { + return []; + } - if (allTables.length === 1) { - // If there was only one table we return only the data for that table - // This prevents an unnecessary array around that single table, which - // is the case we have in most tests. - return allData[0]; - } + const allData = await Promise.all( + allTables.map(async (t) => { + let data = await this.getDataFromElement(t); + if (stripEmptyRows) { + data = data.filter( + (row) => row.length > 0 && row.some((cell) => cell.trim().length > 0) + ); + } + return data; + }) + ); - return allData; - }); - } + if (allTables.length === 1) { + // If there was only one table we return only the data for that table + // This prevents an unnecessary array around that single table, which + // is the case we have in most tests. + return allData[0]; + } - public async filterOnTableCell(columnIndex: number, rowIndex: number) { - await retry.try(async () => { - const tableVis = await testSubjects.find('tableVis'); - const cell = await tableVis.findByCssSelector( - `tbody tr:nth-child(${rowIndex}) td:nth-child(${columnIndex})` - ); - await cell.moveMouseTo(); - const filterBtn = await testSubjects.findDescendant('filterForCellValue', cell); - await filterBtn.click(); - }); - } + return allData; + }); } - return new LegacyDataTableVis(); + public async filterOnTableCell(columnIndex: number, rowIndex: number) { + await this.retry.try(async () => { + const tableVis = await this.testSubjects.find('tableVis'); + const cell = await tableVis.findByCssSelector( + `tbody tr:nth-child(${rowIndex}) td:nth-child(${columnIndex})` + ); + await cell.moveMouseTo(); + const filterBtn = await this.testSubjects.findDescendant('filterForCellValue', cell); + await filterBtn.click(); + }); + } } diff --git a/test/functional/page_objects/login_page.ts b/test/functional/page_objects/login_page.ts index 606ddf4643c405..5318a2b2d0c154 100644 --- a/test/functional/page_objects/login_page.ts +++ b/test/functional/page_objects/login_page.ts @@ -7,65 +7,61 @@ */ import { delay } from 'bluebird'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function LoginPageProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const find = getService('find'); +export class LoginPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); - const regularLogin = async (user: string, pwd: string) => { - await testSubjects.setValue('loginUsername', user); - await testSubjects.setValue('loginPassword', pwd); - await testSubjects.click('loginSubmit'); - await find.waitForDeletedByCssSelector('.kibanaWelcomeLogo'); - await find.byCssSelector('[data-test-subj="kibanaChrome"]', 60000); // 60 sec waiting - }; - - const samlLogin = async (user: string, pwd: string) => { - try { - await find.clickByButtonText('Login using SAML'); - await find.setValue('input[name="email"]', user); - await find.setValue('input[type="password"]', pwd); - await find.clickByCssSelector('.auth0-label-submit'); - await find.byCssSelector('[data-test-subj="kibanaChrome"]', 60000); // 60 sec waiting - } catch (err) { - log.debug(`${err} \nFailed to find Auth0 login page, trying the Auth0 last login page`); - await find.clickByCssSelector('.auth0-lock-social-button'); + async login(user: string, pwd: string) { + const loginType = process.env.VM || ''; + if (loginType.includes('oidc') || loginType.includes('saml')) { + await this.samlLogin(user, pwd); + return; } - }; - class LoginPage { - async login(user: string, pwd: string) { - const loginType = process.env.VM || ''; - if (loginType.includes('oidc') || loginType.includes('saml')) { - await samlLogin(user, pwd); - return; - } + await this.regularLogin(user, pwd); + } - await regularLogin(user, pwd); - } + async logoutLogin(user: string, pwd: string) { + await this.logout(); + await this.sleep(3002); + await this.login(user, pwd); + } - async logoutLogin(user: string, pwd: string) { - await this.logout(); - await this.sleep(3002); - await this.login(user, pwd); - } + async logout() { + await this.testSubjects.click('userMenuButton'); + await this.sleep(500); + await this.testSubjects.click('logoutLink'); + this.log.debug('### found and clicked log out--------------------------'); + await this.sleep(8002); + } - async logout() { - await testSubjects.click('userMenuButton'); - await this.sleep(500); - await testSubjects.click('logoutLink'); - log.debug('### found and clicked log out--------------------------'); - await this.sleep(8002); - } + async sleep(sleepMilliseconds: number) { + this.log.debug(`... sleep(${sleepMilliseconds}) start`); + await delay(sleepMilliseconds); + this.log.debug(`... sleep(${sleepMilliseconds}) end`); + } - async sleep(sleepMilliseconds: number) { - log.debug(`... sleep(${sleepMilliseconds}) start`); - await delay(sleepMilliseconds); - log.debug(`... sleep(${sleepMilliseconds}) end`); - } + private async regularLogin(user: string, pwd: string) { + await this.testSubjects.setValue('loginUsername', user); + await this.testSubjects.setValue('loginPassword', pwd); + await this.testSubjects.click('loginSubmit'); + await this.find.waitForDeletedByCssSelector('.kibanaWelcomeLogo'); + await this.find.byCssSelector('[data-test-subj="kibanaChrome"]', 60000); // 60 sec waiting } - return new LoginPage(); + private async samlLogin(user: string, pwd: string) { + try { + await this.find.clickByButtonText('Login using SAML'); + await this.find.setValue('input[name="email"]', user); + await this.find.setValue('input[type="password"]', pwd); + await this.find.clickByCssSelector('.auth0-label-submit'); + await this.find.byCssSelector('[data-test-subj="kibanaChrome"]', 60000); // 60 sec waiting + } catch (err) { + this.log.debug(`${err} \nFailed to find Auth0 login page, trying the Auth0 last login page`); + await this.find.clickByCssSelector('.auth0-lock-social-button'); + } + } } diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index fc4de6ed7f82f8..9f48a6f57c8d81 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -8,328 +8,325 @@ import { keyBy } from 'lodash'; import { map as mapAsync } from 'bluebird'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const retry = getService('retry'); - const browser = getService('browser'); - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['header', 'common']); - - class SavedObjectsPage { - async searchForObject(objectName: string) { - const searchBox = await testSubjects.find('savedObjectSearchBar'); - await searchBox.clearValue(); - await searchBox.type(objectName); - await searchBox.pressKeys(browser.keys.ENTER); - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.waitTableIsLoaded(); - } - - async getCurrentSearchValue() { - const searchBox = await testSubjects.find('savedObjectSearchBar'); - return await searchBox.getAttribute('value'); - } +import { FtrService } from '../../ftr_provider_context'; + +export class SavedObjectsPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + + async searchForObject(objectName: string) { + const searchBox = await this.testSubjects.find('savedObjectSearchBar'); + await searchBox.clearValue(); + await searchBox.type(objectName); + await searchBox.pressKeys(this.browser.keys.ENTER); + await this.header.waitUntilLoadingHasFinished(); + await this.waitTableIsLoaded(); + } - async importFile(path: string, overwriteAll = true) { - log.debug(`importFile(${path})`); + async getCurrentSearchValue() { + const searchBox = await this.testSubjects.find('savedObjectSearchBar'); + return await searchBox.getAttribute('value'); + } - log.debug(`Clicking importObjects`); - await testSubjects.click('importObjects'); - await PageObjects.common.setFileInputPath(path); + async importFile(path: string, overwriteAll = true) { + this.log.debug(`importFile(${path})`); - if (!overwriteAll) { - log.debug(`Toggling overwriteAll`); - const radio = await testSubjects.find( - 'savedObjectsManagement-importModeControl-overwriteRadioGroup' - ); - // a radio button consists of a div tag that contains an input, a div, and a label - // we can't click the input directly, need to go up one level and click the parent div - const div = await radio.findByXpath("//div[input[@id='overwriteDisabled']]"); - await div.click(); - } else { - log.debug(`Leaving overwriteAll alone`); - } - await testSubjects.click('importSavedObjectsImportBtn'); - log.debug(`done importing the file`); + this.log.debug(`Clicking importObjects`); + await this.testSubjects.click('importObjects'); + await this.common.setFileInputPath(path); - // Wait for all the saves to happen - await PageObjects.header.waitUntilLoadingHasFinished(); + if (!overwriteAll) { + this.log.debug(`Toggling overwriteAll`); + const radio = await this.testSubjects.find( + 'savedObjectsManagement-importModeControl-overwriteRadioGroup' + ); + // a radio button consists of a div tag that contains an input, a div, and a label + // we can't click the input directly, need to go up one level and click the parent div + const div = await radio.findByXpath("//div[input[@id='overwriteDisabled']]"); + await div.click(); + } else { + this.log.debug(`Leaving overwriteAll alone`); } + await this.testSubjects.click('importSavedObjectsImportBtn'); + this.log.debug(`done importing the file`); - async checkImportSucceeded() { - await testSubjects.existOrFail('importSavedObjectsSuccess', { timeout: 20000 }); - } + // Wait for all the saves to happen + await this.header.waitUntilLoadingHasFinished(); + } - async checkNoneImported() { - await testSubjects.existOrFail('importSavedObjectsSuccessNoneImported', { timeout: 20000 }); - } + async checkImportSucceeded() { + await this.testSubjects.existOrFail('importSavedObjectsSuccess', { timeout: 20000 }); + } - async checkImportConflictsWarning() { - await testSubjects.existOrFail('importSavedObjectsConflictsWarning', { timeout: 20000 }); - } + async checkNoneImported() { + await this.testSubjects.existOrFail('importSavedObjectsSuccessNoneImported', { + timeout: 20000, + }); + } - async checkImportLegacyWarning() { - await testSubjects.existOrFail('importSavedObjectsLegacyWarning', { timeout: 20000 }); - } + async checkImportConflictsWarning() { + await this.testSubjects.existOrFail('importSavedObjectsConflictsWarning', { timeout: 20000 }); + } - async checkImportFailedWarning() { - await testSubjects.existOrFail('importSavedObjectsFailedWarning', { timeout: 20000 }); - } + async checkImportLegacyWarning() { + await this.testSubjects.existOrFail('importSavedObjectsLegacyWarning', { timeout: 20000 }); + } - async checkImportError() { - await testSubjects.existOrFail('importSavedObjectsErrorText', { timeout: 20000 }); - } + async checkImportFailedWarning() { + await this.testSubjects.existOrFail('importSavedObjectsFailedWarning', { timeout: 20000 }); + } - async getImportErrorText() { - return await testSubjects.getVisibleText('importSavedObjectsErrorText'); - } + async checkImportError() { + await this.testSubjects.existOrFail('importSavedObjectsErrorText', { timeout: 20000 }); + } - async clickImportDone() { - await testSubjects.click('importSavedObjectsDoneBtn'); - await this.waitTableIsLoaded(); - } + async getImportErrorText() { + return await this.testSubjects.getVisibleText('importSavedObjectsErrorText'); + } - async clickConfirmChanges() { - await testSubjects.click('importSavedObjectsConfirmBtn'); - } + async clickImportDone() { + await this.testSubjects.click('importSavedObjectsDoneBtn'); + await this.waitTableIsLoaded(); + } - async waitTableIsLoaded() { - return retry.try(async () => { - const isLoaded = await find.existsByDisplayedByCssSelector( - '*[data-test-subj="savedObjectsTable"] :not(.euiBasicTable-loading)' - ); + async clickConfirmChanges() { + await this.testSubjects.click('importSavedObjectsConfirmBtn'); + } - if (isLoaded) { - return true; - } else { - throw new Error('Waiting'); - } - }); - } + async waitTableIsLoaded() { + return this.retry.try(async () => { + const isLoaded = await this.find.existsByDisplayedByCssSelector( + '*[data-test-subj="savedObjectsTable"] :not(.euiBasicTable-loading)' + ); - async clickRelationshipsByTitle(title: string) { - const table = keyBy(await this.getElementsInTable(), 'title'); - // should we check if table size > 0 and log error if not? - if (table[title].menuElement) { - log.debug(`we found a context menu element for (${title}) so click it`); - await table[title].menuElement?.click(); - // Wait for context menu to render - const menuPanel = await find.byCssSelector('.euiContextMenuPanel'); - await (await menuPanel.findByTestSubject('savedObjectsTableAction-relationships')).click(); + if (isLoaded) { + return true; } else { - log.debug( - `we didn't find a menu element so should be a relastionships element for (${title}) to click` - ); - // or the action elements are on the row without the menu - await table[title].relationshipsElement?.click(); + throw new Error('Waiting'); } - } + }); + } - async setOverriddenIndexPatternValue(oldName: string, newName: string) { - const select = await testSubjects.find(`managementChangeIndexSelection-${oldName}`); - const option = await testSubjects.findDescendant(`indexPatternOption-${newName}`, select); - await option.click(); + async clickRelationshipsByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + if (table[title].menuElement) { + this.log.debug(`we found a context menu element for (${title}) so click it`); + await table[title].menuElement?.click(); + // Wait for context menu to render + const menuPanel = await this.find.byCssSelector('.euiContextMenuPanel'); + await (await menuPanel.findByTestSubject('savedObjectsTableAction-relationships')).click(); + } else { + this.log.debug( + `we didn't find a menu element so should be a relastionships element for (${title}) to click` + ); + // or the action elements are on the row without the menu + await table[title].relationshipsElement?.click(); } + } - async clickCopyToSpaceByTitle(title: string) { - const table = keyBy(await this.getElementsInTable(), 'title'); - // should we check if table size > 0 and log error if not? - if (table[title].menuElement) { - log.debug(`we found a context menu element for (${title}) so click it`); - await table[title].menuElement?.click(); - // Wait for context menu to render - const menuPanel = await find.byCssSelector('.euiContextMenuPanel'); - await ( - await menuPanel.findByTestSubject('savedObjectsTableAction-copy_saved_objects_to_space') - ).click(); - } else { - log.debug( - `we didn't find a menu element so should be a "copy to space" element for (${title}) to click` - ); - // or the action elements are on the row without the menu - await table[title].copySaveObjectsElement?.click(); - } - } + async setOverriddenIndexPatternValue(oldName: string, newName: string) { + const select = await this.testSubjects.find(`managementChangeIndexSelection-${oldName}`); + const option = await this.testSubjects.findDescendant(`indexPatternOption-${newName}`, select); + await option.click(); + } - async clickInspectByTitle(title: string) { - const table = keyBy(await this.getElementsInTable(), 'title'); - if (table[title].menuElement) { - await table[title].menuElement?.click(); - // Wait for context menu to render - const menuPanel = await find.byCssSelector('.euiContextMenuPanel'); - const panelButton = await menuPanel.findByTestSubject('savedObjectsTableAction-inspect'); - await panelButton.click(); - } else { - // or the action elements are on the row without the menu - await table[title].copySaveObjectsElement?.click(); - } + async clickCopyToSpaceByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + if (table[title].menuElement) { + this.log.debug(`we found a context menu element for (${title}) so click it`); + await table[title].menuElement?.click(); + // Wait for context menu to render + const menuPanel = await this.find.byCssSelector('.euiContextMenuPanel'); + await ( + await menuPanel.findByTestSubject('savedObjectsTableAction-copy_saved_objects_to_space') + ).click(); + } else { + this.log.debug( + `we didn't find a menu element so should be a "copy to space" element for (${title}) to click` + ); + // or the action elements are on the row without the menu + await table[title].copySaveObjectsElement?.click(); } + } - async clickCheckboxByTitle(title: string) { - const table = keyBy(await this.getElementsInTable(), 'title'); - // should we check if table size > 0 and log error if not? - await table[title].checkbox.click(); + async clickInspectByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + if (table[title].menuElement) { + await table[title].menuElement?.click(); + // Wait for context menu to render + const menuPanel = await this.find.byCssSelector('.euiContextMenuPanel'); + const panelButton = await menuPanel.findByTestSubject('savedObjectsTableAction-inspect'); + await panelButton.click(); + } else { + // or the action elements are on the row without the menu + await table[title].copySaveObjectsElement?.click(); } + } - async getObjectTypeByTitle(title: string) { - const table = keyBy(await this.getElementsInTable(), 'title'); - // should we check if table size > 0 and log error if not? - return table[title].objectType; - } + async clickCheckboxByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + await table[title].checkbox.click(); + } - async getElementsInTable() { - const rows = await testSubjects.findAll('~savedObjectsTableRow'); - return mapAsync(rows, async (row) => { - const checkbox = await row.findByCssSelector('[data-test-subj*="checkboxSelectRow"]'); - // return the object type aria-label="index patterns" - const objectType = await row.findByTestSubject('objectType'); - const titleElement = await row.findByTestSubject('savedObjectsTableRowTitle'); - // not all rows have inspect button - Advanced Settings objects don't - // Advanced Settings has 2 actions, - // data-test-subj="savedObjectsTableAction-relationships" - // data-test-subj="savedObjectsTableAction-copy_saved_objects_to_space" - // Some other objects have the ... - // data-test-subj="euiCollapsedItemActionsButton" - // Maybe some objects still have the inspect element visible? - // !!! Also note that since we don't have spaces on OSS, the actions for the same object can be different depending on OSS or not - let menuElement = null; - let inspectElement = null; - let relationshipsElement = null; - let copySaveObjectsElement = null; - const actions = await row.findByClassName('euiTableRowCell--hasActions'); - // getting the innerHTML and checking if it 'includes' a string is faster than a timeout looking for each element - const actionsHTML = await actions.getAttribute('innerHTML'); - if (actionsHTML.includes('euiCollapsedItemActionsButton')) { - menuElement = await row.findByTestSubject('euiCollapsedItemActionsButton'); - } - if (actionsHTML.includes('savedObjectsTableAction-inspect')) { - inspectElement = await row.findByTestSubject('savedObjectsTableAction-inspect'); - } - if (actionsHTML.includes('savedObjectsTableAction-relationships')) { - relationshipsElement = await row.findByTestSubject( - 'savedObjectsTableAction-relationships' - ); - } - if (actionsHTML.includes('savedObjectsTableAction-copy_saved_objects_to_space')) { - copySaveObjectsElement = await row.findByTestSubject( - 'savedObjectsTableAction-copy_saved_objects_to_space' - ); - } - return { - checkbox, - objectType: await objectType.getAttribute('aria-label'), - titleElement, - title: await titleElement.getVisibleText(), - menuElement, - inspectElement, - relationshipsElement, - copySaveObjectsElement, - }; - }); - } + async getObjectTypeByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + // should we check if table size > 0 and log error if not? + return table[title].objectType; + } - async getRowTitles() { - await this.waitTableIsLoaded(); - const table = await testSubjects.find('savedObjectsTable'); - const $ = await table.parseDomContent(); - return $.findTestSubjects('savedObjectsTableRowTitle') - .toArray() - .map((cell) => $(cell).find('.euiTableCellContent').text()); - } + async getElementsInTable() { + const rows = await this.testSubjects.findAll('~savedObjectsTableRow'); + return mapAsync(rows, async (row) => { + const checkbox = await row.findByCssSelector('[data-test-subj*="checkboxSelectRow"]'); + // return the object type aria-label="index patterns" + const objectType = await row.findByTestSubject('objectType'); + const titleElement = await row.findByTestSubject('savedObjectsTableRowTitle'); + // not all rows have inspect button - Advanced Settings objects don't + // Advanced Settings has 2 actions, + // data-test-subj="savedObjectsTableAction-relationships" + // data-test-subj="savedObjectsTableAction-copy_saved_objects_to_space" + // Some other objects have the ... + // data-test-subj="euiCollapsedItemActionsButton" + // Maybe some objects still have the inspect element visible? + // !!! Also note that since we don't have spaces on OSS, the actions for the same object can be different depending on OSS or not + let menuElement = null; + let inspectElement = null; + let relationshipsElement = null; + let copySaveObjectsElement = null; + const actions = await row.findByClassName('euiTableRowCell--hasActions'); + // getting the innerHTML and checking if it 'includes' a string is faster than a timeout looking for each element + const actionsHTML = await actions.getAttribute('innerHTML'); + if (actionsHTML.includes('euiCollapsedItemActionsButton')) { + menuElement = await row.findByTestSubject('euiCollapsedItemActionsButton'); + } + if (actionsHTML.includes('savedObjectsTableAction-inspect')) { + inspectElement = await row.findByTestSubject('savedObjectsTableAction-inspect'); + } + if (actionsHTML.includes('savedObjectsTableAction-relationships')) { + relationshipsElement = await row.findByTestSubject('savedObjectsTableAction-relationships'); + } + if (actionsHTML.includes('savedObjectsTableAction-copy_saved_objects_to_space')) { + copySaveObjectsElement = await row.findByTestSubject( + 'savedObjectsTableAction-copy_saved_objects_to_space' + ); + } + return { + checkbox, + objectType: await objectType.getAttribute('aria-label'), + titleElement, + title: await titleElement.getVisibleText(), + menuElement, + inspectElement, + relationshipsElement, + copySaveObjectsElement, + }; + }); + } - async getRelationshipFlyout() { - const rows = await testSubjects.findAll('relationshipsTableRow'); - return mapAsync(rows, async (row) => { - const objectType = await row.findByTestSubject('relationshipsObjectType'); - const relationship = await row.findByTestSubject('directRelationship'); - const titleElement = await row.findByTestSubject('relationshipsTitle'); - const inspectElement = await row.findByTestSubject('relationshipsTableAction-inspect'); - return { - objectType: await objectType.getAttribute('aria-label'), - relationship: await relationship.getVisibleText(), - titleElement, - title: await titleElement.getVisibleText(), - inspectElement, - }; - }); - } + async getRowTitles() { + await this.waitTableIsLoaded(); + const table = await this.testSubjects.find('savedObjectsTable'); + const $ = await table.parseDomContent(); + return $.findTestSubjects('savedObjectsTableRowTitle') + .toArray() + .map((cell) => $(cell).find('.euiTableCellContent').text()); + } - async getInvalidRelations() { - const rows = await testSubjects.findAll('invalidRelationshipsTableRow'); - return mapAsync(rows, async (row) => { - const objectType = await row.findByTestSubject('relationshipsObjectType'); - const objectId = await row.findByTestSubject('relationshipsObjectId'); - const relationship = await row.findByTestSubject('directRelationship'); - const error = await row.findByTestSubject('relationshipsError'); + async getRelationshipFlyout() { + const rows = await this.testSubjects.findAll('relationshipsTableRow'); + return mapAsync(rows, async (row) => { + const objectType = await row.findByTestSubject('relationshipsObjectType'); + const relationship = await row.findByTestSubject('directRelationship'); + const titleElement = await row.findByTestSubject('relationshipsTitle'); + const inspectElement = await row.findByTestSubject('relationshipsTableAction-inspect'); + return { + objectType: await objectType.getAttribute('aria-label'), + relationship: await relationship.getVisibleText(), + titleElement, + title: await titleElement.getVisibleText(), + inspectElement, + }; + }); + } + + async getInvalidRelations() { + const rows = await this.testSubjects.findAll('invalidRelationshipsTableRow'); + return mapAsync(rows, async (row) => { + const objectType = await row.findByTestSubject('relationshipsObjectType'); + const objectId = await row.findByTestSubject('relationshipsObjectId'); + const relationship = await row.findByTestSubject('directRelationship'); + const error = await row.findByTestSubject('relationshipsError'); + return { + type: await objectType.getVisibleText(), + id: await objectId.getVisibleText(), + relationship: await relationship.getVisibleText(), + error: await error.getVisibleText(), + }; + }); + } + + async getTableSummary() { + const table = await this.testSubjects.find('savedObjectsTable'); + const $ = await table.parseDomContent(); + return $('tbody tr') + .toArray() + .map((row) => { return { - type: await objectType.getVisibleText(), - id: await objectId.getVisibleText(), - relationship: await relationship.getVisibleText(), - error: await error.getVisibleText(), + title: $(row).find('td:nth-child(3) .euiTableCellContent').text(), + canViewInApp: Boolean($(row).find('td:nth-child(3) a').length), }; }); - } + } - async getTableSummary() { - const table = await testSubjects.find('savedObjectsTable'); - const $ = await table.parseDomContent(); - return $('tbody tr') - .toArray() - .map((row) => { - return { - title: $(row).find('td:nth-child(3) .euiTableCellContent').text(), - canViewInApp: Boolean($(row).find('td:nth-child(3) a').length), - }; - }); - } + async clickTableSelectAll() { + await this.testSubjects.click('checkboxSelectAll'); + } - async clickTableSelectAll() { - await testSubjects.click('checkboxSelectAll'); - } + async canBeDeleted() { + return await this.testSubjects.isEnabled('savedObjectsManagementDelete'); + } - async canBeDeleted() { - return await testSubjects.isEnabled('savedObjectsManagementDelete'); + async clickDelete({ confirmDelete = true }: { confirmDelete?: boolean } = {}) { + await this.testSubjects.click('savedObjectsManagementDelete'); + if (confirmDelete) { + await this.testSubjects.click('confirmModalConfirmButton'); + await this.waitTableIsLoaded(); } + } - async clickDelete({ confirmDelete = true }: { confirmDelete?: boolean } = {}) { - await testSubjects.click('savedObjectsManagementDelete'); - if (confirmDelete) { - await testSubjects.click('confirmModalConfirmButton'); - await this.waitTableIsLoaded(); - } - } + async getImportWarnings() { + const elements = await this.testSubjects.findAll('importSavedObjectsWarning'); + return Promise.all( + elements.map(async (element) => { + const message = await element + .findByClassName('euiCallOutHeader__title') + .then((titleEl) => titleEl.getVisibleText()); + const buttons = await element.findAllByClassName('euiButton'); + return { + message, + type: buttons.length ? 'action_required' : 'simple', + }; + }) + ); + } - async getImportWarnings() { - const elements = await testSubjects.findAll('importSavedObjectsWarning'); - return Promise.all( - elements.map(async (element) => { - const message = await element - .findByClassName('euiCallOutHeader__title') - .then((titleEl) => titleEl.getVisibleText()); - const buttons = await element.findAllByClassName('euiButton'); - return { - message, - type: buttons.length ? 'action_required' : 'simple', - }; - }) - ); + async getImportErrorsCount() { + this.log.debug(`Toggling overwriteAll`); + const errorCountNode = await this.testSubjects.find('importSavedObjectsErrorsCount'); + const errorCountText = await errorCountNode.getVisibleText(); + const match = errorCountText.match(/(\d)+/); + if (!match) { + throw Error(`unable to parse error count from text ${errorCountText}`); } - async getImportErrorsCount() { - log.debug(`Toggling overwriteAll`); - const errorCountNode = await testSubjects.find('importSavedObjectsErrorsCount'); - const errorCountText = await errorCountNode.getVisibleText(); - const match = errorCountText.match(/(\d)+/); - if (!match) { - throw Error(`unable to parse error count from text ${errorCountText}`); - } - - return +match[1]; - } + return +match[1]; } - - return new SavedObjectsPage(); } diff --git a/test/functional/page_objects/newsfeed_page.ts b/test/functional/page_objects/newsfeed_page.ts index 1fa9bb5b900020..3a4bbee9245528 100644 --- a/test/functional/page_objects/newsfeed_page.ts +++ b/test/functional/page_objects/newsfeed_page.ts @@ -6,58 +6,54 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; - -export function NewsfeedPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const find = getService('find'); - const retry = getService('retry'); - const flyout = getService('flyout'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common']); - - class NewsfeedPage { - async resetPage() { - await PageObjects.common.navigateToUrl('home', '', { useActualUrl: true }); - } - - async closeNewsfeedPanel() { - await flyout.ensureClosed('NewsfeedFlyout'); - log.debug('clickNewsfeed icon'); - await retry.waitFor('newsfeed flyout', async () => { - if (await testSubjects.exists('NewsfeedFlyout')) { - await testSubjects.click('NewsfeedFlyout > euiFlyoutCloseButton'); - return false; - } - return true; - }); - } +import { FtrService } from '../ftr_provider_context'; + +export class NewsfeedPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); + private readonly retry = this.ctx.getService('retry'); + private readonly flyout = this.ctx.getService('flyout'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly common = this.ctx.getPageObject('common'); + + async resetPage() { + await this.common.navigateToUrl('home', '', { useActualUrl: true }); + } - async openNewsfeedPanel() { - log.debug('clickNewsfeed icon'); - return await testSubjects.exists('NewsfeedFlyout'); - } + async closeNewsfeedPanel() { + await this.flyout.ensureClosed('NewsfeedFlyout'); + this.log.debug('clickNewsfeed icon'); + await this.retry.waitFor('newsfeed flyout', async () => { + if (await this.testSubjects.exists('NewsfeedFlyout')) { + await this.testSubjects.click('NewsfeedFlyout > euiFlyoutCloseButton'); + return false; + } + return true; + }); + } - async getRedButtonSign() { - return await find.existsByCssSelector('.euiHeaderSectionItemButton__notification--dot'); - } + async openNewsfeedPanel() { + this.log.debug('clickNewsfeed icon'); + return await this.testSubjects.exists('NewsfeedFlyout'); + } - async getNewsfeedList() { - const list = await testSubjects.find('NewsfeedFlyout'); - const cells = await list.findAllByTestSubject('newsHeadAlert'); + async getRedButtonSign() { + return await this.find.existsByCssSelector('.euiHeaderSectionItemButton__notification--dot'); + } - const objects = []; - for (const cell of cells) { - objects.push(await cell.getVisibleText()); - } + async getNewsfeedList() { + const list = await this.testSubjects.find('NewsfeedFlyout'); + const cells = await list.findAllByTestSubject('newsHeadAlert'); - return objects; + const objects = []; + for (const cell of cells) { + objects.push(await cell.getVisibleText()); } - async openNewsfeedEmptyPanel() { - return await testSubjects.exists('emptyNewsfeed'); - } + return objects; } - return new NewsfeedPage(); + async openNewsfeedEmptyPanel() { + return await this.testSubjects.exists('emptyNewsfeed'); + } } diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 699165a51ca8c9..7d7da79b4a3974 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -8,731 +8,730 @@ import { map as mapAsync } from 'bluebird'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function SettingsPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const retry = getService('retry'); - const browser = getService('browser'); - const find = getService('find'); - const flyout = getService('flyout'); - const testSubjects = getService('testSubjects'); - const comboBox = getService('comboBox'); - const PageObjects = getPageObjects(['header', 'common', 'savedObjects']); - - class SettingsPage { - async clickNavigation() { - await find.clickDisplayedByCssSelector('.app-link:nth-child(5) a'); - } +import { FtrService } from '../ftr_provider_context'; + +export class SettingsPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + private readonly find = this.ctx.getService('find'); + private readonly flyout = this.ctx.getService('flyout'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly header = this.ctx.getPageObject('header'); + private readonly common = this.ctx.getPageObject('common'); + private readonly savedObjects = this.ctx.getPageObject('savedObjects'); + + async clickNavigation() { + await this.find.clickDisplayedByCssSelector('.app-link:nth-child(5) a'); + } - async clickLinkText(text: string) { - await find.clickByDisplayedLinkText(text); - } + async clickLinkText(text: string) { + await this.find.clickByDisplayedLinkText(text); + } - async clickKibanaSettings() { - await testSubjects.click('settings'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('managementSettingsTitle'); - } + async clickKibanaSettings() { + await this.testSubjects.click('settings'); + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.existOrFail('managementSettingsTitle'); + } - async clickKibanaSavedObjects() { - await testSubjects.click('objects'); - await PageObjects.savedObjects.waitTableIsLoaded(); - } + async clickKibanaSavedObjects() { + await this.testSubjects.click('objects'); + await this.savedObjects.waitTableIsLoaded(); + } - async clickKibanaIndexPatterns() { - log.debug('clickKibanaIndexPatterns link'); - await testSubjects.click('indexPatterns'); + async clickKibanaIndexPatterns() { + this.log.debug('clickKibanaIndexPatterns link'); + await this.testSubjects.click('indexPatterns'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + await this.header.waitUntilLoadingHasFinished(); + } - async getAdvancedSettings(propertyName: string) { - log.debug('in getAdvancedSettings'); - return await testSubjects.getAttribute(`advancedSetting-editField-${propertyName}`, 'value'); - } + async getAdvancedSettings(propertyName: string) { + this.log.debug('in getAdvancedSettings'); + return await this.testSubjects.getAttribute( + `advancedSetting-editField-${propertyName}`, + 'value' + ); + } - async expectDisabledAdvancedSetting(propertyName: string) { - expect( - await testSubjects.getAttribute(`advancedSetting-editField-${propertyName}`, 'disabled') - ).to.eql('true'); - } + async expectDisabledAdvancedSetting(propertyName: string) { + expect( + await this.testSubjects.getAttribute(`advancedSetting-editField-${propertyName}`, 'disabled') + ).to.eql('true'); + } - async getAdvancedSettingCheckbox(propertyName: string) { - log.debug('in getAdvancedSettingCheckbox'); - return await testSubjects.getAttribute( - `advancedSetting-editField-${propertyName}`, - 'checked' - ); - } + async getAdvancedSettingCheckbox(propertyName: string) { + this.log.debug('in getAdvancedSettingCheckbox'); + return await this.testSubjects.getAttribute( + `advancedSetting-editField-${propertyName}`, + 'checked' + ); + } - async clearAdvancedSettings(propertyName: string) { - await testSubjects.click(`advancedSetting-resetField-${propertyName}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click(`advancedSetting-saveButton`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clearAdvancedSettings(propertyName: string) { + await this.testSubjects.click(`advancedSetting-resetField-${propertyName}`); + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.click(`advancedSetting-saveButton`); + await this.header.waitUntilLoadingHasFinished(); + } - async setAdvancedSettingsSelect(propertyName: string, propertyValue: string) { - await find.clickByCssSelector( - `[data-test-subj="advancedSetting-editField-${propertyName}"] option[value="${propertyValue}"]` - ); - await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click(`advancedSetting-saveButton`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async setAdvancedSettingsSelect(propertyName: string, propertyValue: string) { + await this.find.clickByCssSelector( + `[data-test-subj="advancedSetting-editField-${propertyName}"] option[value="${propertyValue}"]` + ); + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.click(`advancedSetting-saveButton`); + await this.header.waitUntilLoadingHasFinished(); + } - async setAdvancedSettingsInput(propertyName: string, propertyValue: string) { - const input = await testSubjects.find(`advancedSetting-editField-${propertyName}`); - await input.clearValue(); - await input.type(propertyValue); - await testSubjects.click(`advancedSetting-saveButton`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async setAdvancedSettingsInput(propertyName: string, propertyValue: string) { + const input = await this.testSubjects.find(`advancedSetting-editField-${propertyName}`); + await input.clearValue(); + await input.type(propertyValue); + await this.testSubjects.click(`advancedSetting-saveButton`); + await this.header.waitUntilLoadingHasFinished(); + } - async setAdvancedSettingsTextArea(propertyName: string, propertyValue: string) { - const wrapper = await testSubjects.find(`advancedSetting-editField-${propertyName}`); - const textarea = await wrapper.findByTagName('textarea'); - await textarea.focus(); - // only way to properly replace the value of the ace editor is via the JS api - await browser.execute( - (editor: string, value: string) => { - return (window as any).ace.edit(editor).setValue(value); - }, - `advancedSetting-editField-${propertyName}-editor`, - propertyValue - ); - await testSubjects.click(`advancedSetting-saveButton`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async setAdvancedSettingsTextArea(propertyName: string, propertyValue: string) { + const wrapper = await this.testSubjects.find(`advancedSetting-editField-${propertyName}`); + const textarea = await wrapper.findByTagName('textarea'); + await textarea.focus(); + // only way to properly replace the value of the ace editor is via the JS api + await this.browser.execute( + (editor: string, value: string) => { + return (window as any).ace.edit(editor).setValue(value); + }, + `advancedSetting-editField-${propertyName}-editor`, + propertyValue + ); + await this.testSubjects.click(`advancedSetting-saveButton`); + await this.header.waitUntilLoadingHasFinished(); + } - async toggleAdvancedSettingCheckbox(propertyName: string) { - await testSubjects.click(`advancedSetting-editField-${propertyName}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - await testSubjects.click(`advancedSetting-saveButton`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async toggleAdvancedSettingCheckbox(propertyName: string) { + await this.testSubjects.click(`advancedSetting-editField-${propertyName}`); + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.click(`advancedSetting-saveButton`); + await this.header.waitUntilLoadingHasFinished(); + } - async navigateTo() { - await PageObjects.common.navigateToApp('settings'); - } + async navigateTo() { + await this.common.navigateToApp('settings'); + } - async getIndexPatternField() { - return await testSubjects.find('createIndexPatternNameInput'); - } + async getIndexPatternField() { + return await this.testSubjects.find('createIndexPatternNameInput'); + } - async clickTimeFieldNameField() { - return await testSubjects.click('createIndexPatternTimeFieldSelect'); - } + async clickTimeFieldNameField() { + return await this.testSubjects.click('createIndexPatternTimeFieldSelect'); + } - async getTimeFieldNameField() { - return await testSubjects.find('createIndexPatternTimeFieldSelect'); - } + async getTimeFieldNameField() { + return await this.testSubjects.find('createIndexPatternTimeFieldSelect'); + } - async selectTimeFieldOption(selection: string) { - // open dropdown - await this.clickTimeFieldNameField(); - // close dropdown, keep focus - await this.clickTimeFieldNameField(); - await PageObjects.header.waitUntilLoadingHasFinished(); - return await retry.try(async () => { - log.debug(`selectTimeFieldOption(${selection})`); - const timeFieldOption = await this.getTimeFieldOption(selection); - await timeFieldOption.click(); - const selected = await timeFieldOption.isSelected(); - if (!selected) throw new Error('option not selected: ' + selected); - }); - } + async selectTimeFieldOption(selection: string) { + // open dropdown + await this.clickTimeFieldNameField(); + // close dropdown, keep focus + await this.clickTimeFieldNameField(); + await this.header.waitUntilLoadingHasFinished(); + return await this.retry.try(async () => { + this.log.debug(`selectTimeFieldOption(${selection})`); + const timeFieldOption = await this.getTimeFieldOption(selection); + await timeFieldOption.click(); + const selected = await timeFieldOption.isSelected(); + if (!selected) throw new Error('option not selected: ' + selected); + }); + } - async getTimeFieldOption(selection: string) { - return await find.displayedByCssSelector('option[value="' + selection + '"]'); - } + async getTimeFieldOption(selection: string) { + return await this.find.displayedByCssSelector('option[value="' + selection + '"]'); + } - async getCreateIndexPatternButton() { - return await testSubjects.find('createIndexPatternButton'); - } + async getCreateIndexPatternButton() { + return await this.testSubjects.find('createIndexPatternButton'); + } - async getCreateButton() { - return await find.displayedByCssSelector('[type="submit"]'); - } + async getCreateButton() { + return await this.find.displayedByCssSelector('[type="submit"]'); + } - async clickDefaultIndexButton() { - await testSubjects.click('setDefaultIndexPatternButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clickDefaultIndexButton() { + await this.testSubjects.click('setDefaultIndexPatternButton'); + await this.header.waitUntilLoadingHasFinished(); + } - async clickDeletePattern() { - await testSubjects.click('deleteIndexPatternButton'); - } + async clickDeletePattern() { + await this.testSubjects.click('deleteIndexPatternButton'); + } - async getIndexPageHeading() { - return await testSubjects.getVisibleText('indexPatternTitle'); - } + async getIndexPageHeading() { + return await this.testSubjects.getVisibleText('indexPatternTitle'); + } - async getConfigureHeader() { - return await find.byCssSelector('h1'); - } + async getConfigureHeader() { + return await this.find.byCssSelector('h1'); + } - async getTableHeader() { - return await find.allByCssSelector('table.euiTable thead tr th'); - } + async getTableHeader() { + return await this.find.allByCssSelector('table.euiTable thead tr th'); + } - async sortBy(columnName: string) { - const chartTypes = await find.allByCssSelector('table.euiTable thead tr th button'); + async sortBy(columnName: string) { + const chartTypes = await this.find.allByCssSelector('table.euiTable thead tr th button'); - async function getChartType(chart: Record) { - const chartString = await chart.getVisibleText(); - if (chartString === columnName) { - await chart.click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + const getChartType = async (chart: Record) => { + const chartString = await chart.getVisibleText(); + if (chartString === columnName) { + await chart.click(); + await this.header.waitUntilLoadingHasFinished(); } + }; - const getChartTypesPromises = chartTypes.map(getChartType); - return Promise.all(getChartTypesPromises); - } + const getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + } - async getTableRow(rowNumber: number, colNumber: number) { - // passing in zero-based index, but adding 1 for css 1-based indexes - return await find.byCssSelector( - 'table.euiTable tbody tr:nth-child(' + - (rowNumber + 1) + - ') td.euiTableRowCell:nth-child(' + - (colNumber + 1) + - ')' - ); - } + async getTableRow(rowNumber: number, colNumber: number) { + // passing in zero-based index, but adding 1 for css 1-based indexes + return await this.find.byCssSelector( + 'table.euiTable tbody tr:nth-child(' + + (rowNumber + 1) + + ') td.euiTableRowCell:nth-child(' + + (colNumber + 1) + + ')' + ); + } - async getFieldsTabCount() { - return retry.try(async () => { - const text = await testSubjects.getVisibleText('tab-indexedFields'); - return text.split(' ')[1].replace(/\((.*)\)/, '$1'); - }); - } + async getFieldsTabCount() { + return this.retry.try(async () => { + const text = await this.testSubjects.getVisibleText('tab-indexedFields'); + return text.split(' ')[1].replace(/\((.*)\)/, '$1'); + }); + } - async getScriptedFieldsTabCount() { - return await retry.try(async () => { - const text = await testSubjects.getVisibleText('tab-scriptedFields'); - return text.split(' ')[2].replace(/\((.*)\)/, '$1'); - }); - } + async getScriptedFieldsTabCount() { + return await this.retry.try(async () => { + const text = await this.testSubjects.getVisibleText('tab-scriptedFields'); + return text.split(' ')[2].replace(/\((.*)\)/, '$1'); + }); + } - async getFieldNames() { - const fieldNameCells = await testSubjects.findAll('editIndexPattern > indexedFieldName'); - return await mapAsync(fieldNameCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); - } + async getFieldNames() { + const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > indexedFieldName'); + return await mapAsync(fieldNameCells, async (cell) => { + return (await cell.getVisibleText()).trim(); + }); + } - async getFieldTypes() { - const fieldNameCells = await testSubjects.findAll('editIndexPattern > indexedFieldType'); - return await mapAsync(fieldNameCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); - } + async getFieldTypes() { + const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > indexedFieldType'); + return await mapAsync(fieldNameCells, async (cell) => { + return (await cell.getVisibleText()).trim(); + }); + } - async getScriptedFieldLangs() { - const fieldNameCells = await testSubjects.findAll('editIndexPattern > scriptedFieldLang'); - return await mapAsync(fieldNameCells, async (cell) => { - return (await cell.getVisibleText()).trim(); - }); - } + async getScriptedFieldLangs() { + const fieldNameCells = await this.testSubjects.findAll('editIndexPattern > scriptedFieldLang'); + return await mapAsync(fieldNameCells, async (cell) => { + return (await cell.getVisibleText()).trim(); + }); + } - async setFieldTypeFilter(type: string) { - await find.clickByCssSelector( - 'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[value="' + type + '"]' - ); - } + async setFieldTypeFilter(type: string) { + await this.find.clickByCssSelector( + 'select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[value="' + type + '"]' + ); + } - async setScriptedFieldLanguageFilter(language: string) { - await find.clickByCssSelector( - 'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[value="' + - language + - '"]' - ); - } + async setScriptedFieldLanguageFilter(language: string) { + await this.find.clickByCssSelector( + 'select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[value="' + + language + + '"]' + ); + } - async filterField(name: string) { - const input = await testSubjects.find('indexPatternFieldFilter'); - await input.clearValue(); - await input.type(name); - } + async filterField(name: string) { + const input = await this.testSubjects.find('indexPatternFieldFilter'); + await input.clearValue(); + await input.type(name); + } - async openControlsByName(name: string) { - await this.filterField(name); - const tableFields = await ( - await find.byCssSelector( - 'table.euiTable tbody tr.euiTableRow td.euiTableRowCell:first-child' - ) - ).getVisibleText(); - - await find.clickByCssSelector( - `table.euiTable tbody tr.euiTableRow:nth-child(${tableFields.indexOf(name) + 1}) - td:nth-last-child(2) button` - ); - } + async openControlsByName(name: string) { + await this.filterField(name); + const tableFields = await ( + await this.find.byCssSelector( + 'table.euiTable tbody tr.euiTableRow td.euiTableRowCell:first-child' + ) + ).getVisibleText(); + + await this.find.clickByCssSelector( + `table.euiTable tbody tr.euiTableRow:nth-child(${tableFields.indexOf(name) + 1}) + td:nth-last-child(2) button` + ); + } - async increasePopularity() { - await testSubjects.setValue('editorFieldCount', '1', { clearWithKeyboard: true }); - } + async increasePopularity() { + await this.testSubjects.setValue('editorFieldCount', '1', { clearWithKeyboard: true }); + } - async getPopularity() { - return await testSubjects.getAttribute('editorFieldCount', 'value'); - } + async getPopularity() { + return await this.testSubjects.getAttribute('editorFieldCount', 'value'); + } - async controlChangeCancel() { - await testSubjects.click('fieldCancelButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async controlChangeCancel() { + await this.testSubjects.click('fieldCancelButton'); + await this.header.waitUntilLoadingHasFinished(); + } - async controlChangeSave() { - await testSubjects.click('fieldSaveButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async controlChangeSave() { + await this.testSubjects.click('fieldSaveButton'); + await this.header.waitUntilLoadingHasFinished(); + } - async hasIndexPattern(name: string) { - return await find.existsByLinkText(name); - } + async hasIndexPattern(name: string) { + return await this.find.existsByLinkText(name); + } - async clickIndexPatternByName(name: string) { - const indexLink = await find.byXPath(`//a[descendant::*[text()='${name}']]`); - await indexLink.click(); - } + async clickIndexPatternByName(name: string) { + const indexLink = await this.find.byXPath(`//a[descendant::*[text()='${name}']]`); + await indexLink.click(); + } - async clickIndexPatternLogstash() { - await this.clickIndexPatternByName('logstash-*'); - } + async clickIndexPatternLogstash() { + await this.clickIndexPatternByName('logstash-*'); + } - async getIndexPatternList() { - await testSubjects.existOrFail('indexPatternTable', { timeout: 5000 }); - return await find.allByCssSelector( - '[data-test-subj="indexPatternTable"] .euiTable .euiTableRow' - ); - } + async getIndexPatternList() { + await this.testSubjects.existOrFail('indexPatternTable', { timeout: 5000 }); + return await this.find.allByCssSelector( + '[data-test-subj="indexPatternTable"] .euiTable .euiTableRow' + ); + } - async getAllIndexPatternNames() { - const indexPatterns = await this.getIndexPatternList(); - return await mapAsync(indexPatterns, async (index) => { - return await index.getVisibleText(); - }); - } + async getAllIndexPatternNames() { + const indexPatterns = await this.getIndexPatternList(); + return await mapAsync(indexPatterns, async (index) => { + return await index.getVisibleText(); + }); + } - async isIndexPatternListEmpty() { - return !(await testSubjects.exists('indexPatternTable', { timeout: 5000 })); - } + async isIndexPatternListEmpty() { + return !(await this.testSubjects.exists('indexPatternTable', { timeout: 5000 })); + } - async removeLogstashIndexPatternIfExist() { - if (!(await this.isIndexPatternListEmpty())) { - await this.clickIndexPatternLogstash(); - await this.removeIndexPattern(); - } + async removeLogstashIndexPatternIfExist() { + if (!(await this.isIndexPatternListEmpty())) { + await this.clickIndexPatternLogstash(); + await this.removeIndexPattern(); } + } - async createIndexPattern( - indexPatternName: string, - // null to bypass default value - timefield: string | null = '@timestamp', - isStandardIndexPattern = true - ) { - await retry.try(async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.clickKibanaIndexPatterns(); - const exists = await this.hasIndexPattern(indexPatternName); - - if (exists) { - await this.clickIndexPatternByName(indexPatternName); - return; - } + async createIndexPattern( + indexPatternName: string, + // null to bypass default value + timefield: string | null = '@timestamp', + isStandardIndexPattern = true + ) { + await this.retry.try(async () => { + await this.header.waitUntilLoadingHasFinished(); + await this.clickKibanaIndexPatterns(); + const exists = await this.hasIndexPattern(indexPatternName); + + if (exists) { + await this.clickIndexPatternByName(indexPatternName); + return; + } - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.clickAddNewIndexPatternButton(); - if (!isStandardIndexPattern) { - await this.clickCreateNewRollupButton(); - } - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async () => { - await this.setIndexPatternField(indexPatternName); - }); - - const btn = await this.getCreateIndexPatternGoToStep2Button(); - await retry.waitFor(`index pattern Go To Step 2 button to be enabled`, async () => { - return await btn.isEnabled(); - }); - await btn.click(); - - await PageObjects.common.sleep(2000); - if (timefield) { - await this.selectTimeFieldOption(timefield); - } - await (await this.getCreateIndexPatternButton()).click(); + await this.header.waitUntilLoadingHasFinished(); + await this.clickAddNewIndexPatternButton(); + if (!isStandardIndexPattern) { + await this.clickCreateNewRollupButton(); + } + await this.header.waitUntilLoadingHasFinished(); + await this.retry.try(async () => { + await this.setIndexPatternField(indexPatternName); }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async () => { - const currentUrl = await browser.getCurrentUrl(); - log.info('currentUrl', currentUrl); - if (!currentUrl.match(/indexPatterns\/.+\?/)) { - throw new Error('Index pattern not created'); - } else { - log.debug('Index pattern created: ' + currentUrl); - } + + const btn = await this.getCreateIndexPatternGoToStep2Button(); + await this.retry.waitFor(`index pattern Go To Step 2 button to be enabled`, async () => { + return await btn.isEnabled(); }); + await btn.click(); - return await this.getIndexPatternIdFromUrl(); - } + await this.common.sleep(2000); + if (timefield) { + await this.selectTimeFieldOption(timefield); + } + await (await this.getCreateIndexPatternButton()).click(); + }); + await this.header.waitUntilLoadingHasFinished(); + await this.retry.try(async () => { + const currentUrl = await this.browser.getCurrentUrl(); + this.log.info('currentUrl', currentUrl); + if (!currentUrl.match(/indexPatterns\/.+\?/)) { + throw new Error('Index pattern not created'); + } else { + this.log.debug('Index pattern created: ' + currentUrl); + } + }); - async clickAddNewIndexPatternButton() { - await PageObjects.common.scrollKibanaBodyTop(); - await testSubjects.click('createIndexPatternButton'); - } + return await this.getIndexPatternIdFromUrl(); + } - async clickCreateNewRollupButton() { - await testSubjects.click('createRollupIndexPatternButton'); - } + async clickAddNewIndexPatternButton() { + await this.common.scrollKibanaBodyTop(); + await this.testSubjects.click('createIndexPatternButton'); + } - async getIndexPatternIdFromUrl() { - const currentUrl = await browser.getCurrentUrl(); - const indexPatternId = currentUrl.match(/.*\/(.*)/)![1]; + async clickCreateNewRollupButton() { + await this.testSubjects.click('createRollupIndexPatternButton'); + } - log.debug('index pattern ID: ', indexPatternId); + async getIndexPatternIdFromUrl() { + const currentUrl = await this.browser.getCurrentUrl(); + const indexPatternId = currentUrl.match(/.*\/(.*)/)![1]; - return indexPatternId; - } + this.log.debug('index pattern ID: ', indexPatternId); - async setIndexPatternField(indexPatternName = 'logstash-*') { - log.debug(`setIndexPatternField(${indexPatternName})`); - const field = await this.getIndexPatternField(); - await field.clearValue(); - if ( - indexPatternName.charAt(0) === '*' && - indexPatternName.charAt(indexPatternName.length - 1) === '*' - ) { - // this is a special case when the index pattern name starts with '*' - // like '*:makelogs-*' where the UI will not append * - await field.type(indexPatternName, { charByChar: true }); - } else if (indexPatternName.charAt(indexPatternName.length - 1) === '*') { - // the common case where the UI will append '*' automatically so we won't type it - const tempName = indexPatternName.slice(0, -1); - await field.type(tempName, { charByChar: true }); - } else { - // case where we don't want the * appended so we'll remove it if it was added - await field.type(indexPatternName, { charByChar: true }); - const tempName = await field.getAttribute('value'); - if (tempName.length > indexPatternName.length) { - await field.type(browser.keys.DELETE, { charByChar: true }); - } + return indexPatternId; + } + + async setIndexPatternField(indexPatternName = 'logstash-*') { + this.log.debug(`setIndexPatternField(${indexPatternName})`); + const field = await this.getIndexPatternField(); + await field.clearValue(); + if ( + indexPatternName.charAt(0) === '*' && + indexPatternName.charAt(indexPatternName.length - 1) === '*' + ) { + // this is a special case when the index pattern name starts with '*' + // like '*:makelogs-*' where the UI will not append * + await field.type(indexPatternName, { charByChar: true }); + } else if (indexPatternName.charAt(indexPatternName.length - 1) === '*') { + // the common case where the UI will append '*' automatically so we won't type it + const tempName = indexPatternName.slice(0, -1); + await field.type(tempName, { charByChar: true }); + } else { + // case where we don't want the * appended so we'll remove it if it was added + await field.type(indexPatternName, { charByChar: true }); + const tempName = await field.getAttribute('value'); + if (tempName.length > indexPatternName.length) { + await field.type(this.browser.keys.DELETE, { charByChar: true }); } - const currentName = await field.getAttribute('value'); - log.debug(`setIndexPatternField set to ${currentName}`); - expect(currentName).to.eql(indexPatternName); } + const currentName = await field.getAttribute('value'); + this.log.debug(`setIndexPatternField set to ${currentName}`); + expect(currentName).to.eql(indexPatternName); + } - async getCreateIndexPatternGoToStep2Button() { - return await testSubjects.find('createIndexPatternGoToStep2Button'); - } + async getCreateIndexPatternGoToStep2Button() { + return await this.testSubjects.find('createIndexPatternGoToStep2Button'); + } - async removeIndexPattern() { - let alertText; - await retry.try(async () => { - log.debug('click delete index pattern button'); - await this.clickDeletePattern(); - }); - await retry.try(async () => { - log.debug('getAlertText'); - alertText = await testSubjects.getVisibleText('confirmModalTitleText'); - }); - await retry.try(async () => { - log.debug('acceptConfirmation'); - await testSubjects.click('confirmModalConfirmButton'); - }); - await retry.try(async () => { - const currentUrl = await browser.getCurrentUrl(); - if (currentUrl.match(/index_patterns\/.+\?/)) { - throw new Error('Index pattern not removed'); - } - }); - return alertText; - } + async removeIndexPattern() { + let alertText; + await this.retry.try(async () => { + this.log.debug('click delete index pattern button'); + await this.clickDeletePattern(); + }); + await this.retry.try(async () => { + this.log.debug('getAlertText'); + alertText = await this.testSubjects.getVisibleText('confirmModalTitleText'); + }); + await this.retry.try(async () => { + this.log.debug('acceptConfirmation'); + await this.testSubjects.click('confirmModalConfirmButton'); + }); + await this.retry.try(async () => { + const currentUrl = await this.browser.getCurrentUrl(); + if (currentUrl.match(/index_patterns\/.+\?/)) { + throw new Error('Index pattern not removed'); + } + }); + return alertText; + } - async clickFieldsTab() { - log.debug('click Fields tab'); - await testSubjects.click('tab-indexedFields'); - } + async clickFieldsTab() { + this.log.debug('click Fields tab'); + await this.testSubjects.click('tab-indexedFields'); + } - async clickScriptedFieldsTab() { - log.debug('click Scripted Fields tab'); - await testSubjects.click('tab-scriptedFields'); - } + async clickScriptedFieldsTab() { + this.log.debug('click Scripted Fields tab'); + await this.testSubjects.click('tab-scriptedFields'); + } - async clickSourceFiltersTab() { - log.debug('click Source Filters tab'); - await testSubjects.click('tab-sourceFilters'); - } + async clickSourceFiltersTab() { + this.log.debug('click Source Filters tab'); + await this.testSubjects.click('tab-sourceFilters'); + } - async editScriptedField(name: string) { - await this.filterField(name); - await find.clickByCssSelector('.euiTableRowCell--hasActions button:first-child'); - } + async editScriptedField(name: string) { + await this.filterField(name); + await this.find.clickByCssSelector('.euiTableRowCell--hasActions button:first-child'); + } - async addScriptedField( - name: string, - language: string, - type: string, - format: Record, - popularity: string, - script: string - ) { - await this.clickAddScriptedField(); - await this.setScriptedFieldName(name); - if (language) await this.setScriptedFieldLanguage(language); - if (type) await this.setScriptedFieldType(type); - if (format) { - await this.setFieldFormat(format.format); - // null means leave - default - which has no other settings - // Url adds Type, Url Template, and Label Template - // Date adds moment.js format pattern (Default: "MMMM Do YYYY, HH:mm:ss.SSS") - // String adds Transform - switch (format.format) { - case 'url': - await this.setScriptedFieldUrlType(format.type); - await this.setScriptedFieldUrlTemplate(format.template); - await this.setScriptedFieldUrlLabelTemplate(format.labelTemplate); - break; - case 'date': - await this.setScriptedFieldDatePattern(format.datePattern); - break; - case 'string': - await this.setScriptedFieldStringTransform(format.stringTransform); - break; - } + async addScriptedField( + name: string, + language: string, + type: string, + format: Record, + popularity: string, + script: string + ) { + await this.clickAddScriptedField(); + await this.setScriptedFieldName(name); + if (language) await this.setScriptedFieldLanguage(language); + if (type) await this.setScriptedFieldType(type); + if (format) { + await this.setFieldFormat(format.format); + // null means leave - default - which has no other settings + // Url adds Type, Url Template, and Label Template + // Date adds moment.js format pattern (Default: "MMMM Do YYYY, HH:mm:ss.SSS") + // String adds Transform + switch (format.format) { + case 'url': + await this.setScriptedFieldUrlType(format.type); + await this.setScriptedFieldUrlTemplate(format.template); + await this.setScriptedFieldUrlLabelTemplate(format.labelTemplate); + break; + case 'date': + await this.setScriptedFieldDatePattern(format.datePattern); + break; + case 'string': + await this.setScriptedFieldStringTransform(format.stringTransform); + break; } - if (popularity) await this.setScriptedFieldPopularity(popularity); - await this.setScriptedFieldScript(script); - await this.clickSaveScriptedField(); } + if (popularity) await this.setScriptedFieldPopularity(popularity); + await this.setScriptedFieldScript(script); + await this.clickSaveScriptedField(); + } - async addRuntimeField(name: string, type: string, script: string) { - await this.clickAddField(); - await this.setFieldName(name); - await this.setFieldType(type); - if (script) { - await this.setFieldScript(script); - } - await this.clickSaveField(); - await this.closeIndexPatternFieldEditor(); + async addRuntimeField(name: string, type: string, script: string) { + await this.clickAddField(); + await this.setFieldName(name); + await this.setFieldType(type); + if (script) { + await this.setFieldScript(script); } + await this.clickSaveField(); + await this.closeIndexPatternFieldEditor(); + } - public async confirmSave() { - await testSubjects.setValue('saveModalConfirmText', 'change'); - await testSubjects.click('confirmModalConfirmButton'); - } + public async confirmSave() { + await this.testSubjects.setValue('saveModalConfirmText', 'change'); + await this.testSubjects.click('confirmModalConfirmButton'); + } - public async confirmDelete() { - await testSubjects.setValue('deleteModalConfirmText', 'remove'); - await testSubjects.click('confirmModalConfirmButton'); - } + public async confirmDelete() { + await this.testSubjects.setValue('deleteModalConfirmText', 'remove'); + await this.testSubjects.click('confirmModalConfirmButton'); + } - async closeIndexPatternFieldEditor() { - await retry.waitFor('field editor flyout to close', async () => { - return !(await testSubjects.exists('euiFlyoutCloseButton')); - }); - } + async closeIndexPatternFieldEditor() { + await this.retry.waitFor('field editor flyout to close', async () => { + return !(await this.testSubjects.exists('euiFlyoutCloseButton')); + }); + } - async clickAddField() { - log.debug('click Add Field'); - await testSubjects.click('addField'); - } + async clickAddField() { + this.log.debug('click Add Field'); + await this.testSubjects.click('addField'); + } - async clickSaveField() { - log.debug('click Save'); - await testSubjects.click('fieldSaveButton'); - } + async clickSaveField() { + this.log.debug('click Save'); + await this.testSubjects.click('fieldSaveButton'); + } - async setFieldName(name: string) { - log.debug('set field name = ' + name); - await testSubjects.setValue('nameField', name); - } + async setFieldName(name: string) { + this.log.debug('set field name = ' + name); + await this.testSubjects.setValue('nameField', name); + } - async setFieldType(type: string) { - log.debug('set type = ' + type); - await testSubjects.setValue('typeField', type); - } + async setFieldType(type: string) { + this.log.debug('set type = ' + type); + await this.testSubjects.setValue('typeField', type); + } - async setFieldScript(script: string) { - log.debug('set script = ' + script); - const formatRow = await testSubjects.find('valueRow'); - const formatRowToggle = ( - await formatRow.findAllByCssSelector('[data-test-subj="toggle"]') - )[0]; - - await formatRowToggle.click(); - const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0]; - retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea())); - const monacoTextArea = await getMonacoTextArea(); - await monacoTextArea.focus(); - browser.pressKeys(script); - } + async setFieldScript(script: string) { + this.log.debug('set script = ' + script); + const formatRow = await this.testSubjects.find('valueRow'); + const formatRowToggle = (await formatRow.findAllByCssSelector('[data-test-subj="toggle"]'))[0]; + + await formatRowToggle.click(); + const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0]; + this.retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea())); + const monacoTextArea = await getMonacoTextArea(); + await monacoTextArea.focus(); + this.browser.pressKeys(script); + } - async changeFieldScript(script: string) { - log.debug('set script = ' + script); - const formatRow = await testSubjects.find('valueRow'); - const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0]; - retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea())); - const monacoTextArea = await getMonacoTextArea(); - await monacoTextArea.focus(); - browser.pressKeys(browser.keys.DELETE.repeat(30)); - browser.pressKeys(script); - } + async changeFieldScript(script: string) { + this.log.debug('set script = ' + script); + const formatRow = await this.testSubjects.find('valueRow'); + const getMonacoTextArea = async () => (await formatRow.findAllByCssSelector('textarea'))[0]; + this.retry.waitFor('monaco editor is ready', async () => !!(await getMonacoTextArea())); + const monacoTextArea = await getMonacoTextArea(); + await monacoTextArea.focus(); + this.browser.pressKeys(this.browser.keys.DELETE.repeat(30)); + this.browser.pressKeys(script); + } - async clickAddScriptedField() { - log.debug('click Add Scripted Field'); - await testSubjects.click('addScriptedFieldLink'); - } + async clickAddScriptedField() { + this.log.debug('click Add Scripted Field'); + await this.testSubjects.click('addScriptedFieldLink'); + } - async clickSaveScriptedField() { - log.debug('click Save Scripted Field'); - await testSubjects.click('fieldSaveButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clickSaveScriptedField() { + this.log.debug('click Save Scripted Field'); + await this.testSubjects.click('fieldSaveButton'); + await this.header.waitUntilLoadingHasFinished(); + } - async setScriptedFieldName(name: string) { - log.debug('set scripted field name = ' + name); - await testSubjects.setValue('editorFieldName', name); - } + async setScriptedFieldName(name: string) { + this.log.debug('set scripted field name = ' + name); + await this.testSubjects.setValue('editorFieldName', name); + } - async setScriptedFieldLanguage(language: string) { - log.debug('set scripted field language = ' + language); - await find.clickByCssSelector( - 'select[data-test-subj="editorFieldLang"] > option[value="' + language + '"]' - ); - } + async setScriptedFieldLanguage(language: string) { + this.log.debug('set scripted field language = ' + language); + await this.find.clickByCssSelector( + 'select[data-test-subj="editorFieldLang"] > option[value="' + language + '"]' + ); + } - async setScriptedFieldType(type: string) { - log.debug('set scripted field type = ' + type); - await find.clickByCssSelector( - 'select[data-test-subj="editorFieldType"] > option[value="' + type + '"]' - ); - } + async setScriptedFieldType(type: string) { + this.log.debug('set scripted field type = ' + type); + await this.find.clickByCssSelector( + 'select[data-test-subj="editorFieldType"] > option[value="' + type + '"]' + ); + } - async setFieldFormat(format: string) { - log.debug('set scripted field format = ' + format); - await find.clickByCssSelector( - 'select[data-test-subj="editorSelectedFormatId"] > option[value="' + format + '"]' - ); - } + async setFieldFormat(format: string) { + this.log.debug('set scripted field format = ' + format); + await this.find.clickByCssSelector( + 'select[data-test-subj="editorSelectedFormatId"] > option[value="' + format + '"]' + ); + } - async setScriptedFieldUrlType(type: string) { - log.debug('set scripted field Url type = ' + type); - await find.clickByCssSelector( - 'select[data-test-subj="urlEditorType"] > option[value="' + type + '"]' - ); - } + async setScriptedFieldUrlType(type: string) { + this.log.debug('set scripted field Url type = ' + type); + await this.find.clickByCssSelector( + 'select[data-test-subj="urlEditorType"] > option[value="' + type + '"]' + ); + } - async setScriptedFieldUrlTemplate(template: string) { - log.debug('set scripted field Url Template = ' + template); - const urlTemplateField = await find.byCssSelector( - 'input[data-test-subj="urlEditorUrlTemplate"]' - ); - await urlTemplateField.type(template); - } + async setScriptedFieldUrlTemplate(template: string) { + this.log.debug('set scripted field Url Template = ' + template); + const urlTemplateField = await this.find.byCssSelector( + 'input[data-test-subj="urlEditorUrlTemplate"]' + ); + await urlTemplateField.type(template); + } - async setScriptedFieldUrlLabelTemplate(labelTemplate: string) { - log.debug('set scripted field Url Label Template = ' + labelTemplate); - const urlEditorLabelTemplate = await find.byCssSelector( - 'input[data-test-subj="urlEditorLabelTemplate"]' - ); - await urlEditorLabelTemplate.type(labelTemplate); - } + async setScriptedFieldUrlLabelTemplate(labelTemplate: string) { + this.log.debug('set scripted field Url Label Template = ' + labelTemplate); + const urlEditorLabelTemplate = await this.find.byCssSelector( + 'input[data-test-subj="urlEditorLabelTemplate"]' + ); + await urlEditorLabelTemplate.type(labelTemplate); + } - async setScriptedFieldDatePattern(datePattern: string) { - log.debug('set scripted field Date Pattern = ' + datePattern); - const datePatternField = await find.byCssSelector( - 'input[data-test-subj="dateEditorPattern"]' - ); - // clearValue does not work here - // Send Backspace event for each char in value string to clear field - await datePatternField.clearValueWithKeyboard({ charByChar: true }); - await datePatternField.type(datePattern); - } + async setScriptedFieldDatePattern(datePattern: string) { + this.log.debug('set scripted field Date Pattern = ' + datePattern); + const datePatternField = await this.find.byCssSelector( + 'input[data-test-subj="dateEditorPattern"]' + ); + // clearValue does not work here + // Send Backspace event for each char in value string to clear field + await datePatternField.clearValueWithKeyboard({ charByChar: true }); + await datePatternField.type(datePattern); + } - async setScriptedFieldStringTransform(stringTransform: string) { - log.debug('set scripted field string Transform = ' + stringTransform); - await find.clickByCssSelector( - 'select[data-test-subj="stringEditorTransform"] > option[value="' + stringTransform + '"]' - ); - } + async setScriptedFieldStringTransform(stringTransform: string) { + this.log.debug('set scripted field string Transform = ' + stringTransform); + await this.find.clickByCssSelector( + 'select[data-test-subj="stringEditorTransform"] > option[value="' + stringTransform + '"]' + ); + } - async setScriptedFieldPopularity(popularity: string) { - log.debug('set scripted field popularity = ' + popularity); - await testSubjects.setValue('editorFieldCount', popularity); - } + async setScriptedFieldPopularity(popularity: string) { + this.log.debug('set scripted field popularity = ' + popularity); + await this.testSubjects.setValue('editorFieldCount', popularity); + } - async setScriptedFieldScript(script: string) { - log.debug('set scripted field script = ' + script); - const aceEditorCssSelector = '[data-test-subj="editorFieldScript"] .ace_editor'; - const editor = await find.byCssSelector(aceEditorCssSelector); - await editor.click(); - const existingText = await editor.getVisibleText(); - for (let i = 0; i < existingText.length; i++) { - await browser.pressKeys(browser.keys.BACK_SPACE); - } - await browser.pressKeys(...script.split('')); + async setScriptedFieldScript(script: string) { + this.log.debug('set scripted field script = ' + script); + const aceEditorCssSelector = '[data-test-subj="editorFieldScript"] .ace_editor'; + const editor = await this.find.byCssSelector(aceEditorCssSelector); + await editor.click(); + const existingText = await editor.getVisibleText(); + for (let i = 0; i < existingText.length; i++) { + await this.browser.pressKeys(this.browser.keys.BACK_SPACE); } + await this.browser.pressKeys(...script.split('')); + } - async openScriptedFieldHelp(activeTab: string) { - log.debug('open Scripted Fields help'); - let isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); - if (!isOpen) { - await retry.try(async () => { - await testSubjects.click('scriptedFieldsHelpLink'); - isOpen = await testSubjects.exists('scriptedFieldsHelpFlyout'); - if (!isOpen) { - throw new Error('Failed to open scripted fields help'); - } - }); - } - - if (activeTab) { - await testSubjects.click(activeTab); - } + async openScriptedFieldHelp(activeTab: string) { + this.log.debug('open Scripted Fields help'); + let isOpen = await this.testSubjects.exists('scriptedFieldsHelpFlyout'); + if (!isOpen) { + await this.retry.try(async () => { + await this.testSubjects.click('scriptedFieldsHelpLink'); + isOpen = await this.testSubjects.exists('scriptedFieldsHelpFlyout'); + if (!isOpen) { + throw new Error('Failed to open scripted fields help'); + } + }); } - async closeScriptedFieldHelp() { - await flyout.ensureClosed('scriptedFieldsHelpFlyout'); + if (activeTab) { + await this.testSubjects.click(activeTab); } + } - async executeScriptedField(script: string, additionalField: string) { - log.debug('execute Scripted Fields help'); - await this.closeScriptedFieldHelp(); // ensure script help is closed so script input is not blocked - await this.setScriptedFieldScript(script); - await this.openScriptedFieldHelp('testTab'); - if (additionalField) { - await comboBox.set('additionalFieldsSelect', additionalField); - await testSubjects.find('scriptedFieldPreview'); - await testSubjects.click('runScriptButton'); - await testSubjects.waitForDeleted('.euiLoadingSpinner'); - } - let scriptResults; - await retry.try(async () => { - scriptResults = await testSubjects.getVisibleText('scriptedFieldPreview'); - }); - return scriptResults; - } + async closeScriptedFieldHelp() { + await this.flyout.ensureClosed('scriptedFieldsHelpFlyout'); + } - async clickEditFieldFormat() { - await testSubjects.click('editFieldFormat'); - } + async executeScriptedField(script: string, additionalField: string) { + this.log.debug('execute Scripted Fields help'); + await this.closeScriptedFieldHelp(); // ensure script help is closed so script input is not blocked + await this.setScriptedFieldScript(script); + await this.openScriptedFieldHelp('testTab'); + if (additionalField) { + await this.comboBox.set('additionalFieldsSelect', additionalField); + await this.testSubjects.find('scriptedFieldPreview'); + await this.testSubjects.click('runScriptButton'); + await this.testSubjects.waitForDeleted('.euiLoadingSpinner'); + } + let scriptResults; + await this.retry.try(async () => { + scriptResults = await this.testSubjects.getVisibleText('scriptedFieldPreview'); + }); + return scriptResults; + } - async associateIndexPattern(oldIndexPatternId: string, newIndexPatternTitle: string) { - await find.clickByCssSelector( - `select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] > - [data-test-subj="indexPatternOption-${newIndexPatternTitle}"]` - ); - } + async clickEditFieldFormat() { + await this.testSubjects.click('editFieldFormat'); + } - async clickChangeIndexConfirmButton() { - await testSubjects.click('changeIndexConfirmButton'); - } + async associateIndexPattern(oldIndexPatternId: string, newIndexPatternTitle: string) { + await this.find.clickByCssSelector( + `select[data-test-subj="managementChangeIndexSelection-${oldIndexPatternId}"] > + [data-test-subj="indexPatternOption-${newIndexPatternTitle}"]` + ); } - return new SettingsPage(); + async clickChangeIndexConfirmButton() { + await this.testSubjects.click('changeIndexConfirmButton'); + } } diff --git a/test/functional/page_objects/share_page.ts b/test/functional/page_objects/share_page.ts index aa58341600599b..ce1dc4c45e21fb 100644 --- a/test/functional/page_objects/share_page.ts +++ b/test/functional/page_objects/share_page.ts @@ -6,76 +6,72 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function SharePageProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const log = getService('log'); +export class SharePageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); - class SharePage { - async isShareMenuOpen() { - return await testSubjects.exists('shareContextMenu'); - } - - async clickShareTopNavButton() { - return testSubjects.click('shareTopNavButton'); - } + async isShareMenuOpen() { + return await this.testSubjects.exists('shareContextMenu'); + } - async openShareMenuItem(itemTitle: string) { - log.debug(`openShareMenuItem title:${itemTitle}`); - const isShareMenuOpen = await this.isShareMenuOpen(); - if (!isShareMenuOpen) { - await this.clickShareTopNavButton(); - } else { - // there is no easy way to ensure the menu is at the top level - // so just close the existing menu - await this.clickShareTopNavButton(); - // and then re-open the menu - await this.clickShareTopNavButton(); - } - const menuPanel = await find.byCssSelector('div.euiContextMenuPanel'); - await testSubjects.click(`sharePanel-${itemTitle.replace(' ', '')}`); - await testSubjects.waitForDeleted(menuPanel); - } + async clickShareTopNavButton() { + return this.testSubjects.click('shareTopNavButton'); + } - /** - * if there are more entries in the share menu, the permalinks entry has to be clicked first - * else the selection isn't displayed. this happens if you're testing against an instance - * with xpack features enabled, where there's also a csv sharing option - * in a pure OSS environment, the permalinks sharing panel is displayed initially - */ - async openPermaLinks() { - if (await testSubjects.exists('sharePanel-Permalinks')) { - await testSubjects.click(`sharePanel-Permalinks`); - } + async openShareMenuItem(itemTitle: string) { + this.log.debug(`openShareMenuItem title:${itemTitle}`); + const isShareMenuOpen = await this.isShareMenuOpen(); + if (!isShareMenuOpen) { + await this.clickShareTopNavButton(); + } else { + // there is no easy way to ensure the menu is at the top level + // so just close the existing menu + await this.clickShareTopNavButton(); + // and then re-open the menu + await this.clickShareTopNavButton(); } + const menuPanel = await this.find.byCssSelector('div.euiContextMenuPanel'); + await this.testSubjects.click(`sharePanel-${itemTitle.replace(' ', '')}`); + await this.testSubjects.waitForDeleted(menuPanel); + } - async getSharedUrl() { - await this.openPermaLinks(); - return await testSubjects.getAttribute('copyShareUrlButton', 'data-share-url'); + /** + * if there are more entries in the share menu, the permalinks entry has to be clicked first + * else the selection isn't displayed. this happens if you're testing against an instance + * with xpack features enabled, where there's also a csv sharing option + * in a pure OSS environment, the permalinks sharing panel is displayed initially + */ + async openPermaLinks() { + if (await this.testSubjects.exists('sharePanel-Permalinks')) { + await this.testSubjects.click(`sharePanel-Permalinks`); } + } - async createShortUrlExistOrFail() { - await testSubjects.existOrFail('createShortUrl'); - } + async getSharedUrl() { + await this.openPermaLinks(); + return await this.testSubjects.getAttribute('copyShareUrlButton', 'data-share-url'); + } - async createShortUrlMissingOrFail() { - await testSubjects.missingOrFail('createShortUrl'); - } + async createShortUrlExistOrFail() { + await this.testSubjects.existOrFail('createShortUrl'); + } - async checkShortenUrl() { - await this.openPermaLinks(); - const shareForm = await testSubjects.find('shareUrlForm'); - await testSubjects.setCheckbox('useShortUrl', 'check'); - await shareForm.waitForDeletedByCssSelector('.euiLoadingSpinner'); - } + async createShortUrlMissingOrFail() { + await this.testSubjects.missingOrFail('createShortUrl'); + } - async exportAsSavedObject() { - await this.openPermaLinks(); - return await testSubjects.click('exportAsSavedObject'); - } + async checkShortenUrl() { + await this.openPermaLinks(); + const shareForm = await this.testSubjects.find('shareUrlForm'); + await this.testSubjects.setCheckbox('useShortUrl', 'check'); + await shareForm.waitForDeletedByCssSelector('.euiLoadingSpinner'); } - return new SharePage(); + async exportAsSavedObject() { + await this.openPermaLinks(); + return await this.testSubjects.click('exportAsSavedObject'); + } } diff --git a/test/functional/page_objects/tag_cloud_page.ts b/test/functional/page_objects/tag_cloud_page.ts index fa977618e64d77..61e844c813df8c 100644 --- a/test/functional/page_objects/tag_cloud_page.ts +++ b/test/functional/page_objects/tag_cloud_page.ts @@ -6,36 +6,33 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; -export function TagCloudPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const { header, visChart } = getPageObjects(['header', 'visChart']); +export class TagCloudPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly header = this.ctx.getPageObject('header'); + private readonly visChart = this.ctx.getPageObject('visChart'); - class TagCloudPage { - public async selectTagCloudTag(tagDisplayText: string) { - await testSubjects.click(tagDisplayText); - await header.waitUntilLoadingHasFinished(); - } + public async selectTagCloudTag(tagDisplayText: string) { + await this.testSubjects.click(tagDisplayText); + await this.header.waitUntilLoadingHasFinished(); + } - public async getTextTag() { - await visChart.waitForVisualization(); - const elements = await find.allByCssSelector('text'); - return await Promise.all(elements.map(async (element) => await element.getVisibleText())); - } + public async getTextTag() { + await this.visChart.waitForVisualization(); + const elements = await this.find.allByCssSelector('text'); + return await Promise.all(elements.map(async (element) => await element.getVisibleText())); + } - public async getTextSizes() { - const tags = await find.allByCssSelector('text'); - async function returnTagSize(tag: WebElementWrapper) { - const style = await tag.getAttribute('style'); - const fontSize = style.match(/font-size: ([^;]*);/); - return fontSize ? fontSize[1] : ''; - } - return await Promise.all(tags.map(returnTagSize)); + public async getTextSizes() { + const tags = await this.find.allByCssSelector('text'); + async function returnTagSize(tag: WebElementWrapper) { + const style = await tag.getAttribute('style'); + const fontSize = style.match(/font-size: ([^;]*);/); + return fontSize ? fontSize[1] : ''; } + return await Promise.all(tags.map(returnTagSize)); } - - return new TagCloudPage(); } diff --git a/test/functional/page_objects/tile_map_page.ts b/test/functional/page_objects/tile_map_page.ts index 6008d7434bf1d3..079ca919543e22 100644 --- a/test/functional/page_objects/tile_map_page.ts +++ b/test/functional/page_objects/tile_map_page.ts @@ -6,92 +6,88 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; - -export function TileMapPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const log = getService('log'); - const inspector = getService('inspector'); - const monacoEditor = getService('monacoEditor'); - const { header } = getPageObjects(['header']); - - class TileMapPage { - public async getZoomSelectors(zoomSelector: string) { - return await find.allByCssSelector(zoomSelector); - } - - public async clickMapButton(zoomSelector: string, waitForLoading?: boolean) { - await retry.try(async () => { - const zooms = await this.getZoomSelectors(zoomSelector); - for (let i = 0; i < zooms.length; i++) { - await zooms[i].click(); - } - if (waitForLoading) { - await header.waitUntilLoadingHasFinished(); - } - }); - } +import { FtrService } from '../ftr_provider_context'; + +export class TileMapPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly log = this.ctx.getService('log'); + private readonly inspector = this.ctx.getService('inspector'); + private readonly monacoEditor = this.ctx.getService('monacoEditor'); + private readonly header = this.ctx.getPageObject('header'); + + public async getZoomSelectors(zoomSelector: string) { + return await this.find.allByCssSelector(zoomSelector); + } - public async getVisualizationRequest() { - log.debug('getVisualizationRequest'); - await inspector.open(); - await testSubjects.click('inspectorViewChooser'); - await testSubjects.click('inspectorViewChooserRequests'); - await testSubjects.click('inspectorRequestDetailRequest'); - await find.byCssSelector('.react-monaco-editor-container'); + public async clickMapButton(zoomSelector: string, waitForLoading?: boolean) { + await this.retry.try(async () => { + const zooms = await this.getZoomSelectors(zoomSelector); + for (let i = 0; i < zooms.length; i++) { + await zooms[i].click(); + } + if (waitForLoading) { + await this.header.waitUntilLoadingHasFinished(); + } + }); + } - return await monacoEditor.getCodeEditorValue(1); - } + public async getVisualizationRequest() { + this.log.debug('getVisualizationRequest'); + await this.inspector.open(); + await this.testSubjects.click('inspectorViewChooser'); + await this.testSubjects.click('inspectorViewChooserRequests'); + await this.testSubjects.click('inspectorRequestDetailRequest'); + await this.find.byCssSelector('.react-monaco-editor-container'); - public async getMapBounds(): Promise { - const request = await this.getVisualizationRequest(); - const requestObject = JSON.parse(request); + return await this.monacoEditor.getCodeEditorValue(1); + } - return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates']; - } + public async getMapBounds(): Promise { + const request = await this.getVisualizationRequest(); + const requestObject = JSON.parse(request); - public async clickMapZoomIn(waitForLoading = true) { - await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading); - } + return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates']; + } - public async clickMapZoomOut(waitForLoading = true) { - await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading); - } + public async clickMapZoomIn(waitForLoading = true) { + await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading); + } - public async getMapZoomEnabled(zoomSelector: string): Promise { - const zooms = await this.getZoomSelectors(zoomSelector); - const classAttributes = await Promise.all( - zooms.map(async (zoom) => await zoom.getAttribute('class')) - ); - return !classAttributes.join('').includes('leaflet-disabled'); - } + public async clickMapZoomOut(waitForLoading = true) { + await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading); + } - public async zoomAllTheWayOut(): Promise { - // we can tell we're at level 1 because zoom out is disabled - return await retry.try(async () => { - await this.clickMapZoomOut(); - const enabled = await this.getMapZoomOutEnabled(); - // should be able to zoom more as current config has 0 as min level. - if (enabled) { - throw new Error('Not fully zoomed out yet'); - } - }); - } + public async getMapZoomEnabled(zoomSelector: string): Promise { + const zooms = await this.getZoomSelectors(zoomSelector); + const classAttributes = await Promise.all( + zooms.map(async (zoom) => await zoom.getAttribute('class')) + ); + return !classAttributes.join('').includes('leaflet-disabled'); + } - public async getMapZoomInEnabled() { - return await this.getMapZoomEnabled('a.leaflet-control-zoom-in'); - } + public async zoomAllTheWayOut(): Promise { + // we can tell we're at level 1 because zoom out is disabled + return await this.retry.try(async () => { + await this.clickMapZoomOut(); + const enabled = await this.getMapZoomOutEnabled(); + // should be able to zoom more as current config has 0 as min level. + if (enabled) { + throw new Error('Not fully zoomed out yet'); + } + }); + } - public async getMapZoomOutEnabled() { - return await this.getMapZoomEnabled('a.leaflet-control-zoom-out'); - } + public async getMapZoomInEnabled() { + return await this.getMapZoomEnabled('a.leaflet-control-zoom-in'); + } - public async clickMapFitDataBounds() { - return await this.clickMapButton('a.fa-crop'); - } + public async getMapZoomOutEnabled() { + return await this.getMapZoomEnabled('a.leaflet-control-zoom-out'); } - return new TileMapPage(); + public async clickMapFitDataBounds() { + return await this.clickMapButton('a.fa-crop'); + } } diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 4d0930c3ff932d..e8f6afc365f5d1 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -7,7 +7,7 @@ */ import moment from 'moment'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; export type CommonlyUsed = @@ -22,275 +22,270 @@ export type CommonlyUsed = | 'Last_90 days' | 'Last_1 year'; -export function TimePickerProvider({ getService, getPageObjects }: FtrProviderContext) { - const log = getService('log'); - const find = getService('find'); - const browser = getService('browser'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const { header } = getPageObjects(['header']); - const kibanaServer = getService('kibanaServer'); - const menuToggle = getService('menuToggle'); - - const quickSelectTimeMenuToggle = menuToggle.create({ +export class TimePickerPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); + private readonly browser = this.ctx.getService('browser'); + private readonly retry = this.ctx.getService('retry'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly header = this.ctx.getPageObject('header'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + + private readonly quickSelectTimeMenuToggle = this.ctx.getService('menuToggle').create({ name: 'QuickSelectTime Menu', menuTestSubject: 'superDatePickerQuickMenu', toggleButtonTestSubject: 'superDatePickerToggleQuickMenuButton', }); - class TimePicker { - defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000'; - defaultEndTime = 'Sep 23, 2015 @ 18:31:44.000'; - defaultStartTimeUTC = '2015-09-18T06:31:44.000Z'; - defaultEndTimeUTC = '2015-09-23T18:31:44.000Z'; - - async setDefaultAbsoluteRange() { - await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime); - } + public readonly defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000'; + public readonly defaultEndTime = 'Sep 23, 2015 @ 18:31:44.000'; + public readonly defaultStartTimeUTC = '2015-09-18T06:31:44.000Z'; + public readonly defaultEndTimeUTC = '2015-09-23T18:31:44.000Z'; - async ensureHiddenNoDataPopover() { - const isVisible = await testSubjects.exists('noDataPopoverDismissButton'); - if (isVisible) { - await testSubjects.click('noDataPopoverDismissButton'); - } - } + async setDefaultAbsoluteRange() { + await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime); + } - /** - * the provides a quicker way to set the timepicker to the default range, saves a few seconds - */ - async setDefaultAbsoluteRangeViaUiSettings() { - await kibanaServer.uiSettings.update({ - 'timepicker:timeDefaults': `{ "from": "${this.defaultStartTimeUTC}", "to": "${this.defaultEndTimeUTC}"}`, - }); + async ensureHiddenNoDataPopover() { + const isVisible = await this.testSubjects.exists('noDataPopoverDismissButton'); + if (isVisible) { + await this.testSubjects.click('noDataPopoverDismissButton'); } + } - async resetDefaultAbsoluteRangeViaUiSettings() { - await kibanaServer.uiSettings.replace({}); - } + /** + * the provides a quicker way to set the timepicker to the default range, saves a few seconds + */ + async setDefaultAbsoluteRangeViaUiSettings() { + await this.kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': `{ "from": "${this.defaultStartTimeUTC}", "to": "${this.defaultEndTimeUTC}"}`, + }); + } - private async getTimePickerPanel() { - return await retry.try(async () => { - return await find.byCssSelector('div.euiPopover__panel-isOpen'); - }); - } + async resetDefaultAbsoluteRangeViaUiSettings() { + await this.kibanaServer.uiSettings.replace({}); + } - private async waitPanelIsGone(panelElement: WebElementWrapper) { - await find.waitForElementStale(panelElement); - } + private async getTimePickerPanel() { + return await this.retry.try(async () => { + return await this.find.byCssSelector('div.euiPopover__panel-isOpen'); + }); + } - public async timePickerExists() { - return await testSubjects.exists('superDatePickerToggleQuickMenuButton'); - } + private async waitPanelIsGone(panelElement: WebElementWrapper) { + await this.find.waitForElementStale(panelElement); + } - /** - * Sets commonly used time - * @param option 'Today' | 'This_week' | 'Last_15 minutes' | 'Last_24 hours' ... - */ - async setCommonlyUsedTime(option: CommonlyUsed | string) { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - await testSubjects.click(`superDatePickerCommonlyUsed_${option}`); - } + public async timePickerExists() { + return await this.testSubjects.exists('superDatePickerToggleQuickMenuButton'); + } - public async inputValue(dataTestSubj: string, value: string) { - if (browser.isFirefox) { - const input = await testSubjects.find(dataTestSubj); - await input.clearValue(); - await input.type(value); - } else { - await testSubjects.setValue(dataTestSubj, value); - } - } + /** + * Sets commonly used time + * @param option 'Today' | 'This_week' | 'Last_15 minutes' | 'Last_24 hours' ... + */ + async setCommonlyUsedTime(option: CommonlyUsed | string) { + await this.testSubjects.click('superDatePickerToggleQuickMenuButton'); + await this.testSubjects.click(`superDatePickerCommonlyUsed_${option}`); + } - private async showStartEndTimes() { - // This first await makes sure the superDatePicker has loaded before we check for the ShowDatesButton - await testSubjects.exists('superDatePickerToggleQuickMenuButton', { timeout: 20000 }); - const isShowDatesButton = await testSubjects.exists('superDatePickerShowDatesButton'); - if (isShowDatesButton) { - await testSubjects.click('superDatePickerShowDatesButton'); - } - await testSubjects.exists('superDatePickerstartDatePopoverButton'); + public async inputValue(dataTestSubj: string, value: string) { + if (this.browser.isFirefox) { + const input = await this.testSubjects.find(dataTestSubj); + await input.clearValue(); + await input.type(value); + } else { + await this.testSubjects.setValue(dataTestSubj, value); } + } - /** - * @param {String} fromTime MMM D, YYYY @ HH:mm:ss.SSS - * @param {String} toTime MMM D, YYYY @ HH:mm:ss.SSS - */ - public async setAbsoluteRange(fromTime: string, toTime: string) { - log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); - await this.showStartEndTimes(); - - // 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 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.inputValue('superDatePickerAbsoluteDateInput', fromTime); - - 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(); + private async showStartEndTimes() { + // This first await makes sure the superDatePicker has loaded before we check for the ShowDatesButton + await this.testSubjects.exists('superDatePickerToggleQuickMenuButton', { timeout: 20000 }); + const isShowDatesButton = await this.testSubjects.exists('superDatePickerShowDatesButton'); + if (isShowDatesButton) { + await this.testSubjects.click('superDatePickerShowDatesButton'); } + await this.testSubjects.exists('superDatePickerstartDatePopoverButton'); + } - public async isOff() { - return await find.existsByCssSelector('.euiDatePickerRange--readOnly'); + /** + * @param {String} fromTime MMM D, YYYY @ HH:mm:ss.SSS + * @param {String} toTime MMM D, YYYY @ HH:mm:ss.SSS + */ + public async setAbsoluteRange(fromTime: string, toTime: string) { + this.log.debug(`Setting absolute range to ${fromTime} to ${toTime}`); + await this.showStartEndTimes(); + + // set to time + await this.testSubjects.click('superDatePickerendDatePopoverButton'); + let panel = await this.getTimePickerPanel(); + await this.testSubjects.click('superDatePickerAbsoluteTab'); + await this.testSubjects.click('superDatePickerAbsoluteDateInput'); + await this.inputValue('superDatePickerAbsoluteDateInput', toTime); + await this.browser.pressKeys(this.browser.keys.ESCAPE); // close popover because sometimes browser can't find start input + + // set from time + await this.testSubjects.click('superDatePickerstartDatePopoverButton'); + await this.waitPanelIsGone(panel); + panel = await this.getTimePickerPanel(); + await this.testSubjects.click('superDatePickerAbsoluteTab'); + await this.testSubjects.click('superDatePickerAbsoluteDateInput'); + await this.inputValue('superDatePickerAbsoluteDateInput', fromTime); + + const superDatePickerApplyButtonExists = await this.testSubjects.exists( + 'superDatePickerApplyTimeButton' + ); + if (superDatePickerApplyButtonExists) { + // Timepicker is in top nav + // Click super date picker apply button to apply time range + await this.testSubjects.click('superDatePickerApplyTimeButton'); + } else { + // Timepicker is embedded in query bar + // click query bar submit button to apply time range + await this.testSubjects.click('querySubmitButton'); } - public async getRefreshConfig(keepQuickSelectOpen = false) { - await quickSelectTimeMenuToggle.open(); - const interval = await testSubjects.getAttribute( - 'superDatePickerRefreshIntervalInput', - 'value' - ); - - let selectedUnit; - const select = await testSubjects.find('superDatePickerRefreshIntervalUnitsSelect'); - const options = await find.allDescendantDisplayedByCssSelector('option', select); - await Promise.all( - options.map(async (optionElement) => { - const isSelected = await optionElement.isSelected(); - if (isSelected) { - selectedUnit = await optionElement.getVisibleText(); - } - }) - ); - - const toggleButtonText = await testSubjects.getVisibleText( - 'superDatePickerToggleRefreshButton' - ); - if (!keepQuickSelectOpen) { - await quickSelectTimeMenuToggle.close(); - } - - return { - interval, - units: selectedUnit, - isPaused: toggleButtonText === 'Start' ? true : false, - }; - } + await this.waitPanelIsGone(panel); + await this.header.awaitGlobalLoadingIndicatorHidden(); + } - public async getTimeConfig() { - await this.showStartEndTimes(); - const start = await testSubjects.getVisibleText('superDatePickerstartDatePopoverButton'); - const end = await testSubjects.getVisibleText('superDatePickerendDatePopoverButton'); - return { - start, - end, - }; - } + public async isOff() { + return await this.find.existsByCssSelector('.euiDatePickerRange--readOnly'); + } - public async getShowDatesButtonText() { - const button = await testSubjects.find('superDatePickerShowDatesButton'); - const text = await button.getVisibleText(); - return text; + public async getRefreshConfig(keepQuickSelectOpen = false) { + await this.quickSelectTimeMenuToggle.open(); + const interval = await this.testSubjects.getAttribute( + 'superDatePickerRefreshIntervalInput', + 'value' + ); + + let selectedUnit; + const select = await this.testSubjects.find('superDatePickerRefreshIntervalUnitsSelect'); + const options = await this.find.allDescendantDisplayedByCssSelector('option', select); + await Promise.all( + options.map(async (optionElement) => { + const isSelected = await optionElement.isSelected(); + if (isSelected) { + selectedUnit = await optionElement.getVisibleText(); + } + }) + ); + + const toggleButtonText = await this.testSubjects.getVisibleText( + 'superDatePickerToggleRefreshButton' + ); + if (!keepQuickSelectOpen) { + await this.quickSelectTimeMenuToggle.close(); } - public async getTimeDurationForSharing() { - return await testSubjects.getAttribute( - 'dataSharedTimefilterDuration', - 'data-shared-timefilter-duration' - ); - } + return { + interval, + units: selectedUnit, + isPaused: toggleButtonText === 'Start' ? true : false, + }; + } - public async getTimeConfigAsAbsoluteTimes() { - await this.showStartEndTimes(); - - // get to time - await testSubjects.click('superDatePickerendDatePopoverButton'); - const panel = await this.getTimePickerPanel(); - await testSubjects.click('superDatePickerAbsoluteTab'); - const end = await testSubjects.getAttribute('superDatePickerAbsoluteDateInput', 'value'); - - // get from time - await testSubjects.click('superDatePickerstartDatePopoverButton'); - await this.waitPanelIsGone(panel); - await testSubjects.click('superDatePickerAbsoluteTab'); - const start = await testSubjects.getAttribute('superDatePickerAbsoluteDateInput', 'value'); - - return { - start, - end, - }; - } + public async getTimeConfig() { + await this.showStartEndTimes(); + const start = await this.testSubjects.getVisibleText('superDatePickerstartDatePopoverButton'); + const end = await this.testSubjects.getVisibleText('superDatePickerendDatePopoverButton'); + return { + start, + end, + }; + } - public async getTimeDurationInHours() { - const DEFAULT_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; - const { start, end } = await this.getTimeConfigAsAbsoluteTimes(); - const startMoment = moment(start, DEFAULT_DATE_FORMAT); - const endMoment = moment(end, DEFAULT_DATE_FORMAT); - return moment.duration(endMoment.diff(startMoment)).asHours(); - } + public async getShowDatesButtonText() { + const button = await this.testSubjects.find('superDatePickerShowDatesButton'); + const text = await button.getVisibleText(); + return text; + } - public async startAutoRefresh(intervalS = 3) { - 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 quickSelectTimeMenuToggle.close(); - } + public async getTimeDurationForSharing() { + return await this.testSubjects.getAttribute( + 'dataSharedTimefilterDuration', + 'data-shared-timefilter-duration' + ); + } - public async pauseAutoRefresh() { - log.debug('pauseAutoRefresh'); - const refreshConfig = await this.getRefreshConfig(true); + public async getTimeConfigAsAbsoluteTimes() { + await this.showStartEndTimes(); + + // get to time + await this.testSubjects.click('superDatePickerendDatePopoverButton'); + const panel = await this.getTimePickerPanel(); + await this.testSubjects.click('superDatePickerAbsoluteTab'); + const end = await this.testSubjects.getAttribute('superDatePickerAbsoluteDateInput', 'value'); + + // get from time + await this.testSubjects.click('superDatePickerstartDatePopoverButton'); + await this.waitPanelIsGone(panel); + await this.testSubjects.click('superDatePickerAbsoluteTab'); + const start = await this.testSubjects.getAttribute('superDatePickerAbsoluteDateInput', 'value'); + + return { + start, + end, + }; + } - if (!refreshConfig.isPaused) { - log.debug('pause auto refresh'); - await testSubjects.click('superDatePickerToggleRefreshButton'); - } + public async getTimeDurationInHours() { + const DEFAULT_DATE_FORMAT = 'MMM D, YYYY @ HH:mm:ss.SSS'; + const { start, end } = await this.getTimeConfigAsAbsoluteTimes(); + const startMoment = moment(start, DEFAULT_DATE_FORMAT); + const endMoment = moment(end, DEFAULT_DATE_FORMAT); + return moment.duration(endMoment.diff(startMoment)).asHours(); + } - await quickSelectTimeMenuToggle.close(); + public async startAutoRefresh(intervalS = 3) { + await this.quickSelectTimeMenuToggle.open(); + await this.inputValue('superDatePickerRefreshIntervalInput', intervalS.toString()); + const refreshConfig = await this.getRefreshConfig(true); + if (refreshConfig.isPaused) { + this.log.debug('start auto refresh'); + await this.testSubjects.click('superDatePickerToggleRefreshButton'); } + await this.quickSelectTimeMenuToggle.close(); + } - public async resumeAutoRefresh() { - log.debug('resumeAutoRefresh'); - const refreshConfig = await this.getRefreshConfig(true); - if (refreshConfig.isPaused) { - log.debug('resume auto refresh'); - await testSubjects.click('superDatePickerToggleRefreshButton'); - } + public async pauseAutoRefresh() { + this.log.debug('pauseAutoRefresh'); + const refreshConfig = await this.getRefreshConfig(true); - await quickSelectTimeMenuToggle.close(); + if (!refreshConfig.isPaused) { + this.log.debug('pause auto refresh'); + await this.testSubjects.click('superDatePickerToggleRefreshButton'); } - public async setHistoricalDataRange() { - await this.setDefaultAbsoluteRange(); - } + await this.quickSelectTimeMenuToggle.close(); + } - public async setDefaultDataRange() { - const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; - const toTime = 'Apr 13, 2018 @ 00:00:00.000'; - await this.setAbsoluteRange(fromTime, toTime); + public async resumeAutoRefresh() { + this.log.debug('resumeAutoRefresh'); + const refreshConfig = await this.getRefreshConfig(true); + if (refreshConfig.isPaused) { + this.log.debug('resume auto refresh'); + await this.testSubjects.click('superDatePickerToggleRefreshButton'); } - public async setLogstashDataRange() { - const fromTime = 'Apr 9, 2018 @ 00:00:00.000'; - const toTime = 'Apr 13, 2018 @ 00:00:00.000'; - await this.setAbsoluteRange(fromTime, toTime); - } + await this.quickSelectTimeMenuToggle.close(); } - return new TimePicker(); + public async setHistoricalDataRange() { + await this.setDefaultAbsoluteRange(); + } + + public async setDefaultDataRange() { + const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; + const toTime = 'Apr 13, 2018 @ 00:00:00.000'; + await this.setAbsoluteRange(fromTime, toTime); + } + + public async setLogstashDataRange() { + const fromTime = 'Apr 9, 2018 @ 00:00:00.000'; + const toTime = 'Apr 13, 2018 @ 00:00:00.000'; + await this.setAbsoluteRange(fromTime, toTime); + } } diff --git a/test/functional/page_objects/time_to_visualize_page.ts b/test/functional/page_objects/time_to_visualize_page.ts index 458b4dd3e60a13..287b03ec60d88a 100644 --- a/test/functional/page_objects/time_to_visualize_page.ts +++ b/test/functional/page_objects/time_to_visualize_page.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; interface SaveModalArgs { addToDashboard?: 'new' | 'existing' | null; @@ -21,117 +21,108 @@ type DashboardPickerOption = | 'existing-dashboard-option' | 'new-dashboard-option'; -export function TimeToVisualizePageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const find = getService('find'); - const { common, dashboard } = getPageObjects(['common', 'dashboard']); +export class TimeToVisualizePageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); + private readonly common = this.ctx.getPageObject('common'); + private readonly dashboard = this.ctx.getPageObject('dashboard'); - class TimeToVisualizePage { - public async ensureSaveModalIsOpen() { - await testSubjects.exists('savedObjectSaveModal', { timeout: 5000 }); - } + public async ensureSaveModalIsOpen() { + await this.testSubjects.exists('savedObjectSaveModal', { timeout: 5000 }); + } - public async ensureDashboardOptionsAreDisabled() { - const dashboardSelector = await testSubjects.find('add-to-dashboard-options'); - await dashboardSelector.findByCssSelector(`input[id="new-dashboard-option"]:disabled`); - await dashboardSelector.findByCssSelector(`input[id="existing-dashboard-option"]:disabled`); + public async ensureDashboardOptionsAreDisabled() { + const dashboardSelector = await this.testSubjects.find('add-to-dashboard-options'); + await dashboardSelector.findByCssSelector(`input[id="new-dashboard-option"]:disabled`); + await dashboardSelector.findByCssSelector(`input[id="existing-dashboard-option"]:disabled`); - const librarySelector = await testSubjects.find('add-to-library-checkbox'); - await librarySelector.findByCssSelector(`input[id="add-to-library-checkbox"]:disabled`); - } - - public async resetNewDashboard() { - await common.navigateToApp('dashboard'); - await dashboard.gotoDashboardLandingPage(true); - await dashboard.clickNewDashboard(false); - } + const librarySelector = await this.testSubjects.find('add-to-library-checkbox'); + await librarySelector.findByCssSelector(`input[id="add-to-library-checkbox"]:disabled`); + } - public async setSaveModalValues( - vizName: string, - { - saveAsNew, - redirectToOrigin, - addToDashboard, - dashboardId, - saveToLibrary, - }: SaveModalArgs = {} - ) { - await testSubjects.setValue('savedObjectTitle', vizName); - - const hasSaveAsNew = await testSubjects.exists('saveAsNewCheckbox'); - if (hasSaveAsNew && saveAsNew !== undefined) { - const state = saveAsNew ? 'check' : 'uncheck'; - log.debug('save as new checkbox exists. Setting its state to', state); - await testSubjects.setEuiSwitch('saveAsNewCheckbox', state); - } + public async resetNewDashboard() { + await this.common.navigateToApp('dashboard'); + await this.dashboard.gotoDashboardLandingPage(true); + await this.dashboard.clickNewDashboard(false); + } - const hasDashboardSelector = await testSubjects.exists('add-to-dashboard-options'); - if (hasDashboardSelector && addToDashboard !== undefined) { - let option: DashboardPickerOption = 'add-to-library-option'; - if (addToDashboard) { - option = dashboardId ? 'existing-dashboard-option' : 'new-dashboard-option'; - } - log.debug('save modal dashboard selector, choosing option:', option); - const dashboardSelector = await testSubjects.find('add-to-dashboard-options'); - const label = await dashboardSelector.findByCssSelector(`label[for="${option}"]`); - await label.click(); + public async setSaveModalValues( + vizName: string, + { saveAsNew, redirectToOrigin, addToDashboard, dashboardId, saveToLibrary }: SaveModalArgs = {} + ) { + await this.testSubjects.setValue('savedObjectTitle', vizName); + + const hasSaveAsNew = await this.testSubjects.exists('saveAsNewCheckbox'); + if (hasSaveAsNew && saveAsNew !== undefined) { + const state = saveAsNew ? 'check' : 'uncheck'; + this.log.debug('save as new checkbox exists. Setting its state to', state); + await this.testSubjects.setEuiSwitch('saveAsNewCheckbox', state); + } - if (dashboardId) { - await testSubjects.setValue('dashboardPickerInput', dashboardId); - await find.clickByButtonText(dashboardId); - } + const hasDashboardSelector = await this.testSubjects.exists('add-to-dashboard-options'); + if (hasDashboardSelector && addToDashboard !== undefined) { + let option: DashboardPickerOption = 'add-to-library-option'; + if (addToDashboard) { + option = dashboardId ? 'existing-dashboard-option' : 'new-dashboard-option'; } - - const hasSaveToLibrary = await testSubjects.exists('add-to-library-checkbox'); - if (hasSaveToLibrary && saveToLibrary !== undefined) { - const libraryCheckbox = await find.byCssSelector('#add-to-library-checkbox'); - const isChecked = await libraryCheckbox.isSelected(); - const needsClick = isChecked !== saveToLibrary; - const state = saveToLibrary ? 'check' : 'uncheck'; - - log.debug('save to library checkbox exists. Setting its state to', state); - if (needsClick) { - const selector = await testSubjects.find('add-to-library-checkbox'); - const label = await selector.findByCssSelector(`label[for="add-to-library-checkbox"]`); - await label.click(); - } + this.log.debug('save modal dashboard selector, choosing option:', option); + const dashboardSelector = await this.testSubjects.find('add-to-dashboard-options'); + const label = await dashboardSelector.findByCssSelector(`label[for="${option}"]`); + await label.click(); + + if (dashboardId) { + await this.testSubjects.setValue('dashboardPickerInput', dashboardId); + await this.find.clickByButtonText(dashboardId); } + } - const hasRedirectToOrigin = await testSubjects.exists('returnToOriginModeSwitch'); - if (hasRedirectToOrigin && redirectToOrigin !== undefined) { - const state = redirectToOrigin ? 'check' : 'uncheck'; - log.debug('redirect to origin checkbox exists. Setting its state to', state); - await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); + const hasSaveToLibrary = await this.testSubjects.exists('add-to-library-checkbox'); + if (hasSaveToLibrary && saveToLibrary !== undefined) { + const libraryCheckbox = await this.find.byCssSelector('#add-to-library-checkbox'); + const isChecked = await libraryCheckbox.isSelected(); + const needsClick = isChecked !== saveToLibrary; + const state = saveToLibrary ? 'check' : 'uncheck'; + + this.log.debug('save to library checkbox exists. Setting its state to', state); + if (needsClick) { + const selector = await this.testSubjects.find('add-to-library-checkbox'); + const label = await selector.findByCssSelector(`label[for="add-to-library-checkbox"]`); + await label.click(); } } - public async libraryNotificationExists(panelTitle: string) { - log.debug('searching for library modal on panel:', panelTitle); - const panel = await testSubjects.find( - `embeddablePanelHeading-${panelTitle.replace(/ /g, '')}` - ); - const libraryActionExists = await testSubjects.descendantExists( - 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', - panel - ); - return libraryActionExists; + const hasRedirectToOrigin = await this.testSubjects.exists('returnToOriginModeSwitch'); + if (hasRedirectToOrigin && redirectToOrigin !== undefined) { + const state = redirectToOrigin ? 'check' : 'uncheck'; + this.log.debug('redirect to origin checkbox exists. Setting its state to', state); + await this.testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); } + } - public async saveFromModal( - vizName: string, - saveModalArgs: SaveModalArgs = { addToDashboard: null } - ) { - await this.ensureSaveModalIsOpen(); + public async libraryNotificationExists(panelTitle: string) { + this.log.debug('searching for library modal on panel:', panelTitle); + const panel = await this.testSubjects.find( + `embeddablePanelHeading-${panelTitle.replace(/ /g, '')}` + ); + const libraryActionExists = await this.testSubjects.descendantExists( + 'embeddablePanelNotification-ACTION_LIBRARY_NOTIFICATION', + panel + ); + return libraryActionExists; + } - await this.setSaveModalValues(vizName, saveModalArgs); - log.debug('Click Save Visualization button'); + public async saveFromModal( + vizName: string, + saveModalArgs: SaveModalArgs = { addToDashboard: null } + ) { + await this.ensureSaveModalIsOpen(); - await testSubjects.click('confirmSaveSavedObjectButton'); + await this.setSaveModalValues(vizName, saveModalArgs); + this.log.debug('Click Save Visualization button'); - await common.waitForSaveModalToClose(); - } - } + await this.testSubjects.click('confirmSaveSavedObjectButton'); - return new TimeToVisualizePage(); + await this.common.waitForSaveModalToClose(); + } } diff --git a/test/functional/page_objects/timelion_page.ts b/test/functional/page_objects/timelion_page.ts index 7f4c4eb125c8e1..57913f8e2413dc 100644 --- a/test/functional/page_objects/timelion_page.ts +++ b/test/functional/page_objects/timelion_page.ts @@ -6,79 +6,75 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function TimelionPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const PageObjects = getPageObjects(['common', 'header']); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); +export class TimelionPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly log = this.ctx.getService('log'); + private readonly common = this.ctx.getPageObject('common'); + private readonly esArchiver = this.ctx.getService('esArchiver'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); - class TimelionPage { - public async initTests() { - await kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', - }); + public async initTests() { + await this.kibanaServer.uiSettings.replace({ + defaultIndex: 'logstash-*', + }); - log.debug('load kibana index'); - await esArchiver.load('timelion'); + this.log.debug('load kibana index'); + await this.esArchiver.load('timelion'); - await PageObjects.common.navigateToApp('timelion'); - } - - public async setExpression(expression: string) { - const input = await testSubjects.find('timelionExpressionTextArea'); - await input.clearValue(); - await input.type(expression); - } + await this.common.navigateToApp('timelion'); + } - public async updateExpression(updates: string) { - const input = await testSubjects.find('timelionExpressionTextArea'); - await input.type(updates); - await PageObjects.common.sleep(1000); - } + public async setExpression(expression: string) { + const input = await this.testSubjects.find('timelionExpressionTextArea'); + await input.clearValue(); + await input.type(expression); + } - public async getExpression() { - const input = await testSubjects.find('timelionExpressionTextArea'); - return input.getVisibleText(); - } + public async updateExpression(updates: string) { + const input = await this.testSubjects.find('timelionExpressionTextArea'); + await input.type(updates); + await this.common.sleep(1000); + } - public async getSuggestionItemsText() { - const elements = await testSubjects.findAll('timelionSuggestionListItem'); - return await Promise.all(elements.map(async (element) => await element.getVisibleText())); - } + public async getExpression() { + const input = await this.testSubjects.find('timelionExpressionTextArea'); + return input.getVisibleText(); + } - public async clickSuggestion(suggestionIndex = 0, waitTime = 1000) { - const elements = await testSubjects.findAll('timelionSuggestionListItem'); - if (suggestionIndex > elements.length) { - throw new Error( - `Unable to select suggestion ${suggestionIndex}, only ${elements.length} suggestions available.` - ); - } - await elements[suggestionIndex].click(); - // Wait for timelion expression to be updated after clicking suggestions - await PageObjects.common.sleep(waitTime); - } + public async getSuggestionItemsText() { + const elements = await this.testSubjects.findAll('timelionSuggestionListItem'); + return await Promise.all(elements.map(async (element) => await element.getVisibleText())); + } - public async saveTimelionSheet() { - await testSubjects.click('timelionSaveButton'); - await testSubjects.click('timelionSaveAsSheetButton'); - await testSubjects.click('timelionFinishSaveButton'); - await testSubjects.existOrFail('timelionSaveSuccessToast'); - await testSubjects.waitForDeleted('timelionSaveSuccessToast'); + public async clickSuggestion(suggestionIndex = 0, waitTime = 1000) { + const elements = await this.testSubjects.findAll('timelionSuggestionListItem'); + if (suggestionIndex > elements.length) { + throw new Error( + `Unable to select suggestion ${suggestionIndex}, only ${elements.length} suggestions available.` + ); } + await elements[suggestionIndex].click(); + // Wait for timelion expression to be updated after clicking suggestions + await this.common.sleep(waitTime); + } - public async expectWriteControls() { - await testSubjects.existOrFail('timelionSaveButton'); - await testSubjects.existOrFail('timelionDeleteButton'); - } + public async saveTimelionSheet() { + await this.testSubjects.click('timelionSaveButton'); + await this.testSubjects.click('timelionSaveAsSheetButton'); + await this.testSubjects.click('timelionFinishSaveButton'); + await this.testSubjects.existOrFail('timelionSaveSuccessToast'); + await this.testSubjects.waitForDeleted('timelionSaveSuccessToast'); + } - public async expectMissingWriteControls() { - await testSubjects.missingOrFail('timelionSaveButton'); - await testSubjects.missingOrFail('timelionDeleteButton'); - } + public async expectWriteControls() { + await this.testSubjects.existOrFail('timelionSaveButton'); + await this.testSubjects.existOrFail('timelionDeleteButton'); } - return new TimelionPage(); + public async expectMissingWriteControls() { + await this.testSubjects.missingOrFail('timelionSaveButton'); + await this.testSubjects.missingOrFail('timelionDeleteButton'); + } } diff --git a/test/functional/page_objects/vega_chart_page.ts b/test/functional/page_objects/vega_chart_page.ts index 3e165b3434f8a4..f83c5e193034eb 100644 --- a/test/functional/page_objects/vega_chart_page.ts +++ b/test/functional/page_objects/vega_chart_page.ts @@ -7,110 +7,103 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; const compareSpecs = (first: string, second: string) => { const normalizeSpec = (spec: string) => spec.replace(/[\n ]/g, ''); return normalizeSpec(first) === normalizeSpec(second); }; -export function VegaChartPageProvider({ - getService, - getPageObjects, -}: FtrProviderContext & { updateBaselines: boolean }) { - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const browser = getService('browser'); - const retry = getService('retry'); - - class VegaChartPage { - public getEditor() { - return testSubjects.find('vega-editor'); - } +export class VegaChartPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly browser = this.ctx.getService('browser'); + private readonly retry = this.ctx.getService('retry'); - public getViewContainer() { - return find.byCssSelector('div.vgaVis__view'); - } + public getEditor() { + return this.testSubjects.find('vega-editor'); + } - public getControlContainer() { - return find.byCssSelector('div.vgaVis__controls'); - } + public getViewContainer() { + return this.find.byCssSelector('div.vgaVis__view'); + } - public getYAxisContainer() { - return find.byCssSelector('[aria-label^="Y-axis"]'); - } + public getControlContainer() { + return this.find.byCssSelector('div.vgaVis__controls'); + } - public async getAceGutterContainer() { - const editor = await this.getEditor(); - return editor.findByClassName('ace_gutter'); - } + public getYAxisContainer() { + return this.find.byCssSelector('[aria-label^="Y-axis"]'); + } - public async getRawSpec() { - // Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file? - const editor = await this.getEditor(); - const lines = await editor.findAllByClassName('ace_line_group'); + public async getAceGutterContainer() { + const editor = await this.getEditor(); + return editor.findByClassName('ace_gutter'); + } - return await Promise.all( - lines.map(async (line) => { - return await line.getVisibleText(); - }) - ); - } + public async getRawSpec() { + // Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file? + const editor = await this.getEditor(); + const lines = await editor.findAllByClassName('ace_line_group'); - public async getSpec() { - return (await this.getRawSpec()).join('\n'); - } + return await Promise.all( + lines.map(async (line) => { + return await line.getVisibleText(); + }) + ); + } - public async focusEditor() { - const editor = await this.getEditor(); - const textarea = await editor.findByClassName('ace_content'); + public async getSpec() { + return (await this.getRawSpec()).join('\n'); + } - await textarea.click(); - } + public async focusEditor() { + const editor = await this.getEditor(); + const textarea = await editor.findByClassName('ace_content'); - public async fillSpec(newSpec: string) { - await retry.try(async () => { - await this.cleanSpec(); - await this.focusEditor(); - await browser.pressKeys(newSpec); + await textarea.click(); + } - expect(compareSpecs(await this.getSpec(), newSpec)).to.be(true); - }); - } + public async fillSpec(newSpec: string) { + await this.retry.try(async () => { + await this.cleanSpec(); + await this.focusEditor(); + await this.browser.pressKeys(newSpec); - public async typeInSpec(text: string) { - const aceGutter = await this.getAceGutterContainer(); + expect(compareSpecs(await this.getSpec(), newSpec)).to.be(true); + }); + } - await aceGutter.doubleClick(); - await browser.pressKeys(browser.keys.RIGHT); - await browser.pressKeys(browser.keys.LEFT); - await browser.pressKeys(browser.keys.LEFT); - await browser.pressKeys(text); - } + public async typeInSpec(text: string) { + const aceGutter = await this.getAceGutterContainer(); - public async cleanSpec() { - const aceGutter = await this.getAceGutterContainer(); + await aceGutter.doubleClick(); + await this.browser.pressKeys(this.browser.keys.RIGHT); + await this.browser.pressKeys(this.browser.keys.LEFT); + await this.browser.pressKeys(this.browser.keys.LEFT); + await this.browser.pressKeys(text); + } - await retry.try(async () => { - await aceGutter.doubleClick(); - await browser.pressKeys(browser.keys.BACK_SPACE); + public async cleanSpec() { + const aceGutter = await this.getAceGutterContainer(); - expect(await this.getSpec()).to.be(''); - }); - } + await this.retry.try(async () => { + await aceGutter.doubleClick(); + await this.browser.pressKeys(this.browser.keys.BACK_SPACE); - public async getYAxisLabels() { - const yAxis = await this.getYAxisContainer(); - const tickGroup = await yAxis.findByClassName('role-axis-label'); - const labels = await tickGroup.findAllByCssSelector('text'); - const labelTexts: string[] = []; + expect(await this.getSpec()).to.be(''); + }); + } + + public async getYAxisLabels() { + const yAxis = await this.getYAxisContainer(); + const tickGroup = await yAxis.findByClassName('role-axis-label'); + const labels = await tickGroup.findAllByCssSelector('text'); + const labelTexts: string[] = []; - for (const label of labels) { - labelTexts.push(await label.getVisibleText()); - } - return labelTexts; + for (const label of labels) { + labelTexts.push(await label.getVisibleText()); } + return labelTexts; } - - return new VegaChartPage(); } diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 997a1127005ee5..d796067372fa87 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -6,654 +6,651 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; -export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const log = getService('log'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const comboBox = getService('comboBox'); - const PageObjects = getPageObjects(['common', 'header', 'visualize', 'timePicker', 'visChart']); - - type Duration = - | 'Milliseconds' - | 'Seconds' - | 'Minutes' - | 'Hours' - | 'Days' - | 'Weeks' - | 'Months' - | 'Years'; - - type FromDuration = Duration | 'Picoseconds' | 'Nanoseconds' | 'Microseconds'; - type ToDuration = Duration | 'Human readable'; - - class VisualBuilderPage { - public async resetPage( - fromTime = 'Sep 19, 2015 @ 06:31:44.000', - toTime = 'Sep 22, 2015 @ 18:31:44.000' - ) { - await PageObjects.common.navigateToUrl('visualize', 'create?type=metrics', { - useActualUrl: true, - }); - log.debug('Wait for initializing TSVB editor'); - await this.checkVisualBuilderIsPresent(); - log.debug('Set absolute time range from "' + fromTime + '" to "' + toTime + '"'); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - // 2 sec sleep until https://github.com/elastic/kibana/issues/46353 is fixed - await PageObjects.common.sleep(2000); - } +type Duration = + | 'Milliseconds' + | 'Seconds' + | 'Minutes' + | 'Hours' + | 'Days' + | 'Weeks' + | 'Months' + | 'Years'; + +type FromDuration = Duration | 'Picoseconds' | 'Nanoseconds' | 'Microseconds'; +type ToDuration = Duration | 'Human readable'; + +export class VisualBuilderPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly timePicker = this.ctx.getPageObject('timePicker'); + private readonly visChart = this.ctx.getPageObject('visChart'); + + public async resetPage( + fromTime = 'Sep 19, 2015 @ 06:31:44.000', + toTime = 'Sep 22, 2015 @ 18:31:44.000' + ) { + await this.common.navigateToUrl('visualize', 'create?type=metrics', { + useActualUrl: true, + }); + this.log.debug('Wait for initializing TSVB editor'); + await this.checkVisualBuilderIsPresent(); + this.log.debug('Set absolute time range from "' + fromTime + '" to "' + toTime + '"'); + await this.timePicker.setAbsoluteRange(fromTime, toTime); + // 2 sec sleep until https://github.com/elastic/kibana/issues/46353 is fixed + await this.common.sleep(2000); + } - public async checkTabIsLoaded(testSubj: string, name: string) { - let isPresent = false; - await retry.try(async () => { - isPresent = await testSubjects.exists(testSubj, { timeout: 20000 }); - if (!isPresent) { - isPresent = await testSubjects.exists('visNoResult', { timeout: 1000 }); - } - }); + public async checkTabIsLoaded(testSubj: string, name: string) { + let isPresent = false; + await this.retry.try(async () => { + isPresent = await this.testSubjects.exists(testSubj, { timeout: 20000 }); if (!isPresent) { - throw new Error(`TSVB ${name} tab is not loaded`); + isPresent = await this.testSubjects.exists('visNoResult', { timeout: 1000 }); } + }); + if (!isPresent) { + throw new Error(`TSVB ${name} tab is not loaded`); } + } - public async checkTabIsSelected(chartType: string) { - const chartTypeBtn = await testSubjects.find(`${chartType}TsvbTypeBtn`); - const isSelected = await chartTypeBtn.getAttribute('aria-selected'); + public async checkTabIsSelected(chartType: string) { + const chartTypeBtn = await this.testSubjects.find(`${chartType}TsvbTypeBtn`); + const isSelected = await chartTypeBtn.getAttribute('aria-selected'); - if (isSelected !== 'true') { - throw new Error(`TSVB ${chartType} tab is not selected`); - } + if (isSelected !== 'true') { + throw new Error(`TSVB ${chartType} tab is not selected`); } + } - public async checkPanelConfigIsPresent(chartType: string) { - await testSubjects.existOrFail(`tvbPanelConfig__${chartType}`); - } + public async checkPanelConfigIsPresent(chartType: string) { + await this.testSubjects.existOrFail(`tvbPanelConfig__${chartType}`); + } - public async checkVisualBuilderIsPresent() { - await this.checkTabIsLoaded('tvbVisEditor', 'Time Series'); - } + public async checkVisualBuilderIsPresent() { + await this.checkTabIsLoaded('tvbVisEditor', 'Time Series'); + } - public async checkTimeSeriesChartIsPresent() { - const isPresent = await find.existsByCssSelector('.tvbVisTimeSeries'); - if (!isPresent) { - throw new Error(`TimeSeries chart is not loaded`); - } + public async checkTimeSeriesChartIsPresent() { + const isPresent = await this.find.existsByCssSelector('.tvbVisTimeSeries'); + if (!isPresent) { + throw new Error(`TimeSeries chart is not loaded`); } + } - public async checkTimeSeriesIsLight() { - return await find.existsByCssSelector('.tvbVisTimeSeriesLight'); - } + public async checkTimeSeriesIsLight() { + return await this.find.existsByCssSelector('.tvbVisTimeSeriesLight'); + } - public async checkTimeSeriesLegendIsPresent() { - const isPresent = await find.existsByCssSelector('.echLegend'); - if (!isPresent) { - throw new Error(`TimeSeries legend is not loaded`); - } + public async checkTimeSeriesLegendIsPresent() { + const isPresent = await this.find.existsByCssSelector('.echLegend'); + if (!isPresent) { + throw new Error(`TimeSeries legend is not loaded`); } + } - public async checkMetricTabIsPresent() { - await this.checkTabIsLoaded('tsvbMetricValue', 'Metric'); - } + public async checkMetricTabIsPresent() { + await this.checkTabIsLoaded('tsvbMetricValue', 'Metric'); + } - public async checkGaugeTabIsPresent() { - await this.checkTabIsLoaded('tvbVisGaugeContainer', 'Gauge'); - } + public async checkGaugeTabIsPresent() { + await this.checkTabIsLoaded('tvbVisGaugeContainer', 'Gauge'); + } - public async checkTopNTabIsPresent() { - await this.checkTabIsLoaded('tvbVisTopNTable', 'TopN'); - } + public async checkTopNTabIsPresent() { + await this.checkTabIsLoaded('tvbVisTopNTable', 'TopN'); + } - public async clickMetric() { - const button = await testSubjects.find('metricTsvbTypeBtn'); - await button.click(); - } + public async clickMetric() { + const button = await this.testSubjects.find('metricTsvbTypeBtn'); + await button.click(); + } - public async clickMarkdown() { - const button = await testSubjects.find('markdownTsvbTypeBtn'); - await button.click(); - } + public async clickMarkdown() { + const button = await this.testSubjects.find('markdownTsvbTypeBtn'); + await button.click(); + } - public async getMetricValue() { - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - const metricValue = await find.byCssSelector('.tvbVisMetric__value--primary'); - return metricValue.getVisibleText(); - } + public async getMetricValue() { + await this.visChart.waitForVisualizationRenderingStabilized(); + const metricValue = await this.find.byCssSelector('.tvbVisMetric__value--primary'); + return metricValue.getVisibleText(); + } - public async enterMarkdown(markdown: string) { - await this.clearMarkdown(); - const input = await find.byCssSelector('.tvbMarkdownEditor__editor textarea'); - await input.type(markdown); - await PageObjects.common.sleep(3000); - } + public async enterMarkdown(markdown: string) { + await this.clearMarkdown(); + const input = await this.find.byCssSelector('.tvbMarkdownEditor__editor textarea'); + await input.type(markdown); + await this.common.sleep(3000); + } - public async clearMarkdown() { - // Since we use ACE editor and that isn't really storing its value inside - // a textarea we must really select all text and remove it, and cannot use - // clearValue(). - await retry.waitForWithTimeout('text area is cleared', 20000, async () => { - const editor = await testSubjects.find('codeEditorContainer'); - const $ = await editor.parseDomContent(); - const value = $('.ace_line').text(); - if (value.length > 0) { - log.debug('Clearing text area input'); - this.waitForMarkdownTextAreaCleaned(); - } - - return value.length === 0; - }); - } + public async clearMarkdown() { + // Since we use ACE editor and that isn't really storing its value inside + // a textarea we must really select all text and remove it, and cannot use + // clearValue(). + await this.retry.waitForWithTimeout('text area is cleared', 20000, async () => { + const editor = await this.testSubjects.find('codeEditorContainer'); + const $ = await editor.parseDomContent(); + const value = $('.ace_line').text(); + if (value.length > 0) { + this.log.debug('Clearing text area input'); + this.waitForMarkdownTextAreaCleaned(); + } - public async waitForMarkdownTextAreaCleaned() { - const input = await find.byCssSelector('.tvbMarkdownEditor__editor textarea'); - await input.clearValueWithKeyboard(); - const text = await this.getMarkdownText(); - return text.length === 0; - } + return value.length === 0; + }); + } - public async getMarkdownText(): Promise { - const el = await find.byCssSelector('.tvbVis'); - const text = await el.getVisibleText(); - return text; - } + public async waitForMarkdownTextAreaCleaned() { + const input = await this.find.byCssSelector('.tvbMarkdownEditor__editor textarea'); + await input.clearValueWithKeyboard(); + const text = await this.getMarkdownText(); + return text.length === 0; + } - /** - * - * getting all markdown variables list which located on `table` section - * - * **Note**: if `table` not have variables, use `getMarkdownTableNoVariables` method instead - * @see {getMarkdownTableNoVariables} - * @returns {Promise>} - * @memberof VisualBuilderPage - */ - public async getMarkdownTableVariables(): Promise< - Array<{ key: string; value: string; selector: WebElementWrapper }> - > { - const testTableVariables = await testSubjects.find('tsvbMarkdownVariablesTable'); - const variablesSelector = 'tbody tr'; - const exists = await find.existsByCssSelector(variablesSelector); - if (!exists) { - log.debug('variable list is empty'); - return []; - } - const variables = await testTableVariables.findAllByCssSelector(variablesSelector); - - const variablesKeyValueSelectorMap = await Promise.all( - variables.map(async (variable) => { - const subVars = await variable.findAllByCssSelector('td'); - const selector = await subVars[0].findByTagName('a'); - const key = await selector.getVisibleText(); - const value = await subVars[1].getVisibleText(); - log.debug(`markdown table variables table is: ${key} ${value}`); - return { key, value, selector }; - }) - ); - return variablesKeyValueSelectorMap; - } + public async getMarkdownText(): Promise { + const el = await this.find.byCssSelector('.tvbVis'); + const text = await el.getVisibleText(); + return text; + } - /** - * return variable table message, if `table` is empty it will be fail - * - * **Note:** if `table` have variables, use `getMarkdownTableVariables` method instead - * @see {@link VisualBuilderPage#getMarkdownTableVariables} - * @returns - * @memberof VisualBuilderPage - */ - public async getMarkdownTableNoVariables() { - return await testSubjects.getVisibleText('tvbMarkdownEditor__noVariables'); - } + /** + * + * getting all markdown variables list which located on `table` section + * + * **Note**: if `table` not have variables, use `getMarkdownTableNoVariables` method instead + * @see {getMarkdownTableNoVariables} + * @returns {Promise>} + * @memberof VisualBuilderPage + */ + public async getMarkdownTableVariables(): Promise< + Array<{ key: string; value: string; selector: WebElementWrapper }> + > { + const testTableVariables = await this.testSubjects.find('tsvbMarkdownVariablesTable'); + const variablesSelector = 'tbody tr'; + const exists = await this.find.existsByCssSelector(variablesSelector); + if (!exists) { + this.log.debug('variable list is empty'); + return []; + } + const variables = await testTableVariables.findAllByCssSelector(variablesSelector); + + const variablesKeyValueSelectorMap = await Promise.all( + variables.map(async (variable) => { + const subVars = await variable.findAllByCssSelector('td'); + const selector = await subVars[0].findByTagName('a'); + const key = await selector.getVisibleText(); + const value = await subVars[1].getVisibleText(); + this.log.debug(`markdown table variables table is: ${key} ${value}`); + return { key, value, selector }; + }) + ); + return variablesKeyValueSelectorMap; + } - /** - * get all sub-tabs count for `time series`, `metric`, `top n`, `gauge`, `markdown` or `table` tab. - * - * @returns {Promise} - * @memberof VisualBuilderPage - */ - public async getSubTabs(): Promise { - return await find.allByCssSelector('[data-test-subj$="-subtab"]'); - } + /** + * return variable table message, if `table` is empty it will be fail + * + * **Note:** if `table` have variables, use `getMarkdownTableVariables` method instead + * @see {@link VisualBuilderPage#getMarkdownTableVariables} + * @returns + * @memberof VisualBuilderPage + */ + public async getMarkdownTableNoVariables() { + return await this.testSubjects.getVisibleText('tvbMarkdownEditor__noVariables'); + } - /** - * switch markdown sub-tab for visualization - * - * @param {'data' | 'options'| 'markdown'} subTab - * @memberof VisualBuilderPage - */ - public async markdownSwitchSubTab(subTab: 'data' | 'options' | 'markdown') { - const tab = await testSubjects.find(`${subTab}-subtab`); - const isSelected = await tab.getAttribute('aria-selected'); - if (isSelected !== 'true') { - await tab.click(); - } - } + /** + * get all sub-tabs count for `time series`, `metric`, `top n`, `gauge`, `markdown` or `table` tab. + * + * @returns {Promise} + * @memberof VisualBuilderPage + */ + public async getSubTabs(): Promise { + return await this.find.allByCssSelector('[data-test-subj$="-subtab"]'); + } - /** - * setting label for markdown visualization - * - * @param {string} variableName - * @param type - * @memberof VisualBuilderPage - */ - public async setMarkdownDataVariable(variableName: string, type: 'variable' | 'label') { - const SELECTOR = type === 'label' ? '[placeholder="Label"]' : '[placeholder="Variable name"]'; - if (variableName) { - await find.setValue(SELECTOR, variableName); - } else { - const input = await find.byCssSelector(SELECTOR); - await input.clearValueWithKeyboard({ charByChar: true }); - } + /** + * switch markdown sub-tab for visualization + * + * @param {'data' | 'options'| 'markdown'} subTab + * @memberof VisualBuilderPage + */ + public async markdownSwitchSubTab(subTab: 'data' | 'options' | 'markdown') { + const tab = await this.testSubjects.find(`${subTab}-subtab`); + const isSelected = await tab.getAttribute('aria-selected'); + if (isSelected !== 'true') { + await tab.click(); } + } - public async clickSeriesOption(nth = 0) { - const el = await testSubjects.findAll('seriesOptions'); - await el[nth].click(); + /** + * setting label for markdown visualization + * + * @param {string} variableName + * @param type + * @memberof VisualBuilderPage + */ + public async setMarkdownDataVariable(variableName: string, type: 'variable' | 'label') { + const SELECTOR = type === 'label' ? '[placeholder="Label"]' : '[placeholder="Variable name"]'; + if (variableName) { + await this.find.setValue(SELECTOR, variableName); + } else { + const input = await this.find.byCssSelector(SELECTOR); + await input.clearValueWithKeyboard({ charByChar: true }); } + } - public async clearOffsetSeries() { - const el = await testSubjects.find('offsetTimeSeries'); - await el.clearValue(); - } + public async clickSeriesOption(nth = 0) { + const el = await this.testSubjects.findAll('seriesOptions'); + await el[nth].click(); + } - public async toggleAutoApplyChanges() { - await find.clickByCssSelector('#tsvbAutoApplyInput'); - } + public async clearOffsetSeries() { + const el = await this.testSubjects.find('offsetTimeSeries'); + await el.clearValue(); + } - public async applyChanges() { - await testSubjects.clickWhenNotDisabled('applyBtn'); - } + public async toggleAutoApplyChanges() { + await this.find.clickByCssSelector('#tsvbAutoApplyInput'); + } - /** - * change the data formatter for template in an `options` label tab - * - * @param formatter - typeof formatter which you can use for presenting data. By default kibana show `Number` formatter - */ - public async changeDataFormatter( - formatter: 'Bytes' | 'Number' | 'Percent' | 'Duration' | 'Custom' - ) { - const formatterEl = await testSubjects.find('tsvbDataFormatPicker'); - await comboBox.setElement(formatterEl, formatter, { clickWithMouse: true }); - } + public async applyChanges() { + await this.testSubjects.clickWhenNotDisabled('applyBtn'); + } - /** - * set duration formatter additional settings - * - * @param from start format - * @param to end format - * @param decimalPlaces decimals count - */ - public async setDurationFormatterSettings({ - from, - to, - decimalPlaces, - }: { - from?: FromDuration; - to?: ToDuration; - decimalPlaces?: string; - }) { - if (from) { - await retry.try(async () => { - const fromCombobox = await find.byCssSelector('[id$="from-row"] .euiComboBox'); - await comboBox.setElement(fromCombobox, from, { clickWithMouse: true }); - }); - } - if (to) { - const toCombobox = await find.byCssSelector('[id$="to-row"] .euiComboBox'); - await comboBox.setElement(toCombobox, to, { clickWithMouse: true }); - } - if (decimalPlaces) { - const decimalPlacesInput = await find.byCssSelector('[id$="decimal"]'); - await decimalPlacesInput.type(decimalPlaces); - } - } + /** + * change the data formatter for template in an `options` label tab + * + * @param formatter - typeof formatter which you can use for presenting data. By default kibana show `Number` formatter + */ + public async changeDataFormatter( + formatter: 'Bytes' | 'Number' | 'Percent' | 'Duration' | 'Custom' + ) { + const formatterEl = await this.testSubjects.find('tsvbDataFormatPicker'); + await this.comboBox.setElement(formatterEl, formatter, { clickWithMouse: true }); + } - /** - * write template for aggregation row in the `option` tab - * - * @param template always should contain `{{value}}` - * @example - * await visualBuilder.enterSeriesTemplate('$ {{value}}') // add `$` symbol for value - */ - public async enterSeriesTemplate(template: string) { - const el = await testSubjects.find('tsvb_series_value'); - await el.clearValueWithKeyboard(); - await el.type(template); + /** + * set duration formatter additional settings + * + * @param from start format + * @param to end format + * @param decimalPlaces decimals count + */ + public async setDurationFormatterSettings({ + from, + to, + decimalPlaces, + }: { + from?: FromDuration; + to?: ToDuration; + decimalPlaces?: string; + }) { + if (from) { + await this.retry.try(async () => { + const fromCombobox = await this.find.byCssSelector('[id$="from-row"] .euiComboBox'); + await this.comboBox.setElement(fromCombobox, from, { clickWithMouse: true }); + }); } - - public async enterOffsetSeries(value: string) { - const el = await testSubjects.find('offsetTimeSeries'); - await el.clearValue(); - await el.type(value); + if (to) { + const toCombobox = await this.find.byCssSelector('[id$="to-row"] .euiComboBox'); + await this.comboBox.setElement(toCombobox, to, { clickWithMouse: true }); } - - public async getRhythmChartLegendValue(nth = 0) { - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - const metricValue = ( - await find.allByCssSelector(`.echLegendItem .echLegendItem__extra`, 20000) - )[nth]; - await metricValue.moveMouseTo(); - return await metricValue.getVisibleText(); + if (decimalPlaces) { + const decimalPlacesInput = await this.find.byCssSelector('[id$="decimal"]'); + await decimalPlacesInput.type(decimalPlaces); } + } - public async clickGauge() { - await testSubjects.click('gaugeTsvbTypeBtn'); - } + /** + * write template for aggregation row in the `option` tab + * + * @param template always should contain `{{value}}` + * @example + * await visualBuilder.enterSeriesTemplate('$ {{value}}') // add `$` symbol for value + */ + public async enterSeriesTemplate(template: string) { + const el = await this.testSubjects.find('tsvb_series_value'); + await el.clearValueWithKeyboard(); + await el.type(template); + } - public async getGaugeLabel() { - const gaugeLabel = await find.byCssSelector('.tvbVisGauge__label'); - return await gaugeLabel.getVisibleText(); - } + public async enterOffsetSeries(value: string) { + const el = await this.testSubjects.find('offsetTimeSeries'); + await el.clearValue(); + await el.type(value); + } - public async getGaugeCount() { - const gaugeCount = await find.byCssSelector('.tvbVisGauge__value'); - return await gaugeCount.getVisibleText(); - } + public async getRhythmChartLegendValue(nth = 0) { + await this.visChart.waitForVisualizationRenderingStabilized(); + const metricValue = ( + await this.find.allByCssSelector(`.echLegendItem .echLegendItem__extra`, 20000) + )[nth]; + await metricValue.moveMouseTo(); + return await metricValue.getVisibleText(); + } - public async clickTopN() { - await testSubjects.click('top_nTsvbTypeBtn'); - } + public async clickGauge() { + await this.testSubjects.click('gaugeTsvbTypeBtn'); + } - public async getTopNLabel() { - const topNLabel = await find.byCssSelector('.tvbVisTopN__label'); - return await topNLabel.getVisibleText(); - } + public async getGaugeLabel() { + const gaugeLabel = await this.find.byCssSelector('.tvbVisGauge__label'); + return await gaugeLabel.getVisibleText(); + } - public async getTopNCount() { - const gaugeCount = await find.byCssSelector('.tvbVisTopN__value'); - return await gaugeCount.getVisibleText(); - } + public async getGaugeCount() { + const gaugeCount = await this.find.byCssSelector('.tvbVisGauge__value'); + return await gaugeCount.getVisibleText(); + } - public async clickTable() { - await testSubjects.click('tableTsvbTypeBtn'); - } + public async clickTopN() { + await this.testSubjects.click('top_nTsvbTypeBtn'); + } - public async createNewAgg(nth = 0) { - const prevAggs = await testSubjects.findAll('aggSelector'); - const elements = await testSubjects.findAll('addMetricAddBtn'); - await elements[nth].click(); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await retry.waitFor('new agg is added', async () => { - const currentAggs = await testSubjects.findAll('aggSelector'); - return currentAggs.length > prevAggs.length; - }); - } + public async getTopNLabel() { + const topNLabel = await this.find.byCssSelector('.tvbVisTopN__label'); + return await topNLabel.getVisibleText(); + } - public async selectAggType(value: string, nth = 0) { - const elements = await testSubjects.findAll('aggSelector'); - await comboBox.setElement(elements[nth], value); - return await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async getTopNCount() { + const gaugeCount = await this.find.byCssSelector('.tvbVisTopN__value'); + return await gaugeCount.getVisibleText(); + } - public async fillInExpression(expression: string, nth = 0) { - const expressions = await testSubjects.findAll('mathExpression'); - await expressions[nth].type(expression); - return await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async clickTable() { + await this.testSubjects.click('tableTsvbTypeBtn'); + } - public async fillInVariable(name = 'test', metric = 'Count', nth = 0) { - const elements = await testSubjects.findAll('varRow'); - const varNameInput = await elements[nth].findByCssSelector('.tvbAggs__varName'); - await varNameInput.type(name); - const metricSelectWrapper = await elements[nth].findByCssSelector( - '.tvbAggs__varMetricWrapper' - ); - await comboBox.setElement(metricSelectWrapper, metric); - return await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async createNewAgg(nth = 0) { + const prevAggs = await this.testSubjects.findAll('aggSelector'); + const elements = await this.testSubjects.findAll('addMetricAddBtn'); + await elements[nth].click(); + await this.visChart.waitForVisualizationRenderingStabilized(); + await this.retry.waitFor('new agg is added', async () => { + const currentAggs = await this.testSubjects.findAll('aggSelector'); + return currentAggs.length > prevAggs.length; + }); + } - public async selectGroupByField(fieldName: string) { - await comboBox.set('groupByField', fieldName); - } + public async selectAggType(value: string, nth = 0) { + const elements = await this.testSubjects.findAll('aggSelector'); + await this.comboBox.setElement(elements[nth], value); + return await this.header.waitUntilLoadingHasFinished(); + } - public async setColumnLabelValue(value: string) { - const el = await testSubjects.find('columnLabelName'); - await el.clearValue(); - await el.type(value); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async fillInExpression(expression: string, nth = 0) { + const expressions = await this.testSubjects.findAll('mathExpression'); + await expressions[nth].type(expression); + return await this.header.waitUntilLoadingHasFinished(); + } - /** - * get values for rendered table - * - * **Note:** this work only for table visualization - * - * @returns {Promise} - * @memberof VisualBuilderPage - */ - public async getViewTable(): Promise { - const tableView = await testSubjects.find('tableView', 20000); - return await tableView.getVisibleText(); - } + public async fillInVariable(name = 'test', metric = 'Count', nth = 0) { + const elements = await this.testSubjects.findAll('varRow'); + const varNameInput = await elements[nth].findByCssSelector('.tvbAggs__varName'); + await varNameInput.type(name); + const metricSelectWrapper = await elements[nth].findByCssSelector('.tvbAggs__varMetricWrapper'); + await this.comboBox.setElement(metricSelectWrapper, metric); + return await this.header.waitUntilLoadingHasFinished(); + } - public async clickPanelOptions(tabName: string) { - await testSubjects.click(`${tabName}EditorPanelOptionsBtn`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async selectGroupByField(fieldName: string) { + await this.comboBox.set('groupByField', fieldName); + } - public async clickDataTab(tabName: string) { - await testSubjects.click(`${tabName}EditorDataBtn`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + public async setColumnLabelValue(value: string) { + const el = await this.testSubjects.find('columnLabelName'); + await el.clearValue(); + await el.type(value); + await this.header.waitUntilLoadingHasFinished(); + } - public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) { - await testSubjects.click('switchIndexPatternSelectionModePopover'); - await testSubjects.setEuiSwitch( - 'switchIndexPatternSelectionMode', - useKibanaIndices ? 'check' : 'uncheck' - ); - } + /** + * get values for rendered table + * + * **Note:** this work only for table visualization + * + * @returns {Promise} + * @memberof VisualBuilderPage + */ + public async getViewTable(): Promise { + const tableView = await this.testSubjects.find('tableView', 20000); + return await tableView.getVisibleText(); + } - public async setIndexPatternValue(value: string, useKibanaIndices?: boolean) { - const metricsIndexPatternInput = 'metricsIndexPatternInput'; + public async clickPanelOptions(tabName: string) { + await this.testSubjects.click(`${tabName}EditorPanelOptionsBtn`); + await this.header.waitUntilLoadingHasFinished(); + } - if (useKibanaIndices !== undefined) { - await this.switchIndexPatternSelectionMode(useKibanaIndices); - } + public async clickDataTab(tabName: string) { + await this.testSubjects.click(`${tabName}EditorDataBtn`); + await this.header.waitUntilLoadingHasFinished(); + } - if (useKibanaIndices === false) { - const el = await testSubjects.find(metricsIndexPatternInput); - await el.clearValue(); - if (value) { - await el.type(value, { charByChar: true }); - } - } else { - await comboBox.clearInputField(metricsIndexPatternInput); - if (value) { - await comboBox.setCustom(metricsIndexPatternInput, value); - } - } + public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) { + await this.testSubjects.click('switchIndexPatternSelectionModePopover'); + await this.testSubjects.setEuiSwitch( + 'switchIndexPatternSelectionMode', + useKibanaIndices ? 'check' : 'uncheck' + ); + } + + public async setIndexPatternValue(value: string, useKibanaIndices?: boolean) { + const metricsIndexPatternInput = 'metricsIndexPatternInput'; - await PageObjects.header.waitUntilLoadingHasFinished(); + if (useKibanaIndices !== undefined) { + await this.switchIndexPatternSelectionMode(useKibanaIndices); } - public async setIntervalValue(value: string) { - const el = await testSubjects.find('metricsIndexPatternInterval'); + if (useKibanaIndices === false) { + const el = await this.testSubjects.find(metricsIndexPatternInput); await el.clearValue(); - await el.type(value); - await PageObjects.header.waitUntilLoadingHasFinished(); + if (value) { + await el.type(value, { charByChar: true }); + } + } else { + await this.comboBox.clearInputField(metricsIndexPatternInput); + if (value) { + await this.comboBox.setCustom(metricsIndexPatternInput, value); + } } - public async setDropLastBucket(value: boolean) { - const option = await testSubjects.find(`metricsDropLastBucket-${value ? 'yes' : 'no'}`); - (await option.findByCssSelector('label')).click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + await this.header.waitUntilLoadingHasFinished(); + } - public async waitForIndexPatternTimeFieldOptionsLoaded() { - await retry.waitFor('combobox options loaded', async () => { - const options = await comboBox.getOptions('metricsIndexPatternFieldsSelect'); - log.debug(`-- optionsCount=${options.length}`); - return options.length > 0; - }); - } + public async setIntervalValue(value: string) { + const el = await this.testSubjects.find('metricsIndexPatternInterval'); + await el.clearValue(); + await el.type(value); + await this.header.waitUntilLoadingHasFinished(); + } - public async selectIndexPatternTimeField(timeField: string) { - await retry.try(async () => { - await comboBox.clearInputField('metricsIndexPatternFieldsSelect'); - await comboBox.set('metricsIndexPatternFieldsSelect', timeField); - }); - } + public async setDropLastBucket(value: boolean) { + const option = await this.testSubjects.find(`metricsDropLastBucket-${value ? 'yes' : 'no'}`); + (await option.findByCssSelector('label')).click(); + await this.header.waitUntilLoadingHasFinished(); + } - /** - * check that table visualization is visible and ready for interact - * - * @returns {Promise} - * @memberof VisualBuilderPage - */ - public async checkTableTabIsPresent(): Promise { - await testSubjects.existOrFail('visualizationLoader'); - const isDataExists = await testSubjects.exists('tableView'); - log.debug(`data is already rendered: ${isDataExists}`); - if (!isDataExists) { - await this.checkPreviewIsDisabled(); - } - } + public async waitForIndexPatternTimeFieldOptionsLoaded() { + await this.retry.waitFor('combobox options loaded', async () => { + const options = await this.comboBox.getOptions('metricsIndexPatternFieldsSelect'); + this.log.debug(`-- optionsCount=${options.length}`); + return options.length > 0; + }); + } - /** - * set label name for aggregation - * - * @param {string} labelName - * @param {number} [nth=0] - * @memberof VisualBuilderPage - */ - public async setLabel(labelName: string, nth: number = 0): Promise { - const input = (await find.allByCssSelector('[placeholder="Label"]'))[nth]; - await input.type(labelName); - } + public async selectIndexPatternTimeField(timeField: string) { + await this.retry.try(async () => { + await this.comboBox.clearInputField('metricsIndexPatternFieldsSelect'); + await this.comboBox.set('metricsIndexPatternFieldsSelect', timeField); + }); + } - /** - * set field for type of aggregation - * - * @param {string} field name of field - * @param {number} [aggNth=0] number of aggregation. Start by zero - * @default 0 - * @memberof VisualBuilderPage - */ - public async setFieldForAggregation(field: string, aggNth: number = 0): Promise { - const fieldEl = await this.getFieldForAggregation(aggNth); - - await comboBox.setElement(fieldEl, field); + /** + * check that table visualization is visible and ready for interact + * + * @returns {Promise} + * @memberof VisualBuilderPage + */ + public async checkTableTabIsPresent(): Promise { + await this.testSubjects.existOrFail('visualizationLoader'); + const isDataExists = await this.testSubjects.exists('tableView'); + this.log.debug(`data is already rendered: ${isDataExists}`); + if (!isDataExists) { + await this.checkPreviewIsDisabled(); } + } - public async checkFieldForAggregationValidity(aggNth: number = 0): Promise { - const fieldEl = await this.getFieldForAggregation(aggNth); + /** + * set label name for aggregation + * + * @param {string} labelName + * @param {number} [nth=0] + * @memberof VisualBuilderPage + */ + public async setLabel(labelName: string, nth: number = 0): Promise { + const input = (await this.find.allByCssSelector('[placeholder="Label"]'))[nth]; + await input.type(labelName); + } - return await comboBox.checkValidity(fieldEl); - } + /** + * set field for type of aggregation + * + * @param {string} field name of field + * @param {number} [aggNth=0] number of aggregation. Start by zero + * @default 0 + * @memberof VisualBuilderPage + */ + public async setFieldForAggregation(field: string, aggNth: number = 0): Promise { + const fieldEl = await this.getFieldForAggregation(aggNth); + + await this.comboBox.setElement(fieldEl, field); + } - public async getFieldForAggregation(aggNth: number = 0): Promise { - const labels = await testSubjects.findAll('aggRow'); - const label = labels[aggNth]; + public async checkFieldForAggregationValidity(aggNth: number = 0): Promise { + const fieldEl = await this.getFieldForAggregation(aggNth); - return (await label.findAllByTestSubject('comboBoxInput'))[1]; - } + return await this.comboBox.checkValidity(fieldEl); + } - public async clickColorPicker(): Promise { - const picker = await find.byCssSelector('.tvbColorPicker button'); - await picker.clickMouseButton(); - } + public async getFieldForAggregation(aggNth: number = 0): Promise { + const labels = await this.testSubjects.findAll('aggRow'); + const label = labels[aggNth]; - public async setBackgroundColor(colorHex: string): Promise { - await this.clickColorPicker(); - await this.checkColorPickerPopUpIsPresent(); - await find.setValue('.euiColorPicker input', colorHex); - await this.clickColorPicker(); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - } + return (await label.findAllByTestSubject('comboBoxInput'))[1]; + } - public async checkColorPickerPopUpIsPresent(): Promise { - log.debug(`Check color picker popup is present`); - await testSubjects.existOrFail('colorPickerPopover', { timeout: 5000 }); - } + public async clickColorPicker(): Promise { + const picker = await this.find.byCssSelector('.tvbColorPicker button'); + await picker.clickMouseButton(); + } - public async changePanelPreview(nth: number = 0): Promise { - const prevRenderingCount = await PageObjects.visChart.getVisualizationRenderingCount(); - const changePreviewBtnArray = await testSubjects.findAll('AddActivatePanelBtn'); - await changePreviewBtnArray[nth].click(); - await PageObjects.visChart.waitForRenderingCount(prevRenderingCount + 1); - } + public async setBackgroundColor(colorHex: string): Promise { + await this.clickColorPicker(); + await this.checkColorPickerPopUpIsPresent(); + await this.find.setValue('.euiColorPicker input', colorHex); + await this.clickColorPicker(); + await this.visChart.waitForVisualizationRenderingStabilized(); + } - public async checkPreviewIsDisabled(): Promise { - log.debug(`Check no data message is present`); - await testSubjects.existOrFail('timeseriesVis > visNoResult', { timeout: 5000 }); - } + public async checkColorPickerPopUpIsPresent(): Promise { + this.log.debug(`Check color picker popup is present`); + await this.testSubjects.existOrFail('colorPickerPopover', { timeout: 5000 }); + } - public async cloneSeries(nth: number = 0): Promise { - const cloneBtnArray = await testSubjects.findAll('AddCloneBtn'); - await cloneBtnArray[nth].click(); - await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - } + public async changePanelPreview(nth: number = 0): Promise { + const prevRenderingCount = await this.visChart.getVisualizationRenderingCount(); + const changePreviewBtnArray = await this.testSubjects.findAll('AddActivatePanelBtn'); + await changePreviewBtnArray[nth].click(); + await this.visChart.waitForRenderingCount(prevRenderingCount + 1); + } - /** - * Get aggregation count for the current series - * - * @param {number} [nth=0] series - * @returns {Promise} - * @memberof VisualBuilderPage - */ - public async getAggregationCount(nth: number = 0): Promise { - const series = await this.getSeries(); - const aggregation = await series[nth].findAllByTestSubject('draggable'); - return aggregation.length; - } + public async checkPreviewIsDisabled(): Promise { + this.log.debug(`Check no data message is present`); + await this.testSubjects.existOrFail('timeseriesVis > visNoResult', { timeout: 5000 }); + } - public async deleteSeries(nth: number = 0): Promise { - const prevRenderingCount = await PageObjects.visChart.getVisualizationRenderingCount(); - const cloneBtnArray = await testSubjects.findAll('AddDeleteBtn'); - await cloneBtnArray[nth].click(); - await PageObjects.visChart.waitForRenderingCount(prevRenderingCount + 1); - } + public async cloneSeries(nth: number = 0): Promise { + const cloneBtnArray = await this.testSubjects.findAll('AddCloneBtn'); + await cloneBtnArray[nth].click(); + await this.visChart.waitForVisualizationRenderingStabilized(); + } - public async getLegendItems(): Promise { - return await find.allByCssSelector('.echLegendItem'); - } + /** + * Get aggregation count for the current series + * + * @param {number} [nth=0] series + * @returns {Promise} + * @memberof VisualBuilderPage + */ + public async getAggregationCount(nth: number = 0): Promise { + const series = await this.getSeries(); + const aggregation = await series[nth].findAllByTestSubject('draggable'); + return aggregation.length; + } - public async getLegendItemsContent(): Promise { - const legendList = await find.byCssSelector('.echLegendList'); - const $ = await legendList.parseDomContent(); + public async deleteSeries(nth: number = 0): Promise { + const prevRenderingCount = await this.visChart.getVisualizationRenderingCount(); + const cloneBtnArray = await this.testSubjects.findAll('AddDeleteBtn'); + await cloneBtnArray[nth].click(); + await this.visChart.waitForRenderingCount(prevRenderingCount + 1); + } - return $('li') - .toArray() - .map((li) => { - const label = $(li).find('.echLegendItem__label').text(); - const value = $(li).find('.echLegendItem__extra').text(); + public async getLegendItems(): Promise { + return await this.find.allByCssSelector('.echLegendItem'); + } - return `${label}: ${value}`; - }); - } + public async getLegendItemsContent(): Promise { + const legendList = await this.find.byCssSelector('.echLegendList'); + const $ = await legendList.parseDomContent(); - public async getSeries(): Promise { - return await find.allByCssSelector('.tvbSeriesEditor'); - } + return $('li') + .toArray() + .map((li) => { + const label = $(li).find('.echLegendItem__label').text(); + const value = $(li).find('.echLegendItem__extra').text(); - public async setMetricsGroupByTerms(field: string) { - const groupBy = await find.byCssSelector( - '.tvbAggRow--split [data-test-subj="comboBoxInput"]' - ); - await comboBox.setElement(groupBy, 'Terms', { clickWithMouse: true }); - await PageObjects.common.sleep(1000); - const byField = await testSubjects.find('groupByField'); - await comboBox.setElement(byField, field); - } + return `${label}: ${value}`; + }); + } - public async checkSelectedMetricsGroupByValue(value: string) { - const groupBy = await find.byCssSelector( - '.tvbAggRow--split [data-test-subj="comboBoxInput"]' - ); - return await comboBox.isOptionSelected(groupBy, value); - } + public async getSeries(): Promise { + return await this.find.allByCssSelector('.tvbSeriesEditor'); + } - public async setMetricsDataTimerangeMode(value: string) { - const dataTimeRangeMode = await testSubjects.find('dataTimeRangeMode'); - return await comboBox.setElement(dataTimeRangeMode, value); - } + public async setMetricsGroupByTerms(field: string) { + const groupBy = await this.find.byCssSelector( + '.tvbAggRow--split [data-test-subj="comboBoxInput"]' + ); + await this.comboBox.setElement(groupBy, 'Terms', { clickWithMouse: true }); + await this.common.sleep(1000); + const byField = await this.testSubjects.find('groupByField'); + await this.comboBox.setElement(byField, field); + } - public async checkSelectedDataTimerangeMode(value: string) { - const dataTimeRangeMode = await testSubjects.find('dataTimeRangeMode'); - return await comboBox.isOptionSelected(dataTimeRangeMode, value); - } + public async checkSelectedMetricsGroupByValue(value: string) { + const groupBy = await this.find.byCssSelector( + '.tvbAggRow--split [data-test-subj="comboBoxInput"]' + ); + return await this.comboBox.isOptionSelected(groupBy, value); } - return new VisualBuilderPage(); + public async setMetricsDataTimerangeMode(value: string) { + const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode'); + return await this.comboBox.setElement(dataTimeRangeMode, value); + } + + public async checkSelectedDataTimerangeMode(value: string) { + const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode'); + return await this.comboBox.isOptionSelected(dataTimeRangeMode, value); + } } diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 7ecf800b4be7c0..c8587f4ffd3469 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -9,614 +9,618 @@ import { Position } from '@elastic/charts'; import Color from 'color'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; const xyChartSelector = 'visTypeXyChart'; const pieChartSelector = 'visTypePieChart'; -export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const config = getService('config'); - const find = getService('find'); - const log = getService('log'); - const retry = getService('retry'); - const kibanaServer = getService('kibanaServer'); - const elasticChart = getService('elasticChart'); - const dataGrid = getService('dataGrid'); - const defaultFindTimeout = config.get('timeouts.find'); - const { common } = getPageObjects(['common']); - - class VisualizeChart { - public async getEsChartDebugState(chartSelector: string) { - return await elasticChart.getChartDebugData(chartSelector); - } +export class VisualizeChartPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly config = this.ctx.getService('config'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + private readonly elasticChart = this.ctx.getService('elasticChart'); + private readonly dataGrid = this.ctx.getService('dataGrid'); + private readonly common = this.ctx.getPageObject('common'); + + private readonly defaultFindTimeout = this.config.get('timeouts.find'); + + public async getEsChartDebugState(chartSelector: string) { + return await this.elasticChart.getChartDebugData(chartSelector); + } - /** - * Is new charts library advanced setting enabled - */ - public async isNewChartsLibraryEnabled(): Promise { - const legacyChartsLibrary = - Boolean(await kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary')) ?? - true; - const enabled = !legacyChartsLibrary; - log.debug(`-- isNewChartsLibraryEnabled = ${enabled}`); - - return enabled; - } + /** + * Is new charts library advanced setting enabled + */ + public async isNewChartsLibraryEnabled(): Promise { + const legacyChartsLibrary = + Boolean( + await this.kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary') + ) ?? true; + const enabled = !legacyChartsLibrary; + this.log.debug(`-- isNewChartsLibraryEnabled = ${enabled}`); + + return enabled; + } - /** - * Is new charts library enabled and an area, line or histogram chart exists - */ - public async isNewLibraryChart(chartSelector: string): Promise { - const enabled = await this.isNewChartsLibraryEnabled(); + /** + * Is new charts library enabled and an area, line or histogram chart exists + */ + public async isNewLibraryChart(chartSelector: string): Promise { + const enabled = await this.isNewChartsLibraryEnabled(); - if (!enabled) { - log.debug(`-- isNewLibraryChart = false`); - return false; - } - - // check if enabled but not a line, area, histogram or pie chart - if (await find.existsByCssSelector('.visLib__chart', 1)) { - const chart = await find.byCssSelector('.visLib__chart'); - const chartType = await chart.getAttribute('data-vislib-chart-type'); + if (!enabled) { + this.log.debug(`-- isNewLibraryChart = false`); + return false; + } - if (!['line', 'area', 'histogram', 'pie'].includes(chartType)) { - log.debug(`-- isNewLibraryChart = false`); - return false; - } - } + // check if enabled but not a line, area, histogram or pie chart + if (await this.find.existsByCssSelector('.visLib__chart', 1)) { + const chart = await this.find.byCssSelector('.visLib__chart'); + const chartType = await chart.getAttribute('data-vislib-chart-type'); - if (!(await elasticChart.hasChart(chartSelector, 1))) { - // not be a vislib chart type - log.debug(`-- isNewLibraryChart = false`); + if (!['line', 'area', 'histogram', 'pie'].includes(chartType)) { + this.log.debug(`-- isNewLibraryChart = false`); return false; } + } - log.debug(`-- isNewLibraryChart = true`); - return true; + if (!(await this.elasticChart.hasChart(chartSelector, 1))) { + // not be a vislib chart type + this.log.debug(`-- isNewLibraryChart = false`); + return false; } - /** - * Helper method to get expected values that are slightly different - * between vislib and elastic-chart inplementations - * @param vislibValue value expected for vislib chart - * @param elasticChartsValue value expected for `@elastic/charts` chart - */ - public async getExpectedValue(vislibValue: T, elasticChartsValue: T): Promise { - if (await this.isNewLibraryChart(xyChartSelector)) { - return elasticChartsValue; - } + this.log.debug(`-- isNewLibraryChart = true`); + return true; + } - return vislibValue; + /** + * Helper method to get expected values that are slightly different + * between vislib and elastic-chart inplementations + * @param vislibValue value expected for vislib chart + * @param elasticChartsValue value expected for `@elastic/charts` chart + */ + public async getExpectedValue(vislibValue: T, elasticChartsValue: T): Promise { + if (await this.isNewLibraryChart(xyChartSelector)) { + return elasticChartsValue; } - public async getYAxisTitle() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const xAxis = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return xAxis[0]?.title; - } + return vislibValue; + } - const title = await find.byCssSelector('.y-axis-div .y-axis-title text'); - return await title.getVisibleText(); + public async getYAxisTitle() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const xAxis = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; + return xAxis[0]?.title; } - public async getXAxisLabels() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const [xAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.x ?? []; - return xAxis?.labels; - } + const title = await this.find.byCssSelector('.y-axis-div .y-axis-title text'); + return await title.getVisibleText(); + } - const xAxis = await find.byCssSelector('.visAxis--x.visAxis__column--bottom'); - const $ = await xAxis.parseDomContent(); - return $('.x > g > text') - .toArray() - .map((tick) => $(tick).text().trim()); + public async getXAxisLabels() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const [xAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.x ?? []; + return xAxis?.labels; } - public async getYAxisLabels() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const [yAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return yAxis?.labels; - } + const xAxis = await this.find.byCssSelector('.visAxis--x.visAxis__column--bottom'); + const $ = await xAxis.parseDomContent(); + return $('.x > g > text') + .toArray() + .map((tick) => $(tick).text().trim()); + } - const yAxis = await find.byCssSelector('.visAxis__column--y.visAxis__column--left'); - const $ = await yAxis.parseDomContent(); - return $('.y > g > text') - .toArray() - .map((tick) => $(tick).text().trim()); + public async getYAxisLabels() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const [yAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; + return yAxis?.labels; } - public async getYAxisLabelsAsNumbers() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const [yAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return yAxis?.values; - } + const yAxis = await this.find.byCssSelector('.visAxis__column--y.visAxis__column--left'); + const $ = await yAxis.parseDomContent(); + return $('.y > g > text') + .toArray() + .map((tick) => $(tick).text().trim()); + } - return (await this.getYAxisLabels()).map((label) => Number(label.replace(',', ''))); + public async getYAxisLabelsAsNumbers() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const [yAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; + return yAxis?.values; } - /** - * Gets the chart data and scales it based on chart height and label. - * @param dataLabel data-label value - * @param axis axis value, 'ValueAxis-1' by default - * - * Returns an array of height values - */ - public async getAreaChartData(dataLabel: string, axis = 'ValueAxis-1') { - if (await this.isNewLibraryChart(xyChartSelector)) { - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - const points = areas.find(({ name }) => name === dataLabel)?.lines.y1.points ?? []; - return points.map(({ y }) => y); - } - - const yAxisRatio = await this.getChartYAxisRatio(axis); + return (await this.getYAxisLabels()).map((label) => Number(label.replace(',', ''))); + } - const rectangle = await find.byCssSelector('rect.background'); - const yAxisHeight = Number(await rectangle.getAttribute('height')); - log.debug(`height --------- ${yAxisHeight}`); + /** + * Gets the chart data and scales it based on chart height and label. + * @param dataLabel data-label value + * @param axis axis value, 'ValueAxis-1' by default + * + * Returns an array of height values + */ + public async getAreaChartData(dataLabel: string, axis = 'ValueAxis-1') { + if (await this.isNewLibraryChart(xyChartSelector)) { + const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; + const points = areas.find(({ name }) => name === dataLabel)?.lines.y1.points ?? []; + return points.map(({ y }) => y); + } + + const yAxisRatio = await this.getChartYAxisRatio(axis); + + const rectangle = await this.find.byCssSelector('rect.background'); + const yAxisHeight = Number(await rectangle.getAttribute('height')); + this.log.debug(`height --------- ${yAxisHeight}`); + + const path = await this.retry.try( + async () => + await this.find.byCssSelector( + `path[data-label="${dataLabel}"]`, + this.defaultFindTimeout * 2 + ) + ); + const data = await path.getAttribute('d'); + this.log.debug(data); + // This area chart data starts with a 'M'ove to a x,y location, followed + // by a bunch of 'L'ines from that point to the next. Those points are + // the values we're going to use to calculate the data values we're testing. + // So git rid of the one 'M' and split the rest on the 'L's. + const tempArray = data + .replace('M ', '') + .replace('M', '') + .replace(/ L /g, 'L') + .replace(/ /g, ',') + .split('L'); + const chartSections = tempArray.length / 2; + const chartData = []; + for (let i = 0; i < chartSections; i++) { + chartData[i] = Math.round((yAxisHeight - Number(tempArray[i].split(',')[1])) * yAxisRatio); + this.log.debug('chartData[i] =' + chartData[i]); + } + return chartData; + } - const path = await retry.try( - async () => - await find.byCssSelector(`path[data-label="${dataLabel}"]`, defaultFindTimeout * 2) - ); - const data = await path.getAttribute('d'); - log.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - const tempArray = data - .replace('M ', '') - .replace('M', '') - .replace(/ L /g, 'L') - .replace(/ /g, ',') - .split('L'); - const chartSections = tempArray.length / 2; - const chartData = []; - for (let i = 0; i < chartSections; i++) { - chartData[i] = Math.round((yAxisHeight - Number(tempArray[i].split(',')[1])) * yAxisRatio); - log.debug('chartData[i] =' + chartData[i]); - } - return chartData; - } + /** + * Returns the paths that compose an area chart. + * @param dataLabel data-label value + */ + public async getAreaChartPaths(dataLabel: string) { + if (await this.isNewLibraryChart(xyChartSelector)) { + const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; + const path = areas.find(({ name }) => name === dataLabel)?.path ?? ''; + return path.split('L'); + } + + const path = await this.retry.try( + async () => + await this.find.byCssSelector( + `path[data-label="${dataLabel}"]`, + this.defaultFindTimeout * 2 + ) + ); + const data = await path.getAttribute('d'); + this.log.debug(data); + // This area chart data starts with a 'M'ove to a x,y location, followed + // by a bunch of 'L'ines from that point to the next. Those points are + // the values we're going to use to calculate the data values we're testing. + // So git rid of the one 'M' and split the rest on the 'L's. + return data.split('L'); + } - /** - * Returns the paths that compose an area chart. - * @param dataLabel data-label value - */ - public async getAreaChartPaths(dataLabel: string) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - const path = areas.find(({ name }) => name === dataLabel)?.path ?? ''; - return path.split('L'); - } + /** + * Gets the dots and normalizes their height. + * @param dataLabel data-label value + * @param axis axis value, 'ValueAxis-1' by default + */ + public async getLineChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { + if (await this.isNewLibraryChart(xyChartSelector)) { + // For now lines are rendered as areas to enable stacking + const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; + const lines = areas.map(({ lines: { y1 }, name, color }) => ({ ...y1, name, color })); + const points = lines.find(({ name }) => name === dataLabel)?.points ?? []; + return points.map(({ y }) => y); + } + + // 1). get the range/pixel ratio + const yAxisRatio = await this.getChartYAxisRatio(axis); + // 2). find and save the y-axis pixel size (the chart height) + const rectangle = await this.find.byCssSelector('clipPath rect'); + const yAxisHeight = Number(await rectangle.getAttribute('height')); + // 3). get the visWrapper__chart elements + const chartTypes = await this.retry.try( + async () => + await this.find.allByCssSelector( + `.visWrapper__chart circle[data-label="${dataLabel}"][fill-opacity="1"]`, + this.defaultFindTimeout * 2 + ) + ); + // 4). for each chart element, find the green circle, then the cy position + const chartData = await Promise.all( + chartTypes.map(async (chart) => { + const cy = Number(await chart.getAttribute('cy')); + // the point_series_options test has data in the billions range and + // getting 11 digits of precision with these calculations is very hard + return Math.round(Number(((yAxisHeight - cy) * yAxisRatio).toPrecision(6))); + }) + ); + + return chartData; + } - const path = await retry.try( - async () => - await find.byCssSelector(`path[data-label="${dataLabel}"]`, defaultFindTimeout * 2) - ); - const data = await path.getAttribute('d'); - log.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - return data.split('L'); - } + /** + * Returns bar chart data in pixels + * @param dataLabel data-label value + * @param axis axis value, 'ValueAxis-1' by default + */ + public async getBarChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { + if (await this.isNewLibraryChart(xyChartSelector)) { + const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; + const values = bars.find(({ name }) => name === dataLabel)?.bars ?? []; + return values.map(({ y }) => y); + } + + const yAxisRatio = await this.getChartYAxisRatio(axis); + const svg = await this.find.byCssSelector('div.chart'); + const $ = await svg.parseDomContent(); + const chartData = $(`g > g.series > rect[data-label="${dataLabel}"]`) + .toArray() + .map((chart) => { + const barHeight = Number($(chart).attr('height')); + return Math.round(barHeight * yAxisRatio); + }); - /** - * Gets the dots and normalizes their height. - * @param dataLabel data-label value - * @param axis axis value, 'ValueAxis-1' by default - */ - public async getLineChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { - if (await this.isNewLibraryChart(xyChartSelector)) { - // For now lines are rendered as areas to enable stacking - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - const lines = areas.map(({ lines: { y1 }, name, color }) => ({ ...y1, name, color })); - const points = lines.find(({ name }) => name === dataLabel)?.points ?? []; - return points.map(({ y }) => y); - } + return chartData; + } - // 1). get the range/pixel ratio - const yAxisRatio = await this.getChartYAxisRatio(axis); - // 2). find and save the y-axis pixel size (the chart height) - const rectangle = await find.byCssSelector('clipPath rect'); - const yAxisHeight = Number(await rectangle.getAttribute('height')); - // 3). get the visWrapper__chart elements - const chartTypes = await retry.try( - async () => - await find.allByCssSelector( - `.visWrapper__chart circle[data-label="${dataLabel}"][fill-opacity="1"]`, - defaultFindTimeout * 2 - ) - ); - // 4). for each chart element, find the green circle, then the cy position - const chartData = await Promise.all( - chartTypes.map(async (chart) => { - const cy = Number(await chart.getAttribute('cy')); - // the point_series_options test has data in the billions range and - // getting 11 digits of precision with these calculations is very hard - return Math.round(Number(((yAxisHeight - cy) * yAxisRatio).toPrecision(6))); - }) - ); + /** + * Returns the range/pixel ratio + * @param axis axis value, 'ValueAxis-1' by default + */ + private async getChartYAxisRatio(axis = 'ValueAxis-1') { + // 1). get the maximum chart Y-Axis marker value and Y position + const maxYAxisChartMarker = await this.retry.try( + async () => + await this.find.byCssSelector( + `div.visAxis__splitAxes--y > div > svg > g.${axis} > g:last-of-type.tick` + ) + ); + const maxYLabel = (await maxYAxisChartMarker.getVisibleText()).replace(/,/g, ''); + const maxYLabelYPosition = (await maxYAxisChartMarker.getPosition()).y; + this.log.debug(`maxYLabel = ${maxYLabel}, maxYLabelYPosition = ${maxYLabelYPosition}`); + + // 2). get the minimum chart Y-Axis marker value and Y position + const minYAxisChartMarker = await this.find.byCssSelector( + 'div.visAxis__column--y.visAxis__column--left > div > div > svg:nth-child(2) > g > g:nth-child(1).tick' + ); + const minYLabel = (await minYAxisChartMarker.getVisibleText()).replace(',', ''); + const minYLabelYPosition = (await minYAxisChartMarker.getPosition()).y; + return (Number(maxYLabel) - Number(minYLabel)) / (minYLabelYPosition - maxYLabelYPosition); + } - return chartData; - } + public async toggleLegend(show = true) { + const isVisTypeXYChart = await this.isNewLibraryChart(xyChartSelector); + const isVisTypePieChart = await this.isNewLibraryChart(pieChartSelector); + const legendSelector = isVisTypeXYChart || isVisTypePieChart ? '.echLegend' : '.visLegend'; - /** - * Returns bar chart data in pixels - * @param dataLabel data-label value - * @param axis axis value, 'ValueAxis-1' by default - */ - public async getBarChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { - if (await this.isNewLibraryChart(xyChartSelector)) { - const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; - const values = bars.find(({ name }) => name === dataLabel)?.bars ?? []; - return values.map(({ y }) => y); + await this.retry.try(async () => { + const isVisible = await this.find.existsByCssSelector(legendSelector); + if ((show && !isVisible) || (!show && isVisible)) { + await this.testSubjects.click('vislibToggleLegend'); } + }); + } - const yAxisRatio = await this.getChartYAxisRatio(axis); - const svg = await find.byCssSelector('div.chart'); - const $ = await svg.parseDomContent(); - const chartData = $(`g > g.series > rect[data-label="${dataLabel}"]`) - .toArray() - .map((chart) => { - const barHeight = Number($(chart).attr('height')); - return Math.round(barHeight * yAxisRatio); - }); - - return chartData; - } - - /** - * Returns the range/pixel ratio - * @param axis axis value, 'ValueAxis-1' by default - */ - private async getChartYAxisRatio(axis = 'ValueAxis-1') { - // 1). get the maximum chart Y-Axis marker value and Y position - const maxYAxisChartMarker = await retry.try( - async () => - await find.byCssSelector( - `div.visAxis__splitAxes--y > div > svg > g.${axis} > g:last-of-type.tick` - ) - ); - const maxYLabel = (await maxYAxisChartMarker.getVisibleText()).replace(/,/g, ''); - const maxYLabelYPosition = (await maxYAxisChartMarker.getPosition()).y; - log.debug(`maxYLabel = ${maxYLabel}, maxYLabelYPosition = ${maxYLabelYPosition}`); + public async filterLegend(name: string) { + await this.toggleLegend(); + await this.testSubjects.click(`legend-${name}`); + const filterIn = await this.testSubjects.find(`legend-${name}-filterIn`); + await filterIn.click(); + await this.waitForVisualizationRenderingStabilized(); + } - // 2). get the minimum chart Y-Axis marker value and Y position - const minYAxisChartMarker = await find.byCssSelector( - 'div.visAxis__column--y.visAxis__column--left > div > div > svg:nth-child(2) > g > g:nth-child(1).tick' - ); - const minYLabel = (await minYAxisChartMarker.getVisibleText()).replace(',', ''); - const minYLabelYPosition = (await minYAxisChartMarker.getPosition()).y; - return (Number(maxYLabel) - Number(minYLabel)) / (minYLabelYPosition - maxYLabelYPosition); - } + public async doesLegendColorChoiceExist(color: string) { + return await this.testSubjects.exists(`visColorPickerColor-${color}`); + } - public async toggleLegend(show = true) { - const isVisTypeXYChart = await this.isNewLibraryChart(xyChartSelector); - const isVisTypePieChart = await this.isNewLibraryChart(pieChartSelector); - const legendSelector = isVisTypeXYChart || isVisTypePieChart ? '.echLegend' : '.visLegend'; + public async selectNewLegendColorChoice(color: string) { + await this.testSubjects.click(`visColorPickerColor-${color}`); + } - await retry.try(async () => { - const isVisible = await find.existsByCssSelector(legendSelector); - if ((show && !isVisible) || (!show && isVisible)) { - await testSubjects.click('vislibToggleLegend'); - } - }); + public async doesSelectedLegendColorExist(color: string) { + if (await this.isNewLibraryChart(xyChartSelector)) { + const items = (await this.getEsChartDebugState(xyChartSelector))?.legend?.items ?? []; + return items.some(({ color: c }) => c === color); } - public async filterLegend(name: string) { - await this.toggleLegend(); - await testSubjects.click(`legend-${name}`); - const filterIn = await testSubjects.find(`legend-${name}-filterIn`); - await filterIn.click(); - await this.waitForVisualizationRenderingStabilized(); + if (await this.isNewLibraryChart(pieChartSelector)) { + const slices = + (await this.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? []; + return slices.some(({ color: c }) => { + const rgbColor = new Color(color).rgb().toString(); + return c === rgbColor; + }); } - public async doesLegendColorChoiceExist(color: string) { - return await testSubjects.exists(`visColorPickerColor-${color}`); - } + return await this.testSubjects.exists(`legendSelectedColor-${color}`); + } - public async selectNewLegendColorChoice(color: string) { - await testSubjects.click(`visColorPickerColor-${color}`); + public async expectError() { + if (!this.isNewLibraryChart(xyChartSelector)) { + await this.testSubjects.existOrFail('vislibVisualizeError'); } + } - public async doesSelectedLegendColorExist(color: string) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const items = (await this.getEsChartDebugState(xyChartSelector))?.legend?.items ?? []; - return items.some(({ color: c }) => c === color); - } - - if (await this.isNewLibraryChart(pieChartSelector)) { - const slices = - (await this.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? []; - return slices.some(({ color: c }) => { - const rgbColor = new Color(color).rgb().toString(); - return c === rgbColor; - }); - } - - return await testSubjects.exists(`legendSelectedColor-${color}`); - } + public async getVisualizationRenderingCount() { + const visualizationLoader = await this.testSubjects.find('visualizationLoader'); + const renderingCount = await visualizationLoader.getAttribute('data-rendering-count'); + return Number(renderingCount); + } - public async expectError() { - if (!this.isNewLibraryChart(xyChartSelector)) { - await testSubjects.existOrFail('vislibVisualizeError'); + public async waitForRenderingCount(minimumCount = 1) { + await this.retry.waitFor( + `rendering count to be greater than or equal to [${minimumCount}]`, + async () => { + const currentRenderingCount = await this.getVisualizationRenderingCount(); + this.log.debug(`-- currentRenderingCount=${currentRenderingCount}`); + this.log.debug(`-- expectedCount=${minimumCount}`); + return currentRenderingCount >= minimumCount; } - } + ); + } - public async getVisualizationRenderingCount() { - const visualizationLoader = await testSubjects.find('visualizationLoader'); - const renderingCount = await visualizationLoader.getAttribute('data-rendering-count'); - return Number(renderingCount); - } + public async waitForVisualizationRenderingStabilized() { + // assuming rendering is done when data-rendering-count is constant within 1000 ms + await this.retry.waitFor('rendering count to stabilize', async () => { + const firstCount = await this.getVisualizationRenderingCount(); + this.log.debug(`-- firstCount=${firstCount}`); - public async waitForRenderingCount(minimumCount = 1) { - await retry.waitFor( - `rendering count to be greater than or equal to [${minimumCount}]`, - async () => { - const currentRenderingCount = await this.getVisualizationRenderingCount(); - log.debug(`-- currentRenderingCount=${currentRenderingCount}`); - log.debug(`-- expectedCount=${minimumCount}`); - return currentRenderingCount >= minimumCount; - } - ); - } + await this.common.sleep(2000); - public async waitForVisualizationRenderingStabilized() { - // assuming rendering is done when data-rendering-count is constant within 1000 ms - await retry.waitFor('rendering count to stabilize', async () => { - const firstCount = await this.getVisualizationRenderingCount(); - log.debug(`-- firstCount=${firstCount}`); + const secondCount = await this.getVisualizationRenderingCount(); + this.log.debug(`-- secondCount=${secondCount}`); - await common.sleep(2000); + return firstCount === secondCount; + }); + } - const secondCount = await this.getVisualizationRenderingCount(); - log.debug(`-- secondCount=${secondCount}`); + public async waitForVisualization() { + await this.waitForVisualizationRenderingStabilized(); - return firstCount === secondCount; - }); + if (!(await this.isNewLibraryChart(xyChartSelector))) { + await this.find.byCssSelector('.visualization'); } + } - public async waitForVisualization() { - await this.waitForVisualizationRenderingStabilized(); - - if (!(await this.isNewLibraryChart(xyChartSelector))) { - await find.byCssSelector('.visualization'); - } + public async getLegendEntries() { + const isVisTypeXYChart = await this.isNewLibraryChart(xyChartSelector); + const isVisTypePieChart = await this.isNewLibraryChart(pieChartSelector); + if (isVisTypeXYChart) { + const items = (await this.getEsChartDebugState(xyChartSelector))?.legend?.items ?? []; + return items.map(({ name }) => name); } - public async getLegendEntries() { - const isVisTypeXYChart = await this.isNewLibraryChart(xyChartSelector); - const isVisTypePieChart = await this.isNewLibraryChart(pieChartSelector); - if (isVisTypeXYChart) { - const items = (await this.getEsChartDebugState(xyChartSelector))?.legend?.items ?? []; - return items.map(({ name }) => name); - } - - if (isVisTypePieChart) { - const slices = - (await this.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? []; - return slices.map(({ name }) => name); - } - - const legendEntries = await find.allByCssSelector( - '.visLegend__button', - defaultFindTimeout * 2 - ); - return await Promise.all( - legendEntries.map(async (chart) => await chart.getAttribute('data-label')) - ); + if (isVisTypePieChart) { + const slices = + (await this.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? []; + return slices.map(({ name }) => name); } - public async openLegendOptionColors(name: string, chartSelector: string) { - await this.waitForVisualizationRenderingStabilized(); - await retry.try(async () => { - if ( - (await this.isNewLibraryChart(xyChartSelector)) || - (await this.isNewLibraryChart(pieChartSelector)) - ) { - const chart = await find.byCssSelector(chartSelector); - const legendItemColor = await chart.findByCssSelector( - `[data-ech-series-name="${name}"] .echLegendItem__color` - ); - legendItemColor.click(); - } else { - // This click has been flaky in opening the legend, hence the retry. See - // https://github.com/elastic/kibana/issues/17468 - await testSubjects.click(`legend-${name}`); - } - - await this.waitForVisualizationRenderingStabilized(); - // arbitrary color chosen, any available would do - const arbitraryColor = (await this.isNewLibraryChart(xyChartSelector)) - ? '#d36086' - : '#EF843C'; - const isOpen = await this.doesLegendColorChoiceExist(arbitraryColor); - if (!isOpen) { - throw new Error('legend color selector not open'); - } - }); - } + const legendEntries = await this.find.allByCssSelector( + '.visLegend__button', + this.defaultFindTimeout * 2 + ); + return await Promise.all( + legendEntries.map(async (chart) => await chart.getAttribute('data-label')) + ); + } - public async filterOnTableCell(columnIndex: number, rowIndex: number) { - await retry.try(async () => { - const cell = await dataGrid.getCellElement(rowIndex, columnIndex); - await cell.click(); - const filterBtn = await testSubjects.findDescendant( - 'tbvChartCell__filterForCellValue', - cell + public async openLegendOptionColors(name: string, chartSelector: string) { + await this.waitForVisualizationRenderingStabilized(); + await this.retry.try(async () => { + if ( + (await this.isNewLibraryChart(xyChartSelector)) || + (await this.isNewLibraryChart(pieChartSelector)) + ) { + const chart = await this.find.byCssSelector(chartSelector); + const legendItemColor = await chart.findByCssSelector( + `[data-ech-series-name="${name}"] .echLegendItem__color` ); - await common.sleep(2000); - filterBtn.click(); - }); - } + legendItemColor.click(); + } else { + // This click has been flaky in opening the legend, hence the this.retry. See + // https://github.com/elastic/kibana/issues/17468 + await this.testSubjects.click(`legend-${name}`); + } - public async getMarkdownText() { - const markdownContainer = await testSubjects.find('markdownBody'); - return markdownContainer.getVisibleText(); - } + await this.waitForVisualizationRenderingStabilized(); + // arbitrary color chosen, any available would do + const arbitraryColor = (await this.isNewLibraryChart(xyChartSelector)) + ? '#d36086' + : '#EF843C'; + const isOpen = await this.doesLegendColorChoiceExist(arbitraryColor); + if (!isOpen) { + throw new Error('legend color selector not open'); + } + }); + } - public async getMarkdownBodyDescendentText(selector: string) { - const markdownContainer = await testSubjects.find('markdownBody'); - const element = await find.descendantDisplayedByCssSelector(selector, markdownContainer); - return element.getVisibleText(); - } + public async filterOnTableCell(columnIndex: number, rowIndex: number) { + await this.retry.try(async () => { + const cell = await this.dataGrid.getCellElement(rowIndex, columnIndex); + await cell.click(); + const filterBtn = await this.testSubjects.findDescendant( + 'tbvChartCell__filterForCellValue', + cell + ); + await this.common.sleep(2000); + filterBtn.click(); + }); + } - // Table visualization + public async getMarkdownText() { + const markdownContainer = await this.testSubjects.find('markdownBody'); + return markdownContainer.getVisibleText(); + } - public async getTableVisNoResult() { - return await testSubjects.find('tbvChartContainer>visNoResult'); - } + public async getMarkdownBodyDescendentText(selector: string) { + const markdownContainer = await this.testSubjects.find('markdownBody'); + const element = await this.find.descendantDisplayedByCssSelector(selector, markdownContainer); + return element.getVisibleText(); + } - /** - * This function returns the text displayed in the Table Vis header - */ - public async getTableVisHeader() { - return await testSubjects.getVisibleText('dataGridHeader'); - } + // Table visualization - public async getFieldLinkInVisTable(fieldName: string, rowIndex: number = 1) { - const headers = await dataGrid.getHeaders(); - const fieldColumnIndex = headers.indexOf(fieldName); - const cell = await dataGrid.getCellElement(rowIndex, fieldColumnIndex + 1); - return await cell.findByTagName('a'); - } + public async getTableVisNoResult() { + return await this.testSubjects.find('tbvChartContainer>visNoResult'); + } - /** - * Function to retrieve data from within a table visualization. - */ - public async getTableVisContent({ stripEmptyRows = true } = {}) { - return await retry.try(async () => { - const container = await testSubjects.find('tbvChart'); - const allTables = await testSubjects.findAllDescendant('dataGridWrapper', container); - - if (allTables.length === 0) { - return []; - } - - const allData = await Promise.all( - allTables.map(async (t) => { - let data = await dataGrid.getDataFromElement(t, 'tbvChartCellContent'); - if (stripEmptyRows) { - data = data.filter( - (row) => row.length > 0 && row.some((cell) => cell.trim().length > 0) - ); - } - return data; - }) - ); + /** + * This function returns the text displayed in the Table Vis header + */ + public async getTableVisHeader() { + return await this.testSubjects.getVisibleText('dataGridHeader'); + } - if (allTables.length === 1) { - // If there was only one table we return only the data for that table - // This prevents an unnecessary array around that single table, which - // is the case we have in most tests. - return allData[0]; - } + public async getFieldLinkInVisTable(fieldName: string, rowIndex: number = 1) { + const headers = await this.dataGrid.getHeaders(); + const fieldColumnIndex = headers.indexOf(fieldName); + const cell = await this.dataGrid.getCellElement(rowIndex, fieldColumnIndex + 1); + return await cell.findByTagName('a'); + } - return allData; - }); - } + /** + * Function to retrieve data from within a table visualization. + */ + public async getTableVisContent({ stripEmptyRows = true } = {}) { + return await this.retry.try(async () => { + const container = await this.testSubjects.find('tbvChart'); + const allTables = await this.testSubjects.findAllDescendant('dataGridWrapper', container); - public async getMetric() { - const elements = await find.allByCssSelector( - '[data-test-subj="visualizationLoader"] .mtrVis__container' - ); - const values = await Promise.all( - elements.map(async (element) => { - const text = await element.getVisibleText(); - return text; - }) - ); - return values - .filter((item) => item.length > 0) - .reduce((arr: string[], item) => arr.concat(item.split('\n')), []); - } + if (allTables.length === 0) { + return []; + } - public async getGaugeValue() { - const elements = await find.allByCssSelector( - '[data-test-subj="visualizationLoader"] .chart svg text' - ); - const values = await Promise.all( - elements.map(async (element) => { - const text = await element.getVisibleText(); - return text; + const allData = await Promise.all( + allTables.map(async (t) => { + let data = await this.dataGrid.getDataFromElement(t, 'tbvChartCellContent'); + if (stripEmptyRows) { + data = data.filter( + (row) => row.length > 0 && row.some((cell) => cell.trim().length > 0) + ); + } + return data; }) ); - return values.filter((item) => item.length > 0); - } - public async getRightValueAxesCount() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const yAxes = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return yAxes.filter(({ position }) => position === Position.Right).length; + if (allTables.length === 1) { + // If there was only one table we return only the data for that table + // This prevents an unnecessary array around that single table, which + // is the case we have in most tests. + return allData[0]; } - const axes = await find.allByCssSelector('.visAxis__column--right g.axis'); - return axes.length; - } - public async clickOnGaugeByLabel(label: string) { - const gauge = await testSubjects.find(`visGauge__meter--${label}`); - const gaugeSize = await gauge.getSize(); - const gaugeHeight = gaugeSize.height; - // To click at Gauge arc instead of the center of SVG element - // the offset for a click is calculated as half arc height without 1 pixel - const yOffset = 1 - Math.floor(gaugeHeight / 2); + return allData; + }); + } - await gauge.clickMouseButton({ xOffset: 0, yOffset }); - } + public async getMetric() { + const elements = await this.find.allByCssSelector( + '[data-test-subj="visualizationLoader"] .mtrVis__container' + ); + const values = await Promise.all( + elements.map(async (element) => { + const text = await element.getVisibleText(); + return text; + }) + ); + return values + .filter((item) => item.length > 0) + .reduce((arr: string[], item) => arr.concat(item.split('\n')), []); + } - public async getHistogramSeriesCount() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; - return bars.filter(({ visible }) => visible).length; - } + public async getGaugeValue() { + const elements = await this.find.allByCssSelector( + '[data-test-subj="visualizationLoader"] .chart svg text' + ); + const values = await Promise.all( + elements.map(async (element) => { + const text = await element.getVisibleText(); + return text; + }) + ); + return values.filter((item) => item.length > 0); + } - const series = await find.allByCssSelector('.series.histogram'); - return series.length; + public async getRightValueAxesCount() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const yAxes = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; + return yAxes.filter(({ position }) => position === Position.Right).length; } + const axes = await this.find.allByCssSelector('.visAxis__column--right g.axis'); + return axes.length; + } - public async getGridLines(): Promise> { - if (await this.isNewLibraryChart(xyChartSelector)) { - const { x, y } = (await this.getEsChartDebugState(xyChartSelector))?.axes ?? { - x: [], - y: [], - }; - return [...x, ...y].flatMap(({ gridlines }) => gridlines); - } + public async clickOnGaugeByLabel(label: string) { + const gauge = await this.testSubjects.find(`visGauge__meter--${label}`); + const gaugeSize = await gauge.getSize(); + const gaugeHeight = gaugeSize.height; + // To click at Gauge arc instead of the center of SVG element + // the offset for a click is calculated as half arc height without 1 pixel + const yOffset = 1 - Math.floor(gaugeHeight / 2); - const grid = await find.byCssSelector('g.grid'); - const $ = await grid.parseDomContent(); - return $('path') - .toArray() - .map((line) => { - const dAttribute = $(line).attr('d'); - const firstPoint = dAttribute.split('L')[0].replace('M', '').split(','); - return { - x: parseFloat(firstPoint[0]), - y: parseFloat(firstPoint[1]), - }; - }); + await gauge.clickMouseButton({ xOffset: 0, yOffset }); + } + + public async getHistogramSeriesCount() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; + return bars.filter(({ visible }) => visible).length; } - public async getChartValues() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const barSeries = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; - return barSeries.filter(({ visible }) => visible).flatMap((bars) => bars.labels); - } + const series = await this.find.allByCssSelector('.series.histogram'); + return series.length; + } - const elements = await find.allByCssSelector('.series.histogram text'); - const values = await Promise.all( - elements.map(async (element) => { - const text = await element.getVisibleText(); - return text; - }) - ); - return values; - } + public async getGridLines(): Promise> { + if (await this.isNewLibraryChart(xyChartSelector)) { + const { x, y } = (await this.getEsChartDebugState(xyChartSelector))?.axes ?? { + x: [], + y: [], + }; + return [...x, ...y].flatMap(({ gridlines }) => gridlines); + } + + const grid = await this.find.byCssSelector('g.grid'); + const $ = await grid.parseDomContent(); + return $('path') + .toArray() + .map((line) => { + const dAttribute = $(line).attr('d'); + const firstPoint = dAttribute.split('L')[0].replace('M', '').split(','); + return { + x: parseFloat(firstPoint[0]), + y: parseFloat(firstPoint[1]), + }; + }); } - return new VisualizeChart(); + public async getChartValues() { + if (await this.isNewLibraryChart(xyChartSelector)) { + const barSeries = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; + return barSeries.filter(({ visible }) => visible).flatMap((bars) => bars.labels); + } + + const elements = await this.find.allByCssSelector('.series.histogram text'); + const values = await Promise.all( + elements.map(async (element) => { + const text = await element.getVisibleText(); + return text; + }) + ); + return values; + } } diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index d311f752fd4907..ab458c2c0fdc12 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -7,535 +7,529 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const log = getService('log'); - const retry = getService('retry'); - const browser = getService('browser'); - const testSubjects = getService('testSubjects'); - const comboBox = getService('comboBox'); - const elasticChart = getService('elasticChart'); - const { common, header, visChart } = getPageObjects(['common', 'header', 'visChart']); - - interface IntervalOptions { - type?: 'default' | 'numeric' | 'custom'; - aggNth?: number; - append?: boolean; - } - - class VisualizeEditorPage { - public async clickDataTab() { - await testSubjects.click('visEditorTab__data'); - } +import { FtrService } from '../ftr_provider_context'; - public async clickOptionsTab() { - await testSubjects.click('visEditorTab__options'); - } +interface IntervalOptions { + type?: 'default' | 'numeric' | 'custom'; + aggNth?: number; + append?: boolean; +} - public async clickMetricsAndAxes() { - await testSubjects.click('visEditorTab__advanced'); - } +export class VisualizeEditorPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly elasticChart = this.ctx.getService('elasticChart'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly visChart = this.ctx.getPageObject('visChart'); + + public async clickDataTab() { + await this.testSubjects.click('visEditorTab__data'); + } - public async clickVisEditorTab(tabName: string) { - await testSubjects.click(`visEditorTab__${tabName}`); - await header.waitUntilLoadingHasFinished(); - } + public async clickOptionsTab() { + await this.testSubjects.click('visEditorTab__options'); + } - public async addInputControl(type?: string) { - if (type) { - const selectInput = await testSubjects.find('selectControlType'); - await selectInput.type(type); - } - await testSubjects.click('inputControlEditorAddBtn'); - await header.waitUntilLoadingHasFinished(); - } + public async clickMetricsAndAxes() { + await this.testSubjects.click('visEditorTab__advanced'); + } - public async inputControlClear() { - await testSubjects.click('inputControlClearBtn'); - await header.waitUntilLoadingHasFinished(); - } + public async clickVisEditorTab(tabName: string) { + await this.testSubjects.click(`visEditorTab__${tabName}`); + await this.header.waitUntilLoadingHasFinished(); + } - public async inputControlSubmit() { - await testSubjects.clickWhenNotDisabled('inputControlSubmitBtn'); - await visChart.waitForVisualizationRenderingStabilized(); + public async addInputControl(type?: string) { + if (type) { + const selectInput = await this.testSubjects.find('selectControlType'); + await selectInput.type(type); } + await this.testSubjects.click('inputControlEditorAddBtn'); + await this.header.waitUntilLoadingHasFinished(); + } - public async clickGo() { - if (await visChart.isNewChartsLibraryEnabled()) { - await elasticChart.setNewChartUiDebugFlag(); - } + public async inputControlClear() { + await this.testSubjects.click('inputControlClearBtn'); + await this.header.waitUntilLoadingHasFinished(); + } - const prevRenderingCount = await visChart.getVisualizationRenderingCount(); - log.debug(`Before Rendering count ${prevRenderingCount}`); - await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton'); - await visChart.waitForRenderingCount(prevRenderingCount + 1); - } + public async inputControlSubmit() { + await this.testSubjects.clickWhenNotDisabled('inputControlSubmitBtn'); + await this.visChart.waitForVisualizationRenderingStabilized(); + } - public async removeDimension(aggNth: number) { - await testSubjects.click(`visEditorAggAccordion${aggNth} > removeDimensionBtn`); + public async clickGo() { + if (await this.visChart.isNewChartsLibraryEnabled()) { + await this.elasticChart.setNewChartUiDebugFlag(); } - public async setFilterParams(aggNth: number, indexPattern: string, field: string) { - await comboBox.set(`indexPatternSelect-${aggNth}`, indexPattern); - await comboBox.set(`fieldSelect-${aggNth}`, field); - } + const prevRenderingCount = await this.visChart.getVisualizationRenderingCount(); + this.log.debug(`Before Rendering count ${prevRenderingCount}`); + await this.testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton'); + await this.visChart.waitForRenderingCount(prevRenderingCount + 1); + } - public async setFilterRange(aggNth: number, min: string, max: string) { - const control = await testSubjects.find(`inputControl${aggNth}`); - const inputMin = await control.findByCssSelector('[name$="minValue"]'); - await inputMin.type(min); - const inputMax = await control.findByCssSelector('[name$="maxValue"]'); - await inputMax.type(max); - } + public async removeDimension(aggNth: number) { + await this.testSubjects.click(`visEditorAggAccordion${aggNth} > removeDimensionBtn`); + } - public async clickSplitDirection(direction: string) { - const radioBtn = await find.byCssSelector(`[data-test-subj="visEditorSplitBy-${direction}"]`); - await radioBtn.click(); - } + public async setFilterParams(aggNth: number, indexPattern: string, field: string) { + await this.comboBox.set(`indexPatternSelect-${aggNth}`, indexPattern); + await this.comboBox.set(`fieldSelect-${aggNth}`, field); + } - public async clickAddDateRange() { - await testSubjects.click(`visEditorAddDateRange`); - } + public async setFilterRange(aggNth: number, min: string, max: string) { + const control = await this.testSubjects.find(`inputControl${aggNth}`); + const inputMin = await control.findByCssSelector('[name$="minValue"]'); + await inputMin.type(min); + const inputMax = await control.findByCssSelector('[name$="maxValue"]'); + await inputMax.type(max); + } - public async setDateRangeByIndex(index: string, from: string, to: string) { - await testSubjects.setValue(`visEditorDateRange${index}__from`, from); - await testSubjects.setValue(`visEditorDateRange${index}__to`, to); - } + public async clickSplitDirection(direction: string) { + const radioBtn = await this.find.byCssSelector( + `[data-test-subj="visEditorSplitBy-${direction}"]` + ); + await radioBtn.click(); + } - /** - * Adds new bucket - * @param bucketName bucket name, like 'X-axis', 'Split rows', 'Split series' - * @param type aggregation type, like 'buckets', 'metrics' - */ - public async clickBucket(bucketName: string, type = 'buckets') { - await testSubjects.click(`visEditorAdd_${type}`); - await testSubjects.click(`visEditorAdd_${type}_${bucketName}`); - } + public async clickAddDateRange() { + await this.testSubjects.click(`visEditorAddDateRange`); + } - public async clickEnableCustomRanges() { - await testSubjects.click('heatmapUseCustomRanges'); - } + public async setDateRangeByIndex(index: string, from: string, to: string) { + await this.testSubjects.setValue(`visEditorDateRange${index}__from`, from); + await this.testSubjects.setValue(`visEditorDateRange${index}__to`, to); + } - public async clickAddRange() { - await testSubjects.click(`heatmapColorRange__addRangeButton`); - } + /** + * Adds new bucket + * @param bucketName bucket name, like 'X-axis', 'Split rows', 'Split series' + * @param type aggregation type, like 'buckets', 'metrics' + */ + public async clickBucket(bucketName: string, type = 'buckets') { + await this.testSubjects.click(`visEditorAdd_${type}`); + await this.testSubjects.click(`visEditorAdd_${type}_${bucketName}`); + } - public async setCustomRangeByIndex(index: string | number, from: string, to: string) { - await testSubjects.setValue(`heatmapColorRange${index}__from`, from); - await testSubjects.setValue(`heatmapColorRange${index}__to`, to); - } + public async clickEnableCustomRanges() { + await this.testSubjects.click('heatmapUseCustomRanges'); + } - public async changeHeatmapColorNumbers(value = 6) { - await testSubjects.setValue('heatmapColorsNumber', `${value}`); - } + public async clickAddRange() { + await this.testSubjects.click(`heatmapColorRange__addRangeButton`); + } - public async getBucketErrorMessage() { - const error = await find.byCssSelector( - '[data-test-subj="bucketsAggGroup"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' - ); - const errorMessage = await error.getAttribute('innerText'); - log.debug(errorMessage); - return errorMessage; - } + public async setCustomRangeByIndex(index: string | number, from: string, to: string) { + await this.testSubjects.setValue(`heatmapColorRange${index}__from`, from); + await this.testSubjects.setValue(`heatmapColorRange${index}__to`, to); + } - public async addNewFilterAggregation() { - await testSubjects.click('visEditorAddFilterButton'); - } + public async changeHeatmapColorNumbers(value = 6) { + await this.testSubjects.setValue('heatmapColorsNumber', `${value}`); + } - public async selectField( - fieldValue: string, - groupName = 'buckets', - isChildAggregation = false - ) { - log.debug(`selectField ${fieldValue}`); - const selector = ` - [data-test-subj="${groupName}AggGroup"] - [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen - [data-test-subj="visAggEditorParams"] - ${isChildAggregation ? '.visEditorAgg__subAgg' : ''} - [data-test-subj="visDefaultEditorField"] - `; - const fieldEl = await find.byCssSelector(selector); - await comboBox.setElement(fieldEl, fieldValue); - } + public async getBucketErrorMessage() { + const error = await this.find.byCssSelector( + '[data-test-subj="bucketsAggGroup"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' + ); + const errorMessage = await error.getAttribute('innerText'); + this.log.debug(errorMessage); + return errorMessage; + } - public async selectOrderByMetric(aggNth: number, metric: string) { - const sortSelect = await testSubjects.find(`visEditorOrderBy${aggNth}`); - const sortMetric = await sortSelect.findByCssSelector(`option[value="${metric}"]`); - await sortMetric.click(); - } + public async addNewFilterAggregation() { + await this.testSubjects.click('visEditorAddFilterButton'); + } - public async selectCustomSortMetric(aggNth: number, metric: string, field: string) { - await this.selectOrderByMetric(aggNth, 'custom'); - await this.selectAggregation(metric, 'buckets', true); - await this.selectField(field, 'buckets', true); - } + public async selectField(fieldValue: string, groupName = 'buckets', isChildAggregation = false) { + this.log.debug(`selectField ${fieldValue}`); + const selector = ` + [data-test-subj="${groupName}AggGroup"] + [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen + [data-test-subj="visAggEditorParams"] + ${isChildAggregation ? '.visEditorAgg__subAgg' : ''} + [data-test-subj="visDefaultEditorField"] + `; + const fieldEl = await this.find.byCssSelector(selector); + await this.comboBox.setElement(fieldEl, fieldValue); + } - public async selectAggregation( - aggValue: string, - groupName = 'buckets', - isChildAggregation = false - ) { - const comboBoxElement = await find.byCssSelector(` - [data-test-subj="${groupName}AggGroup"] - [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen - ${isChildAggregation ? '.visEditorAgg__subAgg' : ''} - [data-test-subj="defaultEditorAggSelect"] - `); - - await comboBox.setElement(comboBoxElement, aggValue); - await common.sleep(500); - } + public async selectOrderByMetric(aggNth: number, metric: string) { + const sortSelect = await this.testSubjects.find(`visEditorOrderBy${aggNth}`); + const sortMetric = await sortSelect.findByCssSelector(`option[value="${metric}"]`); + await sortMetric.click(); + } - /** - * Set the test for a filter aggregation. - * @param {*} filterValue the string value of the filter - * @param {*} filterIndex used when multiple filters are configured on the same aggregation - * @param {*} aggregationId the ID if the aggregation. On Tests, it start at from 2 - */ - public async setFilterAggregationValue( - filterValue: string, - filterIndex = 0, - aggregationId = 2 - ) { - await testSubjects.setValue( - `visEditorFilterInput_${aggregationId}_${filterIndex}`, - filterValue - ); - } + public async selectCustomSortMetric(aggNth: number, metric: string, field: string) { + await this.selectOrderByMetric(aggNth, 'custom'); + await this.selectAggregation(metric, 'buckets', true); + await this.selectField(field, 'buckets', true); + } - public async setValue(newValue: string) { - const input = await find.byCssSelector('[data-test-subj="visEditorPercentileRanks"] input'); - await input.clearValue(); - await input.type(newValue); - } + public async selectAggregation( + aggValue: string, + groupName = 'buckets', + isChildAggregation = false + ) { + const comboBoxElement = await this.find.byCssSelector(` + [data-test-subj="${groupName}AggGroup"] + [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen + ${isChildAggregation ? '.visEditorAgg__subAgg' : ''} + [data-test-subj="defaultEditorAggSelect"] + `); + + await this.comboBox.setElement(comboBoxElement, aggValue); + await this.common.sleep(500); + } - public async clickEditorSidebarCollapse() { - await testSubjects.click('collapseSideBarButton'); - } + /** + * Set the test for a filter aggregation. + * @param {*} filterValue the string value of the filter + * @param {*} filterIndex used when multiple filters are configured on the same aggregation + * @param {*} aggregationId the ID if the aggregation. On Tests, it start at from 2 + */ + public async setFilterAggregationValue(filterValue: string, filterIndex = 0, aggregationId = 2) { + await this.testSubjects.setValue( + `visEditorFilterInput_${aggregationId}_${filterIndex}`, + filterValue + ); + } - public async clickDropPartialBuckets() { - await testSubjects.click('dropPartialBucketsCheckbox'); - } + public async setValue(newValue: string) { + const input = await this.find.byCssSelector( + '[data-test-subj="visEditorPercentileRanks"] input' + ); + await input.clearValue(); + await input.type(newValue); + } - public async expectMarkdownTextArea() { - await testSubjects.existOrFail('markdownTextarea'); - } + public async clickEditorSidebarCollapse() { + await this.testSubjects.click('collapseSideBarButton'); + } - public async setMarkdownTxt(markdownTxt: string) { - const input = await testSubjects.find('markdownTextarea'); - await input.clearValue(); - await input.type(markdownTxt); - } + public async clickDropPartialBuckets() { + await this.testSubjects.click('dropPartialBucketsCheckbox'); + } - public async isSwitchChecked(selector: string) { - const checkbox = await testSubjects.find(selector); - const isChecked = await checkbox.getAttribute('aria-checked'); - return isChecked === 'true'; - } + public async expectMarkdownTextArea() { + await this.testSubjects.existOrFail('markdownTextarea'); + } - public async checkSwitch(selector: string) { - const isChecked = await this.isSwitchChecked(selector); - if (!isChecked) { - log.debug(`checking switch ${selector}`); - await testSubjects.click(selector); - } - } + public async setMarkdownTxt(markdownTxt: string) { + const input = await this.testSubjects.find('markdownTextarea'); + await input.clearValue(); + await input.type(markdownTxt); + } - public async uncheckSwitch(selector: string) { - const isChecked = await this.isSwitchChecked(selector); - if (isChecked) { - log.debug(`unchecking switch ${selector}`); - await testSubjects.click(selector); - } - } + public async isSwitchChecked(selector: string) { + const checkbox = await this.testSubjects.find(selector); + const isChecked = await checkbox.getAttribute('aria-checked'); + return isChecked === 'true'; + } - public async setIsFilteredByCollarCheckbox(value = true) { - await retry.try(async () => { - const isChecked = await this.isSwitchChecked('isFilteredByCollarCheckbox'); - if (isChecked !== value) { - await testSubjects.click('isFilteredByCollarCheckbox'); - throw new Error('isFilteredByCollar not set correctly'); - } - }); + public async checkSwitch(selector: string) { + const isChecked = await this.isSwitchChecked(selector); + if (!isChecked) { + this.log.debug(`checking switch ${selector}`); + await this.testSubjects.click(selector); } + } - public async setCustomLabel(label: string, index: number | string = 1) { - const customLabel = await testSubjects.find(`visEditorStringInput${index}customLabel`); - customLabel.type(label); + public async uncheckSwitch(selector: string) { + const isChecked = await this.isSwitchChecked(selector); + if (isChecked) { + this.log.debug(`unchecking switch ${selector}`); + await this.testSubjects.click(selector); } + } - public async selectYAxisAggregation(agg: string, field: string, label: string, index = 1) { - // index starts on the first "count" metric at 1 - // Each new metric or aggregation added to a visualization gets the next index. - // So to modify a metric or aggregation tests need to keep track of the - // order they are added. - await this.toggleOpenEditor(index); + public async setIsFilteredByCollarCheckbox(value = true) { + await this.retry.try(async () => { + const isChecked = await this.isSwitchChecked('isFilteredByCollarCheckbox'); + if (isChecked !== value) { + await this.testSubjects.click('isFilteredByCollarCheckbox'); + throw new Error('isFilteredByCollar not set correctly'); + } + }); + } - // select our agg - const aggSelect = await find.byCssSelector( - `#visEditorAggAccordion${index} [data-test-subj="defaultEditorAggSelect"]` - ); - await comboBox.setElement(aggSelect, agg); + public async setCustomLabel(label: string, index: number | string = 1) { + const customLabel = await this.testSubjects.find(`visEditorStringInput${index}customLabel`); + customLabel.type(label); + } - const fieldSelect = await find.byCssSelector( - `#visEditorAggAccordion${index} [data-test-subj="visDefaultEditorField"]` - ); - // select our field - await comboBox.setElement(fieldSelect, field); - // enter custom label - await this.setCustomLabel(label, index); - } + public async selectYAxisAggregation(agg: string, field: string, label: string, index = 1) { + // index starts on the first "count" metric at 1 + // Each new metric or aggregation added to a visualization gets the next index. + // So to modify a metric or aggregation tests need to keep track of the + // order they are added. + await this.toggleOpenEditor(index); + + // select our agg + const aggSelect = await this.find.byCssSelector( + `#visEditorAggAccordion${index} [data-test-subj="defaultEditorAggSelect"]` + ); + await this.comboBox.setElement(aggSelect, agg); + + const fieldSelect = await this.find.byCssSelector( + `#visEditorAggAccordion${index} [data-test-subj="visDefaultEditorField"]` + ); + // select our field + await this.comboBox.setElement(fieldSelect, field); + // enter custom label + await this.setCustomLabel(label, index); + } - public async getField() { - return await comboBox.getComboBoxSelectedOptions('visDefaultEditorField'); - } + public async getField() { + return await this.comboBox.getComboBoxSelectedOptions('visDefaultEditorField'); + } - public async sizeUpEditor() { - const resizerPanel = await testSubjects.find('euiResizableButton'); - // Drag panel 100 px left - await browser.dragAndDrop({ location: resizerPanel }, { location: { x: -100, y: 0 } }); - } + public async sizeUpEditor() { + const resizerPanel = await this.testSubjects.find('euiResizableButton'); + // Drag panel 100 px left + await this.browser.dragAndDrop({ location: resizerPanel }, { location: { x: -100, y: 0 } }); + } - public async toggleDisabledAgg(agg: string | number) { - await testSubjects.click(`visEditorAggAccordion${agg} > ~toggleDisableAggregationBtn`); - await header.waitUntilLoadingHasFinished(); - } + public async toggleDisabledAgg(agg: string | number) { + await this.testSubjects.click(`visEditorAggAccordion${agg} > ~toggleDisableAggregationBtn`); + await this.header.waitUntilLoadingHasFinished(); + } - public async toggleAggregationEditor(agg: string | number) { - await find.clickByCssSelector( - `[data-test-subj="visEditorAggAccordion${agg}"] .euiAccordion__button` - ); - await header.waitUntilLoadingHasFinished(); - } + public async toggleAggregationEditor(agg: string | number) { + await this.find.clickByCssSelector( + `[data-test-subj="visEditorAggAccordion${agg}"] .euiAccordion__button` + ); + await this.header.waitUntilLoadingHasFinished(); + } - public async toggleOtherBucket(agg: string | number = 2) { - await testSubjects.click(`visEditorAggAccordion${agg} > otherBucketSwitch`); - } + public async toggleOtherBucket(agg: string | number = 2) { + await this.testSubjects.click(`visEditorAggAccordion${agg} > otherBucketSwitch`); + } - public async toggleMissingBucket(agg: string | number = 2) { - await testSubjects.click(`visEditorAggAccordion${agg} > missingBucketSwitch`); - } + public async toggleMissingBucket(agg: string | number = 2) { + await this.testSubjects.click(`visEditorAggAccordion${agg} > missingBucketSwitch`); + } - public async toggleScaleMetrics() { - await testSubjects.click('scaleMetricsSwitch'); - } + public async toggleScaleMetrics() { + await this.testSubjects.click('scaleMetricsSwitch'); + } - public async toggleAutoMode() { - await testSubjects.click('visualizeEditorAutoButton'); - } + public async toggleAutoMode() { + await this.testSubjects.click('visualizeEditorAutoButton'); + } - public async togglePieLegend() { - await testSubjects.click('visTypePieAddLegendSwitch'); - } + public async togglePieLegend() { + await this.testSubjects.click('visTypePieAddLegendSwitch'); + } - public async togglePieNestedLegend() { - await testSubjects.click('visTypePieNestedLegendSwitch'); - } + public async togglePieNestedLegend() { + await this.testSubjects.click('visTypePieNestedLegendSwitch'); + } - public async isApplyEnabled() { - const applyButton = await testSubjects.find('visualizeEditorRenderButton'); - return await applyButton.isEnabled(); - } + public async isApplyEnabled() { + const applyButton = await this.testSubjects.find('visualizeEditorRenderButton'); + return await applyButton.isEnabled(); + } - public async toggleAccordion(id: string, toState = 'true') { - const toggle = await find.byCssSelector(`button[aria-controls="${id}"]`); - const toggleOpen = await toggle.getAttribute('aria-expanded'); - log.debug(`toggle ${id} expand = ${toggleOpen}`); - if (toggleOpen !== toState) { - log.debug(`toggle ${id} click()`); - await toggle.click(); - } + public async toggleAccordion(id: string, toState = 'true') { + const toggle = await this.find.byCssSelector(`button[aria-controls="${id}"]`); + const toggleOpen = await toggle.getAttribute('aria-expanded'); + this.log.debug(`toggle ${id} expand = ${toggleOpen}`); + if (toggleOpen !== toState) { + this.log.debug(`toggle ${id} click()`); + await toggle.click(); } + } - public async toggleOpenEditor(index: number, toState = 'true') { - // index, see selectYAxisAggregation - await this.toggleAccordion(`visEditorAggAccordion${index}`, toState); - } + public async toggleOpenEditor(index: number, toState = 'true') { + // index, see selectYAxisAggregation + await this.toggleAccordion(`visEditorAggAccordion${index}`, toState); + } - public async toggleAdvancedParams(aggId: string) { - const accordion = await testSubjects.find(`advancedParams-${aggId}`); - const accordionButton = await find.descendantDisplayedByCssSelector('button', accordion); - await accordionButton.click(); - } + public async toggleAdvancedParams(aggId: string) { + const accordion = await this.testSubjects.find(`advancedParams-${aggId}`); + const accordionButton = await this.find.descendantDisplayedByCssSelector('button', accordion); + await accordionButton.click(); + } - public async inputValueInCodeEditor(value: string) { - const codeEditor = await find.byCssSelector('.react-monaco-editor-container'); - const textarea = await codeEditor.findByClassName('monaco-mouse-cursor-text'); + public async inputValueInCodeEditor(value: string) { + const codeEditor = await this.find.byCssSelector('.react-monaco-editor-container'); + const textarea = await codeEditor.findByClassName('monaco-mouse-cursor-text'); - await textarea.click(); - await browser.pressKeys(value); - } + await textarea.click(); + await this.browser.pressKeys(value); + } - public async clickReset() { - await testSubjects.click('visualizeEditorResetButton'); - await visChart.waitForVisualization(); - } + public async clickReset() { + await this.testSubjects.click('visualizeEditorResetButton'); + await this.visChart.waitForVisualization(); + } - public async clickYAxisOptions(axisId: string) { - await testSubjects.click(`toggleYAxisOptions-${axisId}`); - } + public async clickYAxisOptions(axisId: string) { + await this.testSubjects.click(`toggleYAxisOptions-${axisId}`); + } - public async changeYAxisShowCheckbox(axisId: string, enabled: boolean) { - const selector = `valueAxisShow-${axisId}`; - const button = await testSubjects.find(selector); - const isEnabled = (await button.getAttribute('aria-checked')) === 'true'; - if (enabled !== isEnabled) { - await button.click(); - } + public async changeYAxisShowCheckbox(axisId: string, enabled: boolean) { + const selector = `valueAxisShow-${axisId}`; + const button = await this.testSubjects.find(selector); + const isEnabled = (await button.getAttribute('aria-checked')) === 'true'; + if (enabled !== isEnabled) { + await button.click(); } + } - public async changeYAxisFilterLabelsCheckbox(axisId: string, enabled: boolean) { - const selector = `yAxisFilterLabelsCheckbox-${axisId}`; - const button = await testSubjects.find(selector); - const isEnabled = (await button.getAttribute('aria-checked')) === 'true'; - if (enabled !== isEnabled) { - await button.click(); - } + public async changeYAxisFilterLabelsCheckbox(axisId: string, enabled: boolean) { + const selector = `yAxisFilterLabelsCheckbox-${axisId}`; + const button = await this.testSubjects.find(selector); + const isEnabled = (await button.getAttribute('aria-checked')) === 'true'; + if (enabled !== isEnabled) { + await button.click(); } + } - public async setSize(newValue: number, aggId?: number) { - const dataTestSubj = aggId - ? `visEditorAggAccordion${aggId} > sizeParamEditor` - : 'sizeParamEditor'; - await testSubjects.setValue(dataTestSubj, String(newValue)); - } + public async setSize(newValue: number, aggId?: number) { + const dataTestSubj = aggId + ? `visEditorAggAccordion${aggId} > sizeParamEditor` + : 'sizeParamEditor'; + await this.testSubjects.setValue(dataTestSubj, String(newValue)); + } - public async selectChartMode(mode: string) { - const selector = await find.byCssSelector(`#seriesMode0 > option[value="${mode}"]`); - await selector.click(); - } + public async selectChartMode(mode: string) { + const selector = await this.find.byCssSelector(`#seriesMode0 > option[value="${mode}"]`); + await selector.click(); + } - public async selectYAxisScaleType(axisId: string, scaleType: string) { - const selector = await find.byCssSelector( - `#scaleSelectYAxis-${axisId} > option[value="${scaleType}"]` - ); - await selector.click(); - } + public async selectYAxisScaleType(axisId: string, scaleType: string) { + const selector = await this.find.byCssSelector( + `#scaleSelectYAxis-${axisId} > option[value="${scaleType}"]` + ); + await selector.click(); + } - public async selectXAxisPosition(position: string) { - const option = await (await testSubjects.find('categoryAxisPosition')).findByCssSelector( - `option[value="${position}"]` - ); - await option.click(); - } + public async selectXAxisPosition(position: string) { + const option = await (await this.testSubjects.find('categoryAxisPosition')).findByCssSelector( + `option[value="${position}"]` + ); + await option.click(); + } - public async selectYAxisMode(mode: string) { - const selector = await find.byCssSelector(`#valueAxisMode0 > option[value="${mode}"]`); - await selector.click(); - } + public async selectYAxisMode(mode: string) { + const selector = await this.find.byCssSelector(`#valueAxisMode0 > option[value="${mode}"]`); + await selector.click(); + } - public async setAxisExtents(min: string, max: string, axisId = 'ValueAxis-1') { - await this.toggleAccordion(`yAxisAccordion${axisId}`); - await this.toggleAccordion(`yAxisOptionsAccordion${axisId}`); + public async setAxisExtents(min: string, max: string, axisId = 'ValueAxis-1') { + await this.toggleAccordion(`yAxisAccordion${axisId}`); + await this.toggleAccordion(`yAxisOptionsAccordion${axisId}`); - await testSubjects.click('yAxisSetYExtents'); - await testSubjects.setValue('yAxisYExtentsMax', max); - await testSubjects.setValue('yAxisYExtentsMin', min); - } + await this.testSubjects.click('yAxisSetYExtents'); + await this.testSubjects.setValue('yAxisYExtentsMax', max); + await this.testSubjects.setValue('yAxisYExtentsMin', min); + } - public async selectAggregateWith(fieldValue: string) { - await testSubjects.selectValue('visDefaultEditorAggregateWith', fieldValue); - } + public async selectAggregateWith(fieldValue: string) { + await this.testSubjects.selectValue('visDefaultEditorAggregateWith', fieldValue); + } - public async setInterval(newValue: string | number, options: IntervalOptions = {}) { - const newValueString = `${newValue}`; - const { type = 'default', aggNth = 2, append = false } = options; - log.debug(`visEditor.setInterval(${newValueString}, {${type}, ${aggNth}, ${append}})`); - if (type === 'default') { - await comboBox.set('visEditorInterval', newValueString); - } else if (type === 'custom') { - await comboBox.setCustom('visEditorInterval', newValueString); - } else { - if (type === 'numeric') { - const autoMode = await testSubjects.getAttribute( - `visEditorIntervalSwitch${aggNth}`, - 'aria-checked' - ); - if (autoMode === 'true') { - await testSubjects.click(`visEditorIntervalSwitch${aggNth}`); - } - } - if (append) { - await testSubjects.append(`visEditorInterval${aggNth}`, String(newValueString)); - } else { - await testSubjects.setValue(`visEditorInterval${aggNth}`, String(newValueString)); + public async setInterval(newValue: string | number, options: IntervalOptions = {}) { + const newValueString = `${newValue}`; + const { type = 'default', aggNth = 2, append = false } = options; + this.log.debug(`visEditor.setInterval(${newValueString}, {${type}, ${aggNth}, ${append}})`); + if (type === 'default') { + await this.comboBox.set('visEditorInterval', newValueString); + } else if (type === 'custom') { + await this.comboBox.setCustom('visEditorInterval', newValueString); + } else { + if (type === 'numeric') { + const autoMode = await this.testSubjects.getAttribute( + `visEditorIntervalSwitch${aggNth}`, + 'aria-checked' + ); + if (autoMode === 'true') { + await this.testSubjects.click(`visEditorIntervalSwitch${aggNth}`); } } + if (append) { + await this.testSubjects.append(`visEditorInterval${aggNth}`, String(newValueString)); + } else { + await this.testSubjects.setValue(`visEditorInterval${aggNth}`, String(newValueString)); + } } + } - public async getInterval() { - return await comboBox.getComboBoxSelectedOptions('visEditorInterval'); - } + public async getInterval() { + return await this.comboBox.getComboBoxSelectedOptions('visEditorInterval'); + } - public async getNumericInterval(aggNth = 2) { - return await testSubjects.getAttribute(`visEditorInterval${aggNth}`, 'value'); - } + public async getNumericInterval(aggNth = 2) { + return await this.testSubjects.getAttribute(`visEditorInterval${aggNth}`, 'value'); + } - public async clickMetricEditor() { - await find.clickByCssSelector('[data-test-subj="metricsAggGroup"] .euiAccordion__button'); - } + public async clickMetricEditor() { + await this.find.clickByCssSelector('[data-test-subj="metricsAggGroup"] .euiAccordion__button'); + } - public async clickMetricByIndex(index: number) { - const metrics = await find.allByCssSelector( - '[data-test-subj="visualizationLoader"] .mtrVis .mtrVis__container' - ); - expect(metrics.length).greaterThan(index); - await metrics[index].click(); - } + public async clickMetricByIndex(index: number) { + const metrics = await this.find.allByCssSelector( + '[data-test-subj="visualizationLoader"] .mtrVis .mtrVis__container' + ); + expect(metrics.length).greaterThan(index); + await metrics[index].click(); + } - public async setSelectByOptionText(selectId: string, optionText: string) { - const selectField = await find.byCssSelector(`#${selectId}`); - const options = await find.allByCssSelector(`#${selectId} > option`); - const $ = await selectField.parseDomContent(); - const optionsText = $('option') - .toArray() - .map((option) => $(option).text()); - const optionIndex = optionsText.indexOf(optionText); - - if (optionIndex === -1) { - throw new Error( - `Unable to find option '${optionText}' in select ${selectId}. Available options: ${optionsText.join( - ',' - )}` - ); - } - await options[optionIndex].click(); + public async setSelectByOptionText(selectId: string, optionText: string) { + const selectField = await this.find.byCssSelector(`#${selectId}`); + const options = await this.find.allByCssSelector(`#${selectId} > option`); + const $ = await selectField.parseDomContent(); + const optionsText = $('option') + .toArray() + .map((option) => $(option).text()); + const optionIndex = optionsText.indexOf(optionText); + + if (optionIndex === -1) { + throw new Error( + `Unable to find option '${optionText}' in select ${selectId}. Available options: ${optionsText.join( + ',' + )}` + ); } + await options[optionIndex].click(); + } - // point series - - async clickAddAxis() { - return await testSubjects.click('visualizeAddYAxisButton'); - } + // point series - async setAxisTitle(title: string, aggNth = 0) { - return await testSubjects.setValue(`valueAxisTitle${aggNth}`, title); - } + async clickAddAxis() { + return await this.testSubjects.click('visualizeAddYAxisButton'); + } - public async toggleGridCategoryLines() { - return await testSubjects.click('showCategoryLines'); - } + async setAxisTitle(title: string, aggNth = 0) { + return await this.testSubjects.setValue(`valueAxisTitle${aggNth}`, title); + } - public async toggleValuesOnChart() { - return await testSubjects.click('showValuesOnChart'); - } + public async toggleGridCategoryLines() { + return await this.testSubjects.click('showCategoryLines'); + } - public async setGridValueAxis(axis: string) { - log.debug(`setGridValueAxis(${axis})`); - await find.selectValue('select#gridAxis', axis); - } + public async toggleValuesOnChart() { + return await this.testSubjects.click('showValuesOnChart'); + } - public async setSeriesAxis(seriesNth: number, axis: string) { - await find.selectValue(`select#seriesValueAxis${seriesNth}`, axis); - } + public async setGridValueAxis(axis: string) { + this.log.debug(`setGridValueAxis(${axis})`); + await this.find.selectValue('select#gridAxis', axis); + } - public async setSeriesType(seriesNth: number, type: string) { - await find.selectValue(`select#seriesType${seriesNth}`, type); - } + public async setSeriesAxis(seriesNth: number, axis: string) { + await this.find.selectValue(`select#seriesValueAxis${seriesNth}`, axis); } - return new VisualizeEditorPage(); + public async setSeriesType(seriesNth: number, type: string) { + await this.find.selectValue(`select#seriesType${seriesNth}`, type); + } } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 78a963867b8c23..efd48346524299 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { VisualizeConstants } from '../../../src/plugins/visualize/public/application/visualize_constants'; import { UI_SETTINGS } from '../../../src/plugins/data/common'; @@ -23,455 +23,451 @@ type DashboardPickerOption = | 'existing-dashboard-option' | 'new-dashboard-option'; -export function VisualizePageProvider({ getService, getPageObjects }: FtrProviderContext) { - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const find = getService('find'); - const log = getService('log'); - const globalNav = getService('globalNav'); - const listingTable = getService('listingTable'); - const queryBar = getService('queryBar'); - const elasticChart = getService('elasticChart'); - const { common, header, visEditor, visChart } = getPageObjects([ - 'common', - 'header', - 'visEditor', - 'visChart', - ]); - - /** - * This page object contains the visualization type selection, the landing page, - * and the open/save dialog functions - */ - class VisualizePage { - index = { - LOGSTASH_TIME_BASED: 'logstash-*', - LOGSTASH_NON_TIME_BASED: 'logstash*', - }; - - public async initTests() { - await kibanaServer.savedObjects.clean({ types: ['visualization'] }); - await kibanaServer.importExport.load('visualize'); - - await kibanaServer.uiSettings.replace({ - defaultIndex: 'logstash-*', - [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b', - }); - } - - public async gotoVisualizationLandingPage() { - await common.navigateToApp('visualize'); - } +/** + * This page object contains the visualization type selection, the landing page, + * and the open/save dialog functions + */ +export class VisualizePageObject extends FtrService { + private readonly kibanaServer = this.ctx.getService('kibanaServer'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly globalNav = this.ctx.getService('globalNav'); + private readonly listingTable = this.ctx.getService('listingTable'); + private readonly queryBar = this.ctx.getService('queryBar'); + private readonly elasticChart = this.ctx.getService('elasticChart'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly visEditor = this.ctx.getPageObject('visEditor'); + private readonly visChart = this.ctx.getPageObject('visChart'); + + index = { + LOGSTASH_TIME_BASED: 'logstash-*', + LOGSTASH_NON_TIME_BASED: 'logstash*', + }; + + public async initTests() { + await this.kibanaServer.savedObjects.clean({ types: ['visualization'] }); + await this.kibanaServer.importExport.load('visualize'); + + await this.kibanaServer.uiSettings.replace({ + defaultIndex: 'logstash-*', + [UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b', + }); + } - public async clickNewVisualization() { - await listingTable.clickNewButton('createVisualizationPromptButton'); - } + public async gotoVisualizationLandingPage() { + await this.common.navigateToApp('visualize'); + } - public async clickAggBasedVisualizations() { - await testSubjects.click('visGroupAggBasedExploreLink'); - } + public async clickNewVisualization() { + await this.listingTable.clickNewButton('createVisualizationPromptButton'); + } - public async goBackToGroups() { - await testSubjects.click('goBackLink'); - } + public async clickAggBasedVisualizations() { + await this.testSubjects.click('visGroupAggBasedExploreLink'); + } - public async createVisualizationPromptButton() { - await testSubjects.click('createVisualizationPromptButton'); - } + public async goBackToGroups() { + await this.testSubjects.click('goBackLink'); + } - public async getChartTypes() { - const chartTypeField = await testSubjects.find('visNewDialogTypes'); - const $ = await chartTypeField.parseDomContent(); - return $('button') - .toArray() - .map((chart) => $(chart).findTestSubject('visTypeTitle').text().trim()); - } + public async createVisualizationPromptButton() { + await this.testSubjects.click('createVisualizationPromptButton'); + } - public async getPromotedVisTypes() { - const chartTypeField = await testSubjects.find('visNewDialogGroups'); - const $ = await chartTypeField.parseDomContent(); - const promotedVisTypes: string[] = []; - $('button') - .toArray() - .forEach((chart) => { - const title = $(chart).findTestSubject('visTypeTitle').text().trim(); - if (title) { - promotedVisTypes.push(title); - } - }); - return promotedVisTypes; - } + public async getChartTypes() { + const chartTypeField = await this.testSubjects.find('visNewDialogTypes'); + const $ = await chartTypeField.parseDomContent(); + return $('button') + .toArray() + .map((chart) => $(chart).findTestSubject('visTypeTitle').text().trim()); + } - public async waitForVisualizationSelectPage() { - await retry.try(async () => { - const visualizeSelectTypePage = await testSubjects.find('visNewDialogTypes'); - if (!(await visualizeSelectTypePage.isDisplayed())) { - throw new Error('wait for visualization select page'); + public async getPromotedVisTypes() { + const chartTypeField = await this.testSubjects.find('visNewDialogGroups'); + const $ = await chartTypeField.parseDomContent(); + const promotedVisTypes: string[] = []; + $('button') + .toArray() + .forEach((chart) => { + const title = $(chart).findTestSubject('visTypeTitle').text().trim(); + if (title) { + promotedVisTypes.push(title); } }); - } + return promotedVisTypes; + } - public async clickRefresh() { - if (await visChart.isNewChartsLibraryEnabled()) { - await elasticChart.setNewChartUiDebugFlag(); + public async waitForVisualizationSelectPage() { + await this.retry.try(async () => { + const visualizeSelectTypePage = await this.testSubjects.find('visNewDialogTypes'); + if (!(await visualizeSelectTypePage.isDisplayed())) { + throw new Error('wait for visualization select page'); } - await queryBar.clickQuerySubmitButton(); - } - - public async waitForGroupsSelectPage() { - await retry.try(async () => { - const visualizeSelectGroupStep = await testSubjects.find('visNewDialogGroups'); - if (!(await visualizeSelectGroupStep.isDisplayed())) { - throw new Error('wait for vis groups select step'); - } - }); - } - - public async navigateToNewVisualization() { - await this.gotoVisualizationLandingPage(); - await header.waitUntilLoadingHasFinished(); - await this.clickNewVisualization(); - await this.waitForGroupsSelectPage(); - } + }); + } - public async navigateToNewAggBasedVisualization() { - await this.gotoVisualizationLandingPage(); - await header.waitUntilLoadingHasFinished(); - await this.clickNewVisualization(); - await this.clickAggBasedVisualizations(); - await this.waitForVisualizationSelectPage(); + public async clickRefresh() { + if (await this.visChart.isNewChartsLibraryEnabled()) { + await this.elasticChart.setNewChartUiDebugFlag(); } + await this.queryBar.clickQuerySubmitButton(); + } - public async hasVisType(type: string) { - return await testSubjects.exists(`visType-${type}`); - } + public async waitForGroupsSelectPage() { + await this.retry.try(async () => { + const visualizeSelectGroupStep = await this.testSubjects.find('visNewDialogGroups'); + if (!(await visualizeSelectGroupStep.isDisplayed())) { + throw new Error('wait for vis groups select step'); + } + }); + } - public async clickVisType(type: string) { - await testSubjects.click(`visType-${type}`); - await header.waitUntilLoadingHasFinished(); - } + public async navigateToNewVisualization() { + await this.gotoVisualizationLandingPage(); + await this.header.waitUntilLoadingHasFinished(); + await this.clickNewVisualization(); + await this.waitForGroupsSelectPage(); + } - public async clickAreaChart() { - await this.clickVisType('area'); - } + public async navigateToNewAggBasedVisualization() { + await this.gotoVisualizationLandingPage(); + await this.header.waitUntilLoadingHasFinished(); + await this.clickNewVisualization(); + await this.clickAggBasedVisualizations(); + await this.waitForVisualizationSelectPage(); + } - public async clickDataTable() { - await this.clickVisType('table'); - } + public async hasVisType(type: string) { + return await this.testSubjects.exists(`visType-${type}`); + } - public async clickLineChart() { - await this.clickVisType('line'); - } + public async clickVisType(type: string) { + await this.testSubjects.click(`visType-${type}`); + await this.header.waitUntilLoadingHasFinished(); + } - public async clickRegionMap() { - await this.clickVisType('region_map'); - } + public async clickAreaChart() { + await this.clickVisType('area'); + } - public async hasRegionMap() { - return await this.hasVisType('region_map'); - } + public async clickDataTable() { + await this.clickVisType('table'); + } - public async clickMarkdownWidget() { - await this.clickVisType('markdown'); - } + public async clickLineChart() { + await this.clickVisType('line'); + } - public async clickMetric() { - await this.clickVisType('metric'); - } + public async clickRegionMap() { + await this.clickVisType('region_map'); + } - public async clickGauge() { - await this.clickVisType('gauge'); - } + public async hasRegionMap() { + return await this.hasVisType('region_map'); + } - public async clickPieChart() { - await this.clickVisType('pie'); - } + public async clickMarkdownWidget() { + await this.clickVisType('markdown'); + } - public async clickTileMap() { - await this.clickVisType('tile_map'); - } + public async clickMetric() { + await this.clickVisType('metric'); + } - public async hasTileMap() { - return await this.hasVisType('tile_map'); - } + public async clickGauge() { + await this.clickVisType('gauge'); + } - public async clickTagCloud() { - await this.clickVisType('tagcloud'); - } + public async clickPieChart() { + await this.clickVisType('pie'); + } - public async clickVega() { - await this.clickVisType('vega'); - } + public async clickTileMap() { + await this.clickVisType('tile_map'); + } - public async clickVisualBuilder() { - await this.clickVisType('metrics'); - } + public async hasTileMap() { + return await this.hasVisType('tile_map'); + } - public async clickVerticalBarChart() { - await this.clickVisType('histogram'); - } + public async clickTagCloud() { + await this.clickVisType('tagcloud'); + } - public async clickHeatmapChart() { - await this.clickVisType('heatmap'); - } + public async clickVega() { + await this.clickVisType('vega'); + } - public async clickInputControlVis() { - await this.clickVisType('input_control_vis'); - } + public async clickVisualBuilder() { + await this.clickVisType('metrics'); + } - public async clickLensWidget() { - await this.clickVisType('lens'); - } + public async clickVerticalBarChart() { + await this.clickVisType('histogram'); + } - public async clickMapsApp() { - await this.clickVisType('maps'); - } + public async clickHeatmapChart() { + await this.clickVisType('heatmap'); + } - public async hasMapsApp() { - return await this.hasVisType('maps'); - } + public async clickInputControlVis() { + await this.clickVisType('input_control_vis'); + } - public async createSimpleMarkdownViz(vizName: string) { - await this.gotoVisualizationLandingPage(); - await this.navigateToNewVisualization(); - await this.clickMarkdownWidget(); - await visEditor.setMarkdownTxt(vizName); - await visEditor.clickGo(); - await this.saveVisualization(vizName); - } + public async clickLensWidget() { + await this.clickVisType('lens'); + } - public async clickNewSearch(indexPattern = this.index.LOGSTASH_TIME_BASED) { - await testSubjects.click(`savedObjectTitle${indexPattern.split(' ').join('-')}`); - await header.waitUntilLoadingHasFinished(); - } + public async clickMapsApp() { + await this.clickVisType('maps'); + } - public async selectVisSourceIfRequired() { - log.debug('selectVisSourceIfRequired'); - const selectPage = await testSubjects.findAll('visualizeSelectSearch'); - if (selectPage.length) { - log.debug('a search is required for this visualization'); - await this.clickNewSearch(); - } - } + public async hasMapsApp() { + return await this.hasVisType('maps'); + } - /** - * Deletes all existing visualizations - */ - public async deleteAllVisualizations() { - await retry.try(async () => { - await listingTable.checkListingSelectAllCheckbox(); - await listingTable.clickDeleteSelected(); - await common.clickConfirmOnModal(); - await testSubjects.find('createVisualizationPromptButton'); - }); - } + public async createSimpleMarkdownViz(vizName: string) { + await this.gotoVisualizationLandingPage(); + await this.navigateToNewVisualization(); + await this.clickMarkdownWidget(); + await this.visEditor.setMarkdownTxt(vizName); + await this.visEditor.clickGo(); + await this.saveVisualization(vizName); + } - public async isBetaInfoShown() { - return await testSubjects.exists('betaVisInfo'); - } + public async clickNewSearch(indexPattern = this.index.LOGSTASH_TIME_BASED) { + await this.testSubjects.click(`savedObjectTitle${indexPattern.split(' ').join('-')}`); + await this.header.waitUntilLoadingHasFinished(); + } - public async getBetaTypeLinks() { - return await find.allByCssSelector('[data-vis-stage="beta"]'); + public async selectVisSourceIfRequired() { + this.log.debug('selectVisSourceIfRequired'); + const selectPage = await this.testSubjects.findAll('visualizeSelectSearch'); + if (selectPage.length) { + this.log.debug('a search is required for this visualization'); + await this.clickNewSearch(); } + } - public async getExperimentalTypeLinks() { - return await find.allByCssSelector('[data-vis-stage="experimental"]'); - } + /** + * Deletes all existing visualizations + */ + public async deleteAllVisualizations() { + await this.retry.try(async () => { + await this.listingTable.checkListingSelectAllCheckbox(); + await this.listingTable.clickDeleteSelected(); + await this.common.clickConfirmOnModal(); + await this.testSubjects.find('createVisualizationPromptButton'); + }); + } - public async isExperimentalInfoShown() { - return await testSubjects.exists('experimentalVisInfo'); - } + public async isBetaInfoShown() { + return await this.testSubjects.exists('betaVisInfo'); + } - public async getExperimentalInfo() { - return await testSubjects.find('experimentalVisInfo'); - } + public async getBetaTypeLinks() { + return await this.find.allByCssSelector('[data-vis-stage="beta"]'); + } - public async getSideEditorExists() { - return await find.existsByCssSelector('.visEditor__collapsibleSidebar'); - } + public async getExperimentalTypeLinks() { + return await this.find.allByCssSelector('[data-vis-stage="experimental"]'); + } - public async clickSavedSearch(savedSearchName: string) { - await testSubjects.click(`savedObjectTitle${savedSearchName.split(' ').join('-')}`); - await header.waitUntilLoadingHasFinished(); - } + public async isExperimentalInfoShown() { + return await this.testSubjects.exists('experimentalVisInfo'); + } - public async clickUnlinkSavedSearch() { - await testSubjects.click('showUnlinkSavedSearchPopover'); - await testSubjects.click('unlinkSavedSearch'); - await header.waitUntilLoadingHasFinished(); - } + public async getExperimentalInfo() { + return await this.testSubjects.find('experimentalVisInfo'); + } - public async ensureSavePanelOpen() { - log.debug('ensureSavePanelOpen'); - await header.waitUntilLoadingHasFinished(); - const isOpen = await testSubjects.exists('savedObjectSaveModal', { timeout: 5000 }); - if (!isOpen) { - await testSubjects.click('visualizeSaveButton'); - } - } + public async getSideEditorExists() { + return await this.find.existsByCssSelector('.visEditor__collapsibleSidebar'); + } - public async clickLoadSavedVisButton() { - // TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb - // element as a child instead of building the breadcrumbs dynamically. - await find.clickByCssSelector('[href="#/"]'); - } + public async clickSavedSearch(savedSearchName: string) { + await this.testSubjects.click(`savedObjectTitle${savedSearchName.split(' ').join('-')}`); + await this.header.waitUntilLoadingHasFinished(); + } - public async loadSavedVisualization(vizName: string, { navigateToVisualize = true } = {}) { - if (navigateToVisualize) { - await this.clickLoadSavedVisButton(); - } - await this.openSavedVisualization(vizName); - } + public async clickUnlinkSavedSearch() { + await this.testSubjects.click('showUnlinkSavedSearchPopover'); + await this.testSubjects.click('unlinkSavedSearch'); + await this.header.waitUntilLoadingHasFinished(); + } - public async openSavedVisualization(vizName: string) { - const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`; - await testSubjects.click(dataTestSubj, 20000); - await header.waitUntilLoadingHasFinished(); + public async ensureSavePanelOpen() { + this.log.debug('ensureSavePanelOpen'); + await this.header.waitUntilLoadingHasFinished(); + const isOpen = await this.testSubjects.exists('savedObjectSaveModal', { timeout: 5000 }); + if (!isOpen) { + await this.testSubjects.click('visualizeSaveButton'); } + } - public async waitForVisualizationSavedToastGone() { - await testSubjects.waitForDeleted('saveVisualizationSuccess'); - } + public async clickLoadSavedVisButton() { + // TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb + // element as a child instead of building the breadcrumbs dynamically. + await this.find.clickByCssSelector('[href="#/"]'); + } - public async clickLandingPageBreadcrumbLink() { - log.debug('clickLandingPageBreadcrumbLink'); - await find.clickByCssSelector(`a[href="#${VisualizeConstants.LANDING_PAGE_PATH}"]`); + public async loadSavedVisualization(vizName: string, { navigateToVisualize = true } = {}) { + if (navigateToVisualize) { + await this.clickLoadSavedVisButton(); } + await this.openSavedVisualization(vizName); + } - /** - * Returns true if already on the landing page (that page doesn't have a link to itself). - * @returns {Promise} - */ - public async onLandingPage() { - log.debug(`VisualizePage.onLandingPage`); - return await testSubjects.exists('visualizationLandingPage'); - } + public async openSavedVisualization(vizName: string) { + const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`; + await this.testSubjects.click(dataTestSubj, 20000); + await this.header.waitUntilLoadingHasFinished(); + } - public async gotoLandingPage() { - log.debug('VisualizePage.gotoLandingPage'); - const onPage = await this.onLandingPage(); - if (!onPage) { - await retry.try(async () => { - await this.clickLandingPageBreadcrumbLink(); - const onLandingPage = await this.onLandingPage(); - if (!onLandingPage) throw new Error('Not on the landing page.'); - }); - } - } + public async waitForVisualizationSavedToastGone() { + await this.testSubjects.waitForDeleted('saveVisualizationSuccess'); + } - public async saveVisualization(vizName: string, saveModalArgs: VisualizeSaveModalArgs = {}) { - await this.ensureSavePanelOpen(); + public async clickLandingPageBreadcrumbLink() { + this.log.debug('clickLandingPageBreadcrumbLink'); + await this.find.clickByCssSelector(`a[href="#${VisualizeConstants.LANDING_PAGE_PATH}"]`); + } - await this.setSaveModalValues(vizName, saveModalArgs); - log.debug('Click Save Visualization button'); + /** + * Returns true if already on the landing page (that page doesn't have a link to itself). + * @returns {Promise} + */ + public async onLandingPage() { + this.log.debug(`VisualizePage.onLandingPage`); + return await this.testSubjects.exists('visualizationLandingPage'); + } - await testSubjects.click('confirmSaveSavedObjectButton'); + public async gotoLandingPage() { + this.log.debug('VisualizePage.gotoLandingPage'); + const onPage = await this.onLandingPage(); + if (!onPage) { + await this.retry.try(async () => { + await this.clickLandingPageBreadcrumbLink(); + const onLandingPage = await this.onLandingPage(); + if (!onLandingPage) throw new Error('Not on the landing page.'); + }); + } + } - // Confirm that the Visualization has actually been saved - await testSubjects.existOrFail('saveVisualizationSuccess'); - const message = await common.closeToast(); - await header.waitUntilLoadingHasFinished(); - await common.waitForSaveModalToClose(); + public async saveVisualization(vizName: string, saveModalArgs: VisualizeSaveModalArgs = {}) { + await this.ensureSavePanelOpen(); - return message; - } + await this.setSaveModalValues(vizName, saveModalArgs); + this.log.debug('Click Save Visualization button'); - public async setSaveModalValues( - vizName: string, - { saveAsNew, redirectToOrigin, addToDashboard, dashboardId }: VisualizeSaveModalArgs = {} - ) { - await testSubjects.setValue('savedObjectTitle', vizName); - - const saveAsNewCheckboxExists = await testSubjects.exists('saveAsNewCheckbox'); - if (saveAsNewCheckboxExists) { - const state = saveAsNew ? 'check' : 'uncheck'; - log.debug('save as new checkbox exists. Setting its state to', state); - await testSubjects.setEuiSwitch('saveAsNewCheckbox', state); - } + await this.testSubjects.click('confirmSaveSavedObjectButton'); - const redirectToOriginCheckboxExists = await testSubjects.exists('returnToOriginModeSwitch'); - if (redirectToOriginCheckboxExists) { - const state = redirectToOrigin ? 'check' : 'uncheck'; - log.debug('redirect to origin checkbox exists. Setting its state to', state); - await testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); - } + // Confirm that the Visualization has actually been saved + await this.testSubjects.existOrFail('saveVisualizationSuccess'); + const message = await this.common.closeToast(); + await this.header.waitUntilLoadingHasFinished(); + await this.common.waitForSaveModalToClose(); - const dashboardSelectorExists = await testSubjects.exists('add-to-dashboard-options'); - if (dashboardSelectorExists) { - let option: DashboardPickerOption = 'add-to-library-option'; - if (addToDashboard) { - option = dashboardId ? 'existing-dashboard-option' : 'new-dashboard-option'; - } - log.debug('save modal dashboard selector, choosing option:', option); - const dashboardSelector = await testSubjects.find('add-to-dashboard-options'); - const label = await dashboardSelector.findByCssSelector(`label[for="${option}"]`); - await label.click(); + return message; + } - if (dashboardId) { - // TODO - selecting an existing dashboard - } + public async setSaveModalValues( + vizName: string, + { saveAsNew, redirectToOrigin, addToDashboard, dashboardId }: VisualizeSaveModalArgs = {} + ) { + await this.testSubjects.setValue('savedObjectTitle', vizName); + + const saveAsNewCheckboxExists = await this.testSubjects.exists('saveAsNewCheckbox'); + if (saveAsNewCheckboxExists) { + const state = saveAsNew ? 'check' : 'uncheck'; + this.log.debug('save as new checkbox exists. Setting its state to', state); + await this.testSubjects.setEuiSwitch('saveAsNewCheckbox', state); + } + + const redirectToOriginCheckboxExists = await this.testSubjects.exists( + 'returnToOriginModeSwitch' + ); + if (redirectToOriginCheckboxExists) { + const state = redirectToOrigin ? 'check' : 'uncheck'; + this.log.debug('redirect to origin checkbox exists. Setting its state to', state); + await this.testSubjects.setEuiSwitch('returnToOriginModeSwitch', state); + } + + const dashboardSelectorExists = await this.testSubjects.exists('add-to-dashboard-options'); + if (dashboardSelectorExists) { + let option: DashboardPickerOption = 'add-to-library-option'; + if (addToDashboard) { + option = dashboardId ? 'existing-dashboard-option' : 'new-dashboard-option'; } - } + this.log.debug('save modal dashboard selector, choosing option:', option); + const dashboardSelector = await this.testSubjects.find('add-to-dashboard-options'); + const label = await dashboardSelector.findByCssSelector(`label[for="${option}"]`); + await label.click(); - public async saveVisualizationExpectSuccess( - vizName: string, - { saveAsNew, redirectToOrigin, addToDashboard, dashboardId }: VisualizeSaveModalArgs = {} - ) { - const saveMessage = await this.saveVisualization(vizName, { - saveAsNew, - redirectToOrigin, - addToDashboard, - dashboardId, - }); - if (!saveMessage) { - throw new Error( - `Expected saveVisualization to respond with the saveMessage from the toast, got ${saveMessage}` - ); + if (dashboardId) { + // TODO - selecting an existing dashboard } } + } - public async saveVisualizationExpectSuccessAndBreadcrumb( - vizName: string, - { saveAsNew = false, redirectToOrigin = false } = {} - ) { - await this.saveVisualizationExpectSuccess(vizName, { saveAsNew, redirectToOrigin }); - await retry.waitFor( - 'last breadcrumb to have new vis name', - async () => (await globalNav.getLastBreadcrumb()) === vizName + public async saveVisualizationExpectSuccess( + vizName: string, + { saveAsNew, redirectToOrigin, addToDashboard, dashboardId }: VisualizeSaveModalArgs = {} + ) { + const saveMessage = await this.saveVisualization(vizName, { + saveAsNew, + redirectToOrigin, + addToDashboard, + dashboardId, + }); + if (!saveMessage) { + throw new Error( + `Expected saveVisualization to respond with the saveMessage from the toast, got ${saveMessage}` ); } + } - public async saveVisualizationAndReturn() { - await header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('visualizesaveAndReturnButton'); - await testSubjects.click('visualizesaveAndReturnButton'); - } + public async saveVisualizationExpectSuccessAndBreadcrumb( + vizName: string, + { saveAsNew = false, redirectToOrigin = false } = {} + ) { + await this.saveVisualizationExpectSuccess(vizName, { saveAsNew, redirectToOrigin }); + await this.retry.waitFor( + 'last breadcrumb to have new vis name', + async () => (await this.globalNav.getLastBreadcrumb()) === vizName + ); + } - public async linkedToOriginatingApp() { - await header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('visualizesaveAndReturnButton'); - } + public async saveVisualizationAndReturn() { + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.existOrFail('visualizesaveAndReturnButton'); + await this.testSubjects.click('visualizesaveAndReturnButton'); + } - public async notLinkedToOriginatingApp() { - await header.waitUntilLoadingHasFinished(); - await testSubjects.missingOrFail('visualizesaveAndReturnButton'); - } + public async linkedToOriginatingApp() { + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.existOrFail('visualizesaveAndReturnButton'); + } - public async cancelAndReturn(showConfirmModal: boolean) { - await header.waitUntilLoadingHasFinished(); - await testSubjects.existOrFail('visualizeCancelAndReturnButton'); - await testSubjects.click('visualizeCancelAndReturnButton'); - if (showConfirmModal) { - await retry.waitFor( - 'confirm modal to show', - async () => await testSubjects.exists('appLeaveConfirmModal') - ); - await testSubjects.exists('confirmModalConfirmButton'); - await testSubjects.click('confirmModalConfirmButton'); - } - } + public async notLinkedToOriginatingApp() { + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.missingOrFail('visualizesaveAndReturnButton'); } - return new VisualizePage(); + public async cancelAndReturn(showConfirmModal: boolean) { + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.existOrFail('visualizeCancelAndReturnButton'); + await this.testSubjects.click('visualizeCancelAndReturnButton'); + if (showConfirmModal) { + await this.retry.waitFor( + 'confirm modal to show', + async () => await this.testSubjects.exists('appLeaveConfirmModal') + ); + await this.testSubjects.exists('confirmModalConfirmButton'); + await this.testSubjects.click('confirmModalConfirmButton'); + } + } } diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts index a198aec1d16960..6706db82ce7086 100644 --- a/test/functional/services/combo_box.ts +++ b/test/functional/services/combo_box.ts @@ -21,7 +21,7 @@ export class ComboBoxService extends FtrService { private readonly log = this.ctx.getService('log'); private readonly retry = this.ctx.getService('retry'); private readonly browser = this.ctx.getService('browser'); - private readonly PageObjects = this.ctx.getPageObjects(['common']); + private readonly common = this.ctx.getPageObject('common'); private readonly WAIT_FOR_EXISTS_TIME: number = this.config.get('timeouts.waitForExists'); @@ -113,7 +113,7 @@ export class ComboBoxService extends FtrService { this.log.debug(`comboBox.setCustom, comboBoxSelector: ${comboBoxSelector}, value: ${value}`); const comboBoxElement = await this.testSubjects.find(comboBoxSelector); await this.setFilterValue(comboBoxElement, value); - await this.PageObjects.common.pressEnterKey(); + await this.common.pressEnterKey(); await this.closeOptionsList(comboBoxElement); } diff --git a/test/functional/services/dashboard/add_panel.ts b/test/functional/services/dashboard/add_panel.ts index 98e947541b52d7..43ab1f966bc9a0 100644 --- a/test/functional/services/dashboard/add_panel.ts +++ b/test/functional/services/dashboard/add_panel.ts @@ -13,20 +13,21 @@ export class DashboardAddPanelService extends FtrService { private readonly retry = this.ctx.getService('retry'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly flyout = this.ctx.getService('flyout'); - private readonly PageObjects = this.ctx.getPageObjects(['header', 'common']); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); async clickOpenAddPanel() { this.log.debug('DashboardAddPanel.clickOpenAddPanel'); await this.testSubjects.click('dashboardAddPanelButton'); // Give some time for the animation to complete - await this.PageObjects.common.sleep(500); + await this.common.sleep(500); } async clickCreateNewLink() { this.log.debug('DashboardAddPanel.clickAddNewPanelButton'); await this.testSubjects.click('dashboardAddNewPanelButton'); // Give some time for the animation to complete - await this.PageObjects.common.sleep(500); + await this.common.sleep(500); } async clickQuickButton(visType: string) { @@ -94,7 +95,7 @@ export class DashboardAddPanelService extends FtrService { } await embeddableRows[i].click(); - await this.PageObjects.common.closeToast(); + await this.common.closeToast(); embeddableList.push(name); } }); @@ -104,7 +105,7 @@ export class DashboardAddPanelService extends FtrService { async clickPagerNextButton() { // Clear all toasts that could hide pagination controls - await this.PageObjects.common.clearAllToasts(); + await this.common.clearAllToasts(); const isNext = await this.testSubjects.exists('pagination-button-next'); if (!isNext) { @@ -118,9 +119,9 @@ export class DashboardAddPanelService extends FtrService { return false; } - await this.PageObjects.header.waitUntilLoadingHasFinished(); + await this.header.waitUntilLoadingHasFinished(); await pagerNextButton.click(); - await this.PageObjects.header.waitUntilLoadingHasFinished(); + await this.header.waitUntilLoadingHasFinished(); return true; } diff --git a/test/functional/services/dashboard/expectations.ts b/test/functional/services/dashboard/expectations.ts index 34a4a9de7899a9..c22eddb032cf9e 100644 --- a/test/functional/services/dashboard/expectations.ts +++ b/test/functional/services/dashboard/expectations.ts @@ -16,20 +16,21 @@ export class DashboardExpectService extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly find = this.ctx.getService('find'); private readonly filterBar = this.ctx.getService('filterBar'); - private readonly PageObjects = this.ctx.getPageObjects(['dashboard', 'visualize', 'visChart']); + private readonly dashboard = this.ctx.getPageObject('dashboard'); + private readonly visChart = this.ctx.getPageObject('visChart'); private readonly findTimeout = 2500; async panelCount(expectedCount: number) { this.log.debug(`DashboardExpect.panelCount(${expectedCount})`); await this.retry.try(async () => { - const panelCount = await this.PageObjects.dashboard.getPanelCount(); + const panelCount = await this.dashboard.getPanelCount(); expect(panelCount).to.be(expectedCount); }); } async visualizationsArePresent(vizList: string[]) { this.log.debug('Checking all visualisations are present on dashsboard'); - let notLoaded = await this.PageObjects.dashboard.getNotLoadedVisualizations(vizList); + let notLoaded = await this.dashboard.getNotLoadedVisualizations(vizList); // TODO: Determine issue occasionally preventing 'geo map' from loading notLoaded = notLoaded.filter((x) => x !== 'Rendering Test: geo map'); expect(notLoaded).to.be.empty(); @@ -231,7 +232,7 @@ export class DashboardExpectService extends FtrService { async dataTableRowCount(expectedCount: number) { this.log.debug(`DashboardExpect.dataTableRowCount(${expectedCount})`); await this.retry.try(async () => { - const dataTableRows = await this.PageObjects.visChart.getTableVisContent(); + const dataTableRows = await this.visChart.getTableVisContent(); expect(dataTableRows.length).to.be(expectedCount); }); } @@ -239,7 +240,7 @@ export class DashboardExpectService extends FtrService { async dataTableNoResult() { this.log.debug(`DashboardExpect.dataTableNoResult`); await this.retry.try(async () => { - await this.PageObjects.visChart.getTableVisNoResult(); + await this.visChart.getTableVisNoResult(); }); } diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index e7c028acc0e1bf..9aca790b0b4379 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -25,7 +25,9 @@ export class DashboardPanelActionsService extends FtrService { private readonly log = this.ctx.getService('log'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly inspector = this.ctx.getService('inspector'); - private readonly PageObjects = this.ctx.getPageObjects(['header', 'common', 'dashboard']); + private readonly header = this.ctx.getPageObject('header'); + private readonly common = this.ctx.getPageObject('common'); + private readonly dashboard = this.ctx.getPageObject('dashboard'); async findContextMenu(parent?: WebElementWrapper) { return parent @@ -78,8 +80,8 @@ export class DashboardPanelActionsService extends FtrService { 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.PageObjects.header.waitUntilLoadingHasFinished(); - await this.PageObjects.common.waitForTopNavToBeVisible(); + await this.header.waitUntilLoadingHasFinished(); + await this.common.waitForTopNavToBeVisible(); } async editPanelByTitle(title?: string) { @@ -146,7 +148,7 @@ export class DashboardPanelActionsService extends FtrService { await this.openContextMenu(); } await this.testSubjects.click(CLONE_PANEL_DATA_TEST_SUBJ); - await this.PageObjects.dashboard.waitForRenderComplete(); + await this.dashboard.waitForRenderComplete(); } async openCopyToModalByTitle(title?: string) { diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts index a6b88802d7b814..8688d375f7a7b9 100644 --- a/test/functional/services/dashboard/visualizations.ts +++ b/test/functional/services/dashboard/visualizations.ts @@ -13,25 +13,23 @@ export class DashboardVisualizationsService extends FtrService { private readonly queryBar = this.ctx.getService('queryBar'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly dashboardAddPanel = this.ctx.getService('dashboardAddPanel'); - private readonly PageObjects = this.ctx.getPageObjects([ - 'dashboard', - 'visualize', - 'visEditor', - 'header', - 'discover', - 'timePicker', - ]); + private readonly dashboard = this.ctx.getPageObject('dashboard'); + private readonly visualize = this.ctx.getPageObject('visualize'); + private readonly visEditor = this.ctx.getPageObject('visEditor'); + private readonly header = this.ctx.getPageObject('header'); + private readonly discover = this.ctx.getPageObject('discover'); + private readonly timePicker = this.ctx.getPageObject('timePicker'); async createAndAddTSVBVisualization(name: string) { this.log.debug(`createAndAddTSVBVisualization(${name})`); - const inViewMode = await this.PageObjects.dashboard.getIsInViewMode(); + const inViewMode = await this.dashboard.getIsInViewMode(); if (inViewMode) { - await this.PageObjects.dashboard.switchToEditMode(); + await this.dashboard.switchToEditMode(); } await this.dashboardAddPanel.clickEditorMenuButton(); await this.dashboardAddPanel.clickAddNewEmbeddableLink('metrics'); - await this.PageObjects.visualize.clickVisualBuilder(); - await this.PageObjects.visualize.saveVisualizationExpectSuccess(name); + await this.visualize.clickVisualBuilder(); + await this.visualize.saveVisualizationExpectSuccess(name); } async createSavedSearch({ @@ -44,8 +42,8 @@ export class DashboardVisualizationsService extends FtrService { fields?: string[]; }) { this.log.debug(`createSavedSearch(${name})`); - await this.PageObjects.header.clickDiscover(true); - await this.PageObjects.timePicker.setHistoricalDataRange(); + await this.header.clickDiscover(true); + await this.timePicker.setHistoricalDataRange(); if (query) { await this.queryBar.setQuery(query); @@ -54,12 +52,12 @@ export class DashboardVisualizationsService extends FtrService { if (fields) { for (let i = 0; i < fields.length; i++) { - await this.PageObjects.discover.clickFieldListItemAdd(fields[i]); + await this.discover.clickFieldListItemAdd(fields[i]); } } - await this.PageObjects.discover.saveSearch(name); - await this.PageObjects.header.waitUntilLoadingHasFinished(); + await this.discover.saveSearch(name); + await this.header.waitUntilLoadingHasFinished(); await this.testSubjects.exists('saveSearchSuccess'); } @@ -75,25 +73,25 @@ export class DashboardVisualizationsService extends FtrService { this.log.debug(`createAndAddSavedSearch(${name})`); await this.createSavedSearch({ name, query, fields }); - await this.PageObjects.header.clickDashboard(); + await this.header.clickDashboard(); - const inViewMode = await this.PageObjects.dashboard.getIsInViewMode(); + const inViewMode = await this.dashboard.getIsInViewMode(); if (inViewMode) { - await this.PageObjects.dashboard.switchToEditMode(); + await this.dashboard.switchToEditMode(); } await this.dashboardAddPanel.addSavedSearch(name); } async createAndAddMarkdown({ name, markdown }: { name: string; markdown: string }) { this.log.debug(`createAndAddMarkdown(${markdown})`); - const inViewMode = await this.PageObjects.dashboard.getIsInViewMode(); + const inViewMode = await this.dashboard.getIsInViewMode(); if (inViewMode) { - await this.PageObjects.dashboard.switchToEditMode(); + await this.dashboard.switchToEditMode(); } await this.dashboardAddPanel.clickMarkdownQuickButton(); - await this.PageObjects.visEditor.setMarkdownTxt(markdown); - await this.PageObjects.visEditor.clickGo(); - await this.PageObjects.visualize.saveVisualizationExpectSuccess(name, { + await this.visEditor.setMarkdownTxt(markdown); + await this.visEditor.clickGo(); + await this.visualize.saveVisualizationExpectSuccess(name, { saveAsNew: false, redirectToOrigin: true, }); @@ -101,9 +99,9 @@ export class DashboardVisualizationsService extends FtrService { async createAndEmbedMetric(name: string) { this.log.debug(`createAndEmbedMetric(${name})`); - const inViewMode = await this.PageObjects.dashboard.getIsInViewMode(); + const inViewMode = await this.dashboard.getIsInViewMode(); if (inViewMode) { - await this.PageObjects.dashboard.switchToEditMode(); + await this.dashboard.switchToEditMode(); } await this.dashboardAddPanel.clickEditorMenuButton(); await this.dashboardAddPanel.clickAggBasedVisualizations(); @@ -115,13 +113,13 @@ export class DashboardVisualizationsService extends FtrService { async createAndEmbedMarkdown({ name, markdown }: { name: string; markdown: string }) { this.log.debug(`createAndEmbedMarkdown(${markdown})`); - const inViewMode = await this.PageObjects.dashboard.getIsInViewMode(); + const inViewMode = await this.dashboard.getIsInViewMode(); if (inViewMode) { - await this.PageObjects.dashboard.switchToEditMode(); + await this.dashboard.switchToEditMode(); } await this.dashboardAddPanel.clickMarkdownQuickButton(); - await this.PageObjects.visEditor.setMarkdownTxt(markdown); - await this.PageObjects.visEditor.clickGo(); + await this.visEditor.setMarkdownTxt(markdown); + await this.visEditor.clickGo(); await this.testSubjects.click('visualizesaveAndReturnButton'); } } diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index f2079c02ef5b5c..f54e7b65a46e2a 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -10,7 +10,7 @@ import { chunk } from 'lodash'; import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from './lib/web_element_wrapper'; -interface TabbedGridData { +export interface TabbedGridData { columns: string[]; rows: string[][]; } @@ -22,7 +22,7 @@ interface SelectOptions { export class DataGridService extends FtrService { private readonly find = this.ctx.getService('find'); private readonly testSubjects = this.ctx.getService('testSubjects'); - private readonly PageObjects = this.ctx.getPageObjects(['common', 'header']); + private readonly header = this.ctx.getPageObject('header'); private readonly retry = this.ctx.getService('retry'); async getDataGridTableData(): Promise { @@ -234,7 +234,7 @@ export class DataGridService extends FtrService { const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); await addInclusiveFilterButton.click(); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async getAddInclusiveFilterButton( @@ -263,7 +263,7 @@ export class DataGridService extends FtrService { const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); await addInclusiveFilterButton.click(); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async hasNoResults() { diff --git a/test/functional/services/doc_table.ts b/test/functional/services/doc_table.ts index 6c73faec16b1a9..685f1748d56b28 100644 --- a/test/functional/services/doc_table.ts +++ b/test/functional/services/doc_table.ts @@ -17,7 +17,7 @@ interface SelectOptions { export class DocTableService extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly retry = this.ctx.getService('retry'); - private readonly PageObjects = this.ctx.getPageObjects(['common', 'header']); + private readonly header = this.ctx.getPageObject('header'); public async getTable(selector?: string) { return await this.testSubjects.find(selector ? selector : 'docTable'); @@ -126,7 +126,7 @@ export class DocTableService extends FtrService { const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); const addInclusiveFilterButton = await this.getAddInclusiveFilterButton(tableDocViewRow); await addInclusiveFilterButton.click(); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async getRemoveInclusiveFilterButton( @@ -142,7 +142,7 @@ export class DocTableService extends FtrService { const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); const addInclusiveFilterButton = await this.getRemoveInclusiveFilterButton(tableDocViewRow); await addInclusiveFilterButton.click(); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async getAddExistsFilterButton( @@ -155,7 +155,7 @@ export class DocTableService extends FtrService { const tableDocViewRow = await this.getTableDocViewRow(detailsRow, fieldName); const addInclusiveFilterButton = await this.getAddExistsFilterButton(tableDocViewRow); await addInclusiveFilterButton.click(); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async toggleRowExpanded({ @@ -163,7 +163,7 @@ export class DocTableService extends FtrService { rowIndex = 0, }: SelectOptions = {}): Promise { await this.clickRowToggle({ isAnchorRow, rowIndex }); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); return await this.retry.try(async () => { const row = isAnchorRow ? await this.getAnchorRow() : (await this.getBodyRows())[rowIndex]; const detailsRow = await row.findByXpath( diff --git a/test/functional/services/embedding.ts b/test/functional/services/embedding.ts index e394aff19ab8b6..6d168b00c5447d 100644 --- a/test/functional/services/embedding.ts +++ b/test/functional/services/embedding.ts @@ -11,7 +11,7 @@ import { FtrService } from '../ftr_provider_context'; export class EmbeddingService extends FtrService { private readonly browser = this.ctx.getService('browser'); private readonly log = this.ctx.getService('log'); - private readonly PageObjects = this.ctx.getPageObjects(['header']); + private readonly header = this.ctx.getPageObject('header'); /** * Opens current page in embeded mode @@ -20,6 +20,6 @@ export class EmbeddingService extends FtrService { const currentUrl = await this.browser.getCurrentUrl(); this.log.debug(`Opening in embedded mode: ${currentUrl}`); await this.browser.get(`${currentUrl}&embed=true`); - await this.PageObjects.header.waitUntilLoadingHasFinished(); + await this.header.waitUntilLoadingHasFinished(); } } diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index 5f20d3d4f8b7b5..1d0b85eed3a9c5 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -12,7 +12,8 @@ import { FtrService } from '../ftr_provider_context'; export class FilterBarService extends FtrService { private readonly comboBox = this.ctx.getService('comboBox'); private readonly testSubjects = this.ctx.getService('testSubjects'); - private readonly PageObjects = this.ctx.getPageObjects(['common', 'header']); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); /** * Checks if specified filter exists @@ -56,7 +57,7 @@ export class FilterBarService extends FtrService { public async removeFilter(key: string): Promise { await this.testSubjects.click(`~filter & ~filter-key-${key}`); await this.testSubjects.click(`deleteFilter`); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } /** @@ -65,8 +66,8 @@ export class FilterBarService extends FtrService { public async removeAllFilters(): Promise { await this.testSubjects.click('showFilterActions'); await this.testSubjects.click('removeAllFilters'); - await this.PageObjects.header.waitUntilLoadingHasFinished(); - await this.PageObjects.common.waitUntilUrlIncludes('filters:!()'); + await this.header.waitUntilLoadingHasFinished(); + await this.common.waitUntilUrlIncludes('filters:!()'); } /** @@ -77,13 +78,13 @@ export class FilterBarService extends FtrService { public async toggleFilterEnabled(key: string): Promise { await this.testSubjects.click(`~filter & ~filter-key-${key}`); await this.testSubjects.click(`disableFilter`); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async toggleFilterPinned(key: string): Promise { await this.testSubjects.click(`~filter & ~filter-key-${key}`); await this.testSubjects.click(`pinFilter`); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } public async isFilterPinned(key: string): Promise { @@ -141,7 +142,7 @@ export class FilterBarService extends FtrService { } } await this.testSubjects.click('saveFilter'); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } /** @@ -152,7 +153,7 @@ export class FilterBarService extends FtrService { public async clickEditFilter(key: string, value: string): Promise { await this.testSubjects.click(`~filter & ~filter-key-${key} & ~filter-value-${value}`); await this.testSubjects.click(`editFilter`); - await this.PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await this.header.awaitGlobalLoadingIndicatorHidden(); } /** diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index a509141390f676..26f562799b2974 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -47,7 +47,7 @@ import { ListingTableService } from './listing_table'; import { SavedQueryManagementComponentService } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; import { MenuToggleService } from './menu_toggle'; -import { MonacoEditorProvider } from './monaco_editor'; +import { MonacoEditorService } from './monaco_editor'; export const services = { ...commonServiceProviders, @@ -84,6 +84,6 @@ export const services = { elasticChart: ElasticChartService, supertest: KibanaSupertestProvider, managementMenu: ManagementMenuService, - monacoEditor: MonacoEditorProvider, + monacoEditor: MonacoEditorService, menuToggle: MenuToggleService, }; diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index 79678cf7a812b8..1cd4249df5050c 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -17,8 +17,8 @@ export class ListingTableService extends FtrService { private readonly find = this.ctx.getService('find'); private readonly log = this.ctx.getService('log'); private readonly retry = this.ctx.getService('retry'); - private readonly common = this.ctx.getPageObjects(['common']).common; - private readonly header = this.ctx.getPageObjects(['header']).header; + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); private async getSearchFilter() { return await this.testSubjects.find('tableListSearchBox'); diff --git a/test/functional/services/monaco_editor.ts b/test/functional/services/monaco_editor.ts index 4e791e54c4b09c..572606f8964546 100644 --- a/test/functional/services/monaco_editor.ts +++ b/test/functional/services/monaco_editor.ts @@ -6,26 +6,24 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function MonacoEditorProvider({ getService }: FtrProviderContext) { - const retry = getService('retry'); - const browser = getService('browser'); +export class MonacoEditorService extends FtrService { + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); - return new (class MonacoEditor { - public async getCodeEditorValue(nthIndex: number = 0) { - let values: string[] = []; + public async getCodeEditorValue(nthIndex: number = 0) { + let values: string[] = []; - await retry.try(async () => { - values = await browser.execute( - () => - (window as any).MonacoEnvironment.monaco.editor - .getModels() - .map((model: any) => model.getValue()) as string[] - ); - }); + await this.retry.try(async () => { + values = await this.browser.execute( + () => + (window as any).MonacoEnvironment.monaco.editor + .getModels() + .map((model: any) => model.getValue()) as string[] + ); + }); - return values[nthIndex] as string; - } - })(); + return values[nthIndex] as string; + } } diff --git a/test/functional/services/query_bar.ts b/test/functional/services/query_bar.ts index 31586d92d92a9d..f0728f2b022e30 100644 --- a/test/functional/services/query_bar.ts +++ b/test/functional/services/query_bar.ts @@ -13,7 +13,8 @@ export class QueryBarService extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly retry = this.ctx.getService('retry'); private readonly log = this.ctx.getService('log'); - private readonly PageObjects = this.ctx.getPageObjects(['header', 'common']); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); private readonly find = this.ctx.getService('find'); private readonly browser = this.ctx.getService('browser'); @@ -42,15 +43,15 @@ export class QueryBarService extends FtrService { public async clearQuery(): Promise { await this.setQuery(''); - await this.PageObjects.common.pressTabKey(); // move outside of input into language switcher - await this.PageObjects.common.pressTabKey(); // move outside of language switcher so time picker appears + await this.common.pressTabKey(); // move outside of input into language switcher + await this.common.pressTabKey(); // move outside of language switcher so time picker appears } public async submitQuery(): Promise { this.log.debug('QueryBar.submitQuery'); await this.testSubjects.click('queryInput'); - await this.PageObjects.common.pressEnterKey(); - await this.PageObjects.header.waitUntilLoadingHasFinished(); + await this.common.pressEnterKey(); + await this.header.waitUntilLoadingHasFinished(); } public async clickQuerySubmitButton(): Promise { diff --git a/test/functional/services/remote/prevent_parallel_calls.ts b/test/functional/services/remote/prevent_parallel_calls.ts index d21abc9d268674..338bfbd4278736 100644 --- a/test/functional/services/remote/prevent_parallel_calls.ts +++ b/test/functional/services/remote/prevent_parallel_calls.ts @@ -6,44 +6,49 @@ * Side Public License, v 1. */ -export function preventParallelCalls( - fn: (this: C, arg: A) => Promise, - filter: (arg: A) => boolean -) { - const execQueue: Task[] = []; +class Task { + public promise: Promise; + private resolve!: (result: R) => void; + private reject!: (error: Error) => void; - class Task { - public promise: Promise; - private resolve!: (result: R) => void; - private reject!: (error: Error) => void; - - constructor(private readonly context: C, private readonly arg: A) { - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - } + constructor( + private readonly execQueue: Array>, + private readonly fn: (this: C, arg: A) => Promise, + private readonly context: C, + private readonly arg: A + ) { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } - public async exec() { - try { - this.resolve(await fn.call(this.context, this.arg)); - } catch (error) { - this.reject(error); - } finally { - execQueue.shift(); - if (execQueue.length) { - execQueue[0].exec(); - } + public async exec() { + try { + this.resolve(await this.fn.call(this.context, this.arg)); + } catch (error) { + this.reject(error); + } finally { + this.execQueue.shift(); + if (this.execQueue.length) { + this.execQueue[0].exec(); } } } +} + +export function preventParallelCalls( + fn: (this: C, arg: A) => Promise, + filter: (arg: A) => boolean +) { + const execQueue: Array> = []; return async function (this: C, arg: A) { if (filter(arg)) { return await fn.call(this, arg); } - const task = new Task(this, arg); + const task = new Task(execQueue, fn, this, arg); if (execQueue.push(task) === 1) { // only item in the queue, kick it off task.exec(); diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts index aabe8c0aebb0c6..decf1618c78793 100644 --- a/test/functional/services/saved_query_management_component.ts +++ b/test/functional/services/saved_query_management_component.ts @@ -14,7 +14,7 @@ export class SavedQueryManagementComponentService extends FtrService { private readonly queryBar = this.ctx.getService('queryBar'); private readonly retry = this.ctx.getService('retry'); private readonly config = this.ctx.getService('config'); - private readonly PageObjects = this.ctx.getPageObjects(['common']); + private readonly common = this.ctx.getPageObject('common'); public async getCurrentlyLoadedQueryID() { await this.openSavedQueryManagementComponent(); @@ -93,7 +93,7 @@ export class SavedQueryManagementComponentService extends FtrService { public async deleteSavedQuery(title: string) { await this.openSavedQueryManagementComponent(); await this.testSubjects.click(`~delete-saved-query-${title}-button`); - await this.PageObjects.common.clickConfirmOnModal(); + await this.common.clickConfirmOnModal(); } async clearCurrentlyLoadedQuery() { diff --git a/test/functional/services/visualizations/pie_chart.ts b/test/functional/services/visualizations/pie_chart.ts index f51492d29b4506..99e0bb6ac4c4c6 100644 --- a/test/functional/services/visualizations/pie_chart.ts +++ b/test/functional/services/visualizations/pie_chart.ts @@ -20,16 +20,16 @@ export class PieChartService extends FtrService { private readonly find = this.ctx.getService('find'); private readonly panelActions = this.ctx.getService('dashboardPanelActions'); private readonly defaultFindTimeout = this.config.get('timeouts.find'); - private readonly pageObjects = this.ctx.getPageObjects(['visChart']); + private readonly visChart = this.ctx.getPageObject('visChart'); private readonly filterActionText = 'Apply filter to current view'; async clickOnPieSlice(name?: string) { this.log.debug(`PieChart.clickOnPieSlice(${name})`); - if (await this.pageObjects.visChart.isNewLibraryChart(pieChartSelector)) { + if (await this.visChart.isNewLibraryChart(pieChartSelector)) { const slices = - (await this.pageObjects.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0] - ?.partitions ?? []; + (await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? + []; let sliceLabel = name || slices[0].name; if (name === 'Other') { sliceLabel = '__other__'; @@ -87,10 +87,10 @@ export class PieChartService extends FtrService { async getPieSliceStyle(name: string) { this.log.debug(`VisualizePage.getPieSliceStyle(${name})`); - if (await this.pageObjects.visChart.isNewLibraryChart(pieChartSelector)) { + if (await this.visChart.isNewLibraryChart(pieChartSelector)) { const slices = - (await this.pageObjects.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0] - ?.partitions ?? []; + (await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? + []; const selectedSlice = slices.filter((slice) => { return slice.name.toString() === name.replace(',', ''); }); @@ -102,10 +102,10 @@ export class PieChartService extends FtrService { async getAllPieSliceStyles(name: string) { this.log.debug(`VisualizePage.getAllPieSliceStyles(${name})`); - if (await this.pageObjects.visChart.isNewLibraryChart(pieChartSelector)) { + if (await this.visChart.isNewLibraryChart(pieChartSelector)) { const slices = - (await this.pageObjects.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0] - ?.partitions ?? []; + (await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? + []; const selectedSlice = slices.filter((slice) => { return slice.name.toString() === name.replace(',', ''); }); @@ -129,10 +129,10 @@ export class PieChartService extends FtrService { } async getPieChartLabels() { - if (await this.pageObjects.visChart.isNewLibraryChart(pieChartSelector)) { + if (await this.visChart.isNewLibraryChart(pieChartSelector)) { const slices = - (await this.pageObjects.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0] - ?.partitions ?? []; + (await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? + []; return slices.map((slice) => { if (slice.name === '__missing__') { return 'Missing'; @@ -155,10 +155,10 @@ export class PieChartService extends FtrService { async getPieSliceCount() { this.log.debug('PieChart.getPieSliceCount'); - if (await this.pageObjects.visChart.isNewLibraryChart(pieChartSelector)) { + if (await this.visChart.isNewLibraryChart(pieChartSelector)) { const slices = - (await this.pageObjects.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0] - ?.partitions ?? []; + (await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? + []; return slices?.length; } const slices = await this.find.allByCssSelector('svg > g > g.arcs > path.slice'); @@ -167,8 +167,8 @@ export class PieChartService extends FtrService { async expectPieSliceCountEsCharts(expectedCount: number) { const slices = - (await this.pageObjects.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0] - ?.partitions ?? []; + (await this.visChart.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? + []; expect(slices.length).to.be(expectedCount); } diff --git a/test/plugin_functional/plugins/core_app_status/tsconfig.json b/test/plugin_functional/plugins/core_app_status/tsconfig.json index 4d979fbf7f15fd..0f0e7caf2b486a 100644 --- a/test/plugin_functional/plugins/core_app_status/tsconfig.json +++ b/test/plugin_functional/plugins/core_app_status/tsconfig.json @@ -1,8 +1,11 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "composite": true, "outDir": "./target", - "skipLibCheck": true + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true }, "include": [ "index.ts", @@ -10,5 +13,8 @@ "public/**/*.tsx", "../../../../typings/**/*", ], - "exclude": [] + "exclude": [], + "references": [ + { "path": "../../../../src/core/tsconfig.json" }, + ], } diff --git a/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json index eacd2f5e9aee3e..d0d1f2d99295ab 100644 --- a/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json +++ b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json @@ -1,8 +1,11 @@ { "extends": "../../../../tsconfig.base.json", "compilerOptions": { + "composite": true, "outDir": "./target", - "skipLibCheck": true + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true }, "include": [ "index.ts", @@ -12,6 +15,6 @@ ], "exclude": [], "references": [ - { "path": "../../../../src/core/tsconfig.json" } - ] + { "path": "../../../../src/core/tsconfig.json" }, + ], } diff --git a/test/tsconfig.json b/test/tsconfig.json index 86b97da699ae11..2524755d3f291c 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, "types": ["node", "resize-observer-polyfill"] }, "include": [ @@ -9,7 +13,7 @@ "../typings/**/*", "../packages/kbn-test/types/ftr_globals/**/*" ], - "exclude": ["plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], + "exclude": ["target/**/*", "plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], "references": [ { "path": "../src/core/tsconfig.json" }, { "path": "../src/plugins/telemetry_management_section/tsconfig.json" }, @@ -40,6 +44,9 @@ { "path": "../src/plugins/url_forwarding/tsconfig.json" }, { "path": "../src/plugins/usage_collection/tsconfig.json" }, { "path": "../src/plugins/index_pattern_management/tsconfig.json" }, - { "path": "../src/plugins/legacy_export/tsconfig.json" } + { "path": "../src/plugins/legacy_export/tsconfig.json" }, + { "path": "../src/plugins/visualize/tsconfig.json" }, + { "path": "plugin_functional/plugins/core_app_status/tsconfig.json" }, + { "path": "plugin_functional/plugins/core_provider_plugin/tsconfig.json" }, ] } diff --git a/test/visual_regression/ftr_provider_context.d.ts b/test/visual_regression/ftr_provider_context.ts similarity index 78% rename from test/visual_regression/ftr_provider_context.d.ts rename to test/visual_regression/ftr_provider_context.ts index ba3eb370048b88..28bedd1ca6bc34 100644 --- a/test/visual_regression/ftr_provider_context.d.ts +++ b/test/visual_regression/ftr_provider_context.ts @@ -6,9 +6,10 @@ * Side Public License, v 1. */ -import { GenericFtrProviderContext } from '@kbn/test'; +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; import { pageObjects } from '../functional/page_objects'; import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/test/visual_regression/services/index.ts b/test/visual_regression/services/index.ts index a948e4ef5d94ed..9aefe1f8de7809 100644 --- a/test/visual_regression/services/index.ts +++ b/test/visual_regression/services/index.ts @@ -7,9 +7,9 @@ */ import { services as functionalServices } from '../../functional/services'; -import { VisualTestingProvider } from './visual_testing'; +import { VisualTestingService } from './visual_testing'; export const services = { ...functionalServices, - visualTesting: VisualTestingProvider, + visualTesting: VisualTestingService, }; diff --git a/test/visual_regression/services/visual_testing/index.ts b/test/visual_regression/services/visual_testing/index.ts index 9add3a7f6fd332..156e3814d8a1d0 100644 --- a/test/visual_regression/services/visual_testing/index.ts +++ b/test/visual_regression/services/visual_testing/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { VisualTestingProvider } from './visual_testing'; +export * from './visual_testing'; diff --git a/test/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts index a0d9afa90f3fe6..59c601e6a2b6e6 100644 --- a/test/visual_regression/services/visual_testing/visual_testing.ts +++ b/test/visual_regression/services/visual_testing/visual_testing.ts @@ -10,7 +10,7 @@ import { postSnapshot } from '@percy/agent/dist/utils/sdk-utils'; import testSubjSelector from '@kbn/test-subj-selector'; import { Test } from '@kbn/test'; import { kibanaPackageJson as pkg } from '@kbn/utils'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrService, FtrProviderContext } from '../../ftr_provider_context'; // @ts-ignore internal js that is passed to the browser as is import { takePercySnapshot, takePercySnapshotWithAgent } from './take_percy_snapshot'; @@ -34,79 +34,81 @@ export interface SnapshotOptions { hide?: string[]; } -export async function VisualTestingProvider({ getService }: FtrProviderContext) { - const browser = getService('browser'); - const log = getService('log'); - const lifecycle = getService('lifecycle'); +const statsCache = new WeakMap(); - let currentTest: Test | undefined; - lifecycle.beforeEachTest.add((test) => { - currentTest = test; - }); +function getStats(test: Test) { + if (!statsCache.has(test)) { + statsCache.set(test, { + snapshotCount: 0, + }); + } + + return statsCache.get(test)!; +} - const statsCache = new WeakMap(); +export class VisualTestingService extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly log = this.ctx.getService('log'); - function getStats(test: Test) { - if (!statsCache.has(test)) { - statsCache.set(test, { - snapshotCount: 0, - }); - } + private currentTest: Test | undefined; - return statsCache.get(test)!; + constructor(ctx: FtrProviderContext) { + super(ctx); + + this.ctx.getService('lifecycle').beforeEachTest.add((test) => { + this.currentTest = test; + }); } - return new (class VisualTesting { - public async snapshot(options: SnapshotOptions = {}) { - if (process.env.DISABLE_VISUAL_TESTING) { - log.warning( - 'Capturing of percy snapshots disabled, would normally capture a snapshot here!' - ); - return; - } - - log.debug('Capturing percy snapshot'); - - if (!currentTest) { - throw new Error('unable to determine current test'); - } - - const [domSnapshot, url] = await Promise.all([ - this.getSnapshot(options.show, options.hide), - browser.getCurrentUrl(), - ]); - const stats = getStats(currentTest); - stats.snapshotCount += 1; - - const { name } = options; - const success = await postSnapshot({ - name: `${currentTest.fullTitle()} [${name ? name : stats.snapshotCount}]`, - url, - domSnapshot, - clientInfo: `kibana-ftr:${pkg.version}`, - ...DEFAULT_OPTIONS, - }); - - if (!success) { - throw new Error('Percy snapshot failed'); - } + public async snapshot(options: SnapshotOptions = {}) { + if (process.env.DISABLE_VISUAL_TESTING) { + this.log.warning( + 'Capturing of percy snapshots disabled, would normally capture a snapshot here!' + ); + return; } - private async getSnapshot(show: string[] = [], hide: string[] = []) { - const showSelectors = show.map(testSubjSelector); - const hideSelectors = hide.map(testSubjSelector); - const snapshot = await browser.execute<[string[], string[]], string | false>( - takePercySnapshot, - showSelectors, - hideSelectors - ); - return snapshot !== false - ? snapshot - : await browser.execute<[string[], string[]], string>( - takePercySnapshotWithAgent, - showSelectors, - hideSelectors - ); + this.log.debug('Capturing percy snapshot'); + + if (!this.currentTest) { + throw new Error('unable to determine current test'); + } + + const [domSnapshot, url] = await Promise.all([ + this.getSnapshot(options.show, options.hide), + this.browser.getCurrentUrl(), + ]); + const stats = getStats(this.currentTest); + stats.snapshotCount += 1; + + const { name } = options; + const success = await postSnapshot({ + name: `${this.currentTest.fullTitle()} [${name ? name : stats.snapshotCount}]`, + url, + domSnapshot, + clientInfo: `kibana-ftr:${pkg.version}`, + ...DEFAULT_OPTIONS, + }); + + if (!success) { + throw new Error('Percy snapshot failed'); } - })(); + } + + private async getSnapshot(show: string[] = [], hide: string[] = []) { + const showSelectors = show.map(testSubjSelector); + const hideSelectors = hide.map(testSubjSelector); + const snapshot = await this.browser.execute<[string[], string[]], string | false>( + takePercySnapshot, + showSelectors, + hideSelectors + ); + return snapshot !== false + ? snapshot + : await this.browser.execute<[string[], string[]], string>( + takePercySnapshotWithAgent, + showSelectors, + hideSelectors + ); + } } diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 9aa41cb9bc7559..a2c1ee43a92c46 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -56,6 +56,7 @@ { "path": "./src/plugins/visualizations/tsconfig.json" }, { "path": "./src/plugins/visualize/tsconfig.json" }, { "path": "./src/plugins/index_pattern_management/tsconfig.json" }, + { "path": "./test/tsconfig.json" }, { "path": "./x-pack/plugins/actions/tsconfig.json" }, { "path": "./x-pack/plugins/alerting/tsconfig.json" }, { "path": "./x-pack/plugins/apm/tsconfig.json" },