mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [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:
parent
8cc984d05f
commit
4f4cf054de
55 changed files with 6398 additions and 6440 deletions
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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> {}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { A11yProvider } from './a11y';
|
||||
export * from './a11y';
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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(/ /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(/ /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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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" },
|
||||
],
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
],
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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> {}
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { VisualTestingProvider } from './visual_testing';
|
||||
export * from './visual_testing';
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue