[7.x] [ts] migrate root test dir to project refs (#99148) (#101416)

* [ts] migrate root test dir to project refs (#99148)

Co-authored-by: spalger <spalger@users.noreply.github.com>

* include schema files in telemetry ts project

Co-authored-by: spalger <spalger@users.noreply.github.com>
This commit is contained in:
Spencer 2021-06-04 13:22:00 -07:00 committed by GitHub
parent 8cc984d05f
commit 4f4cf054de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 6398 additions and 6440 deletions

View file

@ -74,6 +74,12 @@ export interface GenericFtrProviderContext<
getService(serviceName: 'failureMetadata'): FailureMetadata;
getService<T extends keyof ServiceMap>(serviceName: T): ServiceMap[T];
/**
* Get the instance of a page object
* @param pageObjectName
*/
getPageObject<K extends keyof PageObjectMap>(pageObjectName: K): PageObjectMap[K];
/**
* Get a map of PageObjects
* @param pageObjects

View file

@ -8,7 +8,14 @@
"declarationMap": true,
"isolatedModules": true
},
"include": ["public/**/**/*", "server/**/**/*", "common/**/*", "../../../typings/**/*"],
"include": [
"public/**/**/*",
"server/**/**/*",
"common/**/*",
"../../../typings/**/*",
"schema/oss_plugins.json",
"schema/oss_root.json",
],
"references": [
{ "path": "../../core/tsconfig.json" },
{ "path": "../../plugins/kibana_react/tsconfig.json" },

View file

@ -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<typeof services, typeof pageObjects>;
export class FtrService extends GenericFtrService<FtrProviderContext> {}

View file

@ -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.
*/
export class AccessibilityService extends FtrService {
private readonly browser = this.ctx.getService('browser');
private readonly Wd = this.ctx.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);
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);
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 assertValidAxeReport(report: AxeReport) {
const errorMsgs = [];
for (const result of report.violations) {
errorMsgs.push(printResult(chalk.red('VIOLATION'), result));
}
public async testGlobalSnapshot(options: TestOptions = {}) {
const context = this.getAxeContext(false, options.excludeTestSubj);
const report = await this.captureAxeReport(context);
await this.testAxeReport(report);
if (errorMsgs.length) {
throw new Error(`a11y report:\n${errorMsgs.join('\n')}`);
}
}
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 = [];
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')}`);
}
}
private async captureAxeReport(context: AxeContext): Promise<AxeReport> {
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<AxeReport> {
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
},
},
};
await (Wd.driver.manage() as any).setTimeouts({
...(await (Wd.driver.manage() as any).getTimeouts()),
script: 600000,
});
await this.Wd.driver.manage().setTimeouts({
...(await this.Wd.driver.manage().getTimeouts()),
script: 600000,
});
const report = normalizeResult(
await browser.executeAsync(analyzeWithAxe, context, axeOptions)
);
const report = normalizeResult(
await this.browser.executeAsync(analyzeWithAxe, context, axeOptions)
);
if (report !== false) {
return report;
}
const withClientReport = normalizeResult(
await browser.executeAsync(analyzeWithAxeWithClient, context, axeOptions)
);
if (withClientReport === false) {
throw new Error('Attempted to analyze with axe but failed to load axe client');
}
return withClientReport;
if (report !== false) {
return report;
}
})();
const withClientReport = normalizeResult(
await this.browser.executeAsync(analyzeWithAxeWithClient, context, axeOptions)
);
if (withClientReport === false) {
throw new Error('Attempted to analyze with axe but failed to load axe client');
}
return withClientReport;
}
}

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { A11yProvider } from './a11y';
export * from './a11y';

View file

@ -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,
};

View file

@ -11,471 +11,467 @@ import expect from '@kbn/expect';
// @ts-ignore
import fetch from 'node-fetch';
import { getUrl } from '@kbn/test';
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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
);
await browser.get(appUrl, insertTimestamp);
currentUrl = await browser.getCurrentUrl();
log.debug(`Finished login process currentUrl = ${currentUrl}`);
}
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);
}
// accept alert if it pops up
const alert = await browser.getAlert();
await alert?.accept();
const currentUrl = shouldLoginIfPrompted
? await this.loginIfPrompted(appUrl, insertTimestamp)
: await browser.getCurrentUrl();
if (ensureCurrentUrl && !currentUrl.includes(appUrl)) {
throw new Error(`expected ${currentUrl}.includes(${appUrl})`);
}
});
}
/**
* 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}`;
}
await this.navigate({
appConfig,
ensureCurrentUrl,
shouldLoginIfPrompted,
useActualUrl,
insertTimestamp,
});
}
/**
* 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,
});
}
/**
* 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 sleep(sleepMilliseconds: number) {
log.debug(`... sleep(${sleepMilliseconds}) start`);
await delay(sleepMilliseconds);
log.debug(`... sleep(${sleepMilliseconds}) end`);
}
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,
});
}
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.try(async () => {
await this.sleep(501);
const currentUrl = await browser.getCurrentUrl();
log.debug('in navigateTo url = ' + currentUrl);
if (lastUrl !== currentUrl) {
lastUrl = currentUrl;
throw new Error('URL changed, waiting for it to settle');
}
});
if (appName === 'status_page') return;
if (await testSubjects.exists('statusPageContainer')) {
throw new Error('Navigation ended up at the status page.');
}
});
}
async waitUntilUrlIncludes(path: string) {
await retry.try(async () => {
const url = await browser.getCurrentUrl();
if (!url.includes(path)) {
throw new Error('Url not found');
}
});
}
async getSharedItemTitleAndDescription() {
const cssSelector = '[data-shared-item][data-title][data-description]';
const element = await find.byCssSelector(cssSelector);
return {
title: await element.getAttribute('data-title'),
description: await element.getAttribute('data-description'),
};
}
async getSharedItemContainers() {
const cssSelector = '[data-shared-items-container]';
return find.allByCssSelector(cssSelector);
}
async ensureModalOverlayHidden() {
return retry.try(async () => {
const shown = await testSubjects.exists('confirmModalTitleText');
if (shown) {
throw new Error('Modal overlay is showing');
}
});
}
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 pressEnterKey() {
await browser.pressKeys(browser.keys.ENTER);
}
async pressTabKey() {
await browser.pressKeys(browser.keys.TAB);
}
/**
* 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 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 isChromeVisible() {
const globalNavShown = await globalNav.exists();
return globalNavShown;
}
async isChromeHidden() {
const globalNavShown = await globalNav.exists();
return !globalNavShown;
}
async waitForTopNavToBeVisible() {
await retry.try(async () => {
const isNavVisible = await testSubjects.exists('top-nav');
if (!isNavVisible) {
throw new Error('Local nav not visible yet');
}
});
}
async closeToast() {
const toast = await find.byCssSelector('.euiToast', 6 * defaultFindTimeout);
await toast.moveMouseTo();
const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText();
await find.clickByCssSelector('.euiToast__closeButton');
return title;
}
async closeToastIfExists() {
const toastShown = await find.existsByCssSelector('.euiToast');
if (toastShown) {
try {
await this.closeToast();
} catch (err) {
// ignore errors, toast clear themselves after timeout
}
}
}
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 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 getBodyText() {
const body = await find.byCssSelector('body');
return await body.getVisibleText();
}
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 setFileInputPath(path: string) {
log.debug(`Setting the path '${path}' on the file input`);
const input = await find.byCssSelector('.euiFilePicker__input');
await input.type(path);
}
async scrollKibanaBodyTop() {
await browser.setScrollToById('kibana-body', 0, 0);
}
/**
* Dismiss Banner if available.
*/
async dismissBanner() {
if (await testSubjects.exists('global-banner-item')) {
const button = await find.byButtonText('Dismiss');
await button.click();
}
}
/**
* Get visible text of the Welcome Banner
*/
async getWelcomeText() {
return await 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 testSubjects.click(clickTarget, undefined, topOffset);
if (isValidatorCssString) {
await find.byCssSelector(validator);
} else {
await testSubjects.exists(validator);
}
}
}
return new CommonPage();
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 login = 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.login.login(
this.config.get('servers.kibana.username'),
this.config.get('servers.kibana.password')
);
} else {
await this.login.login('test_user', 'changeme');
}
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;
}
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);
}
// accept alert if it pops up
const alert = await this.browser.getAlert();
await alert?.accept();
const currentUrl = shouldLoginIfPrompted
? await this.loginIfPrompted(appUrl, insertTimestamp)
: await this.browser.getCurrentUrl();
if (ensureCurrentUrl && !currentUrl.includes(appUrl)) {
throw new Error(`expected ${currentUrl}.includes(${appUrl})`);
}
});
}
/**
* 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}`;
}
await this.navigate({
appConfig,
ensureCurrentUrl,
shouldLoginIfPrompted,
useActualUrl,
insertTimestamp,
});
}
/**
* 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,
});
}
/**
* 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 sleep(sleepMilliseconds: number) {
this.log.debug(`... sleep(${sleepMilliseconds}) start`);
await delay(sleepMilliseconds);
this.log.debug(`... sleep(${sleepMilliseconds}) end`);
}
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,
});
}
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');
}
currentUrl = (await this.browser.getCurrentUrl()).replace(/\/\/\w+:\w+@/, '//');
const navSuccessful = currentUrl
.replace(':80/', '/')
.replace(':443/', '/')
.startsWith(appUrl);
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;
});
await this.retry.try(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');
}
});
if (appName === 'status_page') return;
if (await this.testSubjects.exists('statusPageContainer')) {
throw new Error('Navigation ended up at the status page.');
}
});
}
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 getSharedItemTitleAndDescription() {
const cssSelector = '[data-shared-item][data-title][data-description]';
const element = await this.find.byCssSelector(cssSelector);
return {
title: await element.getAttribute('data-title'),
description: await element.getAttribute('data-description'),
};
}
async getSharedItemContainers() {
const cssSelector = '[data-shared-items-container]';
return this.find.allByCssSelector(cssSelector);
}
async ensureModalOverlayHidden() {
return this.retry.try(async () => {
const shown = await this.testSubjects.exists('confirmModalTitleText');
if (shown) {
throw new Error('Modal overlay is showing');
}
});
}
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 pressEnterKey() {
await this.browser.pressKeys(this.browser.keys.ENTER);
}
async pressTabKey() {
await this.browser.pressKeys(this.browser.keys.TAB);
}
/**
* 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 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'
);
});
}
async isChromeVisible() {
const globalNavShown = await this.globalNav.exists();
return globalNavShown;
}
async isChromeHidden() {
const globalNavShown = await this.globalNav.exists();
return !globalNavShown;
}
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 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.closeToast();
} catch (err) {
// ignore errors, toast clear themselves after timeout
}
}
}
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 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]');
}
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 getBodyText() {
const body = await this.find.byCssSelector('body');
return await body.getVisibleText();
}
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');
}
});
}
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);
}
/**
* Dismiss Banner if available.
*/
async dismissBanner() {
if (await this.testSubjects.exists('global-banner-item')) {
const button = await this.find.byButtonText('Dismiss');
await button.click();
}
}
/**
* 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);
if (isValidatorCssString) {
await this.find.byCssSelector(validator);
} else {
await this.testSubjects.exists(validator);
}
}
}

View file

@ -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 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 hasAutocompleter(): Promise<boolean> {
try {
return Boolean(await find.byCssSelector('.ace_autocomplete'));
} catch (e) {
return false;
}
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.
}
}
return new ConsolePage();
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<boolean> {
try {
return Boolean(await this.find.byCssSelector('.ace_autocomplete'));
} catch (e) {
return false;
}
}
}

View file

@ -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');
}
public async getSuccessorCountPicker() {
return await testSubjects.find('successorsCountPicker');
}
public async getPredecessorLoadMoreButton() {
return await testSubjects.find('predecessorsLoadMoreButton');
}
public async getSuccessorLoadMoreButton() {
return await testSubjects.find('successorsLoadMoreButton');
}
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 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 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');
}
});
}
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);
}
return new ContextPage();
public async getPredecessorCountPicker() {
return await this.testSubjects.find('predecessorsCountPicker');
}
public async getSuccessorCountPicker() {
return await this.testSubjects.find('successorsCountPicker');
}
public async getPredecessorLoadMoreButton() {
return await this.testSubjects.find('predecessorsLoadMoreButton');
}
public async getSuccessorLoadMoreButton() {
return await this.testSubjects.find('successorsLoadMoreButton');
}
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 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();
}
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');
}
});
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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',
})
);
}
}

View file

@ -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 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 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 clickStackManagement() {
await 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;
}
}
await this.awaitGlobalLoadingIndicatorHidden();
}
public async isGlobalLoadingIndicatorVisible() {
log.debug('isGlobalLoadingIndicatorVisible');
return await testSubjects.exists('globalLoadingIndicator', { timeout: 1500 });
}
public async awaitGlobalLoadingIndicatorHidden() {
await testSubjects.existOrFail('globalLoadingIndicator-hidden', {
allowHidden: true,
timeout: defaultFindTimeout * 10,
});
}
public async awaitKibanaChrome() {
log.debug('awaitKibanaChrome');
await testSubjects.find('kibanaChrome', 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 clickDiscover(ignoreAppLeaveWarning = false) {
await this.appsMenu.clickLink('Discover', { category: 'kibana' });
await this.onAppLeaveWarning(ignoreAppLeaveWarning);
await this.common.waitForTopNavToBeVisible();
await this.awaitGlobalLoadingIndicatorHidden();
}
return new HeaderPage();
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 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 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;
}
}
await this.awaitGlobalLoadingIndicatorHidden();
}
public async isGlobalLoadingIndicatorVisible() {
this.log.debug('isGlobalLoadingIndicatorVisible');
return await this.testSubjects.exists('globalLoadingIndicator', { timeout: 1500 });
}
public async awaitGlobalLoadingIndicatorHidden() {
await this.testSubjects.existOrFail('globalLoadingIndicator-hidden', {
allowHidden: true,
timeout: this.defaultFindTimeout * 10,
});
}
public async awaitKibanaChrome() {
this.log.debug('awaitKibanaChrome');
await this.testSubjects.find('kibanaChrome', this.defaultFindTimeout * 10);
}
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'
);
}
});
}
}

View file

@ -6,143 +6,140 @@
* 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 deployment = getService('deployment');
const PageObjects = getPageObjects(['common']);
let isOss = true;
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 deployment = this.ctx.getService('deployment');
private readonly common = this.ctx.getPageObject('common');
class HomePage {
async clickSynopsis(title: string) {
await testSubjects.click(`homeSynopsisLink${title}`);
}
private isOss = true;
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 launchSampleDashboard(id: string) {
await this.launchSampleDataSet(id);
isOss = await deployment.isOss();
if (!isOss) {
await find.clickByLinkText('Dashboard');
}
}
async launchSampleDataSet(id: string) {
await this.addSampleDataSet(id);
await testSubjects.click(`launchSampleDataSet${id}`);
}
async clickAllKibanaPlugins() {
await testSubjects.click('allPlugins');
}
async clickVisualizeExplorePlugins() {
await testSubjects.click('tab-data');
}
async clickAdminPlugin() {
await testSubjects.click('tab-admin');
}
async clickOnConsole() {
await this.clickSynopsis('console');
}
async clickOnLogo() {
await testSubjects.click('logo');
}
async clickOnAddData() {
await this.clickSynopsis('home_tutorial_directory');
}
// clicks on Active MQ logs
async clickOnLogsTutorial() {
await this.clickSynopsis('activemqlogs');
}
// clicks on cloud tutorial link
async clickOnCloudTutorial() {
await testSubjects.click('onCloudTutorial');
}
// click on side nav toggle button to see all of side nav
async clickOnToggleNavButton() {
await testSubjects.click('toggleNavButton');
}
// collapse the observability side nav details
async collapseObservabibilitySideNav() {
await testSubjects.click('collapsibleNavGroup-observability');
}
// dock the side nav
async dockTheSideNav() {
await testSubjects.click('collapsible-nav-lock');
}
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');
}
});
}
}
return new HomePage();
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);
}
// 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 launchSampleDashboard(id: string) {
await this.launchSampleDataSet(id);
this.isOss = await this.deployment.isOss();
if (!this.isOss) {
await this.find.clickByLinkText('Dashboard');
}
}
async launchSampleDataSet(id: string) {
await this.addSampleDataSet(id);
await this.testSubjects.click(`launchSampleDataSet${id}`);
}
async clickAllKibanaPlugins() {
await this.testSubjects.click('allPlugins');
}
async clickVisualizeExplorePlugins() {
await this.testSubjects.click('tab-data');
}
async clickAdminPlugin() {
await this.testSubjects.click('tab-admin');
}
async clickOnConsole() {
await this.clickSynopsis('console');
}
async clickOnLogo() {
await this.testSubjects.click('logo');
}
async clickOnAddData() {
await this.clickSynopsis('home_tutorial_directory');
}
// clicks on Active MQ logs
async clickOnLogsTutorial() {
await this.clickSynopsis('activemqlogs');
}
// clicks on cloud tutorial link
async clickOnCloudTutorial() {
await this.testSubjects.click('onCloudTutorial');
}
// click on side nav toggle button to see all of side nav
async clickOnToggleNavButton() {
await this.testSubjects.click('toggleNavButton');
}
// collapse the observability side nav details
async collapseObservabibilitySideNav() {
await this.testSubjects.click('collapsibleNavGroup-observability');
}
// dock the side nav
async dockTheSideNav() {
await this.testSubjects.click('collapsible-nav-lock');
}
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');
}
});
}
}

View file

@ -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,
};

View file

@ -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<string[][]> {
const $ = await element.parseDomContent();
return $('tr')
.toArray()
.map((row) =>
$(row)
.find('td')
.toArray()
.map((cell) =>
$(cell)
.text()
.replace(/&nbsp;/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);
if (allTables.length === 0) {
return [];
}
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 === 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];
}
return allData;
});
}
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();
});
}
/**
* 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<string[][]> {
const $ = await element.parseDomContent();
return $('tr')
.toArray()
.map((row) =>
$(row)
.find('td')
.toArray()
.map((cell) =>
$(cell)
.text()
.replace(/&nbsp;/g, '')
.trim()
)
);
}
return new LegacyDataTableVis();
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
);
if (allTables.length === 0) {
return [];
}
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 === 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];
}
return allData;
});
}
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();
});
}
}

View file

@ -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');
}
};
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 regularLogin(user, pwd);
async login(user: string, pwd: string) {
const loginType = process.env.VM || '';
if (loginType.includes('oidc') || loginType.includes('saml')) {
await this.samlLogin(user, pwd);
return;
}
async logoutLogin(user: string, pwd: string) {
await this.logout();
await this.sleep(3002);
await this.login(user, pwd);
}
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) {
log.debug(`... sleep(${sleepMilliseconds}) start`);
await delay(sleepMilliseconds);
log.debug(`... sleep(${sleepMilliseconds}) end`);
}
await this.regularLogin(user, pwd);
}
return new LoginPage();
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 sleep(sleepMilliseconds: number) {
this.log.debug(`... sleep(${sleepMilliseconds}) start`);
await delay(sleepMilliseconds);
this.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
}
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');
}
}
}

View file

@ -8,328 +8,325 @@
import { keyBy } from 'lodash';
import { map as mapAsync } from 'bluebird';
import { FtrProviderContext } from '../../ftr_provider_context';
import { FtrService } 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']);
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');
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 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 getCurrentSearchValue() {
const searchBox = await testSubjects.find('savedObjectSearchBar');
return await searchBox.getAttribute('value');
}
async getCurrentSearchValue() {
const searchBox = await this.testSubjects.find('savedObjectSearchBar');
return await searchBox.getAttribute('value');
}
async importFile(path: string, overwriteAll = true) {
log.debug(`importFile(${path})`);
async importFile(path: string, overwriteAll = true) {
this.log.debug(`importFile(${path})`);
log.debug(`Clicking importObjects`);
await testSubjects.click('importObjects');
await PageObjects.common.setFileInputPath(path);
this.log.debug(`Clicking importObjects`);
await this.testSubjects.click('importObjects');
await this.common.setFileInputPath(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`);
// Wait for all the saves to happen
await PageObjects.header.waitUntilLoadingHasFinished();
}
async checkImportSucceeded() {
await testSubjects.existOrFail('importSavedObjectsSuccess', { timeout: 20000 });
}
async checkNoneImported() {
await testSubjects.existOrFail('importSavedObjectsSuccessNoneImported', { timeout: 20000 });
}
async checkImportConflictsWarning() {
await testSubjects.existOrFail('importSavedObjectsConflictsWarning', { timeout: 20000 });
}
async checkImportLegacyWarning() {
await testSubjects.existOrFail('importSavedObjectsLegacyWarning', { timeout: 20000 });
}
async checkImportFailedWarning() {
await testSubjects.existOrFail('importSavedObjectsFailedWarning', { timeout: 20000 });
}
async checkImportError() {
await testSubjects.existOrFail('importSavedObjectsErrorText', { timeout: 20000 });
}
async getImportErrorText() {
return await testSubjects.getVisibleText('importSavedObjectsErrorText');
}
async clickImportDone() {
await testSubjects.click('importSavedObjectsDoneBtn');
await this.waitTableIsLoaded();
}
async clickConfirmChanges() {
await testSubjects.click('importSavedObjectsConfirmBtn');
}
async waitTableIsLoaded() {
return retry.try(async () => {
const isLoaded = await find.existsByDisplayedByCssSelector(
'*[data-test-subj="savedObjectsTable"] :not(.euiBasicTable-loading)'
);
if (isLoaded) {
return true;
} else {
throw new Error('Waiting');
}
});
}
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();
} 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();
}
}
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 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 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 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 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 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 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 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 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');
return {
type: await objectType.getVisibleText(),
id: await objectId.getVisibleText(),
relationship: await relationship.getVisibleText(),
error: await error.getVisibleText(),
};
});
}
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 testSubjects.click('checkboxSelectAll');
}
async canBeDeleted() {
return await testSubjects.isEnabled('savedObjectsManagementDelete');
}
async clickDelete({ confirmDelete = true }: { confirmDelete?: boolean } = {}) {
await testSubjects.click('savedObjectsManagementDelete');
if (confirmDelete) {
await testSubjects.click('confirmModalConfirmButton');
await this.waitTableIsLoaded();
}
}
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',
};
})
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 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}`);
// Wait for all the saves to happen
await this.header.waitUntilLoadingHasFinished();
}
async checkImportSucceeded() {
await this.testSubjects.existOrFail('importSavedObjectsSuccess', { timeout: 20000 });
}
async checkNoneImported() {
await this.testSubjects.existOrFail('importSavedObjectsSuccessNoneImported', {
timeout: 20000,
});
}
async checkImportConflictsWarning() {
await this.testSubjects.existOrFail('importSavedObjectsConflictsWarning', { timeout: 20000 });
}
async checkImportLegacyWarning() {
await this.testSubjects.existOrFail('importSavedObjectsLegacyWarning', { timeout: 20000 });
}
async checkImportFailedWarning() {
await this.testSubjects.existOrFail('importSavedObjectsFailedWarning', { timeout: 20000 });
}
async checkImportError() {
await this.testSubjects.existOrFail('importSavedObjectsErrorText', { timeout: 20000 });
}
async getImportErrorText() {
return await this.testSubjects.getVisibleText('importSavedObjectsErrorText');
}
async clickImportDone() {
await this.testSubjects.click('importSavedObjectsDoneBtn');
await this.waitTableIsLoaded();
}
async clickConfirmChanges() {
await this.testSubjects.click('importSavedObjectsConfirmBtn');
}
async waitTableIsLoaded() {
return this.retry.try(async () => {
const isLoaded = await this.find.existsByDisplayedByCssSelector(
'*[data-test-subj="savedObjectsTable"] :not(.euiBasicTable-loading)'
);
if (isLoaded) {
return true;
} else {
throw new Error('Waiting');
}
});
}
return +match[1];
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();
}
}
return new SavedObjectsPage();
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 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 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 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 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 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 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 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 {
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 canBeDeleted() {
return await this.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 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 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}`);
}
return +match[1];
}
}

View file

@ -6,58 +6,54 @@
* Side Public License, v 1.
*/
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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']);
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');
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;
});
}
async openNewsfeedPanel() {
log.debug('clickNewsfeed icon');
return await testSubjects.exists('NewsfeedFlyout');
}
async getRedButtonSign() {
return await find.existsByCssSelector('.euiHeaderSectionItemButton__notification--dot');
}
async getNewsfeedList() {
const list = await testSubjects.find('NewsfeedFlyout');
const cells = await list.findAllByTestSubject('newsHeadAlert');
const objects = [];
for (const cell of cells) {
objects.push(await cell.getVisibleText());
}
return objects;
}
async openNewsfeedEmptyPanel() {
return await testSubjects.exists('emptyNewsfeed');
}
async resetPage() {
await this.common.navigateToUrl('home', '', { useActualUrl: true });
}
return new NewsfeedPage();
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 openNewsfeedPanel() {
this.log.debug('clickNewsfeed icon');
return await this.testSubjects.exists('NewsfeedFlyout');
}
async getRedButtonSign() {
return await this.find.existsByCssSelector('.euiHeaderSectionItemButton__notification--dot');
}
async getNewsfeedList() {
const list = await this.testSubjects.find('NewsfeedFlyout');
const cells = await list.findAllByTestSubject('newsHeadAlert');
const objects = [];
for (const cell of cells) {
objects.push(await cell.getVisibleText());
}
return objects;
}
async openNewsfeedEmptyPanel() {
return await this.testSubjects.exists('emptyNewsfeed');
}
}

File diff suppressed because it is too large Load diff

View file

@ -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 isShareMenuOpen() {
return await this.testSubjects.exists('shareContextMenu');
}
async clickShareTopNavButton() {
return this.testSubjects.click('shareTopNavButton');
}
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 clickShareTopNavButton() {
return testSubjects.click('shareTopNavButton');
}
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);
}
/**
* 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 getSharedUrl() {
await this.openPermaLinks();
return await testSubjects.getAttribute('copyShareUrlButton', 'data-share-url');
}
async createShortUrlExistOrFail() {
await testSubjects.existOrFail('createShortUrl');
}
async createShortUrlMissingOrFail() {
await testSubjects.missingOrFail('createShortUrl');
}
async checkShortenUrl() {
await this.openPermaLinks();
const shareForm = await testSubjects.find('shareUrlForm');
await testSubjects.setCheckbox('useShortUrl', 'check');
await shareForm.waitForDeletedByCssSelector('.euiLoadingSpinner');
}
async exportAsSavedObject() {
await this.openPermaLinks();
return await testSubjects.click('exportAsSavedObject');
/**
* 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`);
}
}
return new SharePage();
async getSharedUrl() {
await this.openPermaLinks();
return await this.testSubjects.getAttribute('copyShareUrlButton', 'data-share-url');
}
async createShortUrlExistOrFail() {
await this.testSubjects.existOrFail('createShortUrl');
}
async createShortUrlMissingOrFail() {
await this.testSubjects.missingOrFail('createShortUrl');
}
async checkShortenUrl() {
await this.openPermaLinks();
const shareForm = await this.testSubjects.find('shareUrlForm');
await this.testSubjects.setCheckbox('useShortUrl', 'check');
await shareForm.waitForDeletedByCssSelector('.euiLoadingSpinner');
}
async exportAsSavedObject() {
await this.openPermaLinks();
return await this.testSubjects.click('exportAsSavedObject');
}
}

View file

@ -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 getTextTag() {
await visChart.waitForVisualization();
const elements = await 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 selectTagCloudTag(tagDisplayText: string) {
await this.testSubjects.click(tagDisplayText);
await this.header.waitUntilLoadingHasFinished();
}
return new TagCloudPage();
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 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));
}
}

View file

@ -6,92 +6,88 @@
* Side Public License, v 1.
*/
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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']);
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');
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();
}
});
}
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');
return await monacoEditor.getCodeEditorValue(1);
}
public async getMapBounds(): Promise<object> {
const request = await this.getVisualizationRequest();
const requestObject = JSON.parse(request);
return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates'];
}
public async clickMapZoomIn(waitForLoading = true) {
await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading);
}
public async clickMapZoomOut(waitForLoading = true) {
await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading);
}
public async getMapZoomEnabled(zoomSelector: string): Promise<boolean> {
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 zoomAllTheWayOut(): Promise<void> {
// 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 getMapZoomInEnabled() {
return await this.getMapZoomEnabled('a.leaflet-control-zoom-in');
}
public async getMapZoomOutEnabled() {
return await this.getMapZoomEnabled('a.leaflet-control-zoom-out');
}
public async clickMapFitDataBounds() {
return await this.clickMapButton('a.fa-crop');
}
public async getZoomSelectors(zoomSelector: string) {
return await this.find.allByCssSelector(zoomSelector);
}
return new TileMapPage();
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();
}
});
}
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');
return await this.monacoEditor.getCodeEditorValue(1);
}
public async getMapBounds(): Promise<object> {
const request = await this.getVisualizationRequest();
const requestObject = JSON.parse(request);
return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates'];
}
public async clickMapZoomIn(waitForLoading = true) {
await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading);
}
public async clickMapZoomOut(waitForLoading = true) {
await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading);
}
public async getMapZoomEnabled(zoomSelector: string): Promise<boolean> {
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 zoomAllTheWayOut(): Promise<void> {
// 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 getMapZoomInEnabled() {
return await this.getMapZoomEnabled('a.leaflet-control-zoom-in');
}
public async getMapZoomOutEnabled() {
return await this.getMapZoomEnabled('a.leaflet-control-zoom-out');
}
public async clickMapFitDataBounds() {
return await this.clickMapButton('a.fa-crop');
}
}

View file

@ -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');
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');
const quickSelectTimeMenuToggle = menuToggle.create({
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';
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 setDefaultAbsoluteRange() {
await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime);
}
async setDefaultAbsoluteRange() {
await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime);
}
async ensureHiddenNoDataPopover() {
const isVisible = await testSubjects.exists('noDataPopoverDismissButton');
if (isVisible) {
await testSubjects.click('noDataPopoverDismissButton');
}
}
/**
* 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 resetDefaultAbsoluteRangeViaUiSettings() {
await kibanaServer.uiSettings.replace({});
}
private async getTimePickerPanel() {
return await retry.try(async () => {
return await find.byCssSelector('div.euiPopover__panel-isOpen');
});
}
private async waitPanelIsGone(panelElement: WebElementWrapper) {
await find.waitForElementStale(panelElement);
}
public async timePickerExists() {
return await testSubjects.exists('superDatePickerToggleQuickMenuButton');
}
/**
* 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 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);
}
}
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');
}
/**
* @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();
}
public async isOff() {
return await find.existsByCssSelector('.euiDatePickerRange--readOnly');
}
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,
};
}
public async getTimeConfig() {
await this.showStartEndTimes();
const start = await testSubjects.getVisibleText('superDatePickerstartDatePopoverButton');
const end = await testSubjects.getVisibleText('superDatePickerendDatePopoverButton');
return {
start,
end,
};
}
public async getShowDatesButtonText() {
const button = await testSubjects.find('superDatePickerShowDatesButton');
const text = await button.getVisibleText();
return text;
}
public async getTimeDurationForSharing() {
return await testSubjects.getAttribute(
'dataSharedTimefilterDuration',
'data-shared-timefilter-duration'
);
}
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 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 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 pauseAutoRefresh() {
log.debug('pauseAutoRefresh');
const refreshConfig = await this.getRefreshConfig(true);
if (!refreshConfig.isPaused) {
log.debug('pause auto refresh');
await testSubjects.click('superDatePickerToggleRefreshButton');
}
await 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');
}
await quickSelectTimeMenuToggle.close();
}
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);
async ensureHiddenNoDataPopover() {
const isVisible = await this.testSubjects.exists('noDataPopoverDismissButton');
if (isVisible) {
await this.testSubjects.click('noDataPopoverDismissButton');
}
}
return new TimePicker();
/**
* 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}"}`,
});
}
async resetDefaultAbsoluteRangeViaUiSettings() {
await this.kibanaServer.uiSettings.replace({});
}
private async getTimePickerPanel() {
return await this.retry.try(async () => {
return await this.find.byCssSelector('div.euiPopover__panel-isOpen');
});
}
private async waitPanelIsGone(panelElement: WebElementWrapper) {
await this.find.waitForElementStale(panelElement);
}
public async timePickerExists() {
return await this.testSubjects.exists('superDatePickerToggleQuickMenuButton');
}
/**
* 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}`);
}
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);
}
}
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');
}
/**
* @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');
}
await this.waitPanelIsGone(panel);
await this.header.awaitGlobalLoadingIndicatorHidden();
}
public async isOff() {
return await this.find.existsByCssSelector('.euiDatePickerRange--readOnly');
}
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();
}
return {
interval,
units: selectedUnit,
isPaused: toggleButtonText === 'Start' ? true : false,
};
}
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 getShowDatesButtonText() {
const button = await this.testSubjects.find('superDatePickerShowDatesButton');
const text = await button.getVisibleText();
return text;
}
public async getTimeDurationForSharing() {
return await this.testSubjects.getAttribute(
'dataSharedTimefilterDuration',
'data-shared-timefilter-duration'
);
}
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,
};
}
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 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 pauseAutoRefresh() {
this.log.debug('pauseAutoRefresh');
const refreshConfig = await this.getRefreshConfig(true);
if (!refreshConfig.isPaused) {
this.log.debug('pause auto refresh');
await this.testSubjects.click('superDatePickerToggleRefreshButton');
}
await this.quickSelectTimeMenuToggle.close();
}
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');
}
await this.quickSelectTimeMenuToggle.close();
}
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);
}
}

View file

@ -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 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 this.testSubjects.find('add-to-library-checkbox');
await librarySelector.findByCssSelector(`input[id="add-to-library-checkbox"]:disabled`);
}
public async resetNewDashboard() {
await this.common.navigateToApp('dashboard');
await this.dashboard.gotoDashboardLandingPage(true);
await this.dashboard.clickNewDashboard(false);
}
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);
}
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`);
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);
}
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);
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';
}
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();
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}"]`);
if (dashboardId) {
await this.testSubjects.setValue('dashboardPickerInput', dashboardId);
await this.find.clickByButtonText(dashboardId);
}
}
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();
if (dashboardId) {
await testSubjects.setValue('dashboardPickerInput', dashboardId);
await find.clickByButtonText(dashboardId);
}
}
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();
}
}
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);
}
}
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;
}
public async saveFromModal(
vizName: string,
saveModalArgs: SaveModalArgs = { addToDashboard: null }
) {
await this.ensureSaveModalIsOpen();
await this.setSaveModalValues(vizName, saveModalArgs);
log.debug('Click Save Visualization button');
await testSubjects.click('confirmSaveSavedObjectButton');
await common.waitForSaveModalToClose();
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);
}
}
return new TimeToVisualizePage();
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;
}
public async saveFromModal(
vizName: string,
saveModalArgs: SaveModalArgs = { addToDashboard: null }
) {
await this.ensureSaveModalIsOpen();
await this.setSaveModalValues(vizName, saveModalArgs);
this.log.debug('Click Save Visualization button');
await this.testSubjects.click('confirmSaveSavedObjectButton');
await this.common.waitForSaveModalToClose();
}
}

View file

@ -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);
}
public async updateExpression(updates: string) {
const input = await testSubjects.find('timelionExpressionTextArea');
await input.type(updates);
await PageObjects.common.sleep(1000);
}
public async getExpression() {
const input = await testSubjects.find('timelionExpressionTextArea');
return input.getVisibleText();
}
public async getSuggestionItemsText() {
const elements = await testSubjects.findAll('timelionSuggestionListItem');
return await Promise.all(elements.map(async (element) => await element.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 saveTimelionSheet() {
await testSubjects.click('timelionSaveButton');
await testSubjects.click('timelionSaveAsSheetButton');
await testSubjects.click('timelionFinishSaveButton');
await testSubjects.existOrFail('timelionSaveSuccessToast');
await testSubjects.waitForDeleted('timelionSaveSuccessToast');
}
public async expectWriteControls() {
await testSubjects.existOrFail('timelionSaveButton');
await testSubjects.existOrFail('timelionDeleteButton');
}
public async expectMissingWriteControls() {
await testSubjects.missingOrFail('timelionSaveButton');
await testSubjects.missingOrFail('timelionDeleteButton');
}
await this.common.navigateToApp('timelion');
}
return new TimelionPage();
public async setExpression(expression: string) {
const input = await this.testSubjects.find('timelionExpressionTextArea');
await input.clearValue();
await input.type(expression);
}
public async updateExpression(updates: string) {
const input = await this.testSubjects.find('timelionExpressionTextArea');
await input.type(updates);
await this.common.sleep(1000);
}
public async getExpression() {
const input = await this.testSubjects.find('timelionExpressionTextArea');
return input.getVisibleText();
}
public async getSuggestionItemsText() {
const elements = await this.testSubjects.findAll('timelionSuggestionListItem');
return await Promise.all(elements.map(async (element) => await element.getVisibleText()));
}
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 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 expectWriteControls() {
await this.testSubjects.existOrFail('timelionSaveButton');
await this.testSubjects.existOrFail('timelionDeleteButton');
}
public async expectMissingWriteControls() {
await this.testSubjects.missingOrFail('timelionSaveButton');
await this.testSubjects.missingOrFail('timelionDeleteButton');
}
}

View file

@ -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');
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');
class VegaChartPage {
public getEditor() {
return testSubjects.find('vega-editor');
}
public getViewContainer() {
return find.byCssSelector('div.vgaVis__view');
}
public getControlContainer() {
return find.byCssSelector('div.vgaVis__controls');
}
public getYAxisContainer() {
return find.byCssSelector('[aria-label^="Y-axis"]');
}
public async getAceGutterContainer() {
const editor = await this.getEditor();
return editor.findByClassName('ace_gutter');
}
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');
return await Promise.all(
lines.map(async (line) => {
return await line.getVisibleText();
})
);
}
public async getSpec() {
return (await this.getRawSpec()).join('\n');
}
public async focusEditor() {
const editor = await this.getEditor();
const textarea = await editor.findByClassName('ace_content');
await textarea.click();
}
public async fillSpec(newSpec: string) {
await retry.try(async () => {
await this.cleanSpec();
await this.focusEditor();
await browser.pressKeys(newSpec);
expect(compareSpecs(await this.getSpec(), newSpec)).to.be(true);
});
}
public async typeInSpec(text: string) {
const aceGutter = await this.getAceGutterContainer();
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 cleanSpec() {
const aceGutter = await this.getAceGutterContainer();
await retry.try(async () => {
await aceGutter.doubleClick();
await browser.pressKeys(browser.keys.BACK_SPACE);
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;
}
public getEditor() {
return this.testSubjects.find('vega-editor');
}
return new VegaChartPage();
public getViewContainer() {
return this.find.byCssSelector('div.vgaVis__view');
}
public getControlContainer() {
return this.find.byCssSelector('div.vgaVis__controls');
}
public getYAxisContainer() {
return this.find.byCssSelector('[aria-label^="Y-axis"]');
}
public async getAceGutterContainer() {
const editor = await this.getEditor();
return editor.findByClassName('ace_gutter');
}
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');
return await Promise.all(
lines.map(async (line) => {
return await line.getVisibleText();
})
);
}
public async getSpec() {
return (await this.getRawSpec()).join('\n');
}
public async focusEditor() {
const editor = await this.getEditor();
const textarea = await editor.findByClassName('ace_content');
await textarea.click();
}
public async fillSpec(newSpec: string) {
await this.retry.try(async () => {
await this.cleanSpec();
await this.focusEditor();
await this.browser.pressKeys(newSpec);
expect(compareSpecs(await this.getSpec(), newSpec)).to.be(true);
});
}
public async typeInSpec(text: string) {
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);
}
public async cleanSpec() {
const aceGutter = await this.getAceGutterContainer();
await this.retry.try(async () => {
await aceGutter.doubleClick();
await this.browser.pressKeys(this.browser.keys.BACK_SPACE);
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;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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
*/
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 gotoVisualizationLandingPage() {
await this.common.navigateToApp('visualize');
}
public async clickNewVisualization() {
await this.listingTable.clickNewButton('createVisualizationPromptButton');
}
public async clickAggBasedVisualizations() {
await this.testSubjects.click('visGroupAggBasedExploreLink');
}
public async goBackToGroups() {
await this.testSubjects.click('goBackLink');
}
public async createVisualizationPromptButton() {
await this.testSubjects.click('createVisualizationPromptButton');
}
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 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 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');
}
});
}
public async clickRefresh() {
if (await this.visChart.isNewChartsLibraryEnabled()) {
await this.elasticChart.setNewChartUiDebugFlag();
}
await this.queryBar.clickQuerySubmitButton();
}
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 navigateToNewVisualization() {
await this.gotoVisualizationLandingPage();
await this.header.waitUntilLoadingHasFinished();
await this.clickNewVisualization();
await this.waitForGroupsSelectPage();
}
public async navigateToNewAggBasedVisualization() {
await this.gotoVisualizationLandingPage();
await this.header.waitUntilLoadingHasFinished();
await this.clickNewVisualization();
await this.clickAggBasedVisualizations();
await this.waitForVisualizationSelectPage();
}
public async hasVisType(type: string) {
return await this.testSubjects.exists(`visType-${type}`);
}
public async clickVisType(type: string) {
await this.testSubjects.click(`visType-${type}`);
await this.header.waitUntilLoadingHasFinished();
}
public async clickAreaChart() {
await this.clickVisType('area');
}
public async clickDataTable() {
await this.clickVisType('table');
}
public async clickLineChart() {
await this.clickVisType('line');
}
public async clickRegionMap() {
await this.clickVisType('region_map');
}
public async hasRegionMap() {
return await this.hasVisType('region_map');
}
public async clickMarkdownWidget() {
await this.clickVisType('markdown');
}
public async clickMetric() {
await this.clickVisType('metric');
}
public async clickGauge() {
await this.clickVisType('gauge');
}
public async clickPieChart() {
await this.clickVisType('pie');
}
public async clickTileMap() {
await this.clickVisType('tile_map');
}
public async hasTileMap() {
return await this.hasVisType('tile_map');
}
public async clickTagCloud() {
await this.clickVisType('tagcloud');
}
public async clickVega() {
await this.clickVisType('vega');
}
public async clickVisualBuilder() {
await this.clickVisType('metrics');
}
public async clickVerticalBarChart() {
await this.clickVisType('histogram');
}
public async clickHeatmapChart() {
await this.clickVisType('heatmap');
}
public async clickInputControlVis() {
await this.clickVisType('input_control_vis');
}
public async clickLensWidget() {
await this.clickVisType('lens');
}
public async clickMapsApp() {
await this.clickVisType('maps');
}
public async hasMapsApp() {
return await this.hasVisType('maps');
}
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 clickNewSearch(indexPattern = this.index.LOGSTASH_TIME_BASED) {
await this.testSubjects.click(`savedObjectTitle${indexPattern.split(' ').join('-')}`);
await this.header.waitUntilLoadingHasFinished();
}
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();
}
}
/**
* This page object contains the visualization type selection, the landing page,
* and the open/save dialog functions
* Deletes all existing visualizations
*/
class VisualizePage {
index = {
LOGSTASH_TIME_BASED: 'logstash-*',
LOGSTASH_NON_TIME_BASED: 'logstash*',
};
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 initTests() {
await kibanaServer.savedObjects.clean({ types: ['visualization'] });
await kibanaServer.importExport.load('visualize');
public async isBetaInfoShown() {
return await this.testSubjects.exists('betaVisInfo');
}
await kibanaServer.uiSettings.replace({
defaultIndex: 'logstash-*',
[UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b',
public async getBetaTypeLinks() {
return await this.find.allByCssSelector('[data-vis-stage="beta"]');
}
public async getExperimentalTypeLinks() {
return await this.find.allByCssSelector('[data-vis-stage="experimental"]');
}
public async isExperimentalInfoShown() {
return await this.testSubjects.exists('experimentalVisInfo');
}
public async getExperimentalInfo() {
return await this.testSubjects.find('experimentalVisInfo');
}
public async getSideEditorExists() {
return await this.find.existsByCssSelector('.visEditor__collapsibleSidebar');
}
public async clickSavedSearch(savedSearchName: string) {
await this.testSubjects.click(`savedObjectTitle${savedSearchName.split(' ').join('-')}`);
await this.header.waitUntilLoadingHasFinished();
}
public async clickUnlinkSavedSearch() {
await this.testSubjects.click('showUnlinkSavedSearchPopover');
await this.testSubjects.click('unlinkSavedSearch');
await this.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 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 loadSavedVisualization(vizName: string, { navigateToVisualize = true } = {}) {
if (navigateToVisualize) {
await this.clickLoadSavedVisButton();
}
await this.openSavedVisualization(vizName);
}
public async openSavedVisualization(vizName: string) {
const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`;
await this.testSubjects.click(dataTestSubj, 20000);
await this.header.waitUntilLoadingHasFinished();
}
public async waitForVisualizationSavedToastGone() {
await this.testSubjects.waitForDeleted('saveVisualizationSuccess');
}
public async clickLandingPageBreadcrumbLink() {
this.log.debug('clickLandingPageBreadcrumbLink');
await this.find.clickByCssSelector(`a[href="#${VisualizeConstants.LANDING_PAGE_PATH}"]`);
}
/**
* Returns true if already on the landing page (that page doesn't have a link to itself).
* @returns {Promise<boolean>}
*/
public async onLandingPage() {
this.log.debug(`VisualizePage.onLandingPage`);
return await this.testSubjects.exists('visualizationLandingPage');
}
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.');
});
}
}
public async gotoVisualizationLandingPage() {
await common.navigateToApp('visualize');
public async saveVisualization(vizName: string, saveModalArgs: VisualizeSaveModalArgs = {}) {
await this.ensureSavePanelOpen();
await this.setSaveModalValues(vizName, saveModalArgs);
this.log.debug('Click Save Visualization button');
await this.testSubjects.click('confirmSaveSavedObjectButton');
// 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();
return message;
}
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);
}
public async clickNewVisualization() {
await listingTable.clickNewButton('createVisualizationPromptButton');
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);
}
public async clickAggBasedVisualizations() {
await testSubjects.click('visGroupAggBasedExploreLink');
}
public async goBackToGroups() {
await testSubjects.click('goBackLink');
}
public async createVisualizationPromptButton() {
await testSubjects.click('createVisualizationPromptButton');
}
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 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 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 clickRefresh() {
if (await visChart.isNewChartsLibraryEnabled()) {
await elasticChart.setNewChartUiDebugFlag();
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';
}
await queryBar.clickQuerySubmitButton();
}
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 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 hasVisType(type: string) {
return await testSubjects.exists(`visType-${type}`);
}
public async clickVisType(type: string) {
await testSubjects.click(`visType-${type}`);
await header.waitUntilLoadingHasFinished();
}
public async clickAreaChart() {
await this.clickVisType('area');
}
public async clickDataTable() {
await this.clickVisType('table');
}
public async clickLineChart() {
await this.clickVisType('line');
}
public async clickRegionMap() {
await this.clickVisType('region_map');
}
public async hasRegionMap() {
return await this.hasVisType('region_map');
}
public async clickMarkdownWidget() {
await this.clickVisType('markdown');
}
public async clickMetric() {
await this.clickVisType('metric');
}
public async clickGauge() {
await this.clickVisType('gauge');
}
public async clickPieChart() {
await this.clickVisType('pie');
}
public async clickTileMap() {
await this.clickVisType('tile_map');
}
public async hasTileMap() {
return await this.hasVisType('tile_map');
}
public async clickTagCloud() {
await this.clickVisType('tagcloud');
}
public async clickVega() {
await this.clickVisType('vega');
}
public async clickVisualBuilder() {
await this.clickVisType('metrics');
}
public async clickVerticalBarChart() {
await this.clickVisType('histogram');
}
public async clickHeatmapChart() {
await this.clickVisType('heatmap');
}
public async clickInputControlVis() {
await this.clickVisType('input_control_vis');
}
public async clickLensWidget() {
await this.clickVisType('lens');
}
public async clickMapsApp() {
await this.clickVisType('maps');
}
public async hasMapsApp() {
return await this.hasVisType('maps');
}
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 clickNewSearch(indexPattern = this.index.LOGSTASH_TIME_BASED) {
await testSubjects.click(`savedObjectTitle${indexPattern.split(' ').join('-')}`);
await header.waitUntilLoadingHasFinished();
}
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();
}
}
/**
* 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 isBetaInfoShown() {
return await testSubjects.exists('betaVisInfo');
}
public async getBetaTypeLinks() {
return await find.allByCssSelector('[data-vis-stage="beta"]');
}
public async getExperimentalTypeLinks() {
return await find.allByCssSelector('[data-vis-stage="experimental"]');
}
public async isExperimentalInfoShown() {
return await testSubjects.exists('experimentalVisInfo');
}
public async getExperimentalInfo() {
return await testSubjects.find('experimentalVisInfo');
}
public async getSideEditorExists() {
return await find.existsByCssSelector('.visEditor__collapsibleSidebar');
}
public async clickSavedSearch(savedSearchName: string) {
await testSubjects.click(`savedObjectTitle${savedSearchName.split(' ').join('-')}`);
await header.waitUntilLoadingHasFinished();
}
public async clickUnlinkSavedSearch() {
await testSubjects.click('showUnlinkSavedSearchPopover');
await testSubjects.click('unlinkSavedSearch');
await header.waitUntilLoadingHasFinished();
}
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 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 loadSavedVisualization(vizName: string, { navigateToVisualize = true } = {}) {
if (navigateToVisualize) {
await this.clickLoadSavedVisButton();
}
await this.openSavedVisualization(vizName);
}
public async openSavedVisualization(vizName: string) {
const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`;
await testSubjects.click(dataTestSubj, 20000);
await header.waitUntilLoadingHasFinished();
}
public async waitForVisualizationSavedToastGone() {
await testSubjects.waitForDeleted('saveVisualizationSuccess');
}
public async clickLandingPageBreadcrumbLink() {
log.debug('clickLandingPageBreadcrumbLink');
await find.clickByCssSelector(`a[href="#${VisualizeConstants.LANDING_PAGE_PATH}"]`);
}
/**
* Returns true if already on the landing page (that page doesn't have a link to itself).
* @returns {Promise<boolean>}
*/
public async onLandingPage() {
log.debug(`VisualizePage.onLandingPage`);
return await testSubjects.exists('visualizationLandingPage');
}
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 saveVisualization(vizName: string, saveModalArgs: VisualizeSaveModalArgs = {}) {
await this.ensureSavePanelOpen();
await this.setSaveModalValues(vizName, saveModalArgs);
log.debug('Click Save Visualization button');
await testSubjects.click('confirmSaveSavedObjectButton');
// Confirm that the Visualization has actually been saved
await testSubjects.existOrFail('saveVisualizationSuccess');
const message = await common.closeToast();
await header.waitUntilLoadingHasFinished();
await common.waitForSaveModalToClose();
return message;
}
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);
}
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);
}
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();
if (dashboardId) {
// TODO - selecting an existing dashboard
}
}
}
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 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 saveVisualizationAndReturn() {
await header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('visualizesaveAndReturnButton');
await testSubjects.click('visualizesaveAndReturnButton');
}
public async linkedToOriginatingApp() {
await header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('visualizesaveAndReturnButton');
}
public async notLinkedToOriginatingApp() {
await header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('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');
if (dashboardId) {
// TODO - selecting an existing dashboard
}
}
}
return new VisualizePage();
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 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 saveVisualizationAndReturn() {
await this.header.waitUntilLoadingHasFinished();
await this.testSubjects.existOrFail('visualizesaveAndReturnButton');
await this.testSubjects.click('visualizesaveAndReturnButton');
}
public async linkedToOriginatingApp() {
await this.header.waitUntilLoadingHasFinished();
await this.testSubjects.existOrFail('visualizesaveAndReturnButton');
}
public async notLinkedToOriginatingApp() {
await this.header.waitUntilLoadingHasFinished();
await this.testSubjects.missingOrFail('visualizesaveAndReturnButton');
}
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');
}
}
}

View file

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

View file

@ -13,7 +13,8 @@ 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');
@ -24,7 +25,7 @@ export class DashboardAddPanelService extends FtrService {
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) {
@ -92,7 +93,7 @@ export class DashboardAddPanelService extends FtrService {
}
await embeddableRows[i].click();
await this.PageObjects.common.closeToast();
await this.common.closeToast();
embeddableList.push(name);
}
});
@ -102,7 +103,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) {
@ -116,9 +117,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;
}

View file

@ -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();
});
}

View file

@ -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) {

View file

@ -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');
}
}

View file

@ -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<TabbedGridData> {
@ -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() {

View file

@ -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<WebElementWrapper> {
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(

View file

@ -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();
}
}

View file

@ -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<void> {
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<void> {
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<void> {
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<void> {
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<boolean> {
@ -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<void> {
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();
}
/**

View file

@ -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,
};

View file

@ -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');

View file

@ -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;
}
}

View file

@ -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<void> {
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<void> {
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<void> {

View file

@ -6,44 +6,49 @@
* Side Public License, v 1.
*/
class Task<C, A, R> {
public promise: Promise<R>;
private resolve!: (result: R) => void;
private reject!: (error: Error) => void;
constructor(
private readonly execQueue: Array<Task<C, A, R>>,
private readonly fn: (this: C, arg: A) => Promise<R>,
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 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<C extends void, A, R>(
fn: (this: C, arg: A) => Promise<R>,
filter: (arg: A) => boolean
) {
const execQueue: Task[] = [];
class Task {
public promise: Promise<R>;
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;
});
}
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();
}
}
}
}
const execQueue: Array<Task<C, A, R>> = [];
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();

View file

@ -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() {

View file

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

View file

@ -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" },
],
}

View file

@ -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" },
],
}

View file

@ -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" },
]
}

View file

@ -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<typeof services, typeof pageObjects>;
export class FtrService extends GenericFtrService<FtrProviderContext> {}

View file

@ -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,
};

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { VisualTestingProvider } from './visual_testing';
export * from './visual_testing';

View file

@ -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<Test, { snapshotCount: number }>();
let currentTest: Test | undefined;
lifecycle.beforeEachTest.add((test) => {
currentTest = test;
});
const statsCache = new WeakMap<Test, { snapshotCount: number }>();
function getStats(test: Test) {
if (!statsCache.has(test)) {
statsCache.set(test, {
snapshotCount: 0,
});
}
return statsCache.get(test)!;
function getStats(test: Test) {
if (!statsCache.has(test)) {
statsCache.set(test, {
snapshotCount: 0,
});
}
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');
}
}
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
);
}
})();
return statsCache.get(test)!;
}
export class VisualTestingService extends FtrService {
private readonly browser = this.ctx.getService('browser');
private readonly log = this.ctx.getService('log');
private currentTest: Test | undefined;
constructor(ctx: FtrProviderContext) {
super(ctx);
this.ctx.getService('lifecycle').beforeEachTest.add((test) => {
this.currentTest = test;
});
}
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;
}
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
);
}
}

View file

@ -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" },