diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index 3b043b8aab895..710e209537b8e 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -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" }, diff --git a/test/common/services/kibana_server/extend_es_archiver.js b/test/common/services/kibana_server/extend_es_archiver.ts similarity index 70% rename from test/common/services/kibana_server/extend_es_archiver.js rename to test/common/services/kibana_server/extend_es_archiver.ts index 9a06dd7b74969..98c28960bf523 100644 --- a/test/common/services/kibana_server/extend_es_archiver.js +++ b/test/common/services/kibana_server/extend_es_archiver.ts @@ -6,10 +6,23 @@ * Side Public License, v 1. */ -const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload', 'emptyKibanaIndex']; +import type { ProvidedType } from '@kbn/test'; + +import type { EsArchiverProvider } from '../es_archiver'; +import type { RetryService } from '../retry'; +import type { KibanaServerProvider } from './kibana_server'; + +const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload', 'emptyKibanaIndex'] as const; const KIBANA_INDEX = '.kibana'; -export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) { +interface Options { + esArchiver: ProvidedType; + kibanaServer: ProvidedType; + retry: RetryService; + defaults: Record; +} + +export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }: Options) { // only extend the esArchiver if there are default uiSettings to restore if (!defaults) { return; @@ -18,9 +31,9 @@ export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) ES_ARCHIVER_LOAD_METHODS.forEach((method) => { const originalMethod = esArchiver[method]; - esArchiver[method] = async (...args) => { + esArchiver[method] = async (...args: unknown[]) => { // esArchiver methods return a stats object, with information about the indexes created - const stats = await originalMethod.apply(esArchiver, args); + const stats = await originalMethod.apply(esArchiver, args as any); const statsKeys = Object.keys(stats); const kibanaKeys = statsKeys.filter( diff --git a/test/common/services/kibana_server/index.ts b/test/common/services/kibana_server/index.ts index 7f727179032f2..2a749de2b4ce8 100644 --- a/test/common/services/kibana_server/index.ts +++ b/test/common/services/kibana_server/index.ts @@ -7,5 +7,4 @@ */ export { KibanaServerProvider } from './kibana_server'; -// @ts-ignore export { extendEsArchiver } from './extend_es_archiver'; diff --git a/test/tsconfig.json b/test/tsconfig.json index 2524755d3f291..3e02283946080 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -11,7 +11,11 @@ "include": [ "**/*", "../typings/**/*", - "../packages/kbn-test/types/ftr_globals/**/*" + "../packages/kbn-test/types/ftr_globals/**/*", + "api_integration/apis/logstash/pipeline/fixtures/*.json", + "api_integration/apis/logstash/pipelines/fixtures/*.json", + "api_integration/apis/telemetry/fixtures/*.json", + "api_integration/apis/telemetry/fixtures/*.json", ], "exclude": ["target/**/*", "plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], "references": [ diff --git a/tsconfig.refs.json b/tsconfig.refs.json index a2c1ee43a92c4..3baf5c323ef81 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -119,5 +119,6 @@ { "path": "./x-pack/plugins/index_lifecycle_management/tsconfig.json" }, { "path": "./x-pack/plugins/uptime/tsconfig.json" }, { "path": "./x-pack/plugins/xpack_legacy/tsconfig.json" }, + { "path": "./x-pack/test/tsconfig.json" }, ] } diff --git a/x-pack/plugins/telemetry_collection_xpack/tsconfig.json b/x-pack/plugins/telemetry_collection_xpack/tsconfig.json index 1221200c7548c..f4c17c4317a9f 100644 --- a/x-pack/plugins/telemetry_collection_xpack/tsconfig.json +++ b/x-pack/plugins/telemetry_collection_xpack/tsconfig.json @@ -11,7 +11,10 @@ "include": [ "common/**/*", "server/**/*", - "../../../typings/*" + "../../../typings/*", + "schema/xpack_monitoring.json", + "schema/xpack_plugins.json", + "schema/xpack_root.json", ], "references": [ { "path": "../../../src/core/tsconfig.json" }, diff --git a/x-pack/test/functional/page_objects/account_settings_page.ts b/x-pack/test/functional/page_objects/account_settings_page.ts index 2aeb255ad7eb7..5bf5be9030b75 100644 --- a/x-pack/test/functional/page_objects/account_settings_page.ts +++ b/x-pack/test/functional/page_objects/account_settings_page.ts @@ -6,33 +6,30 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function AccountSettingProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const userMenu = getService('userMenu'); +export class AccountSettingsPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly userMenu = this.ctx.getService('userMenu'); - class AccountSettingsPage { - async verifyAccountSettings(expectedEmail: string, expectedUserName: string) { - await userMenu.clickProvileLink(); + async verifyAccountSettings(expectedEmail: string, expectedUserName: string) { + await this.userMenu.clickProvileLink(); - const usernameField = await testSubjects.find('username'); - const userName = await usernameField.getVisibleText(); - expect(userName).to.be(expectedUserName); + const usernameField = await this.testSubjects.find('username'); + const userName = await usernameField.getVisibleText(); + expect(userName).to.be(expectedUserName); - const emailIdField = await testSubjects.find('email'); - const emailField = await emailIdField.getVisibleText(); - expect(emailField).to.be(expectedEmail); - await userMenu.closeMenu(); - } + const emailIdField = await this.testSubjects.find('email'); + const emailField = await emailIdField.getVisibleText(); + expect(emailField).to.be(expectedEmail); + await this.userMenu.closeMenu(); + } - async changePassword(currentPassword: string, newPassword: string) { - await testSubjects.setValue('currentPassword', currentPassword); - await testSubjects.setValue('newPassword', newPassword); - await testSubjects.setValue('confirmNewPassword', newPassword); - await testSubjects.click('changePasswordButton'); - await testSubjects.existOrFail('passwordUpdateSuccess'); - } + async changePassword(currentPassword: string, newPassword: string) { + await this.testSubjects.setValue('currentPassword', currentPassword); + await this.testSubjects.setValue('newPassword', newPassword); + await this.testSubjects.setValue('confirmNewPassword', newPassword); + await this.testSubjects.click('changePasswordButton'); + await this.testSubjects.existOrFail('passwordUpdateSuccess'); } - return new AccountSettingsPage(); } diff --git a/x-pack/test/functional/page_objects/banners_page.ts b/x-pack/test/functional/page_objects/banners_page.ts index d2e4e43cec117..b9e9b29b4eeb2 100644 --- a/x-pack/test/functional/page_objects/banners_page.ts +++ b/x-pack/test/functional/page_objects/banners_page.ts @@ -5,26 +5,22 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function BannersPageProvider({ getService }: FtrProviderContext) { - const find = getService('find'); +export class BannersPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); - class BannersPage { - isTopBannerVisible() { - return find.existsByCssSelector('.header__topBanner .kbnUserBanner__container'); - } + isTopBannerVisible() { + return this.find.existsByCssSelector('.header__topBanner .kbnUserBanner__container'); + } - async getTopBannerText() { - if (!(await this.isTopBannerVisible())) { - return ''; - } - const bannerContainer = await find.byCssSelector( - '.header__topBanner .kbnUserBanner__container' - ); - return bannerContainer.getVisibleText(); + async getTopBannerText() { + if (!(await this.isTopBannerVisible())) { + return ''; } + const bannerContainer = await this.find.byCssSelector( + '.header__topBanner .kbnUserBanner__container' + ); + return bannerContainer.getVisibleText(); } - - return new BannersPage(); } diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 99d7172651a4d..c2c92b2370381 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -7,717 +7,718 @@ import _ from 'lodash'; import { APP_ID } from '../../../plugins/maps/common/constants'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function GisPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'header', 'timePicker', 'visualize']); - - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const inspector = getService('inspector'); - const find = getService('find'); - const queryBar = getService('queryBar'); - const comboBox = getService('comboBox'); - const renderable = getService('renderable'); - const browser = getService('browser'); - const menuToggle = getService('menuToggle'); - const listingTable = getService('listingTable'); - const monacoEditor = getService('monacoEditor'); - const dashboardPanelActions = getService('dashboardPanelActions'); - - const setViewPopoverToggle = menuToggle.create({ +import { FtrService } from '../ftr_provider_context'; + +function escapeLayerName(layerName: string) { + return layerName.split(' ').join('_'); +} + +export class GisPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly timePicker = this.ctx.getPageObject('timePicker'); + private readonly visualize = this.ctx.getPageObject('visualize'); + + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly inspector = this.ctx.getService('inspector'); + private readonly find = this.ctx.getService('find'); + private readonly queryBar = this.ctx.getService('queryBar'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly renderable = this.ctx.getService('renderable'); + private readonly browser = this.ctx.getService('browser'); + private readonly listingTable = this.ctx.getService('listingTable'); + private readonly monacoEditor = this.ctx.getService('monacoEditor'); + private readonly dashboardPanelActions = this.ctx.getService('dashboardPanelActions'); + + private readonly setViewPopoverToggle = this.ctx.getService('menuToggle').create({ name: 'SetView Popover', menuTestSubject: 'mapSetViewForm', toggleButtonTestSubject: 'toggleSetViewVisibilityButton', }); - function escapeLayerName(layerName: string) { - return layerName.split(' ').join('_'); - } + basePath = ''; - class GisPage { - basePath; - - constructor() { - this.basePath = ''; - } + setBasePath(basePath: string) { + this.basePath = basePath; + } - setBasePath(basePath: string) { - this.basePath = basePath; - } + async setAbsoluteRange(start: string, end: string) { + await this.timePicker.setAbsoluteRange(start, end); + await this.waitForLayersToLoad(); + } - async setAbsoluteRange(start: string, end: string) { - await PageObjects.timePicker.setAbsoluteRange(start, end); - await this.waitForLayersToLoad(); - } + async setAndSubmitQuery(query: string) { + await this.queryBar.setQuery(query); + await this.queryBar.submitQuery(); + await this.waitForLayersToLoad(); + } - async setAndSubmitQuery(query: string) { - await queryBar.setQuery(query); - await queryBar.submitQuery(); - await this.waitForLayersToLoad(); - } + async refreshQuery() { + await this.queryBar.submitQuery(); + await this.waitForLayersToLoad(); + } - async refreshQuery() { - await queryBar.submitQuery(); - await this.waitForLayersToLoad(); - } + async enterFullScreen() { + this.log.debug(`enterFullScreen`); + await this.testSubjects.click('mapsFullScreenMode'); + await this.retry.try(async () => { + await this.testSubjects.exists('exitFullScreenModeLogo'); + }); + await this.waitForLayersToLoad(); + } - async enterFullScreen() { - log.debug(`enterFullScreen`); - await testSubjects.click('mapsFullScreenMode'); - await retry.try(async () => { - await testSubjects.exists('exitFullScreenModeLogo'); - }); - await this.waitForLayersToLoad(); + // TODO combine with dashboard full screen into a service + async existFullScreen() { + this.log.debug(`existFullScreen`); + const isFullScreen = await this.testSubjects.exists('exitFullScreenModeLogo'); + if (isFullScreen) { + await this.testSubjects.click('exitFullScreenModeLogo'); } + } - // TODO combine with dashboard full screen into a service - async existFullScreen() { - log.debug(`existFullScreen`); - const isFullScreen = await testSubjects.exists('exitFullScreenModeLogo'); - if (isFullScreen) { - await testSubjects.click('exitFullScreenModeLogo'); + // Since there are no DOM indicators that signal when map pan and zoom actions are complete, + // this method waits until the map view has stabilized, signaling that the panning/zooming is complete. + // Pass origView parameter when the new map view determinition is async + // so method knows when panning/zooming has started. + async waitForMapPanAndZoom(origView?: { lon: number; lat: number; zoom: number }) { + await this.retry.try(async () => { + this.log.debug('Waiting for map pan and zoom to complete'); + const prevView = await this.getView(); + await this.common.sleep(1000); + const currentView = await this.getView(); + if (origView && _.isEqual(origView, currentView)) { + throw new Error('Map pan and zoom has not started yet'); } - } + if (!_.isEqual(prevView, currentView)) { + throw new Error('Map is still panning and zooming'); + } + }); + await this.waitForLayersToLoad(); + } - // Since there are no DOM indicators that signal when map pan and zoom actions are complete, - // this method waits until the map view has stabilized, signaling that the panning/zooming is complete. - // Pass origView parameter when the new map view determinition is async - // so method knows when panning/zooming has started. - async waitForMapPanAndZoom(origView?: { lon: number; lat: number; zoom: number }) { - await retry.try(async () => { - log.debug('Waiting for map pan and zoom to complete'); - const prevView = await this.getView(); - await PageObjects.common.sleep(1000); - const currentView = await this.getView(); - if (origView && _.isEqual(origView, currentView)) { - throw new Error('Map pan and zoom has not started yet'); - } - if (!_.isEqual(prevView, currentView)) { - throw new Error('Map is still panning and zooming'); - } - }); - await this.waitForLayersToLoad(); - } + async waitForLayersToLoad() { + this.log.debug('Wait for layers to load'); + await this.retry.try(async () => { + const tableOfContents = await this.testSubjects.find('mapLayerTOC'); + await tableOfContents.waitForDeletedByCssSelector('.euiLoadingSpinner'); + }); + } - async waitForLayersToLoad() { - log.debug('Wait for layers to load'); - await retry.try(async () => { - const tableOfContents = await testSubjects.find('mapLayerTOC'); - await tableOfContents.waitForDeletedByCssSelector('.euiLoadingSpinner'); - }); - } + async waitForLayerDeleted(layerName: string) { + this.log.debug('Wait for layer deleted'); + await this.retry.waitFor('Layer to be deleted', async () => { + const doesLayerExist = await this.doesLayerExist(layerName); + return !doesLayerExist; + }); + } - async waitForLayerDeleted(layerName: string) { - log.debug('Wait for layer deleted'); - await retry.waitFor('Layer to be deleted', async () => { - const doesLayerExist = await this.doesLayerExist(layerName); - return !doesLayerExist; - }); - } + // use the search filter box to narrow the results down to a single + // entry, or at least to a single page of results + async loadSavedMap(name: string) { + this.log.debug(`Load Saved Map ${name}`); - // use the search filter box to narrow the results down to a single - // entry, or at least to a single page of results - async loadSavedMap(name: string) { - log.debug(`Load Saved Map ${name}`); - - await retry.try(async () => { - await this.searchForMapWithName(name); - await listingTable.clickItemLink('map', name); - await PageObjects.header.waitUntilLoadingHasFinished(); - // check Map landing page is not present - await testSubjects.missingOrFail('mapLandingPage', { timeout: 10000 }); - }); + await this.retry.try(async () => { + await this.searchForMapWithName(name); + await this.listingTable.clickItemLink('map', name); + await this.header.waitUntilLoadingHasFinished(); + // check Map landing page is not present + await this.testSubjects.missingOrFail('mapLandingPage', { timeout: 10000 }); + }); - await this.waitForLayersToLoad(); - } + await this.waitForLayersToLoad(); + } - async deleteSavedMaps(search: string) { - await this.searchForMapWithName(search); - await listingTable.checkListingSelectAllCheckbox(); - await listingTable.clickDeleteSelected(); - await PageObjects.common.clickConfirmOnModal(); + async deleteSavedMaps(search: string) { + await this.searchForMapWithName(search); + await this.listingTable.checkListingSelectAllCheckbox(); + await this.listingTable.clickDeleteSelected(); + await this.common.clickConfirmOnModal(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + await this.header.waitUntilLoadingHasFinished(); + } - async openNewMap() { - log.debug(`Open new Map`); + async openNewMap() { + this.log.debug(`Open new Map`); - // Navigate directly because we don't need to go through the map listing - // page. The listing page is skipped if there are no saved objects - await PageObjects.common.navigateToUrlWithBrowserHistory(APP_ID, '/map'); - await renderable.waitForRender(); - } + // Navigate directly because we don't need to go through the map listing + // page. The listing page is skipped if there are no saved objects + await this.common.navigateToUrlWithBrowserHistory(APP_ID, '/map'); + await this.renderable.waitForRender(); + } - async saveMap(name: string, redirectToOrigin = true, tags?: string[]) { - await testSubjects.click('mapSaveButton'); - await testSubjects.setValue('savedObjectTitle', name); - await PageObjects.visualize.setSaveModalValues(name, { - addToDashboard: false, - redirectToOrigin, - saveAsNew: true, - }); - if (tags) { - await testSubjects.click('savedObjectTagSelector'); - for (const tagName of tags) { - await testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); - } - await testSubjects.click('savedObjectTitle'); + async saveMap(name: string, redirectToOrigin = true, tags?: string[]) { + await this.testSubjects.click('mapSaveButton'); + await this.testSubjects.setValue('savedObjectTitle', name); + await this.visualize.setSaveModalValues(name, { + addToDashboard: false, + redirectToOrigin, + saveAsNew: true, + }); + if (tags) { + await this.testSubjects.click('savedObjectTagSelector'); + for (const tagName of tags) { + await this.testSubjects.click(`tagSelectorOption-${tagName.replace(' ', '_')}`); } - await testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton'); - } - - async clickSaveAndReturnButton() { - await testSubjects.click('mapSaveAndReturnButton'); - } - - async expectMissingSaveAndReturnButton() { - await testSubjects.missingOrFail('mapSaveAndReturnButton'); + await this.testSubjects.click('savedObjectTitle'); } + await this.testSubjects.clickWhenNotDisabled('confirmSaveSavedObjectButton'); + } - async expectMissingSaveButton() { - await testSubjects.missingOrFail('mapSaveButton'); - } + async clickSaveAndReturnButton() { + await this.testSubjects.click('mapSaveAndReturnButton'); + } - async expectMissingCreateNewButton() { - await testSubjects.missingOrFail('newItemButton'); - } + async expectMissingSaveAndReturnButton() { + await this.testSubjects.missingOrFail('mapSaveAndReturnButton'); + } - async expectMissingAddLayerButton() { - await testSubjects.missingOrFail('addLayerButton'); - } + async expectMissingSaveButton() { + await this.testSubjects.missingOrFail('mapSaveButton'); + } - async expectExistAddLayerButton() { - await testSubjects.existOrFail('addLayerButton'); - } + async expectMissingCreateNewButton() { + await this.testSubjects.missingOrFail('newItemButton'); + } - async onMapListingPage() { - log.debug(`onMapListingPage`); - return await listingTable.onListingPage('map'); - } + async expectMissingAddLayerButton() { + await this.testSubjects.missingOrFail('addLayerButton'); + } - async onMapPage() { - log.debug(`onMapPage`); - return await testSubjects.exists('mapLayerTOC', { - timeout: 5000, - }); - } + async expectExistAddLayerButton() { + await this.testSubjects.existOrFail('addLayerButton'); + } - async searchForMapWithName(name: string) { - log.debug(`searchForMapWithName: ${name}`); + async onMapListingPage() { + this.log.debug(`onMapListingPage`); + return await this.listingTable.onListingPage('map'); + } - await this.gotoMapListingPage(); + async onMapPage() { + this.log.debug(`onMapPage`); + return await this.testSubjects.exists('mapLayerTOC', { + timeout: 5000, + }); + } - await listingTable.searchForItemWithName(name); + async searchForMapWithName(name: string) { + this.log.debug(`searchForMapWithName: ${name}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + await this.gotoMapListingPage(); - async getHits() { - await inspector.open(); - await inspector.openInspectorRequestsView(); - const requestStats = await inspector.getTableData(); - const hits = this.getInspectorStatRowHit(requestStats, 'Hits'); - await inspector.close(); - return hits; - } + await this.listingTable.searchForItemWithName(name); - async gotoMapListingPage() { - log.debug('gotoMapListingPage'); - const onPage = await this.onMapListingPage(); - if (!onPage) { - await retry.try(async () => { - await PageObjects.common.navigateToUrlWithBrowserHistory(APP_ID, '/'); - const onMapListingPage = await this.onMapListingPage(); - if (!onMapListingPage) throw new Error('Not on map listing page.'); - }); - } - } + await this.header.waitUntilLoadingHasFinished(); + } - async searchAndExpectItemsCount(name: string, count: number) { - await this.gotoMapListingPage(); + async getHits() { + await this.inspector.open(); + await this.inspector.openInspectorRequestsView(); + const requestStats = await this.inspector.getTableData(); + const hits = this.getInspectorStatRowHit(requestStats, 'Hits'); + await this.inspector.close(); + return hits; + } - log.debug(`searchAndExpectItemsCount: ${name}`); - await listingTable.searchAndExpectItemsCount('map', name, count); + async gotoMapListingPage() { + this.log.debug('gotoMapListingPage'); + const onPage = await this.onMapListingPage(); + if (!onPage) { + await this.retry.try(async () => { + await this.common.navigateToUrlWithBrowserHistory(APP_ID, '/'); + const onMapListingPage = await this.onMapListingPage(); + if (!onMapListingPage) throw new Error('Not on map listing page.'); + }); } + } - async setView(lat: number, lon: number, zoom: number) { - log.debug( - `Set view lat: ${lat.toString()}, lon: ${lon.toString()}, zoom: ${zoom.toString()}` - ); - await setViewPopoverToggle.open(); - await testSubjects.setValue('latitudeInput', lat.toString()); - await testSubjects.setValue('longitudeInput', lon.toString()); - await testSubjects.setValue('zoomInput', zoom.toString()); - await testSubjects.click('submitViewButton'); - await this.waitForMapPanAndZoom(); - } + async searchAndExpectItemsCount(name: string, count: number) { + await this.gotoMapListingPage(); - async getView() { - log.debug('Get view'); - await setViewPopoverToggle.open(); - // this method is regularly called within a retry, so we need to reduce the timeouts - // of the retries done within the getAttribute method in order to ensure that they fail - // early enough to retry getView() - const getAttributeOptions = { - tryTimeout: 5000, - findTimeout: 1000, - }; - - const lat = await testSubjects.getAttribute('latitudeInput', 'value', getAttributeOptions); - const lon = await testSubjects.getAttribute('longitudeInput', 'value', getAttributeOptions); - const zoom = await testSubjects.getAttribute('zoomInput', 'value', getAttributeOptions); - - await setViewPopoverToggle.close(); - return { - lat: parseFloat(lat), - lon: parseFloat(lon), - zoom: parseFloat(zoom), - }; - } + this.log.debug(`searchAndExpectItemsCount: ${name}`); + await this.listingTable.searchAndExpectItemsCount('map', name, count); + } - async toggleLayerVisibility(layerName: string) { - log.debug(`Toggle layer visibility, layer: ${layerName}`); - await this.openLayerTocActionsPanel(layerName); - await testSubjects.click('layerVisibilityToggleButton'); - } + async setView(lat: number, lon: number, zoom: number) { + this.log.debug( + `Set view lat: ${lat.toString()}, lon: ${lon.toString()}, zoom: ${zoom.toString()}` + ); + await this.setViewPopoverToggle.open(); + await this.testSubjects.setValue('latitudeInput', lat.toString()); + await this.testSubjects.setValue('longitudeInput', lon.toString()); + await this.testSubjects.setValue('zoomInput', zoom.toString()); + await this.testSubjects.click('submitViewButton'); + await this.waitForMapPanAndZoom(); + } - async closeLegend() { - const isOpen = await testSubjects.exists('mapLayerTOC'); - if (isOpen) { - await testSubjects.click('mapToggleLegendButton'); - await testSubjects.waitForDeleted('mapLayerTOC'); - } - } + async getView() { + this.log.debug('Get view'); + await this.setViewPopoverToggle.open(); + // this method is regularly called within a retry, so we need to reduce the timeouts + // of the retries done within the getAttribute method in order to ensure that they fail + // early enough to retry getView() + const getAttributeOptions = { + tryTimeout: 5000, + findTimeout: 1000, + }; + + const lat = await this.testSubjects.getAttribute('latitudeInput', 'value', getAttributeOptions); + const lon = await this.testSubjects.getAttribute( + 'longitudeInput', + 'value', + getAttributeOptions + ); + const zoom = await this.testSubjects.getAttribute('zoomInput', 'value', getAttributeOptions); + + await this.setViewPopoverToggle.close(); + return { + lat: parseFloat(lat), + lon: parseFloat(lon), + zoom: parseFloat(zoom), + }; + } - async clickFitToBounds(layerName: string) { - log.debug(`Fit to bounds, layer: ${layerName}`); - const origView = await this.getView(); - await this.openLayerTocActionsPanel(layerName); - await testSubjects.click('fitToBoundsButton'); - await this.waitForMapPanAndZoom(origView); - } + async toggleLayerVisibility(layerName: string) { + this.log.debug(`Toggle layer visibility, layer: ${layerName}`); + await this.openLayerTocActionsPanel(layerName); + await this.testSubjects.click('layerVisibilityToggleButton'); + } - async openLayerTocActionsPanel(layerName: string) { - const escapedDisplayName = escapeLayerName(layerName); - const isOpen = await testSubjects.exists(`layerTocActionsPanel${escapedDisplayName}`); - if (!isOpen) { - await testSubjects.click(`layerTocActionsPanelToggleButton${escapedDisplayName}`); - } + async closeLegend() { + const isOpen = await this.testSubjects.exists('mapLayerTOC'); + if (isOpen) { + await this.testSubjects.click('mapToggleLegendButton'); + await this.testSubjects.waitForDeleted('mapLayerTOC'); } + } - async openLayerPanel(layerName: string) { - log.debug(`Open layer panel, layer: ${layerName}`); - await this.openLayerTocActionsPanel(layerName); - await testSubjects.click('editLayerButton'); - } + async clickFitToBounds(layerName: string) { + this.log.debug(`Fit to bounds, layer: ${layerName}`); + const origView = await this.getView(); + await this.openLayerTocActionsPanel(layerName); + await this.testSubjects.click('fitToBoundsButton'); + await this.waitForMapPanAndZoom(origView); + } - async closeLayerPanel() { - await testSubjects.click('layerPanelCancelButton'); - await this.waitForLayersToLoad(); + async openLayerTocActionsPanel(layerName: string) { + const escapedDisplayName = escapeLayerName(layerName); + const isOpen = await this.testSubjects.exists(`layerTocActionsPanel${escapedDisplayName}`); + if (!isOpen) { + await this.testSubjects.click(`layerTocActionsPanelToggleButton${escapedDisplayName}`); } + } - async getLayerTOCDetails(layerName: string) { - return await testSubjects.getVisibleText(`mapLayerTOCDetails${escapeLayerName(layerName)}`); - } + async openLayerPanel(layerName: string) { + this.log.debug(`Open layer panel, layer: ${layerName}`); + await this.openLayerTocActionsPanel(layerName); + await this.testSubjects.click('editLayerButton'); + } - async disableApplyGlobalQuery() { - const isSelected = await testSubjects.getAttribute( - 'mapLayerPanelApplyGlobalQueryCheckbox', - 'aria-checked' - ); - if (isSelected === 'true') { - await retry.try(async () => { - log.debug(`disabling applyGlobalQuery`); - await testSubjects.click('mapLayerPanelApplyGlobalQueryCheckbox'); - const isStillSelected = await testSubjects.getAttribute( - 'mapLayerPanelApplyGlobalQueryCheckbox', - 'aria-checked' - ); - if (isStillSelected === 'true') { - throw new Error('applyGlobalQuery not disabled'); - } - }); - await this.waitForLayersToLoad(); - } - } + async closeLayerPanel() { + await this.testSubjects.click('layerPanelCancelButton'); + await this.waitForLayersToLoad(); + } - async getNumberOfLayers() { - const tocEntries = await find.allByCssSelector('.mapTocEntry'); - return tocEntries.length; - } + async getLayerTOCDetails(layerName: string) { + return await this.testSubjects.getVisibleText( + `mapLayerTOCDetails${escapeLayerName(layerName)}` + ); + } - async doesLayerExist(layerName: string) { - return await testSubjects.exists( - `layerTocActionsPanelToggleButton${escapeLayerName(layerName)}` - ); + async disableApplyGlobalQuery() { + const isSelected = await this.testSubjects.getAttribute( + 'mapLayerPanelApplyGlobalQueryCheckbox', + 'aria-checked' + ); + if (isSelected === 'true') { + await this.retry.try(async () => { + this.log.debug(`disabling applyGlobalQuery`); + await this.testSubjects.click('mapLayerPanelApplyGlobalQueryCheckbox'); + const isStillSelected = await this.testSubjects.getAttribute( + 'mapLayerPanelApplyGlobalQueryCheckbox', + 'aria-checked' + ); + if (isStillSelected === 'true') { + throw new Error('applyGlobalQuery not disabled'); + } + }); + await this.waitForLayersToLoad(); } + } - async hasFilePickerLoadedFile(fileName: string) { - log.debug(`Has file picker loaded file ${fileName}`); - const filePickerText = await find.byCssSelector('.euiFilePicker__promptText'); - const filePickerTextContent = await filePickerText.getVisibleText(); + async getNumberOfLayers() { + const tocEntries = await this.find.allByCssSelector('.mapTocEntry'); + return tocEntries.length; + } - return fileName === filePickerTextContent; - } + async doesLayerExist(layerName: string) { + return await this.testSubjects.exists( + `layerTocActionsPanelToggleButton${escapeLayerName(layerName)}` + ); + } - /* - * Layer panel utility functions - */ - async isLayerAddPanelOpen() { - log.debug(`Is layer add panel open`); - return await testSubjects.exists('layerAddForm'); - } + async hasFilePickerLoadedFile(fileName: string) { + this.log.debug(`Has file picker loaded file ${fileName}`); + const filePickerText = await this.find.byCssSelector('.euiFilePicker__promptText'); + const filePickerTextContent = await filePickerText.getVisibleText(); - async waitForLayerAddPanelClosed() { - let layerAddPanelOpen = false; - await retry.waitForWithTimeout('Layer add panel closed', 1000, async () => { - layerAddPanelOpen = await this.isLayerAddPanelOpen(); - return !layerAddPanelOpen; - }); - } + return fileName === filePickerTextContent; + } - async clickAddLayer() { - log.debug('Click add layer'); - await retry.try(async () => { - await testSubjects.click('addLayerButton'); - const isOpen = await this.isLayerAddPanelOpen(); - if (!isOpen) { - throw new Error('Add layer panel still not open, trying again.'); - } - }); - } + /* + * Layer panel utility functions + */ + async isLayerAddPanelOpen() { + this.log.debug(`Is layer add panel open`); + return await this.testSubjects.exists('layerAddForm'); + } - async cancelLayerAdd(layerName: string) { - log.debug(`Cancel layer add`); - const cancelExists = await testSubjects.exists('layerAddCancelButton'); - if (cancelExists) { - await testSubjects.click('layerAddCancelButton'); - await this.waitForLayerAddPanelClosed(); - if (layerName) { - await this.waitForLayerDeleted(layerName); - } - } - } + async waitForLayerAddPanelClosed() { + let layerAddPanelOpen = false; + await this.retry.waitForWithTimeout('Layer add panel closed', 1000, async () => { + layerAddPanelOpen = await this.isLayerAddPanelOpen(); + return !layerAddPanelOpen; + }); + } - async closeOrCancelLayer(layerName: string) { - log.debug(`Close or cancel layer add`); - const cancelExists = await testSubjects.exists('layerAddCancelButton'); - const closeExists = await testSubjects.exists('layerPanelCancelButton'); - if (cancelExists) { - log.debug(`Cancel layer add.`); - await testSubjects.click('layerAddCancelButton'); - } else if (closeExists) { - log.debug(`Close layer add.`); - await testSubjects.click('layerPanelCancelButton'); - } else { - log.debug(`No need to close or cancel.`); - return; + async clickAddLayer() { + this.log.debug('Click add layer'); + await this.retry.try(async () => { + await this.testSubjects.click('addLayerButton'); + const isOpen = await this.isLayerAddPanelOpen(); + if (!isOpen) { + throw new Error('Add layer panel still not open, trying again.'); } + }); + } + async cancelLayerAdd(layerName: string) { + this.log.debug(`Cancel layer add`); + const cancelExists = await this.testSubjects.exists('layerAddCancelButton'); + if (cancelExists) { + await this.testSubjects.click('layerAddCancelButton'); await this.waitForLayerAddPanelClosed(); if (layerName) { await this.waitForLayerDeleted(layerName); } } + } - async importFileButtonEnabled() { - log.debug(`Check "Import file" button enabled`); - const importFileButton = await testSubjects.find('importFileButton'); - const isDisabled = await importFileButton.getAttribute('disabled'); - return !isDisabled; + async closeOrCancelLayer(layerName: string) { + this.log.debug(`Close or cancel layer add`); + const cancelExists = await this.testSubjects.exists('layerAddCancelButton'); + const closeExists = await this.testSubjects.exists('layerPanelCancelButton'); + if (cancelExists) { + this.log.debug(`Cancel layer add.`); + await this.testSubjects.click('layerAddCancelButton'); + } else if (closeExists) { + this.log.debug(`Close layer add.`); + await this.testSubjects.click('layerPanelCancelButton'); + } else { + this.log.debug(`No need to close or cancel.`); + return; + } + + await this.waitForLayerAddPanelClosed(); + if (layerName) { + await this.waitForLayerDeleted(layerName); } + } - async importLayerReadyForAdd() { - log.debug(`Wait until import complete`); - await testSubjects.find('indexRespCopyButton', 5000); - let layerAddReady = false; - await retry.waitForWithTimeout('Add layer button ready', 2000, async () => { - layerAddReady = await this.importFileButtonEnabled(); - return layerAddReady; - }); - return layerAddReady; - } + async importFileButtonEnabled() { + this.log.debug(`Check "Import file" button enabled`); + const importFileButton = await this.testSubjects.find('importFileButton'); + const isDisabled = await importFileButton.getAttribute('disabled'); + return !isDisabled; + } - async clickImportFileButton() { - log.debug(`Click "Import file" button`); - await testSubjects.click('importFileButton'); - } + async importLayerReadyForAdd() { + this.log.debug(`Wait until import complete`); + await this.testSubjects.find('indexRespCopyButton', 5000); + let layerAddReady = false; + await this.retry.waitForWithTimeout('Add layer button ready', 2000, async () => { + layerAddReady = await this.importFileButtonEnabled(); + return layerAddReady; + }); + return layerAddReady; + } - async setIndexName(indexName: string) { - log.debug(`Set index name to: ${indexName}`); - await testSubjects.setValue('fileUploadIndexNameInput', indexName); - } + async clickImportFileButton() { + this.log.debug(`Click "Import file" button`); + await this.testSubjects.click('importFileButton'); + } - async setIndexType(indexType: string) { - log.debug(`Set index type to: ${indexType}`); - await testSubjects.selectValue('fileImportIndexSelect', indexType); - } + async setIndexName(indexName: string) { + this.log.debug(`Set index name to: ${indexName}`); + await this.testSubjects.setValue('fileUploadIndexNameInput', indexName); + } - async indexTypeOptionExists(indexType: string) { - log.debug(`Check index type "${indexType}" available`); - return await find.existsByCssSelector( - `select[data-test-subj="fileImportIndexSelect"] > option[value="${indexType}"]` - ); - } + async setIndexType(indexType: string) { + this.log.debug(`Set index type to: ${indexType}`); + await this.testSubjects.selectValue('fileImportIndexSelect', indexType); + } - async clickCopyButton(dataTestSubj: string): Promise { - log.debug(`Click ${dataTestSubj} copy button`); + async indexTypeOptionExists(indexType: string) { + this.log.debug(`Check index type "${indexType}" available`); + return await this.find.existsByCssSelector( + `select[data-test-subj="fileImportIndexSelect"] > option[value="${indexType}"]` + ); + } - await testSubjects.click(dataTestSubj); + async clickCopyButton(dataTestSubj: string): Promise { + this.log.debug(`Click ${dataTestSubj} copy button`); - return await browser.getClipboardValue(); - } + await this.testSubjects.click(dataTestSubj); - async getIndexResults() { - return JSON.parse(await this.clickCopyButton('indexRespCopyButton')); - } + return await this.browser.getClipboardValue(); + } - async getIndexPatternResults() { - return JSON.parse(await this.clickCopyButton('indexPatternRespCopyButton')); - } + async getIndexResults() { + return JSON.parse(await this.clickCopyButton('indexRespCopyButton')); + } - async setLayerQuery(layerName: string, query: string) { - await this.openLayerPanel(layerName); - await testSubjects.click('mapLayerPanelOpenFilterEditorButton'); - const filterEditorContainer = await testSubjects.find('mapFilterEditor'); - const queryBarInFilterEditor = await testSubjects.findDescendant( - 'queryInput', - filterEditorContainer - ); - await queryBarInFilterEditor.click(); - const input = await find.activeElement(); - await retry.try(async () => { - await input.clearValue(); - await input.type(query); - const value = await input.getAttribute('value'); - if (value !== query) { - throw new Error(`Layer query set to ${value} instead of ${query}`); - } - }); - await testSubjects.click('mapFilterEditorSubmitButton'); - await this.waitForLayersToLoad(); - } + async getIndexPatternResults() { + return JSON.parse(await this.clickCopyButton('indexPatternRespCopyButton')); + } - async setJoinWhereQuery(layerName: string, query: string) { - await this.openLayerPanel(layerName); - await testSubjects.click('mapJoinWhereExpressionButton'); - const filterEditorContainer = await testSubjects.find('mapJoinWhereFilterEditor'); - const queryBarInFilterEditor = await testSubjects.findDescendant( - 'queryInput', - filterEditorContainer - ); - await queryBarInFilterEditor.click(); - const input = await find.activeElement(); + async setLayerQuery(layerName: string, query: string) { + await this.openLayerPanel(layerName); + await this.testSubjects.click('mapLayerPanelOpenFilterEditorButton'); + const filterEditorContainer = await this.testSubjects.find('mapFilterEditor'); + const queryBarInFilterEditor = await this.testSubjects.findDescendant( + 'queryInput', + filterEditorContainer + ); + await queryBarInFilterEditor.click(); + const input = await this.find.activeElement(); + await this.retry.try(async () => { await input.clearValue(); await input.type(query); - await testSubjects.click('mapWhereFilterEditorSubmitButton'); - await this.waitForLayersToLoad(); - } + const value = await input.getAttribute('value'); + if (value !== query) { + throw new Error(`Layer query set to ${value} instead of ${query}`); + } + }); + await this.testSubjects.click('mapFilterEditorSubmitButton'); + await this.waitForLayersToLoad(); + } - async selectEMSBoundariesSource() { - log.debug(`Select EMS boundaries source`); - await testSubjects.click('emsBoundaries'); - } + async setJoinWhereQuery(layerName: string, query: string) { + await this.openLayerPanel(layerName); + await this.testSubjects.click('mapJoinWhereExpressionButton'); + const filterEditorContainer = await this.testSubjects.find('mapJoinWhereFilterEditor'); + const queryBarInFilterEditor = await this.testSubjects.findDescendant( + 'queryInput', + filterEditorContainer + ); + await queryBarInFilterEditor.click(); + const input = await this.find.activeElement(); + await input.clearValue(); + await input.type(query); + await this.testSubjects.click('mapWhereFilterEditorSubmitButton'); + await this.waitForLayersToLoad(); + } - async selectGeoJsonUploadSource() { - log.debug(`Select upload geojson source`); - await testSubjects.click('uploadGeoJson'); - } + async selectEMSBoundariesSource() { + this.log.debug(`Select EMS boundaries source`); + await this.testSubjects.click('emsBoundaries'); + } - async uploadJsonFileForIndexing(path: string) { - await PageObjects.common.setFileInputPath(path); - log.debug(`File selected`); + async selectGeoJsonUploadSource() { + this.log.debug(`Select upload geojson source`); + await this.testSubjects.click('uploadGeoJson'); + } - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.waitForLayersToLoad(); - } + async uploadJsonFileForIndexing(path: string) { + await this.common.setFileInputPath(path); + this.log.debug(`File selected`); - // Returns first layer by default - async selectVectorLayer(vectorLayerName: string) { - log.debug(`Select EMS vector layer ${vectorLayerName}`); - if (!vectorLayerName) { - throw new Error(`You did not provide the EMS layer to select`); - } - await comboBox.set('emsVectorComboBox', vectorLayerName); - await this.waitForLayersToLoad(); - } + await this.header.waitUntilLoadingHasFinished(); + await this.waitForLayersToLoad(); + } - async removeLayer(layerName: string) { - log.debug(`Remove layer ${layerName}`); - await this.openLayerPanel(layerName); - await testSubjects.click(`mapRemoveLayerButton`); - await this.waitForLayerDeleted(layerName); + // Returns first layer by default + async selectVectorLayer(vectorLayerName: string) { + this.log.debug(`Select EMS vector layer ${vectorLayerName}`); + if (!vectorLayerName) { + throw new Error(`You did not provide the EMS layer to select`); } + await this.comboBox.set('emsVectorComboBox', vectorLayerName); + await this.waitForLayersToLoad(); + } - async getLayerErrorText(layerName: string) { - log.debug(`Remove layer ${layerName}`); - await this.openLayerPanel(layerName); - return await testSubjects.getVisibleText(`layerErrorMessage`); - } + async removeLayer(layerName: string) { + this.log.debug(`Remove layer ${layerName}`); + await this.openLayerPanel(layerName); + await this.testSubjects.click(`mapRemoveLayerButton`); + await this.waitForLayerDeleted(layerName); + } - async fullScreenModeMenuItemExists() { - return await testSubjects.exists('mapsFullScreenMode'); - } + async getLayerErrorText(layerName: string) { + this.log.debug(`Remove layer ${layerName}`); + await this.openLayerPanel(layerName); + return await this.testSubjects.getVisibleText(`layerErrorMessage`); + } - async clickFullScreenMode() { - log.debug(`clickFullScreenMode`); - await testSubjects.click('mapsFullScreenMode'); - } + async fullScreenModeMenuItemExists() { + return await this.testSubjects.exists('mapsFullScreenMode'); + } - async exitFullScreenLogoButtonExists() { - return await testSubjects.exists('exitFullScreenModeLogo'); - } + async clickFullScreenMode() { + this.log.debug(`clickFullScreenMode`); + await this.testSubjects.click('mapsFullScreenMode'); + } - async getExitFullScreenLogoButton() { - return await testSubjects.find('exitFullScreenModeLogo'); - } + async exitFullScreenLogoButtonExists() { + return await this.testSubjects.exists('exitFullScreenModeLogo'); + } - async clickExitFullScreenTextButton() { - await testSubjects.click('exitFullScreenModeText'); - } + async getExitFullScreenLogoButton() { + return await this.testSubjects.find('exitFullScreenModeLogo'); + } - async openInspectorMapView() { - await inspector.openInspectorView('~inspectorViewChooserMap'); - } + async clickExitFullScreenTextButton() { + await this.testSubjects.click('exitFullScreenModeText'); + } - // Method should only be used when multiple requests are expected - // RequestSelector will only display inspectorRequestChooser when there is more than one request - async openInspectorRequest(requestName: string) { - await inspector.open(); - await inspector.openInspectorRequestsView(); - log.debug(`Open Inspector request ${requestName}`); - await testSubjects.click('inspectorRequestChooser'); - await testSubjects.click(`inspectorRequestChooser${requestName}`); - } + async openInspectorMapView() { + await this.inspector.openInspectorView('~inspectorViewChooserMap'); + } - async doesInspectorHaveRequests() { - await inspector.open(); - await inspector.openInspectorRequestsView(); - return await testSubjects.exists('inspectorNoRequestsMessage'); - } + // Method should only be used when multiple requests are expected + // RequestSelector will only display inspectorRequestChooser when there is more than one request + async openInspectorRequest(requestName: string) { + await this.inspector.open(); + await this.inspector.openInspectorRequestsView(); + this.log.debug(`Open Inspector request ${requestName}`); + await this.testSubjects.click('inspectorRequestChooser'); + await this.testSubjects.click(`inspectorRequestChooser${requestName}`); + } - async getMapboxStyle() { - log.debug('getMapboxStyle'); - await inspector.open(); - await this.openInspectorMapView(); - await testSubjects.click('mapboxStyleTab'); - const mapboxStyleContainer = await testSubjects.find('mapboxStyleContainer'); - const mapboxStyleJson = await mapboxStyleContainer.getVisibleText(); - await inspector.close(); - let mapboxStyle; - try { - mapboxStyle = JSON.parse(mapboxStyleJson); - } catch (err) { - throw new Error(`Unable to parse mapbox style, error: ${err.message}`); - } - return mapboxStyle; - } + async doesInspectorHaveRequests() { + await this.inspector.open(); + await this.inspector.openInspectorRequestsView(); + return await this.testSubjects.exists('inspectorNoRequestsMessage'); + } - async getResponse(requestName: string) { - await inspector.open(); - const response = await this._getResponse(requestName); - await inspector.close(); - return response; - } + async getMapboxStyle() { + this.log.debug('getMapboxStyle'); + await this.inspector.open(); + await this.openInspectorMapView(); + await this.testSubjects.click('mapboxStyleTab'); + const mapboxStyleContainer = await this.testSubjects.find('mapboxStyleContainer'); + const mapboxStyleJson = await mapboxStyleContainer.getVisibleText(); + await this.inspector.close(); + let mapboxStyle; + try { + mapboxStyle = JSON.parse(mapboxStyleJson); + } catch (err) { + throw new Error(`Unable to parse mapbox style, error: ${err.message}`); + } + return mapboxStyle; + } - async _getResponse(requestName: string) { - if (requestName) { - await testSubjects.click('inspectorRequestChooser'); - await testSubjects.click(`inspectorRequestChooser${requestName}`); - } - await inspector.openInspectorRequestsView(); - await testSubjects.click('inspectorRequestDetailResponse'); - await find.byCssSelector('.react-monaco-editor-container'); - const responseBody = await monacoEditor.getCodeEditorValue(); - return JSON.parse(responseBody); - } + async getResponse(requestName: string) { + await this.inspector.open(); + const response = await this._getResponse(requestName); + await this.inspector.close(); + return response; + } - async getResponseFromDashboardPanel(panelTitle: string, requestName: string) { - await dashboardPanelActions.openInspectorByTitle(panelTitle); - const response = await this._getResponse(requestName); - await inspector.close(); - return response; + async _getResponse(requestName: string) { + if (requestName) { + await this.testSubjects.click('inspectorRequestChooser'); + await this.testSubjects.click(`inspectorRequestChooser${requestName}`); } + await this.inspector.openInspectorRequestsView(); + await this.testSubjects.click('inspectorRequestDetailResponse'); + await this.find.byCssSelector('.react-monaco-editor-container'); + const responseBody = await this.monacoEditor.getCodeEditorValue(); + return JSON.parse(responseBody); + } - getInspectorStatRowHit(stats: string[][], rowName: string) { - const STATS_ROW_NAME_INDEX = 0; - const STATS_ROW_VALUE_INDEX = 1; + async getResponseFromDashboardPanel(panelTitle: string, requestName: string) { + await this.dashboardPanelActions.openInspectorByTitle(panelTitle); + const response = await this._getResponse(requestName); + await this.inspector.close(); + return response; + } - const statsRow = stats.find((row) => { - return row[STATS_ROW_NAME_INDEX] === rowName; - }); - if (!statsRow) { - throw new Error(`Unable to find value for row ${rowName} in ${stats}`); - } + getInspectorStatRowHit(stats: string[][], rowName: string) { + const STATS_ROW_NAME_INDEX = 0; + const STATS_ROW_VALUE_INDEX = 1; - return statsRow[STATS_ROW_VALUE_INDEX]; + const statsRow = stats.find((row) => { + return row[STATS_ROW_NAME_INDEX] === rowName; + }); + if (!statsRow) { + throw new Error(`Unable to find value for row ${rowName} in ${stats}`); } - async triggerSingleRefresh(refreshInterval: number) { - log.debug(`triggerSingleRefresh, refreshInterval: ${refreshInterval}`); - await PageObjects.timePicker.resumeAutoRefresh(); - log.debug('waiting to give time for refresh timer to fire'); - await PageObjects.common.sleep(refreshInterval + refreshInterval / 2); - await PageObjects.timePicker.pauseAutoRefresh(); - await this.waitForLayersToLoad(); - } + return statsRow[STATS_ROW_VALUE_INDEX]; + } - async lockTooltipAtPosition(xOffset: number, yOffset: number) { - await retry.try(async () => { - const mapContainerElement = await testSubjects.find('mapContainer'); - await mapContainerElement.moveMouseTo({ xOffset, yOffset }); - await mapContainerElement.clickMouseButton({ xOffset, yOffset }); - // Close button is only displayed with tooltip is locked - const hasCloseButton = await testSubjects.exists('mapTooltipCloseButton'); - if (!hasCloseButton) { - throw new Error('Tooltip is not locked at position'); - } - }); - } + async triggerSingleRefresh(refreshInterval: number) { + this.log.debug(`triggerSingleRefresh, refreshInterval: ${refreshInterval}`); + await this.timePicker.resumeAutoRefresh(); + this.log.debug('waiting to give time for refresh timer to fire'); + await this.common.sleep(refreshInterval + refreshInterval / 2); + await this.timePicker.pauseAutoRefresh(); + await this.waitForLayersToLoad(); + } - async setStyleByValue(styleName: string, fieldName: string) { - await testSubjects.selectValue(`staticDynamicSelect_${styleName}`, 'DYNAMIC'); - await comboBox.set(`styleFieldSelect_${styleName}`, fieldName); - } + async lockTooltipAtPosition(xOffset: number, yOffset: number) { + await this.retry.try(async () => { + const mapContainerElement = await this.testSubjects.find('mapContainer'); + await mapContainerElement.moveMouseTo({ xOffset, yOffset }); + await mapContainerElement.clickMouseButton({ xOffset, yOffset }); + // Close button is only displayed with tooltip is locked + const hasCloseButton = await this.testSubjects.exists('mapTooltipCloseButton'); + if (!hasCloseButton) { + throw new Error('Tooltip is not locked at position'); + } + }); + } - async selectCustomColorRamp(styleName: string) { - // open super select menu - await testSubjects.click(`colorMapSelect_${styleName}`); - // Click option - await testSubjects.click(`colorMapSelectOption_CUSTOM_COLOR_MAP`); - } + async setStyleByValue(styleName: string, fieldName: string) { + await this.testSubjects.selectValue(`staticDynamicSelect_${styleName}`, 'DYNAMIC'); + await this.comboBox.set(`styleFieldSelect_${styleName}`, fieldName); + } - async getCategorySuggestions() { - return await comboBox.getOptionsList(`colorStopInput1`); - } + async selectCustomColorRamp(styleName: string) { + // open super select menu + await this.testSubjects.click(`colorMapSelect_${styleName}`); + // Click option + await this.testSubjects.click(`colorMapSelectOption_CUSTOM_COLOR_MAP`); + } - async enableAutoFitToBounds() { - await testSubjects.click('openSettingsButton'); - const isEnabled = await testSubjects.getAttribute('autoFitToDataBoundsSwitch', 'checked'); - if (!isEnabled) { - await retry.try(async () => { - await testSubjects.click('autoFitToDataBoundsSwitch'); - const ensureEnabled = await testSubjects.getAttribute( - 'autoFitToDataBoundsSwitch', - 'checked' - ); - if (!ensureEnabled) { - throw new Error('autoFitToDataBoundsSwitch is not enabled'); - } - }); - } - await testSubjects.click('mapSettingSubmitButton'); - } + async getCategorySuggestions() { + return await this.comboBox.getOptionsList(`colorStopInput1`); + } - async refreshAndClearUnsavedChangesWarning() { - await browser.refresh(); - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); + async enableAutoFitToBounds() { + await this.testSubjects.click('openSettingsButton'); + const isEnabled = await this.testSubjects.getAttribute('autoFitToDataBoundsSwitch', 'checked'); + if (!isEnabled) { + await this.retry.try(async () => { + await this.testSubjects.click('autoFitToDataBoundsSwitch'); + const ensureEnabled = await this.testSubjects.getAttribute( + 'autoFitToDataBoundsSwitch', + 'checked' + ); + if (!ensureEnabled) { + throw new Error('autoFitToDataBoundsSwitch is not enabled'); + } + }); } + await this.testSubjects.click('mapSettingSubmitButton'); + } + + async refreshAndClearUnsavedChangesWarning() { + await this.browser.refresh(); + // accept alert if it pops up + const alert = await this.browser.getAlert(); + await alert?.accept(); } - return new GisPage(); } diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 28d72ef844615..bd9e5100e0c57 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -6,7 +6,7 @@ */ import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; interface Node { circle: WebElementWrapper; @@ -20,264 +20,263 @@ interface Edge { element: WebElementWrapper; } -export function GraphPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header']); - const retry = getService('retry'); - const browser = getService('browser'); - - class GraphPage { - async selectIndexPattern(pattern: string) { - await testSubjects.click('graphDatasourceButton'); - await testSubjects.click(`savedObjectTitle${pattern.split(' ').join('-')}`); - // wait till add fields button becomes available, then the index pattern is loaded completely - await testSubjects.waitForAttributeToChange( - 'graph-add-field-button', - 'aria-disabled', - 'false' - ); - // Need document focus to not be on `graphDatasourceButton` so its tooltip does not - // obscure the next intended click area. Focus the adjaecnt input instead. - await testSubjects.click('queryInput'); - } +export class GraphPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + + async selectIndexPattern(pattern: string) { + await this.testSubjects.click('graphDatasourceButton'); + await this.testSubjects.click(`savedObjectTitle${pattern.split(' ').join('-')}`); + // wait till add fields button becomes available, then the index pattern is loaded completely + await this.testSubjects.waitForAttributeToChange( + 'graph-add-field-button', + 'aria-disabled', + 'false' + ); + // Need document focus to not be on `graphDatasourceButton` so its tooltip does not + // obscure the next intended click area. Focus the adjaecnt input instead. + await this.testSubjects.click('queryInput'); + } - async clickAddField() { - await retry.try(async () => { - await testSubjects.click('graph-add-field-button'); - await testSubjects.existOrFail('graph-field-search', { timeout: 3000 }); - }); - } + async clickAddField() { + await this.retry.try(async () => { + await this.testSubjects.click('graph-add-field-button'); + await this.testSubjects.existOrFail('graph-field-search', { timeout: 3000 }); + }); + } - async selectField(field: string) { - await testSubjects.setValue('graph-field-search', field); - await find.clickDisplayedByCssSelector(`[title="${field}"]`); - } + async selectField(field: string) { + await this.testSubjects.setValue('graph-field-search', field); + await this.find.clickDisplayedByCssSelector(`[title="${field}"]`); + } - async addFields(fields: string[]) { - log.debug('click Add Field icon'); - await this.clickAddField(); - for (const field of fields) { - log.debug('select field ' + field); - await this.selectField(field); - } + async addFields(fields: string[]) { + this.log.debug('click Add Field icon'); + await this.clickAddField(); + for (const field of fields) { + this.log.debug('select field ' + field); + await this.selectField(field); } + } - async query(str: string) { - await testSubjects.click('queryInput'); - await testSubjects.setValue('queryInput', str); - await testSubjects.click('graph-explore-button'); - } + async query(str: string) { + await this.testSubjects.click('queryInput'); + await this.testSubjects.setValue('queryInput', str); + await this.testSubjects.click('graph-explore-button'); + } - private getPositionAsString(x: string, y: string) { - return `${x}-${y}`; - } + private getPositionAsString(x: string, y: string) { + return `${x}-${y}`; + } - private async getCirclePosition(element: WebElementWrapper) { - const x = await element.getAttribute('cx'); - const y = await element.getAttribute('cy'); - return this.getPositionAsString(x, y); - } + private async getCirclePosition(element: WebElementWrapper) { + const x = await element.getAttribute('cx'); + const y = await element.getAttribute('cy'); + return this.getPositionAsString(x, y); + } - private async getLinePositions(element: WebElementWrapper) { - const x1 = await element.getAttribute('x1'); - const y1 = await element.getAttribute('y1'); - const x2 = await element.getAttribute('x2'); - const y2 = await element.getAttribute('y2'); - return [this.getPositionAsString(x1, y1), this.getPositionAsString(x2, y2)]; - } + private async getLinePositions(element: WebElementWrapper) { + const x1 = await element.getAttribute('x1'); + const y1 = await element.getAttribute('y1'); + const x2 = await element.getAttribute('x2'); + const y2 = await element.getAttribute('y2'); + return [this.getPositionAsString(x1, y1), this.getPositionAsString(x2, y2)]; + } - async isolateEdge(from: string, to: string) { - // select all nodes - await testSubjects.click('graphSelectAll'); - - // go through all nodes and remove every node not source or target - const selections = await find.allByCssSelector('.gphSelectionList__field'); - for (const selection of selections) { - const labelElement = await selection.findByTagName('span'); - const selectionLabel = await labelElement.getVisibleText(); - log.debug('Looking at selection ' + selectionLabel); - if (selectionLabel !== from && selectionLabel !== to) { - (await selection.findByClassName('gphNode__text')).click(); - await PageObjects.common.sleep(200); - } + async isolateEdge(from: string, to: string) { + // select all nodes + await this.testSubjects.click('graphSelectAll'); + + // go through all nodes and remove every node not source or target + const selections = await this.find.allByCssSelector('.gphSelectionList__field'); + for (const selection of selections) { + const labelElement = await selection.findByTagName('span'); + const selectionLabel = await labelElement.getVisibleText(); + this.log.debug('Looking at selection ' + selectionLabel); + if (selectionLabel !== from && selectionLabel !== to) { + (await selection.findByClassName('gphNode__text')).click(); + await this.common.sleep(200); } + } - // invert selection to select all nodes not source or target - await testSubjects.click('graphInvertSelection'); + // invert selection to select all nodes not source or target + await this.testSubjects.click('graphInvertSelection'); - // remove all other nodes - await testSubjects.click('graphRemoveSelection'); - } + // remove all other nodes + await this.testSubjects.click('graphRemoveSelection'); + } - async stopLayout() { - if (await testSubjects.exists('graphPauseLayout')) { - await testSubjects.click('graphPauseLayout'); - } + async stopLayout() { + if (await this.testSubjects.exists('graphPauseLayout')) { + await this.testSubjects.click('graphPauseLayout'); } + } - async startLayout() { - if (await testSubjects.exists('graphResumeLayout')) { - await testSubjects.click('graphResumeLayout'); - } + async startLayout() { + if (await this.testSubjects.exists('graphResumeLayout')) { + await this.testSubjects.click('graphResumeLayout'); } + } - async getGraphObjects() { - await this.stopLayout(); - // read node labels directly from DOM because getVisibleText is not reliable for the way the graph is rendered - const nodeNames: string[] = await browser.execute(` - const elements = document.querySelectorAll('#graphSvg text.gphNode__label'); - return [...elements].map(element => element.innerHTML); - `); - const graphElements = await find.allByCssSelector('#graphSvg line, #graphSvg circle'); - const nodes: Node[] = []; - const nodePositionMap: Record = {}; - const edges: Edge[] = []; - - // find all nodes and save their positions - for (const element of graphElements) { - const tagName: string = await element.getTagName(); - // check the position of the circle element - if (tagName === 'circle') { - nodes.push({ circle: element, label: nodeNames[nodes.length] }); - const position = await this.getCirclePosition(element); - nodePositionMap[position] = nodes.length - 1; - } + async getGraphObjects() { + await this.stopLayout(); + // read node labels directly from DOM because getVisibleText is not reliable for the way the graph is rendered + const nodeNames: string[] = await this.browser.execute(` + const elements = document.querySelectorAll('#graphSvg text.gphNode__label'); + return [...elements].map(element => element.innerHTML); + `); + const graphElements = await this.find.allByCssSelector('#graphSvg line, #graphSvg circle'); + const nodes: Node[] = []; + const nodePositionMap: Record = {}; + const edges: Edge[] = []; + + // find all nodes and save their positions + for (const element of graphElements) { + const tagName: string = await element.getTagName(); + // check the position of the circle element + if (tagName === 'circle') { + nodes.push({ circle: element, label: nodeNames[nodes.length] }); + const position = await this.getCirclePosition(element); + nodePositionMap[position] = nodes.length - 1; } + } - // find all edges - for (const element of graphElements) { - const tagName: string = await element.getTagName(); - if (tagName === 'line') { - const [sourcePosition, targetPosition] = await this.getLinePositions(element); - const lineStyle = await element.getAttribute('style'); - // grep out the width of the connection from the style attribute - const strokeWidth = Number(/stroke-width: ?(\d+(\.\d+)?)/.exec(lineStyle)![1]); - edges.push({ - element, - width: strokeWidth, - // look up source and target node by matching start and end coordinates - // of the edges and the nodes - sourceNode: nodes[nodePositionMap[sourcePosition]], - targetNode: nodes[nodePositionMap[targetPosition]], - }); - } + // find all edges + for (const element of graphElements) { + const tagName: string = await element.getTagName(); + if (tagName === 'line') { + const [sourcePosition, targetPosition] = await this.getLinePositions(element); + const lineStyle = await element.getAttribute('style'); + // grep out the width of the connection from the style attribute + const strokeWidth = Number(/stroke-width: ?(\d+(\.\d+)?)/.exec(lineStyle)![1]); + edges.push({ + element, + width: strokeWidth, + // look up source and target node by matching start and end coordinates + // of the edges and the nodes + sourceNode: nodes[nodePositionMap[sourcePosition]], + targetNode: nodes[nodePositionMap[targetPosition]], + }); } + } - await this.startLayout(); + await this.startLayout(); - return { - nodes, - edges, - }; - } + return { + nodes, + edges, + }; + } - async createWorkspace() { - await testSubjects.click('graphCreateGraphPromptButton'); - } + async createWorkspace() { + await this.testSubjects.click('graphCreateGraphPromptButton'); + } - async newGraph() { - log.debug('Click New Workspace'); - await retry.try(async () => { - await testSubjects.click('graphNewButton'); - await testSubjects.existOrFail('confirmModal', { timeout: 3000 }); - }); - await PageObjects.common.clickConfirmOnModal(); - await testSubjects.existOrFail('graphGuidancePanel'); - } + async newGraph() { + this.log.debug('Click New Workspace'); + await this.retry.try(async () => { + await this.testSubjects.click('graphNewButton'); + await this.testSubjects.existOrFail('confirmModal', { timeout: 3000 }); + }); + await this.common.clickConfirmOnModal(); + await this.testSubjects.existOrFail('graphGuidancePanel'); + } - async saveGraph(name: string) { - await retry.try(async () => { - await testSubjects.click('graphSaveButton'); - await testSubjects.existOrFail('savedObjectTitle', { timeout: 3000 }); - }); - await testSubjects.setValue('savedObjectTitle', name); - await testSubjects.click('confirmSaveSavedObjectButton'); + async saveGraph(name: string) { + await this.retry.try(async () => { + await this.testSubjects.click('graphSaveButton'); + await this.testSubjects.existOrFail('savedObjectTitle', { timeout: 3000 }); + }); + await this.testSubjects.setValue('savedObjectTitle', name); + await this.testSubjects.click('confirmSaveSavedObjectButton'); - // Confirm that the Graph has been saved. - return await testSubjects.exists('saveGraphSuccess', { timeout: 10000 }); - } + // Confirm that the Graph has been saved. + return await this.testSubjects.exists('saveGraphSuccess', { timeout: 10000 }); + } - async getSearchFilter() { - const searchFilter = await find.allByCssSelector('main .euiFieldSearch'); - return searchFilter[0]; - } + async getSearchFilter() { + const searchFilter = await this.find.allByCssSelector('main .euiFieldSearch'); + return searchFilter[0]; + } - async searchForWorkspaceWithName(name: string) { - await retry.try(async () => { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - await searchFilter.type(name); - await PageObjects.common.pressEnterKey(); - await find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); - }); - - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async searchForWorkspaceWithName(name: string) { + await this.retry.try(async () => { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); + await searchFilter.type(name); + await this.common.pressEnterKey(); + await this.find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); + }); + + await this.header.waitUntilLoadingHasFinished(); + } - async goToListingPage() { - await retry.try(async () => { - await testSubjects.click('breadcrumb graphHomeBreadcrumb first'); - await testSubjects.existOrFail('graphLandingPage', { timeout: 3000 }); - }); - } + async goToListingPage() { + await this.retry.try(async () => { + await this.testSubjects.click('breadcrumb graphHomeBreadcrumb first'); + await this.testSubjects.existOrFail('graphLandingPage', { timeout: 3000 }); + }); + } - async openGraph(name: string) { - await this.goToListingPage(); - await this.searchForWorkspaceWithName(name); - await find.clickByLinkText(name); - // wait for nodes to show up - if (!(await find.existsByCssSelector('.gphNode', 10000))) { - throw new Error('nodes did not show up'); - } - // let force simulation settle down before continuing - await PageObjects.common.sleep(5000); + async openGraph(name: string) { + await this.goToListingPage(); + await this.searchForWorkspaceWithName(name); + await this.find.clickByLinkText(name); + // wait for nodes to show up + if (!(await this.find.existsByCssSelector('.gphNode', 10000))) { + throw new Error('nodes did not show up'); } + // let force simulation settle down before continuing + await this.common.sleep(5000); + } - async deleteGraph(name: string) { - await testSubjects.click('checkboxSelectAll'); - await this.clickDeleteSelectedWorkspaces(); - await PageObjects.common.clickConfirmOnModal(); - await testSubjects.find('graphCreateGraphPromptButton'); - } + async deleteGraph(name: string) { + await this.testSubjects.click('checkboxSelectAll'); + await this.clickDeleteSelectedWorkspaces(); + await this.common.clickConfirmOnModal(); + await this.testSubjects.find('graphCreateGraphPromptButton'); + } - async getWorkspaceCount() { - const workspaceTitles = await find.allByCssSelector( - '[data-test-subj^="graphListingTitleLink"]' - ); - return workspaceTitles.length; - } + async getWorkspaceCount() { + const workspaceTitles = await this.find.allByCssSelector( + '[data-test-subj^="graphListingTitleLink"]' + ); + return workspaceTitles.length; + } - async clickDeleteSelectedWorkspaces() { - await testSubjects.click('deleteSelectedItems'); - } + async clickDeleteSelectedWorkspaces() { + await this.testSubjects.click('deleteSelectedItems'); + } - async getVennTerm1() { - const el = await find.byCssSelector('span.gphLinkSummary__term--1'); - return await el.getVisibleText(); - } + async getVennTerm1() { + const el = await this.find.byCssSelector('span.gphLinkSummary__term--1'); + return await el.getVisibleText(); + } - async getVennTerm2() { - const el = await find.byCssSelector('span.gphLinkSummary__term--2'); - return await el.getVisibleText(); - } + async getVennTerm2() { + const el = await this.find.byCssSelector('span.gphLinkSummary__term--2'); + return await el.getVisibleText(); + } - async getSmallVennTerm1() { - const el = await find.byCssSelector('small.gphLinkSummary__term--1'); - return await el.getVisibleText(); - } + async getSmallVennTerm1() { + const el = await this.find.byCssSelector('small.gphLinkSummary__term--1'); + return await el.getVisibleText(); + } - async getSmallVennTerm12() { - const el = await find.byCssSelector('small.gphLinkSummary__term--1-2'); - return await el.getVisibleText(); - } + async getSmallVennTerm12() { + const el = await this.find.byCssSelector('small.gphLinkSummary__term--1-2'); + return await el.getVisibleText(); + } - async getSmallVennTerm2() { - const el = await find.byCssSelector('small.gphLinkSummary__term--2'); - return await el.getVisibleText(); - } + async getSmallVennTerm2() { + const el = await this.find.byCssSelector('small.gphLinkSummary__term--2'); + return await el.getVisibleText(); } - return new GraphPage(); } diff --git a/x-pack/test/functional/page_objects/grok_debugger_page.ts b/x-pack/test/functional/page_objects/grok_debugger_page.ts index dc875588f381e..89017683f632f 100644 --- a/x-pack/test/functional/page_objects/grok_debugger_page.ts +++ b/x-pack/test/functional/page_objects/grok_debugger_page.ts @@ -5,16 +5,14 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function GrokDebuggerPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['common']); - const grokDebugger = getService('grokDebugger'); +export class GrokDebuggerPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly grokDebugger = this.ctx.getService('grokDebugger'); - return new (class LogstashPage { - async gotoGrokDebugger() { - await PageObjects.common.navigateToApp('grokDebugger'); - await grokDebugger.assertExists(); - } - })(); + async gotoGrokDebugger() { + await this.common.navigateToApp('grokDebugger'); + await this.grokDebugger.assertExists(); + } } diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index e83420a9cea1d..a362fd7e4b7c2 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -8,22 +8,21 @@ import { pageObjects as kibanaFunctionalPageObjects } from '../../../../test/functional/page_objects'; import { CanvasPageProvider } from './canvas_page'; -import { SecurityPageProvider } from './security_page'; -import { MonitoringPageProvider } from './monitoring_page'; -// @ts-ignore not ts yet -import { LogstashPageProvider } from './logstash_page'; -import { GraphPageProvider } from './graph_page'; -import { GrokDebuggerPageProvider } from './grok_debugger_page'; -import { WatcherPageProvider } from './watcher_page'; -import { ReportingPageProvider } from './reporting_page'; -import { AccountSettingProvider } from './account_settings_page'; +import { SecurityPageObject } from './security_page'; +import { MonitoringPageObject } from './monitoring_page'; +import { LogstashPageObject } from './logstash_page'; +import { GraphPageObject } from './graph_page'; +import { GrokDebuggerPageObject } from './grok_debugger_page'; +import { WatcherPageObject } from './watcher_page'; +import { ReportingPageObject } from './reporting_page'; +import { AccountSettingsPageObject } from './account_settings_page'; import { InfraHomePageProvider } from './infra_home_page'; import { InfraLogsPageProvider } from './infra_logs_page'; -import { GisPageProvider } from './gis_page'; -import { StatusPagePageProvider } from './status_page'; -import { UpgradeAssistantPageProvider } from './upgrade_assistant_page'; -import { RollupPageProvider } from './rollup_page'; -import { UptimePageProvider } from './uptime_page'; +import { GisPageObject } from './gis_page'; +import { StatusPageObject } from './status_page'; +import { UpgradeAssistantPageObject } from './upgrade_assistant_page'; +import { RollupPageObject } from './rollup_page'; +import { UptimePageObject } from './uptime_page'; import { SyntheticsIntegrationPageProvider } from './synthetics_integration_page'; import { ApiKeysPageProvider } from './api_keys_page'; import { LicenseManagementPageProvider } from './license_management_page'; @@ -36,43 +35,43 @@ import { CopySavedObjectsToSpacePageProvider } from './copy_saved_objects_to_spa import { LensPageProvider } from './lens_page'; import { InfraMetricExplorerProvider } from './infra_metric_explorer'; import { RoleMappingsPageProvider } from './role_mappings_page'; -import { SpaceSelectorPageProvider } from './space_selector_page'; +import { SpaceSelectorPageObject } from './space_selector_page'; import { IngestPipelinesPageProvider } from './ingest_pipelines_page'; -import { TagManagementPageProvider } from './tag_management_page'; -import { NavigationalSearchProvider } from './navigational_search'; +import { TagManagementPageObject } from './tag_management_page'; +import { NavigationalSearchPageObject } from './navigational_search'; import { SearchSessionsPageProvider } from './search_sessions_management_page'; -import { DetectionsPageProvider } from '../../security_solution_ftr/page_objects/detections'; -import { BannersPageProvider } from './banners_page'; +import { DetectionsPageObject } from '../../security_solution_ftr/page_objects/detections'; +import { BannersPageObject } from './banners_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones export const pageObjects = { ...kibanaFunctionalPageObjects, canvas: CanvasPageProvider, - security: SecurityPageProvider, - accountSetting: AccountSettingProvider, - monitoring: MonitoringPageProvider, - logstash: LogstashPageProvider, - graph: GraphPageProvider, - grokDebugger: GrokDebuggerPageProvider, - watcher: WatcherPageProvider, - reporting: ReportingPageProvider, - spaceSelector: SpaceSelectorPageProvider, + security: SecurityPageObject, + accountSetting: AccountSettingsPageObject, + monitoring: MonitoringPageObject, + logstash: LogstashPageObject, + graph: GraphPageObject, + grokDebugger: GrokDebuggerPageObject, + watcher: WatcherPageObject, + reporting: ReportingPageObject, + spaceSelector: SpaceSelectorPageObject, infraHome: InfraHomePageProvider, infraMetricExplorer: InfraMetricExplorerProvider, infraLogs: InfraLogsPageProvider, - maps: GisPageProvider, - statusPage: StatusPagePageProvider, - upgradeAssistant: UpgradeAssistantPageProvider, - uptime: UptimePageProvider, + maps: GisPageObject, + statusPage: StatusPageObject, + upgradeAssistant: UpgradeAssistantPageObject, + uptime: UptimePageObject, syntheticsIntegration: SyntheticsIntegrationPageProvider, - rollup: RollupPageProvider, + rollup: RollupPageObject, apiKeys: ApiKeysPageProvider, licenseManagement: LicenseManagementPageProvider, indexManagement: IndexManagementPageProvider, searchSessionsManagement: SearchSessionsPageProvider, indexLifecycleManagement: IndexLifecycleManagementPageProvider, - tagManagement: TagManagementPageProvider, + tagManagement: TagManagementPageObject, snapshotRestore: SnapshotRestorePageProvider, crossClusterReplication: CrossClusterReplicationPageProvider, remoteClusters: RemoteClustersPageProvider, @@ -80,7 +79,7 @@ export const pageObjects = { lens: LensPageProvider, roleMappings: RoleMappingsPageProvider, ingestPipelines: IngestPipelinesPageProvider, - navigationalSearch: NavigationalSearchProvider, - banners: BannersPageProvider, - detections: DetectionsPageProvider, + navigationalSearch: NavigationalSearchPageObject, + banners: BannersPageObject, + detections: DetectionsPageObject, }; diff --git a/x-pack/test/functional/page_objects/logstash_page.js b/x-pack/test/functional/page_objects/logstash_page.js deleted file mode 100644 index 3b12728d29610..0000000000000 --- a/x-pack/test/functional/page_objects/logstash_page.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export function LogstashPageProvider({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['common']); - const pipelineList = getService('pipelineList'); - const pipelineEditor = getService('pipelineEditor'); - - return new (class LogstashPage { - async gotoPipelineList() { - await PageObjects.common.navigateToApp('logstashPipelines'); - await pipelineList.assertExists(); - } - - async gotoNewPipelineEditor() { - await this.gotoPipelineList(); - await pipelineList.clickAdd(); - await pipelineEditor.assertExists(); - } - })(); -} diff --git a/x-pack/test/functional/page_objects/logstash_page.ts b/x-pack/test/functional/page_objects/logstash_page.ts new file mode 100644 index 0000000000000..37b907b34fa32 --- /dev/null +++ b/x-pack/test/functional/page_objects/logstash_page.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrService } from '../ftr_provider_context'; + +export class LogstashPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly pipelineList = this.ctx.getService('pipelineList'); + private readonly pipelineEditor = this.ctx.getService('pipelineEditor'); + + async gotoPipelineList() { + await this.common.navigateToApp('logstashPipelines'); + await this.pipelineList.assertExists(); + } + + async gotoNewPipelineEditor() { + await this.gotoPipelineList(); + await this.pipelineList.clickAdd(); + await this.pipelineEditor.assertExists(); + } +} diff --git a/x-pack/test/functional/page_objects/monitoring_page.ts b/x-pack/test/functional/page_objects/monitoring_page.ts index d32528f44613c..151b6733509e6 100644 --- a/x-pack/test/functional/page_objects/monitoring_page.ts +++ b/x-pack/test/functional/page_objects/monitoring_page.ts @@ -5,45 +5,45 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; - -export function MonitoringPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'header', 'security', 'login']); - const testSubjects = getService('testSubjects'); - return new (class MonitoringPage { - async getAccessDeniedMessage() { - return testSubjects.getVisibleText('accessDeniedTitle'); - } - - async clickBreadcrumb(subj: string) { - return testSubjects.click(subj); - } - - async assertTableNoData(subj: string) { - if (!(await testSubjects.exists(subj))) { - throw new Error('Expected to find the no data message'); - } - } +import { FtrService } from '../ftr_provider_context'; - async tableGetRows(subj: string) { - const table = await testSubjects.find(subj); - return table.findAllByTagName('tr'); - } +export class MonitoringPageObject extends FtrService { + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - async tableGetRowsFromContainer(subj: string) { - const table = await testSubjects.find(subj); - const tbody = await table.findByTagName('tbody'); - return tbody.findAllByTagName('tr'); - } + async getAccessDeniedMessage() { + return this.testSubjects.getVisibleText('accessDeniedTitle'); + } - async tableSetFilter(subj: string, text: string) { - await testSubjects.setValue(subj, text); - await PageObjects.common.pressEnterKey(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clickBreadcrumb(subj: string) { + return this.testSubjects.click(subj); + } - async tableClearFilter(subj: string) { - return await testSubjects.setValue(subj, ' \uE003'); // space and backspace to trigger onChange event + async assertTableNoData(subj: string) { + if (!(await this.testSubjects.exists(subj))) { + throw new Error('Expected to find the no data message'); } - })(); + } + + async tableGetRows(subj: string) { + const table = await this.testSubjects.find(subj); + return table.findAllByTagName('tr'); + } + + async tableGetRowsFromContainer(subj: string) { + const table = await this.testSubjects.find(subj); + const tbody = await table.findByTagName('tbody'); + return tbody.findAllByTagName('tr'); + } + + async tableSetFilter(subj: string, text: string) { + await this.testSubjects.setValue(subj, text); + await this.common.pressEnterKey(); + await this.header.waitUntilLoadingHasFinished(); + } + + async tableClearFilter(subj: string) { + return await this.testSubjects.setValue(subj, ' \uE003'); // space and backspace to trigger onChange event + } } diff --git a/x-pack/test/functional/page_objects/navigational_search.ts b/x-pack/test/functional/page_objects/navigational_search.ts index a3f91e7921cc4..46fed2814c0df 100644 --- a/x-pack/test/functional/page_objects/navigational_search.ts +++ b/x-pack/test/functional/page_objects/navigational_search.ts @@ -6,7 +6,7 @@ */ import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; interface SearchResult { label: string; @@ -14,88 +14,84 @@ interface SearchResult { const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); -export function NavigationalSearchProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const testSubjects = getService('testSubjects'); +export class NavigationalSearchPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - class NavigationalSearch { - async focus() { - const field = await testSubjects.find('nav-search-input'); - await field.click(); - } + async focus() { + const field = await this.testSubjects.find('nav-search-input'); + await field.click(); + } - async blur() { - await testSubjects.click('helpMenuButton'); - await testSubjects.click('helpMenuButton'); - await find.waitForDeletedByCssSelector('.navSearch__panel'); - } + async blur() { + await this.testSubjects.click('helpMenuButton'); + await this.testSubjects.click('helpMenuButton'); + await this.find.waitForDeletedByCssSelector('.navSearch__panel'); + } - async searchFor( - term: string, - { clear = true, wait = true }: { clear?: boolean; wait?: boolean } = {} - ) { - if (clear) { - await this.clearField(); - } - const field = await testSubjects.find('nav-search-input'); - await field.type(term); - if (wait) { - await this.waitForResultsLoaded(); - } + async searchFor( + term: string, + { clear = true, wait = true }: { clear?: boolean; wait?: boolean } = {} + ) { + if (clear) { + await this.clearField(); } - - async getFieldValue() { - const field = await testSubjects.find('nav-search-input'); - return field.getAttribute('value'); - } - - async clearField() { - const field = await testSubjects.find('nav-search-input'); - await field.clearValueWithKeyboard(); + const field = await this.testSubjects.find('nav-search-input'); + await field.type(term); + if (wait) { + await this.waitForResultsLoaded(); } + } - async isPopoverDisplayed() { - return await find.existsByCssSelector('.navSearch__panel'); - } + async getFieldValue() { + const field = await this.testSubjects.find('nav-search-input'); + return field.getAttribute('value'); + } - async clickOnOption(index: number) { - const options = await testSubjects.findAll('nav-search-option'); - await options[index].click(); - } + async clearField() { + const field = await this.testSubjects.find('nav-search-input'); + await field.clearValueWithKeyboard(); + } - async waitForResultsLoaded(waitUntil: number = 3000) { - await testSubjects.exists('nav-search-option'); - // results are emitted in multiple batches. Each individual batch causes a re-render of - // the component, causing the current elements to become stale. We can't perform DOM access - // without heavy flakiness in this situation. - // there is NO ui indication of any kind to detect when all the emissions are done, - // so we are forced to fallback to awaiting a given amount of time once the first options are displayed. - await delay(waitUntil); - } + async isPopoverDisplayed() { + return await this.find.existsByCssSelector('.navSearch__panel'); + } - async getDisplayedResults() { - const resultElements = await testSubjects.findAll('nav-search-option'); - return Promise.all(resultElements.map((el) => this.convertResultElement(el))); - } + async clickOnOption(index: number) { + const options = await this.testSubjects.findAll('nav-search-option'); + await options[index].click(); + } - async isNoResultsPlaceholderDisplayed(checkAfter: number = 3000) { - // see comment in `waitForResultsLoaded` - await delay(checkAfter); - return testSubjects.exists('nav-search-no-results'); - } + async waitForResultsLoaded(waitUntil: number = 3000) { + await this.testSubjects.exists('nav-search-option'); + // results are emitted in multiple batches. Each individual batch causes a re-render of + // the component, causing the current elements to become stale. We can't perform DOM access + // without heavy flakiness in this situation. + // there is NO ui indication of any kind to detect when all the emissions are done, + // so we are forced to fallback to awaiting a given amount of time once the first options are displayed. + await delay(waitUntil); + } - private async convertResultElement(resultEl: WebElementWrapper): Promise { - const labelEl = await find.allDescendantDisplayedByCssSelector( - '.euiSelectableTemplateSitewide__listItemTitle', - resultEl - ); - const label = await labelEl[0].getVisibleText(); + async getDisplayedResults() { + const resultElements = await this.testSubjects.findAll('nav-search-option'); + return Promise.all(resultElements.map((el) => this.convertResultElement(el))); + } - return { - label, - }; - } + async isNoResultsPlaceholderDisplayed(checkAfter: number = 3000) { + // see comment in `waitForResultsLoaded` + await delay(checkAfter); + return this.testSubjects.exists('nav-search-no-results'); } - return new NavigationalSearch(); + private async convertResultElement(resultEl: WebElementWrapper): Promise { + const labelEl = await this.find.allDescendantDisplayedByCssSelector( + '.euiSelectableTemplateSitewide__listItemTitle', + resultEl + ); + const label = await labelEl[0].getVisibleText(); + + return { + label, + }; + } } diff --git a/x-pack/test/functional/page_objects/reporting_page.ts b/x-pack/test/functional/page_objects/reporting_page.ts index 746df14d31ac4..e8999999ce50b 100644 --- a/x-pack/test/functional/page_objects/reporting_page.ts +++ b/x-pack/test/functional/page_objects/reporting_page.ts @@ -9,148 +9,152 @@ import expect from '@kbn/expect'; import { format as formatUrl } from 'url'; import supertestAsPromised from 'supertest-as-promised'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function ReportingPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const config = getService('config'); - const log = getService('log'); - const retry = getService('retry'); - const security = getService('security'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['security', 'share', 'timePicker']); - - class ReportingPage { - async forceSharedItemsContainerSize({ width }: { width: number }) { - await browser.execute(` - var el = document.querySelector('[data-shared-items-container]'); - el.style.flex="none"; - el.style.width="${width}px"; - `); - } +import { FtrService } from '../ftr_provider_context'; + +export class ReportingPageObject extends FtrService { + private readonly browser = this.ctx.getService('browser'); + private readonly config = this.ctx.getService('config'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly security = this.ctx.getService('security'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly share = this.ctx.getPageObject('share'); + private readonly timePicker = this.ctx.getPageObject('timePicker'); + + async forceSharedItemsContainerSize({ width }: { width: number }) { + await this.browser.execute(` + var el = document.querySelector('[data-shared-items-container]'); + el.style.flex="none"; + el.style.width="${width}px"; + `); + } - async getReportURL(timeout: number) { - log.debug('getReportURL'); + async getReportURL(timeout: number) { + this.log.debug('getReportURL'); - const url = await testSubjects.getAttribute('downloadCompletedReportButton', 'href', timeout); + const url = await this.testSubjects.getAttribute( + 'downloadCompletedReportButton', + 'href', + timeout + ); - log.debug(`getReportURL got url: ${url}`); + this.log.debug(`getReportURL got url: ${url}`); - return url; - } + return url; + } - async removeForceSharedItemsContainerSize() { - await browser.execute(` - var el = document.querySelector('[data-shared-items-container]'); - el.style.flex = null; - el.style.width = null; - `); - } + async removeForceSharedItemsContainerSize() { + await this.browser.execute(` + var el = document.querySelector('[data-shared-items-container]'); + el.style.flex = null; + el.style.width = null; + `); + } - async getResponse(fullUrl: string): Promise { - log.debug(`getResponse for ${fullUrl}`); - const kibanaServerConfig = config.get('servers.kibana'); - const baseURL = formatUrl({ - ...kibanaServerConfig, - auth: false, - }); - const urlWithoutBase = fullUrl.replace(baseURL, ''); - const res = await security.testUserSupertest.get(urlWithoutBase); - return res; - } + async getResponse(fullUrl: string): Promise { + this.log.debug(`getResponse for ${fullUrl}`); + const kibanaServerConfig = this.config.get('servers.kibana'); + const baseURL = formatUrl({ + ...kibanaServerConfig, + auth: false, + }); + const urlWithoutBase = fullUrl.replace(baseURL, ''); + const res = await this.security.testUserSupertest.get(urlWithoutBase); + return res; + } - async getRawPdfReportData(url: string): Promise { - log.debug(`getRawPdfReportData for ${url}`); - const response = await this.getResponse(url); - expect(response.body).to.be.a(Buffer); - return response.body as Buffer; - } + async getRawPdfReportData(url: string): Promise { + this.log.debug(`getRawPdfReportData for ${url}`); + const response = await this.getResponse(url); + expect(response.body).to.be.a(Buffer); + return response.body as Buffer; + } - async openCsvReportingPanel() { - log.debug('openCsvReportingPanel'); - await PageObjects.share.openShareMenuItem('CSV Reports'); - } + async openCsvReportingPanel() { + this.log.debug('openCsvReportingPanel'); + await this.share.openShareMenuItem('CSV Reports'); + } - async openPdfReportingPanel() { - log.debug('openPdfReportingPanel'); - await PageObjects.share.openShareMenuItem('PDF Reports'); - } + async openPdfReportingPanel() { + this.log.debug('openPdfReportingPanel'); + await this.share.openShareMenuItem('PDF Reports'); + } - async openPngReportingPanel() { - log.debug('openPngReportingPanel'); - await PageObjects.share.openShareMenuItem('PNG Reports'); - } + async openPngReportingPanel() { + this.log.debug('openPngReportingPanel'); + await this.share.openShareMenuItem('PNG Reports'); + } - async clearToastNotifications() { - const toasts = await testSubjects.findAll('toastCloseButton'); - await Promise.all(toasts.map(async (t) => await t.click())); - } + async clearToastNotifications() { + const toasts = await this.testSubjects.findAll('toastCloseButton'); + await Promise.all(toasts.map(async (t) => await t.click())); + } - async getQueueReportError() { - return await testSubjects.exists('queueReportError'); - } + async getQueueReportError() { + return await this.testSubjects.exists('queueReportError'); + } - async getGenerateReportButton() { - return await retry.try(async () => await testSubjects.find('generateReportButton')); - } + async getGenerateReportButton() { + return await this.retry.try(async () => await this.testSubjects.find('generateReportButton')); + } - async isGenerateReportButtonDisabled() { - const generateReportButton = await this.getGenerateReportButton(); - return await retry.try(async () => { - const isDisabled = await generateReportButton.getAttribute('disabled'); - return isDisabled; - }); - } + async isGenerateReportButtonDisabled() { + const generateReportButton = await this.getGenerateReportButton(); + return await this.retry.try(async () => { + const isDisabled = await generateReportButton.getAttribute('disabled'); + return isDisabled; + }); + } - async canReportBeCreated() { - await this.clickGenerateReportButton(); - const success = await this.checkForReportingToasts(); - return success; - } + async canReportBeCreated() { + await this.clickGenerateReportButton(); + const success = await this.checkForReportingToasts(); + return success; + } - async checkUsePrintLayout() { - // The print layout checkbox slides in as part of an animation, and tests can - // attempt to click it too quickly, leading to flaky tests. The 500ms wait allows - // the animation to complete before we attempt a click. - const menuAnimationDelay = 500; - await retry.tryForTime(menuAnimationDelay, () => testSubjects.click('usePrintLayout')); - } + async checkUsePrintLayout() { + // The print layout checkbox slides in as part of an animation, and tests can + // attempt to click it too quickly, leading to flaky tests. The 500ms wait allows + // the animation to complete before we attempt a click. + const menuAnimationDelay = 500; + await this.retry.tryForTime(menuAnimationDelay, () => + this.testSubjects.click('usePrintLayout') + ); + } - async clickGenerateReportButton() { - await testSubjects.click('generateReportButton'); - } + async clickGenerateReportButton() { + await this.testSubjects.click('generateReportButton'); + } - async toggleReportMode() { - await testSubjects.click('reportModeToggle'); - } + async toggleReportMode() { + await this.testSubjects.click('reportModeToggle'); + } - async checkForReportingToasts() { - log.debug('Reporting:checkForReportingToasts'); - const isToastPresent = await testSubjects.exists('completeReportSuccess', { - allowHidden: true, - timeout: 90000, - }); - // Close toast so it doesn't obscure the UI. - if (isToastPresent) { - await testSubjects.click('completeReportSuccess > toastCloseButton'); - } - - return isToastPresent; + async checkForReportingToasts() { + this.log.debug('Reporting:checkForReportingToasts'); + const isToastPresent = await this.testSubjects.exists('completeReportSuccess', { + allowHidden: true, + timeout: 90000, + }); + // Close toast so it doesn't obscure the UI. + if (isToastPresent) { + await this.testSubjects.click('completeReportSuccess > toastCloseButton'); } - async setTimepickerInDataRange() { - log.debug('Reporting:setTimepickerInDataRange'); - const fromTime = 'Apr 27, 2019 @ 23:56:51.374'; - const toTime = 'Aug 23, 2019 @ 16:18:51.821'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - } + return isToastPresent; + } - async setTimepickerInNoDataRange() { - log.debug('Reporting:setTimepickerInNoDataRange'); - const fromTime = 'Sep 19, 1999 @ 06:31:44.000'; - const toTime = 'Sep 23, 1999 @ 18:31:44.000'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - } + async setTimepickerInDataRange() { + this.log.debug('Reporting:setTimepickerInDataRange'); + const fromTime = 'Apr 27, 2019 @ 23:56:51.374'; + const toTime = 'Aug 23, 2019 @ 16:18:51.821'; + await this.timePicker.setAbsoluteRange(fromTime, toTime); + } + + async setTimepickerInNoDataRange() { + this.log.debug('Reporting:setTimepickerInNoDataRange'); + const fromTime = 'Sep 19, 1999 @ 06:31:44.000'; + const toTime = 'Sep 23, 1999 @ 18:31:44.000'; + await this.timePicker.setAbsoluteRange(fromTime, toTime); } - return new ReportingPage(); } diff --git a/x-pack/test/functional/page_objects/rollup_page.ts b/x-pack/test/functional/page_objects/rollup_page.ts index dbdfa4e19c555..0740a8f015da1 100644 --- a/x-pack/test/functional/page_objects/rollup_page.ts +++ b/x-pack/test/functional/page_objects/rollup_page.ts @@ -7,137 +7,130 @@ import expect from '@kbn/expect'; import { map as mapAsync } from 'bluebird'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function RollupPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const find = getService('find'); - const PageObjects = getPageObjects(['header', 'common']); - - class RollupJobPage { - async createNewRollUpJob( - jobName: string, - indexPattern: string, - indexName: string, - interval: string, - delay = '1d', - startImmediately = false, - scheduledTime = { time: 'minute', cron: true } - ) { - let stepNum = 1; - // Step 1 - await testSubjects.click('createRollupJobButton'); - await this.verifyStepIsActive(stepNum); - await this.addRollupNameandIndexPattern(jobName, indexPattern); - await this.verifyIndexPatternAccepted(); - await this.setIndexName(indexName); - await this.setScheduleTime(scheduledTime.time, scheduledTime.cron); - await this.setRollupDelay(delay); - stepNum = await this.moveToNextStep(stepNum); - - // Step 2: Histogram - await this.verifyStepIsActive(stepNum); - await this.setJobInterval(interval); - stepNum = await this.moveToNextStep(stepNum); - - // Step 3: Terms (optional) - await this.verifyStepIsActive(stepNum); - stepNum = await this.moveToNextStep(); - - // Step 4: Histogram(optional) - await this.verifyStepIsActive(stepNum); - stepNum = await this.moveToNextStep(); - - // Step 5: Metrics(optional) - await this.verifyStepIsActive(stepNum); - stepNum = await this.moveToNextStep(); - - // Step 6: saveJob and verify the name in the list - await this.verifyStepIsActive(stepNum); - await this.saveJob(startImmediately); - } +import { FtrService } from '../ftr_provider_context'; + +export class RollupPageObject 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 header = this.ctx.getPageObject('header'); + + async createNewRollUpJob( + jobName: string, + indexPattern: string, + indexName: string, + interval: string, + delay = '1d', + startImmediately = false, + scheduledTime = { time: 'minute', cron: true } + ) { + let stepNum = 1; + // Step 1 + await this.testSubjects.click('createRollupJobButton'); + await this.verifyStepIsActive(stepNum); + await this.addRollupNameandIndexPattern(jobName, indexPattern); + await this.verifyIndexPatternAccepted(); + await this.setIndexName(indexName); + await this.setScheduleTime(scheduledTime.time, scheduledTime.cron); + await this.setRollupDelay(delay); + stepNum = await this.moveToNextStep(stepNum); + + // Step 2: Histogram + await this.verifyStepIsActive(stepNum); + await this.setJobInterval(interval); + stepNum = await this.moveToNextStep(stepNum); + + // Step 3: Terms (optional) + await this.verifyStepIsActive(stepNum); + stepNum = await this.moveToNextStep(); + + // Step 4: Histogram(optional) + await this.verifyStepIsActive(stepNum); + stepNum = await this.moveToNextStep(); + + // Step 5: Metrics(optional) + await this.verifyStepIsActive(stepNum); + stepNum = await this.moveToNextStep(); + + // Step 6: saveJob and verify the name in the list + await this.verifyStepIsActive(stepNum); + await this.saveJob(startImmediately); + } - async verifyStepIsActive(stepNumber = 0) { - await testSubjects.exists(`createRollupStep${stepNumber}--active`); - } + async verifyStepIsActive(stepNumber = 0) { + await this.testSubjects.exists(`createRollupStep${stepNumber}--active`); + } - async setScheduleTime(time: string, isCron: boolean) { - if (isCron) { - await testSubjects.click('rollupShowAdvancedCronLink'); - await testSubjects.setValue('rollupAdvancedCron', time); - } - // TODO: Add handling for if Cron is false to go through clicking options. + async setScheduleTime(time: string, isCron: boolean) { + if (isCron) { + await this.testSubjects.click('rollupShowAdvancedCronLink'); + await this.testSubjects.setValue('rollupAdvancedCron', time); } + // TODO: Add handling for if Cron is false to go through clicking options. + } - async addRollupNameandIndexPattern(name: string, indexPattern: string) { - log.debug(`Adding name ${name} to form`); - await testSubjects.setValue('rollupJobName', name); - await testSubjects.setValue('rollupIndexPattern', indexPattern); - } + async addRollupNameandIndexPattern(name: string, indexPattern: string) { + this.log.debug(`Adding name ${name} to form`); + await this.testSubjects.setValue('rollupJobName', name); + await this.testSubjects.setValue('rollupIndexPattern', indexPattern); + } - async setRollupDelay(time: string) { - log.debug(`Setting rollup delay to "${time}"`); - await testSubjects.setValue('rollupDelay', time); - } + async setRollupDelay(time: string) { + this.log.debug(`Setting rollup delay to "${time}"`); + await this.testSubjects.setValue('rollupDelay', time); + } - async verifyIndexPatternAccepted() { - const span = await testSubjects.find('fieldIndexPatternSuccessMessage'); - const message = await span.findByCssSelector('p'); - const text = await message.getVisibleText(); - expect(text).to.be.equal('Success! Index pattern has matching indices.'); - } + async verifyIndexPatternAccepted() { + const span = await this.testSubjects.find('fieldIndexPatternSuccessMessage'); + const message = await span.findByCssSelector('p'); + const text = await message.getVisibleText(); + expect(text).to.be.equal('Success! Index pattern has matching indices.'); + } - async setIndexName(name: string) { - await testSubjects.setValue('rollupIndexName', name); - } + async setIndexName(name: string) { + await this.testSubjects.setValue('rollupIndexName', name); + } - async moveToNextStep(stepNum = 0) { - await testSubjects.click('rollupJobNextButton'); - return stepNum + 1; - } + async moveToNextStep(stepNum = 0) { + await this.testSubjects.click('rollupJobNextButton'); + return stepNum + 1; + } - async setJobInterval(time: string) { - await testSubjects.setValue('rollupJobInterval', time); - } + async setJobInterval(time: string) { + await this.testSubjects.setValue('rollupJobInterval', time); + } - async saveJob(startImmediately: boolean) { - if (startImmediately) { - const checkbox = await find.byCssSelector('.euiCheckbox'); - await checkbox.click(); - } - await testSubjects.click('rollupJobSaveButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); + async saveJob(startImmediately: boolean) { + if (startImmediately) { + const checkbox = await this.find.byCssSelector('.euiCheckbox'); + await checkbox.click(); } + await this.testSubjects.click('rollupJobSaveButton'); + await this.header.waitUntilLoadingHasFinished(); + } - async getJobList() { - const jobs = await testSubjects.findAll('jobTableRow'); - return mapAsync(jobs, async (job) => { - const jobNameElement = await job.findByTestSubject('jobTableCell-id'); - const jobStatusElement = await job.findByTestSubject('jobTableCell-status'); - const jobIndexPatternElement = await job.findByTestSubject('jobTableCell-indexPattern'); - const jobRollUpIndexPatternElement = await job.findByTestSubject( - 'jobTableCell-rollupIndex' - ); - const jobDelayElement = await job.findByTestSubject('jobTableCell-rollupDelay'); - const jobIntervalElement = await job.findByTestSubject( - 'jobTableCell-dateHistogramInterval' - ); - const jobGroupElement = await job.findByTestSubject('jobTableCell-groups'); - const jobMetricsElement = await job.findByTestSubject('jobTableCell-metrics'); - - return { - jobName: await jobNameElement.getVisibleText(), - jobStatus: await jobStatusElement.getVisibleText(), - jobIndexPattern: await jobIndexPatternElement.getVisibleText(), - jobRollUpIndexPattern: await jobRollUpIndexPatternElement.getVisibleText(), - jobDelayElement: await jobDelayElement.getVisibleText(), - jobInterval: await jobIntervalElement.getVisibleText(), - jobGroup: await jobGroupElement.getVisibleText(), - jobMetrics: await jobMetricsElement.getVisibleText(), - }; - }); - } + async getJobList() { + const jobs = await this.testSubjects.findAll('jobTableRow'); + return mapAsync(jobs, async (job) => { + const jobNameElement = await job.findByTestSubject('jobTableCell-id'); + const jobStatusElement = await job.findByTestSubject('jobTableCell-status'); + const jobIndexPatternElement = await job.findByTestSubject('jobTableCell-indexPattern'); + const jobRollUpIndexPatternElement = await job.findByTestSubject('jobTableCell-rollupIndex'); + const jobDelayElement = await job.findByTestSubject('jobTableCell-rollupDelay'); + const jobIntervalElement = await job.findByTestSubject('jobTableCell-dateHistogramInterval'); + const jobGroupElement = await job.findByTestSubject('jobTableCell-groups'); + const jobMetricsElement = await job.findByTestSubject('jobTableCell-metrics'); + + return { + jobName: await jobNameElement.getVisibleText(), + jobStatus: await jobStatusElement.getVisibleText(), + jobIndexPattern: await jobIndexPatternElement.getVisibleText(), + jobRollUpIndexPattern: await jobRollUpIndexPatternElement.getVisibleText(), + jobDelayElement: await jobDelayElement.getVisibleText(), + jobInterval: await jobIntervalElement.getVisibleText(), + jobGroup: await jobGroupElement.getVisibleText(), + jobMetrics: await jobMetricsElement.getVisibleText(), + }; + }); } - return new RollupJobPage(); } diff --git a/x-pack/test/functional/page_objects/security_page.ts b/x-pack/test/functional/page_objects/security_page.ts index 2ce14fa7a2515..f9128ce21b565 100644 --- a/x-pack/test/functional/page_objects/security_page.ts +++ b/x-pack/test/functional/page_objects/security_page.ts @@ -6,130 +6,49 @@ */ import { adminTestUser } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; import { AuthenticatedUser, Role } from '../../../plugins/security/common/model'; import type { UserFormValues } from '../../../plugins/security/public/management/users/edit_user/user_form'; -export function SecurityPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const config = getService('config'); - const retry = getService('retry'); - const find = getService('find'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const esArchiver = getService('esArchiver'); - const userMenu = getService('userMenu'); - const comboBox = getService('comboBox'); - const supertest = getService('supertestWithoutAuth'); - const deployment = getService('deployment'); - const PageObjects = getPageObjects(['common', 'header', 'error']); - - interface LoginOptions { - expectSpaceSelector?: boolean; - expectSuccess?: boolean; - expectForbidden?: boolean; - } - - type LoginExpectedResult = 'spaceSelector' | 'error' | 'chrome'; - - async function waitForLoginPage() { - log.debug('Waiting for Login Page to appear.'); - await retry.waitForWithTimeout('login page', config.get('timeouts.waitFor') * 5, async () => { - // As a part of the cleanup flow tests usually try to log users out, but there are cases when - // browser/Kibana would like users to confirm that they want to navigate away from the current - // page and lose the state (e.g. unsaved changes) via native alert dialog. - const alert = await browser.getAlert(); - if (alert && alert.accept) { - await alert.accept(); - } - return await find.existsByDisplayedByCssSelector('.login-form'); - }); - } - - async function isLoginFormVisible() { - return await testSubjects.exists('loginForm'); - } - - async function waitForLoginForm() { - log.debug('Waiting for Login Form to appear.'); - await retry.waitForWithTimeout('login form', config.get('timeouts.waitFor') * 5, async () => { - return await isLoginFormVisible(); - }); - } - - async function waitForLoginSelector() { - log.debug('Waiting for Login Selector to appear.'); - await retry.waitForWithTimeout( - 'login selector', - config.get('timeouts.waitFor') * 5, - async () => { - return await testSubjects.exists('loginSelector'); - } - ); - } - - async function waitForLoginHelp(helpText: string) { - log.debug(`Waiting for Login Help to appear with text: ${helpText}.`); - await retry.waitForWithTimeout('login help', config.get('timeouts.waitFor') * 5, async () => { - return (await testSubjects.getVisibleText('loginHelp')) === helpText; - }); - } - - async function waitForLoginResult(expectedResult?: LoginExpectedResult) { - log.debug(`Waiting for login result, expected: ${expectedResult}.`); - - // wait for either space selector, kibanaChrome or loginErrorMessage - if (expectedResult === 'spaceSelector') { - await retry.try(() => testSubjects.find('kibanaSpaceSelector')); - log.debug( - `Finished login process, landed on space selector. currentUrl = ${await browser.getCurrentUrl()}` - ); - return; - } - - if (expectedResult === 'error') { - const rawDataTabLocator = 'a[id=rawdata-tab]'; - if (await find.existsByCssSelector(rawDataTabLocator)) { - // Firefox has 3 tabs and requires navigation to see Raw output - await find.clickByCssSelector(rawDataTabLocator); - } - await retry.try(async () => { - if (await find.existsByCssSelector(rawDataTabLocator)) { - await find.clickByCssSelector(rawDataTabLocator); - } - await testSubjects.existOrFail('ResetSessionButton'); - }); - log.debug( - `Finished login process, found reset session button message. currentUrl = ${await browser.getCurrentUrl()}` - ); - return; - } - - if (expectedResult === 'chrome') { - await find.byCssSelector( - '[data-test-subj="kibanaChrome"] .kbnAppWrapper:not(.kbnAppWrapper--hiddenChrome)', - 20000 - ); - log.debug(`Finished login process currentUrl = ${await browser.getCurrentUrl()}`); - } - } +interface LoginOptions { + expectSpaceSelector?: boolean; + expectSuccess?: boolean; + expectForbidden?: boolean; +} - const loginPage = Object.freeze({ - async login(username?: string, password?: string, options: LoginOptions = {}) { - if (!(await isLoginFormVisible())) { - await PageObjects.common.navigateToApp('login'); +type LoginExpectedResult = 'spaceSelector' | 'error' | 'chrome'; + +export class SecurityPageObject 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 find = this.ctx.getService('find'); + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly esArchiver = this.ctx.getService('esArchiver'); + private readonly userMenu = this.ctx.getService('userMenu'); + private readonly comboBox = this.ctx.getService('comboBox'); + private readonly supertest = this.ctx.getService('supertestWithoutAuth'); + private readonly deployment = this.ctx.getService('deployment'); + private readonly common = this.ctx.getPageObject('common'); + private readonly header = this.ctx.getPageObject('header'); + + public loginPage = Object.freeze({ + login: async (username?: string, password?: string, options: LoginOptions = {}) => { + if (!(await this.isLoginFormVisible())) { + await this.common.navigateToApp('login'); } // ensure welcome screen won't be shown. This is relevant for environments which don't allow // to use the yml setting, e.g. cloud - await browser.setLocalStorageItem('home:welcome:show', 'false'); - await waitForLoginForm(); + await this.browser.setLocalStorageItem('home:welcome:show', 'false'); + await this.waitForLoginForm(); - await testSubjects.setValue('loginUsername', username || adminTestUser.username); - await testSubjects.setValue('loginPassword', password || adminTestUser.password); - await testSubjects.click('loginSubmit'); + await this.testSubjects.setValue('loginUsername', username || adminTestUser.username); + await this.testSubjects.setValue('loginPassword', password || adminTestUser.password); + await this.testSubjects.click('loginSubmit'); - await waitForLoginResult( + await this.waitForLoginResult( options.expectSpaceSelector ? 'spaceSelector' : options.expectForbidden @@ -140,9 +59,11 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider ); }, - async getErrorMessage() { - return await retry.try(async () => { - const errorMessageContainer = await retry.try(() => testSubjects.find('loginErrorMessage')); + getErrorMessage: async () => { + return await this.retry.try(async () => { + const errorMessageContainer = await this.retry.try(() => + this.testSubjects.find('loginErrorMessage') + ); const errorMessageText = await errorMessageContainer.getVisibleText(); if (!errorMessageText) { @@ -154,380 +75,477 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider }, }); - const loginSelector = Object.freeze({ - async login(providerType: string, providerName: string, options?: Record) { - log.debug(`Starting login flow for ${providerType}/${providerName}`); + public loginSelector = Object.freeze({ + login: async (providerType: string, providerName: string, options?: Record) => { + this.log.debug(`Starting login flow for ${providerType}/${providerName}`); - await this.verifyLoginSelectorIsVisible(); - await this.selectLoginMethod(providerType, providerName); + await this.loginSelector.verifyLoginSelectorIsVisible(); + await this.loginSelector.selectLoginMethod(providerType, providerName); if (providerType === 'basic' || providerType === 'token') { - await waitForLoginForm(); + await this.waitForLoginForm(); - await testSubjects.setValue('loginUsername', options?.username ?? adminTestUser.username); - await testSubjects.setValue('loginPassword', options?.password ?? adminTestUser.password); - await testSubjects.click('loginSubmit'); + await this.testSubjects.setValue( + 'loginUsername', + options?.username ?? adminTestUser.username + ); + await this.testSubjects.setValue( + 'loginPassword', + options?.password ?? adminTestUser.password + ); + await this.testSubjects.click('loginSubmit'); } - await waitForLoginResult('chrome'); + await this.waitForLoginResult('chrome'); - log.debug(`Finished login process currentUrl = ${await browser.getCurrentUrl()}`); + this.log.debug(`Finished login process currentUrl = ${await this.browser.getCurrentUrl()}`); }, - async selectLoginMethod(providerType: string, providerName: string) { + selectLoginMethod: async (providerType: string, providerName: string) => { // Ensure welcome screen won't be shown. This is relevant for environments which don't allow // to use the yml setting, e.g. cloud. - await browser.setLocalStorageItem('home:welcome:show', 'false'); - await testSubjects.click(`loginCard-${providerType}/${providerName}`); + await this.browser.setLocalStorageItem('home:welcome:show', 'false'); + await this.testSubjects.click(`loginCard-${providerType}/${providerName}`); }, - async verifyLoginFormIsVisible() { - await waitForLoginForm(); + verifyLoginFormIsVisible: async () => { + await this.waitForLoginForm(); }, - async verifyLoginSelectorIsVisible() { - await waitForLoginSelector(); + verifyLoginSelectorIsVisible: async () => { + await this.waitForLoginSelector(); }, - async verifyLoginHelpIsVisible(helpText: string) { - await waitForLoginHelp(helpText); + verifyLoginHelpIsVisible: async (helpText: string) => { + await this.waitForLoginHelp(helpText); }, }); - class SecurityPage { - public loginPage = loginPage; - public loginSelector = loginSelector; - - async initTests() { - log.debug('SecurityPage:initTests'); - await esArchiver.load('empty_kibana'); - await esArchiver.loadIfNeeded('logstash_functional'); - await browser.setWindowSize(1600, 1000); - } + private async waitForLoginPage() { + this.log.debug('Waiting for Login Page to appear.'); + await this.retry.waitForWithTimeout( + 'login page', + this.config.get('timeouts.waitFor') * 5, + async () => { + // As a part of the cleanup flow tests usually try to log users out, but there are cases when + // browser/Kibana would like users to confirm that they want to navigate away from the current + // page and lose the state (e.g. unsaved changes) via native alert dialog. + const alert = await this.browser.getAlert(); + if (alert && alert.accept) { + await alert.accept(); + } + return await this.find.existsByDisplayedByCssSelector('.login-form'); + } + ); + } - async login(username?: string, password?: string, options: LoginOptions = {}) { - await this.loginPage.login(username, password, options); + private async isLoginFormVisible() { + return await this.testSubjects.exists('loginForm'); + } - if (options.expectSpaceSelector || options.expectForbidden) { - return; + private async waitForLoginForm() { + this.log.debug('Waiting for Login Form to appear.'); + await this.retry.waitForWithTimeout( + 'login form', + this.config.get('timeouts.waitFor') * 5, + async () => { + return await this.isLoginFormVisible(); } + ); + } - await retry.waitFor('logout button visible', async () => await userMenu.logoutLinkExists()); - } - - async logout() { - log.debug('SecurityPage.logout'); + private async waitForLoginSelector() { + this.log.debug('Waiting for Login Selector to appear.'); + await this.retry.waitForWithTimeout( + 'login selector', + this.config.get('timeouts.waitFor') * 5, + async () => { + return await this.testSubjects.exists('loginSelector'); + } + ); + } - if (!(await userMenu.logoutLinkExists())) { - log.debug('Logout not found'); - return; + private async waitForLoginHelp(helpText: string) { + this.log.debug(`Waiting for Login Help to appear with text: ${helpText}.`); + await this.retry.waitForWithTimeout( + 'login help', + this.config.get('timeouts.waitFor') * 5, + async () => { + return (await this.testSubjects.getVisibleText('loginHelp')) === helpText; } + ); + } + + private async waitForLoginResult(expectedResult?: LoginExpectedResult) { + this.log.debug(`Waiting for login result, expected: ${expectedResult}.`); - await userMenu.clickLogoutButton(); - await waitForLoginPage(); + // wait for either space selector, kibanaChrome or loginErrorMessage + if (expectedResult === 'spaceSelector') { + await this.retry.try(() => this.testSubjects.find('kibanaSpaceSelector')); + this.log.debug( + `Finished login process, landed on space selector. currentUrl = ${await this.browser.getCurrentUrl()}` + ); + return; } - async getCurrentUser() { - const sidCookie = await browser.getCookie('sid'); - if (!sidCookie?.value) { - log.debug('User is not authenticated yet.'); - return null; + if (expectedResult === 'error') { + const rawDataTabLocator = 'a[id=rawdata-tab]'; + if (await this.find.existsByCssSelector(rawDataTabLocator)) { + // Firefox has 3 tabs and requires navigation to see Raw output + await this.find.clickByCssSelector(rawDataTabLocator); } + await this.retry.try(async () => { + if (await this.find.existsByCssSelector(rawDataTabLocator)) { + await this.find.clickByCssSelector(rawDataTabLocator); + } + await this.testSubjects.existOrFail('ResetSessionButton'); + }); + this.log.debug( + `Finished login process, found reset session button message. currentUrl = ${await this.browser.getCurrentUrl()}` + ); + return; + } - const { body: user } = await supertest - .get('/internal/security/me') - .set('kbn-xsrf', 'xxx') - .set('Cookie', `sid=${sidCookie.value}`) - .expect(200); - return user as AuthenticatedUser; + if (expectedResult === 'chrome') { + await this.find.byCssSelector( + '[data-test-subj="kibanaChrome"] .kbnAppWrapper:not(.kbnAppWrapper--hiddenChrome)', + 20000 + ); + this.log.debug(`Finished login process currentUrl = ${await this.browser.getCurrentUrl()}`); } + } - async forceLogout() { - log.debug('SecurityPage.forceLogout'); - if (await find.existsByDisplayedByCssSelector('.login-form', 100)) { - log.debug('Already on the login page, not forcing anything'); - return; - } + async initTests() { + this.log.debug('SecurityPage:initTests'); + await this.esArchiver.load('empty_kibana'); + await this.esArchiver.loadIfNeeded('logstash_functional'); + await this.browser.setWindowSize(1600, 1000); + } - log.debug('Redirecting to /logout to force the logout'); - const url = deployment.getHostPort() + '/logout'; - await browser.get(url); - log.debug('Waiting on the login form to appear'); - await waitForLoginPage(); - } + async login(username?: string, password?: string, options: LoginOptions = {}) { + await this.loginPage.login(username, password, options); - async clickRolesSection() { - await testSubjects.click('roles'); + if (options.expectSpaceSelector || options.expectForbidden) { + return; } - async clickUsersSection() { - await testSubjects.click('users'); - } + await this.retry.waitFor( + 'logout button visible', + async () => await this.userMenu.logoutLinkExists() + ); + } - async clickCreateNewUser() { - await retry.try(() => testSubjects.click('createUserButton')); - } + async logout() { + this.log.debug('SecurityPage.logout'); - async clickCreateNewRole() { - await retry.try(() => testSubjects.click('createRoleButton')); + if (!(await this.userMenu.logoutLinkExists())) { + this.log.debug('Logout not found'); + return; } - async clickCloneRole(roleName: string) { - await retry.try(() => testSubjects.click(`clone-role-action-${roleName}`)); - } + await this.userMenu.clickLogoutButton(); + await this.waitForLoginPage(); + } - async clickCancelEditUser() { - await find.clickByButtonText('Cancel'); + async getCurrentUser() { + const sidCookie = await this.browser.getCookie('sid'); + if (!sidCookie?.value) { + this.log.debug('User is not authenticated yet.'); + return null; } - async clickCancelEditRole() { - await testSubjects.click('roleFormCancelButton'); - } + const { body: user } = await this.supertest + .get('/internal/security/me') + .set('kbn-xsrf', 'xxx') + .set('Cookie', `sid=${sidCookie.value}`) + .expect(200); + return user as AuthenticatedUser; + } - async clickSaveEditUser() { - await find.clickByButtonText('Update user'); - await PageObjects.header.waitUntilLoadingHasFinished(); + async forceLogout() { + this.log.debug('SecurityPage.forceLogout'); + if (await this.find.existsByDisplayedByCssSelector('.login-form', 100)) { + this.log.debug('Already on the login page, not forcing anything'); + return; } - async clickSaveCreateUser() { - await find.clickByButtonText('Create user'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + this.log.debug('Redirecting to /logout to force the logout'); + const url = this.deployment.getHostPort() + '/logout'; + await this.browser.get(url); + this.log.debug('Waiting on the login form to appear'); + await this.waitForLoginPage(); + } - async clickSaveEditRole() { - const saveButton = await retry.try(() => testSubjects.find('roleFormSaveButton')); - await saveButton.moveMouseTo(); - await saveButton.click(); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clickRolesSection() { + await this.testSubjects.click('roles'); + } - async addIndexToRole(index: string) { - log.debug(`Adding index ${index} to role`); - await comboBox.setCustom('indicesInput0', index); - } + async clickUsersSection() { + await this.testSubjects.click('users'); + } - async addPrivilegeToRole(privilege: string) { - log.debug(`Adding privilege ${privilege} to role`); - const privilegeInput = await retry.try(() => - find.byCssSelector('[data-test-subj="privilegesInput0"] input') - ); - await privilegeInput.type(privilege); + async clickCreateNewUser() { + await this.retry.try(() => this.testSubjects.click('createUserButton')); + } - const btn = await find.byButtonText(privilege); - await btn.click(); + async clickCreateNewRole() { + await this.retry.try(() => this.testSubjects.click('createRoleButton')); + } - // const options = await find.byCssSelector(`.euiFilterSelectItem`); - // Object.entries(options).forEach(([key, prop]) => { - // console.log({ key, proto: prop.__proto__ }); - // }); + async clickCloneRole(roleName: string) { + await this.retry.try(() => this.testSubjects.click(`clone-role-action-${roleName}`)); + } - // await options.click(); - } + async clickCancelEditUser() { + await this.find.clickByButtonText('Cancel'); + } - async assignRoleToUser(role: string) { - await this.selectRole(role); - } + async clickCancelEditRole() { + await this.testSubjects.click('roleFormCancelButton'); + } - async navigateTo() { - await PageObjects.common.navigateToApp('settings'); - } + async clickSaveEditUser() { + await this.find.clickByButtonText('Update user'); + await this.header.waitUntilLoadingHasFinished(); + } - async clickElasticsearchUsers() { - await this.navigateTo(); - await this.clickUsersSection(); - } + async clickSaveCreateUser() { + await this.find.clickByButtonText('Create user'); + await this.header.waitUntilLoadingHasFinished(); + } - async clickElasticsearchRoles() { - await this.navigateTo(); - await this.clickRolesSection(); - } + async clickSaveEditRole() { + const saveButton = await this.retry.try(() => this.testSubjects.find('roleFormSaveButton')); + await saveButton.moveMouseTo(); + await saveButton.click(); + await this.header.waitUntilLoadingHasFinished(); + } - async getElasticsearchUsers() { - const users = []; - await testSubjects.click('tablePaginationPopoverButton'); - await testSubjects.click('tablePagination-100-rows'); - for (const user of await testSubjects.findAll('userRow')) { - const fullnameElement = await user.findByTestSubject('userRowFullName'); - const usernameElement = await user.findByTestSubject('userRowUserName'); - const emailElement = await user.findByTestSubject('userRowEmail'); - const rolesElement = await user.findByTestSubject('userRowRoles'); - // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases - const isUserReserved = (await user.findAllByTestSubject('userReserved', 1)).length > 0; - const isUserDeprecated = (await user.findAllByTestSubject('userDeprecated', 1)).length > 0; - - users.push({ - username: await usernameElement.getVisibleText(), - fullname: await fullnameElement.getVisibleText(), - email: await emailElement.getVisibleText(), - roles: (await rolesElement.getVisibleText()).split('\n').map((role) => role.trim()), - reserved: isUserReserved, - deprecated: isUserDeprecated, - }); - } + async addIndexToRole(index: string) { + this.log.debug(`Adding index ${index} to role`); + await this.comboBox.setCustom('indicesInput0', index); + } - return users; - } + async addPrivilegeToRole(privilege: string) { + this.log.debug(`Adding privilege ${privilege} to role`); + const privilegeInput = await this.retry.try(() => + this.find.byCssSelector('[data-test-subj="privilegesInput0"] input') + ); + await privilegeInput.type(privilege); - async getElasticsearchRoles() { - const roles = []; - await testSubjects.click('tablePaginationPopoverButton'); - await testSubjects.click('tablePagination-100-rows'); - for (const role of await testSubjects.findAll('roleRow')) { - const [rolename, reserved, deprecated] = await Promise.all([ - role.findByTestSubject('roleRowName').then((el) => el.getVisibleText()), - // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases - role.findAllByTestSubject('roleReserved', 1).then((el) => el.length > 0), - // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases - role.findAllByTestSubject('roleDeprecated', 1).then((el) => el.length > 0), - ]); - - roles.push({ rolename, reserved, deprecated }); - } + const btn = await this.find.byButtonText(privilege); + await btn.click(); - return roles; - } + // const options = await this.find.byCssSelector(`.euiFilterSelectItem`); + // Object.entries(options).forEach(([key, prop]) => { + // console.log({ key, proto: prop.__proto__ }); + // }); - /** - * @deprecated Use `PageObjects.security.clickCreateNewUser` instead - */ - async clickNewUser() { - return await testSubjects.click('createUserButton'); - } + // await options.click(); + } - /** - * @deprecated Use `PageObjects.security.clickCreateNewUser` instead - */ - async clickNewRole() { - return await testSubjects.click('createRoleButton'); - } + async assignRoleToUser(role: string) { + await this.selectRole(role); + } - async fillUserForm(user: UserFormValues) { - if (user.username) { - await find.setValue('[name=username]', user.username); - } - if (user.password) { - await find.setValue('[name=password]', user.password); - } - if (user.confirm_password) { - await find.setValue('[name=confirm_password]', user.confirm_password); - } - if (user.full_name) { - await find.setValue('[name=full_name]', user.full_name); - } - if (user.email) { - await find.setValue('[name=email]', user.email); - } + async navigateTo() { + await this.common.navigateToApp('settings'); + } - const rolesToAdd = user.roles || []; - for (let i = 0; i < rolesToAdd.length; i++) { - await this.selectRole(rolesToAdd[i]); - } + async clickElasticsearchUsers() { + await this.navigateTo(); + await this.clickUsersSection(); + } + + async clickElasticsearchRoles() { + await this.navigateTo(); + await this.clickRolesSection(); + } + + async getElasticsearchUsers() { + const users = []; + await this.testSubjects.click('tablePaginationPopoverButton'); + await this.testSubjects.click('tablePagination-100-rows'); + for (const user of await this.testSubjects.findAll('userRow')) { + const fullnameElement = await user.findByTestSubject('userRowFullName'); + const usernameElement = await user.findByTestSubject('userRowUserName'); + const emailElement = await user.findByTestSubject('userRowEmail'); + const rolesElement = await user.findByTestSubject('userRowRoles'); + // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases + const isUserReserved = (await user.findAllByTestSubject('userReserved', 1)).length > 0; + const isUserDeprecated = (await user.findAllByTestSubject('userDeprecated', 1)).length > 0; + + users.push({ + username: await usernameElement.getVisibleText(), + fullname: await fullnameElement.getVisibleText(), + email: await emailElement.getVisibleText(), + roles: (await rolesElement.getVisibleText()).split('\n').map((role) => role.trim()), + reserved: isUserReserved, + deprecated: isUserDeprecated, + }); } - async submitCreateUserForm() { - await find.clickByButtonText('Create user'); + return users; + } + + async getElasticsearchRoles() { + const roles = []; + await this.testSubjects.click('tablePaginationPopoverButton'); + await this.testSubjects.click('tablePagination-100-rows'); + for (const role of await this.testSubjects.findAll('roleRow')) { + const [rolename, reserved, deprecated] = await Promise.all([ + role.findByTestSubject('roleRowName').then((el) => el.getVisibleText()), + // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases + role.findAllByTestSubject('roleReserved', 1).then((el) => el.length > 0), + // findAll is substantially faster than `find.descendantExistsByCssSelector for negative cases + role.findAllByTestSubject('roleDeprecated', 1).then((el) => el.length > 0), + ]); + + roles.push({ rolename, reserved, deprecated }); } - async createUser(user: UserFormValues) { - await this.clickElasticsearchUsers(); - await this.clickCreateNewUser(); - await this.fillUserForm(user); - await this.submitCreateUserForm(); + return roles; + } + + /** + * @deprecated Use `this.security.clickCreateNewUser` instead + */ + async clickNewUser() { + return await this.testSubjects.click('createUserButton'); + } + + /** + * @deprecated Use `this.security.clickCreateNewUser` instead + */ + async clickNewRole() { + return await this.testSubjects.click('createRoleButton'); + } + + async fillUserForm(user: UserFormValues) { + if (user.username) { + await this.find.setValue('[name=username]', user.username); + } + if (user.password) { + await this.find.setValue('[name=password]', user.password); + } + if (user.confirm_password) { + await this.find.setValue('[name=confirm_password]', user.confirm_password); + } + if (user.full_name) { + await this.find.setValue('[name=full_name]', user.full_name); + } + if (user.email) { + await this.find.setValue('[name=email]', user.email); } - async addRole(roleName: string, roleObj: Role) { - const self = this; + const rolesToAdd = user.roles || []; + for (let i = 0; i < rolesToAdd.length; i++) { + await this.selectRole(rolesToAdd[i]); + } + } - await this.clickCreateNewRole(); + async submitCreateUserForm() { + await this.find.clickByButtonText('Create user'); + } - // We have to use non-test-subject selectors because this markup is generated by ui-select. - log.debug('roleObj.indices[0].names = ' + roleObj.elasticsearch.indices[0].names); - await testSubjects.append('roleFormNameInput', roleName); + async createUser(user: UserFormValues) { + await this.clickElasticsearchUsers(); + await this.clickCreateNewUser(); + await this.fillUserForm(user); + await this.submitCreateUserForm(); + } - for (const indexName of roleObj.elasticsearch.indices[0].names) { - await comboBox.setCustom('indicesInput0', indexName); - } + async addRole(roleName: string, roleObj: Role) { + const self = this; - if (roleObj.elasticsearch.indices[0].query) { - await testSubjects.click('restrictDocumentsQuery0'); - await testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query); - } + await this.clickCreateNewRole(); - const globalPrivileges = (roleObj.kibana as any).global; - if (globalPrivileges) { - for (const privilegeName of globalPrivileges) { - await testSubjects.click('addSpacePrivilegeButton'); + // We have to use non-test-subject selectors because this markup is generated by ui-select. + this.log.debug('roleObj.indices[0].names = ' + roleObj.elasticsearch.indices[0].names); + await this.testSubjects.append('roleFormNameInput', roleName); - await testSubjects.click('spaceSelectorComboBox'); + for (const indexName of roleObj.elasticsearch.indices[0].names) { + await this.comboBox.setCustom('indicesInput0', indexName); + } - const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`); - await globalSpaceOption.click(); + if (roleObj.elasticsearch.indices[0].query) { + await this.testSubjects.click('restrictDocumentsQuery0'); + await this.testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query); + } - await testSubjects.click(`basePrivilege_${privilegeName}`); + const globalPrivileges = (roleObj.kibana as any).global; + if (globalPrivileges) { + for (const privilegeName of globalPrivileges) { + await this.testSubjects.click('addSpacePrivilegeButton'); - await testSubjects.click('createSpacePrivilegeButton'); - } - } + await this.testSubjects.click('spaceSelectorComboBox'); - function addPrivilege(privileges: string[]) { - return privileges.reduce(function (promise: Promise, privilegeName: string) { - return promise - .then(() => self.addPrivilegeToRole(privilegeName)) - .then(() => PageObjects.common.sleep(250)); - }, Promise.resolve()); - } + const globalSpaceOption = await this.find.byCssSelector(`#spaceOption_\\*`); + await globalSpaceOption.click(); - await addPrivilege(roleObj.elasticsearch.indices[0].privileges); + await this.testSubjects.click(`basePrivilege_${privilegeName}`); - async function addGrantedField(fields: string[]) { - for (const entry of fields) { - await comboBox.setCustom('fieldInput0', entry); - } + await this.testSubjects.click('createSpacePrivilegeButton'); } + } - // clicking the Granted fields and removing the asterix - if (roleObj.elasticsearch.indices[0].field_security) { - // Toggle FLS switch - await testSubjects.click('restrictFieldsQuery0'); + const addPrivilege = (privileges: string[]) => { + return privileges.reduce((promise: Promise, privilegeName: string) => { + return promise + .then(() => self.addPrivilegeToRole(privilegeName)) + .then(() => this.common.sleep(250)); + }, Promise.resolve()); + }; - // have to remove the '*' - await find.clickByCssSelector( - 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' - ); + await addPrivilege(roleObj.elasticsearch.indices[0].privileges); - await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!); + const addGrantedField = async (fields: string[]) => { + for (const entry of fields) { + await this.comboBox.setCustom('fieldInput0', entry); } + }; - log.debug('click save button'); - await testSubjects.click('roleFormSaveButton'); + // clicking the Granted fields and removing the asterix + if (roleObj.elasticsearch.indices[0].field_security) { + // Toggle FLS switch + await this.testSubjects.click('restrictFieldsQuery0'); - // Signifies that the role management page redirected back to the role grid page, - // and successfully refreshed the grid - await testSubjects.existOrFail('roleRow'); - } + // have to remove the '*' + await this.find.clickByCssSelector( + 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' + ); - async selectRole(role: string) { - const dropdown = await testSubjects.find('rolesDropdown'); - const input = await dropdown.findByCssSelector('input'); - await input.type(role); - await find.clickByCssSelector(`[role=option][title="${role}"]`); - await testSubjects.click('comboBoxToggleListButton'); + await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!); } - async deleteUser(username: string) { - log.debug('Delete user ' + username); - await find.clickByDisplayedLinkText(username); - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + this.log.debug('click save button'); + await this.testSubjects.click('roleFormSaveButton'); + + // Signifies that the role management page redirected back to the role grid page, + // and successfully refreshed the grid + await this.testSubjects.existOrFail('roleRow'); + } - log.debug('Find delete button and click'); - await find.clickByButtonText('Delete user'); - await PageObjects.common.sleep(2000); + async selectRole(role: string) { + const dropdown = await this.testSubjects.find('rolesDropdown'); + const input = await dropdown.findByCssSelector('input'); + await input.type(role); + await this.find.clickByCssSelector(`[role=option][title="${role}"]`); + await this.testSubjects.click('comboBoxToggleListButton'); + } - const confirmText = await testSubjects.getVisibleText('confirmModalBodyText'); - log.debug('Delete user alert text = ' + confirmText); - await testSubjects.click('confirmModalConfirmButton'); - return confirmText; - } + async deleteUser(username: string) { + this.log.debug('Delete user ' + username); + await this.find.clickByDisplayedLinkText(username); + await this.header.awaitGlobalLoadingIndicatorHidden(); + + this.log.debug('Find delete button and click'); + await this.find.clickByButtonText('Delete user'); + await this.common.sleep(2000); + + const confirmText = await this.testSubjects.getVisibleText('confirmModalBodyText'); + this.log.debug('Delete user alert text = ' + confirmText); + await this.testSubjects.click('confirmModalConfirmButton'); + return confirmText; } - return new SecurityPage(); } diff --git a/x-pack/test/functional/page_objects/space_selector_page.ts b/x-pack/test/functional/page_objects/space_selector_page.ts index 0a41e4b90287f..9e05ae1f2c42b 100644 --- a/x-pack/test/functional/page_objects/space_selector_page.ts +++ b/x-pack/test/functional/page_objects/space_selector_page.ts @@ -6,191 +6,187 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function SpaceSelectorPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const log = getService('log'); - const testSubjects = getService('testSubjects'); - const browser = getService('browser'); - const find = getService('find'); - const PageObjects = getPageObjects(['common']); - - class SpaceSelectorPage { - async initTests() { - log.debug('SpaceSelectorPage:initTests'); - } - - async clickSpaceCard(spaceId: string) { - return await retry.try(async () => { - log.info(`SpaceSelectorPage:clickSpaceCard(${spaceId})`); - await testSubjects.click(`space-card-${spaceId}`); - await PageObjects.common.sleep(1000); - }); - } +import { FtrService } from '../ftr_provider_context'; + +export class SpaceSelectorPageObject extends FtrService { + private readonly retry = this.ctx.getService('retry'); + private readonly log = this.ctx.getService('log'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly browser = this.ctx.getService('browser'); + private readonly find = this.ctx.getService('find'); + private readonly common = this.ctx.getPageObject('common'); + + async initTests() { + this.log.debug('SpaceSelectorPage:initTests'); + } - async expectHomePage(spaceId: string) { - return await this.expectRoute(spaceId, `/app/home#/`); - } + async clickSpaceCard(spaceId: string) { + return await this.retry.try(async () => { + this.log.info(`SpaceSelectorPage:clickSpaceCard(${spaceId})`); + await this.testSubjects.click(`space-card-${spaceId}`); + await this.common.sleep(1000); + }); + } - async expectRoute(spaceId: string, route: string) { - return await retry.try(async () => { - log.debug(`expectRoute(${spaceId}, ${route})`); - await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000); - const url = await browser.getCurrentUrl(); - if (spaceId === 'default') { - expect(url).to.contain(route); - } else { - expect(url).to.contain(`/s/${spaceId}${route}`); - } - }); - } + async expectHomePage(spaceId: string) { + return await this.expectRoute(spaceId, `/app/home#/`); + } - async openSpacesNav() { - log.debug('openSpacesNav()'); - return await testSubjects.click('spacesNavSelector'); - } + async expectRoute(spaceId: string, route: string) { + return await this.retry.try(async () => { + this.log.debug(`expectRoute(${spaceId}, ${route})`); + await this.find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000); + const url = await this.browser.getCurrentUrl(); + if (spaceId === 'default') { + expect(url).to.contain(route); + } else { + expect(url).to.contain(`/s/${spaceId}${route}`); + } + }); + } - async clickManageSpaces() { - await testSubjects.click('manageSpaces'); - } + async openSpacesNav() { + this.log.debug('openSpacesNav()'); + return await this.testSubjects.click('spacesNavSelector'); + } - async clickCreateSpace() { - await testSubjects.click('createSpace'); - } + async clickManageSpaces() { + await this.testSubjects.click('manageSpaces'); + } - async clickEnterSpaceName() { - await testSubjects.click('addSpaceName'); - } + async clickCreateSpace() { + await this.testSubjects.click('createSpace'); + } - async addSpaceName(spaceName: string) { - await testSubjects.setValue('addSpaceName', spaceName); - } + async clickEnterSpaceName() { + await this.testSubjects.click('addSpaceName'); + } - async clickCustomizeSpaceAvatar(spaceId: string) { - await testSubjects.click(`space-avatar-${spaceId}`); - } + async addSpaceName(spaceName: string) { + await this.testSubjects.setValue('addSpaceName', spaceName); + } - async clickSpaceInitials() { - await testSubjects.click('spaceLetterInitial'); - } + async clickCustomizeSpaceAvatar(spaceId: string) { + await this.testSubjects.click(`space-avatar-${spaceId}`); + } - async addSpaceInitials(spaceInitials: string) { - await testSubjects.setValue('spaceLetterInitial', spaceInitials); - } + async clickSpaceInitials() { + await this.testSubjects.click('spaceLetterInitial'); + } - async clickColorPicker() { - await testSubjects.click('colorPickerAnchor'); - } + async addSpaceInitials(spaceInitials: string) { + await this.testSubjects.setValue('spaceLetterInitial', spaceInitials); + } - async setColorinPicker(hexValue: string) { - await testSubjects.setValue('colorPickerAnchor', hexValue); - } + async clickColorPicker() { + await this.testSubjects.click('colorPickerAnchor'); + } - async clickShowFeatures() { - await testSubjects.click('show-hide-section-link'); - } + async setColorinPicker(hexValue: string) { + await this.testSubjects.setValue('colorPickerAnchor', hexValue); + } - async clickChangeAllPriv() { - await testSubjects.click('changeAllPrivilegesButton'); - } + async clickShowFeatures() { + await this.testSubjects.click('show-hide-section-link'); + } - async clickSaveSpaceCreation() { - await testSubjects.click('save-space-button'); - } + async clickChangeAllPriv() { + await this.testSubjects.click('changeAllPrivilegesButton'); + } - async clickSpaceEditButton(spaceName: string) { - await testSubjects.click(`${spaceName}-editSpace`); - } + async clickSaveSpaceCreation() { + await this.testSubjects.click('save-space-button'); + } - async clickGoToRolesPage() { - await testSubjects.click('rolesManagementPage'); - } + async clickSpaceEditButton(spaceName: string) { + await this.testSubjects.click(`${spaceName}-editSpace`); + } - async clickCancelSpaceCreation() { - await testSubjects.click('cancel-space-button'); - } + async clickGoToRolesPage() { + await this.testSubjects.click('rolesManagementPage'); + } - async clickOnCustomizeURL() { - await testSubjects.click('CustomizeOrReset'); - } + async clickCancelSpaceCreation() { + await this.testSubjects.click('cancel-space-button'); + } - async clickOnSpaceURLDisplay() { - await testSubjects.click('spaceURLDisplay'); - } + async clickOnCustomizeURL() { + await this.testSubjects.click('CustomizeOrReset'); + } - async setSpaceURL(spaceURL: string) { - await testSubjects.setValue('spaceURLDisplay', spaceURL); - } + async clickOnSpaceURLDisplay() { + await this.testSubjects.click('spaceURLDisplay'); + } - async clickHideAllFeatures() { - await testSubjects.click('spc-toggle-all-features-hide'); - } + async setSpaceURL(spaceURL: string) { + await this.testSubjects.setValue('spaceURLDisplay', spaceURL); + } - async clickShowAllFeatures() { - await testSubjects.click('spc-toggle-all-features-show'); - } + async clickHideAllFeatures() { + await this.testSubjects.click('spc-toggle-all-features-hide'); + } - async openFeatureCategory(categoryName: string) { - const category = await find.byCssSelector( - `button[aria-controls=featureCategory_${categoryName}]` - ); - const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; - if (!isCategoryExpanded) { - await category.click(); - } - } + async clickShowAllFeatures() { + await this.testSubjects.click('spc-toggle-all-features-show'); + } - async closeFeatureCategory(categoryName: string) { - const category = await find.byCssSelector( - `button[aria-controls=featureCategory_${categoryName}]` - ); - const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; - if (isCategoryExpanded) { - await category.click(); - } + async openFeatureCategory(categoryName: string) { + const category = await this.find.byCssSelector( + `button[aria-controls=featureCategory_${categoryName}]` + ); + const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; + if (!isCategoryExpanded) { + await category.click(); } + } - async toggleFeatureCategoryVisibility(categoryName: string) { - await testSubjects.click(`featureCategoryButton_${categoryName}`); + async closeFeatureCategory(categoryName: string) { + const category = await this.find.byCssSelector( + `button[aria-controls=featureCategory_${categoryName}]` + ); + const isCategoryExpanded = (await category.getAttribute('aria-expanded')) === 'true'; + if (isCategoryExpanded) { + await category.click(); } + } - async clickOnDescriptionOfSpace() { - await testSubjects.click('descriptionSpaceText'); - } + async toggleFeatureCategoryVisibility(categoryName: string) { + await this.testSubjects.click(`featureCategoryButton_${categoryName}`); + } - async setOnDescriptionOfSpace(descriptionSpace: string) { - await testSubjects.setValue('descriptionSpaceText', descriptionSpace); - } + async clickOnDescriptionOfSpace() { + await this.testSubjects.click('descriptionSpaceText'); + } - async clickOnDeleteSpaceButton(spaceName: string) { - await testSubjects.click(`${spaceName}-deleteSpace`); - } + async setOnDescriptionOfSpace(descriptionSpace: string) { + await this.testSubjects.setValue('descriptionSpaceText', descriptionSpace); + } - async cancelDeletingSpace() { - await testSubjects.click('confirmModalCancelButton'); - } + async clickOnDeleteSpaceButton(spaceName: string) { + await this.testSubjects.click(`${spaceName}-deleteSpace`); + } - async confirmDeletingSpace() { - await testSubjects.click('confirmModalConfirmButton'); - } + async cancelDeletingSpace() { + await this.testSubjects.click('confirmModalCancelButton'); + } - async clickOnSpaceb() { - await testSubjects.click('space-avatar-space_b'); - } + async confirmDeletingSpace() { + await this.testSubjects.click('confirmModalConfirmButton'); + } - async goToSpecificSpace(spaceName: string) { - await testSubjects.click(`${spaceName}-gotoSpace`); - } + async clickOnSpaceb() { + await this.testSubjects.click('space-avatar-space_b'); + } - async clickSpaceAvatar(spaceId: string) { - return await retry.try(async () => { - log.info(`SpaceSelectorPage:clickSpaceAvatar(${spaceId})`); - await testSubjects.click(`space-avatar-${spaceId}`); - await PageObjects.common.sleep(1000); - }); - } + async goToSpecificSpace(spaceName: string) { + await this.testSubjects.click(`${spaceName}-gotoSpace`); } - return new SpaceSelectorPage(); + async clickSpaceAvatar(spaceId: string) { + return await this.retry.try(async () => { + this.log.info(`SpaceSelectorPage:clickSpaceAvatar(${spaceId})`); + await this.testSubjects.click(`space-avatar-${spaceId}`); + await this.common.sleep(1000); + }); + } } diff --git a/x-pack/test/functional/page_objects/status_page.ts b/x-pack/test/functional/page_objects/status_page.ts index ed90aef954770..fa9ff392f0aa1 100644 --- a/x-pack/test/functional/page_objects/status_page.ts +++ b/x-pack/test/functional/page_objects/status_page.ts @@ -5,20 +5,17 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function StatusPagePageProvider({ getService }: FtrProviderContext) { - const log = getService('log'); - const find = getService('find'); - class StatusPage { - async initTests() { - log.debug('StatusPage:initTests'); - } +export class StatusPageObject extends FtrService { + private readonly log = this.ctx.getService('log'); + private readonly find = this.ctx.getService('find'); - async expectStatusPage(): Promise { - await find.byCssSelector('[data-test-subj="statusPageRoot"]', 20000); - } + async initTests() { + this.log.debug('StatusPage:initTests'); } - return new StatusPage(); + async expectStatusPage(): Promise { + await this.find.byCssSelector('[data-test-subj="statusPageRoot"]', 20000); + } } diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts index c503d2282cf6c..b3f6622b04a57 100644 --- a/x-pack/test/functional/page_objects/tag_management_page.ts +++ b/x-pack/test/functional/page_objects/tag_management_page.ts @@ -5,9 +5,10 @@ * 2.0. */ -// eslint-disable-next-line max-classes-per-file +/* eslint-disable max-classes-per-file */ + import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService, FtrProviderContext } from '../ftr_provider_context'; interface FillTagFormFields { name?: string; @@ -17,463 +18,467 @@ interface FillTagFormFields { type TagFormValidation = FillTagFormFields; -export function TagManagementPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const browser = getService('browser'); - const PageObjects = getPageObjects(['header', 'common', 'savedObjects', 'settings']); - const retry = getService('retry'); +/** + * Sub page object to manipulate the create/edit tag modal. + */ +class TagModal extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); + private readonly header = this.ctx.getPageObject('header'); + + constructor(ctx: FtrProviderContext, private readonly page: TagManagementPageObject) { + super(ctx); + } /** - * Sub page object to manipulate the create/edit tag modal. + * Open the create tag modal, by clicking on the associated button. */ - class TagModal { - constructor(private readonly page: TagManagementPage) {} - /** - * Open the create tag modal, by clicking on the associated button. - */ - async openCreate() { - return await testSubjects.click('createTagButton'); - } - - /** - * Open the edit tag modal for given tag name. The tag must be in the currently displayed tags. - */ - async openEdit(tagName: string) { - await this.page.clickEdit(tagName); - } + async openCreate() { + return await this.testSubjects.click('createTagButton'); + } - /** - * Fills the given fields in the form. - * - * If a field is undefined, will not set the value (use a empty string for that) - * If `submit` is true, will call `clickConfirm` once the fields have been filled. - */ - async fillForm(fields: FillTagFormFields, { submit = false }: { submit?: boolean } = {}) { - if (fields.name !== undefined) { - await testSubjects.click('createModalField-name'); - await testSubjects.setValue('createModalField-name', fields.name); - } - if (fields.color !== undefined) { - // EuiColorPicker does not allow to specify data-test-subj for the colorpicker input - await testSubjects.setValue('colorPickerAnchor', fields.color); - } - if (fields.description !== undefined) { - await testSubjects.click('createModalField-description'); - await testSubjects.setValue('createModalField-description', fields.description); - } + /** + * Open the edit tag modal for given tag name. The tag must be in the currently displayed tags. + */ + async openEdit(tagName: string) { + await this.page.clickEdit(tagName); + } - if (submit) { - await this.clickConfirm(); - } + /** + * Fills the given fields in the form. + * + * If a field is undefined, will not set the value (use a empty string for that) + * If `submit` is true, will call `clickConfirm` once the fields have been filled. + */ + async fillForm(fields: FillTagFormFields, { submit = false }: { submit?: boolean } = {}) { + if (fields.name !== undefined) { + await this.testSubjects.click('createModalField-name'); + await this.testSubjects.setValue('createModalField-name', fields.name); + } + if (fields.color !== undefined) { + // EuiColorPicker does not allow to specify data-test-subj for the colorpicker input + await this.testSubjects.setValue('colorPickerAnchor', fields.color); + } + if (fields.description !== undefined) { + await this.testSubjects.click('createModalField-description'); + await this.testSubjects.setValue('createModalField-description', fields.description); } - /** - * Return the values currently displayed in the form. - */ - async getFormValues(): Promise> { - return { - name: await testSubjects.getAttribute('createModalField-name', 'value'), - color: await testSubjects.getAttribute('colorPickerAnchor', 'value'), - description: await testSubjects.getAttribute('createModalField-description', 'value'), - }; + if (submit) { + await this.clickConfirm(); } + } - /** - * Return the validation errors currently displayed for each field. - */ - async getValidationErrors(): Promise { - const errors: TagFormValidation = {}; + /** + * Return the values currently displayed in the form. + */ + async getFormValues(): Promise> { + return { + name: await this.testSubjects.getAttribute('createModalField-name', 'value'), + color: await this.testSubjects.getAttribute('colorPickerAnchor', 'value'), + description: await this.testSubjects.getAttribute('createModalField-description', 'value'), + }; + } - const getError = async (rowDataTestSubj: string) => { - const row = await testSubjects.find(rowDataTestSubj); - const errorElements = await row.findAllByClassName('euiFormErrorText'); - return errorElements.length ? await errorElements[0].getVisibleText() : undefined; - }; + /** + * Return the validation errors currently displayed for each field. + */ + async getValidationErrors(): Promise { + const errors: TagFormValidation = {}; - errors.name = await getError('createModalRow-name'); - errors.color = await getError('createModalRow-color'); - errors.description = await getError('createModalRow-description'); + const getError = async (rowDataTestSubj: string) => { + const row = await this.testSubjects.find(rowDataTestSubj); + const errorElements = await row.findAllByClassName('euiFormErrorText'); + return errorElements.length ? await errorElements[0].getVisibleText() : undefined; + }; - return errors; - } + errors.name = await getError('createModalRow-name'); + errors.color = await getError('createModalRow-color'); + errors.description = await getError('createModalRow-description'); - /** - * Returns true if the form as at least one error displayed, false otherwise - */ - async hasError() { - const errors = await this.getValidationErrors(); - return Boolean(errors.name || errors.color || errors.description); - } + return errors; + } - /** - * Click on the 'cancel' button in the create/edit modal. - */ - async clickCancel() { - await testSubjects.click('createModalCancelButton'); - } + /** + * Returns true if the form as at least one error displayed, false otherwise + */ + async hasError() { + const errors = await this.getValidationErrors(); + return Boolean(errors.name || errors.color || errors.description); + } - /** - * Click on the 'confirm' button in the create/edit modal if not disabled. - */ - async clickConfirm() { - await testSubjects.click('createModalConfirmButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + /** + * Click on the 'cancel' button in the create/edit modal. + */ + async clickCancel() { + await this.testSubjects.click('createModalCancelButton'); + } - /** - * Return true if the confirm button is disabled, false otherwise. - */ - async isConfirmDisabled() { - const disabled = await testSubjects.getAttribute('createModalConfirmButton', 'disabled'); - return disabled === 'true'; - } + /** + * Click on the 'confirm' button in the create/edit modal if not disabled. + */ + async clickConfirm() { + await this.testSubjects.click('createModalConfirmButton'); + await this.header.waitUntilLoadingHasFinished(); + } - /** - * Return true if the modal is currently opened. - */ - async isOpened() { - return await testSubjects.exists('tagModalForm'); - } + /** + * Return true if the confirm button is disabled, false otherwise. + */ + async isConfirmDisabled() { + const disabled = await this.testSubjects.getAttribute('createModalConfirmButton', 'disabled'); + return disabled === 'true'; + } - /** - * Wait until the modal is closed. - */ - async waitUntilClosed() { - await retry.try(async () => { - if (await this.isOpened()) { - throw new Error('save modal still open'); - } - }); - } + /** + * Return true if the modal is currently opened. + */ + async isOpened() { + return await this.testSubjects.exists('tagModalForm'); + } - /** - * Close the modal if currently opened. - */ - async closeIfOpened() { + /** + * Wait until the modal is closed. + */ + async waitUntilClosed() { + await this.retry.try(async () => { if (await this.isOpened()) { - await this.clickCancel(); + throw new Error('save modal still open'); } - } + }); } /** - * Sub page object to manipulate the assign flyout. + * Close the modal if currently opened. */ - class TagAssignmentFlyout { - constructor(private readonly page: TagManagementPage) {} - - /** - * Open the tag assignment flyout, by selected given `tagNames` in the table, then clicking on the `assign` - * action in the bulk action menu. - */ - async open(tagNames: string[]) { - for (const tagName of tagNames) { - await this.page.selectTagByName(tagName); - } - await this.page.clickOnBulkAction('assign'); - await this.waitUntilResultsAreLoaded(); + async closeIfOpened() { + if (await this.isOpened()) { + await this.clickCancel(); } + } +} - /** - * Click on the 'cancel' button in the assign flyout. - */ - async clickCancel() { - await testSubjects.click('assignFlyoutCancelButton'); - await this.page.waitUntilTableIsLoaded(); - } +/** + * Sub page object to manipulate the assign flyout. + */ +class TagAssignmentFlyout extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); - /** - * Click on the 'confirm' button in the assign flyout. - */ - async clickConfirm() { - await testSubjects.click('assignFlyoutConfirmButton'); - await this.waitForFlyoutToClose(); - await this.page.waitUntilTableIsLoaded(); - } + constructor(ctx: FtrProviderContext, private readonly page: TagManagementPageObject) { + super(ctx); + } - /** - * Click on an assignable object result line in the flyout result list. - */ - async clickOnResult(type: string, id: string) { - await testSubjects.click(`assign-result-${type}-${id}`); + /** + * Open the tag assignment flyout, by selected given `tagNames` in the table, then clicking on the `assign` + * action in the bulk action menu. + */ + async open(tagNames: string[]) { + for (const tagName of tagNames) { + await this.page.selectTagByName(tagName); } + await this.page.clickOnBulkAction('assign'); + await this.waitUntilResultsAreLoaded(); + } - /** - * Wait until the assignable object results are displayed in the flyout. - */ - async waitUntilResultsAreLoaded() { - return find.waitForDeletedByCssSelector( - '*[data-test-subj="assignFlyoutResultList"] .euiLoadingSpinner' - ); - } + /** + * Click on the 'cancel' button in the assign flyout. + */ + async clickCancel() { + await this.testSubjects.click('assignFlyoutCancelButton'); + await this.page.waitUntilTableIsLoaded(); + } - /** - * Wait until the flyout is closed. - */ - async waitForFlyoutToClose() { - return testSubjects.waitForDeleted('assignFlyoutResultList'); - } + /** + * Click on the 'confirm' button in the assign flyout. + */ + async clickConfirm() { + await this.testSubjects.click('assignFlyoutConfirmButton'); + await this.waitForFlyoutToClose(); + await this.page.waitUntilTableIsLoaded(); } /** - * Tag management page object. - * - * @remarks All the table manipulation helpers makes the assumption - * that all tags are displayed on a single page. Pagination - * and finding / interacting with a tag on another page is not supported. - */ - class TagManagementPage { - public readonly tagModal = new TagModal(this); - public readonly assignFlyout = new TagAssignmentFlyout(this); - - /** - * Navigate to the tag management section, by accessing the management app, then clicking - * on the `tags` link. - */ - async navigateTo() { - await PageObjects.settings.navigateTo(); - await testSubjects.click('tags'); - await this.waitUntilTableIsLoaded(); - } + * Click on an assignable object result line in the flyout result list. + */ + async clickOnResult(type: string, id: string) { + await this.testSubjects.click(`assign-result-${type}-${id}`); + } - /** - * Wait until the tags table is displayed and is not in a the loading state - */ - async waitUntilTableIsLoaded() { - return retry.try(async () => { - const isLoaded = await find.existsByDisplayedByCssSelector( - '*[data-test-subj="tagsManagementTable"]:not(.euiBasicTable-loading)' - ); - - if (isLoaded) { - return true; - } else { - throw new Error('Waiting'); - } - }); - } + /** + * Wait until the assignable object results are displayed in the flyout. + */ + async waitUntilResultsAreLoaded() { + return this.find.waitForDeletedByCssSelector( + '*[data-test-subj="assignFlyoutResultList"] .euiLoadingSpinner' + ); + } - /** - * Type given `term` in the table's search bar - */ - async searchForTerm(term: string) { - const searchBox = await testSubjects.find('tagsManagementSearchBar'); - await searchBox.clearValue(); - await searchBox.type(term); - await searchBox.pressKeys(browser.keys.ENTER); - await PageObjects.header.waitUntilLoadingHasFinished(); - await this.waitUntilTableIsLoaded(); - } + /** + * Wait until the flyout is closed. + */ + async waitForFlyoutToClose() { + return this.testSubjects.waitForDeleted('assignFlyoutResultList'); + } +} - /** - * Return true if the `Create new tag` button is visible, false otherwise. - */ - async isCreateButtonVisible() { - return await testSubjects.exists('createTagButton'); - } +/** + * Tag management page object. + * + * @remarks All the table manipulation helpers makes the assumption + * that all tags are displayed on a single page. Pagination + * and finding / interacting with a tag on another page is not supported. + */ +export class TagManagementPageObject extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly find = this.ctx.getService('find'); + private readonly browser = this.ctx.getService('browser'); + private readonly retry = this.ctx.getService('retry'); + private readonly header = this.ctx.getPageObject('header'); + private readonly settings = this.ctx.getPageObject('settings'); - /** - * Returns true if given action is available from the table action column - */ - async isActionAvailable(action: string) { - const rows = await testSubjects.findAll('tagsTableRow'); - const firstRow = rows[0]; - // if there is more than 2 actions, they are wrapped in a popover that opens from a new action. - const menuActionPresent = await testSubjects.descendantExists( - 'euiCollapsedItemActionsButton', - firstRow - ); - if (menuActionPresent) { - const actionButton = await testSubjects.findDescendant( - 'euiCollapsedItemActionsButton', - firstRow - ); - await actionButton.click(); - const actionPresent = await testSubjects.exists(`tagsTableAction-${action}`); - await actionButton.click(); - return actionPresent; - } else { - return await testSubjects.exists(`tagsTableAction-${action}`); - } - } + public readonly tagModal = new TagModal(this.ctx, this); + public readonly assignFlyout = new TagAssignmentFlyout(this.ctx, this); - /** - * Return the (table ordered) name of the tags currently displayed in the table. - */ - async getDisplayedTagNames() { - const tags = await this.getDisplayedTagsInfo(); - return tags.map((tag) => tag.name); - } + /** + * Navigate to the tag management section, by accessing the management app, then clicking + * on the `tags` link. + */ + async navigateTo() { + await this.settings.navigateTo(); + await this.testSubjects.click('tags'); + await this.waitUntilTableIsLoaded(); + } - /** - * Return true if the 'connections' link is displayed for given tag name. - * - * Having the link not displayed can either mean that the user don't have the view - * permission to see the connections, or that the tag don't have any. - */ - async isConnectionLinkDisplayed(tagName: string) { - const tags = await this.getDisplayedTagsInfo(); - const tag = tags.find((t) => t.name === tagName); - return tag ? tag.connectionCount !== undefined : false; - } + /** + * Wait until the tags table is displayed and is not in a the loading state + */ + async waitUntilTableIsLoaded() { + return this.retry.try(async () => { + const isLoaded = await this.find.existsByDisplayedByCssSelector( + '*[data-test-subj="tagsManagementTable"]:not(.euiBasicTable-loading)' + ); - /** - * Click the 'edit' action button in the table for given tag name. - */ - async clickEdit(tagName: string) { - const tagRow = await this.getRowByName(tagName); - if (tagRow) { - const editButton = await testSubjects.findDescendant('tagsTableAction-edit', tagRow); - await editButton?.click(); + if (isLoaded) { + return true; + } else { + throw new Error('Waiting'); } - } + }); + } - /** - * Return the raw `WebElementWrapper` of the table's row for given tag name. - */ - async getRowByName(tagName: string) { - const tagNames = await this.getDisplayedTagNames(); - const tagIndex = tagNames.indexOf(tagName); - const rows = await testSubjects.findAll('tagsTableRow'); - return rows[tagIndex]; - } + /** + * Type given `term` in the table's search bar + */ + async searchForTerm(term: string) { + const searchBox = await this.testSubjects.find('tagsManagementSearchBar'); + await searchBox.clearValue(); + await searchBox.type(term); + await searchBox.pressKeys(this.browser.keys.ENTER); + await this.header.waitUntilLoadingHasFinished(); + await this.waitUntilTableIsLoaded(); + } - /** - * Click on the 'connections' link in the table for given tag name. - */ - async clickOnConnectionsLink(tagName: string) { - const tagRow = await this.getRowByName(tagName); - const connectionLink = await testSubjects.findDescendant( - 'tagsTableRowConnectionsLink', - tagRow - ); - await connectionLink.click(); - } + /** + * Return true if the `Create new tag` button is visible, false otherwise. + */ + async isCreateButtonVisible() { + return await this.testSubjects.exists('createTagButton'); + } - /** - * Return true if the selection column is displayed on the table, false otherwise. - */ - async isSelectionColumnDisplayed() { - const firstRow = await testSubjects.find('tagsTableRow'); - const checkbox = await firstRow.findAllByCssSelector( - '.euiTableRowCellCheckbox .euiCheckbox__input' + /** + * Returns true if given action is available from the table action column + */ + async isActionAvailable(action: string) { + const rows = await this.testSubjects.findAll('tagsTableRow'); + const firstRow = rows[0]; + // if there is more than 2 actions, they are wrapped in a popover that opens from a new action. + const menuActionPresent = await this.testSubjects.descendantExists( + 'euiCollapsedItemActionsButton', + firstRow + ); + if (menuActionPresent) { + const actionButton = await this.testSubjects.findDescendant( + 'euiCollapsedItemActionsButton', + firstRow ); - return Boolean(checkbox.length); + await actionButton.click(); + const actionPresent = await this.testSubjects.exists(`tagsTableAction-${action}`); + await actionButton.click(); + return actionPresent; + } else { + return await this.testSubjects.exists(`tagsTableAction-${action}`); } + } - /** - * Click on the selection checkbox of the tag matching given tag name. - */ - async selectTagByName(tagName: string) { - const tagRow = await this.getRowByName(tagName); - const checkbox = await tagRow.findByCssSelector( - '.euiTableRowCellCheckbox .euiCheckbox__input' - ); - await checkbox.click(); - } + /** + * Return the (table ordered) name of the tags currently displayed in the table. + */ + async getDisplayedTagNames() { + const tags = await this.getDisplayedTagsInfo(); + return tags.map((tag) => tag.name); + } - /** - * Returns true if the tag bulk action menu is displayed, false otherwise. - */ - async isActionMenuButtonDisplayed() { - return testSubjects.exists('actionBar-contextMenuButton'); - } + /** + * Return true if the 'connections' link is displayed for given tag name. + * + * Having the link not displayed can either mean that the user don't have the view + * permission to see the connections, or that the tag don't have any. + */ + async isConnectionLinkDisplayed(tagName: string) { + const tags = await this.getDisplayedTagsInfo(); + const tag = tags.find((t) => t.name === tagName); + return tag ? tag.connectionCount !== undefined : false; + } - /** - * Open the bulk action menu if not already opened. - */ - async openActionMenu() { - if (!(await this.isActionMenuOpened())) { - await this.toggleActionMenu(); - } + /** + * Click the 'edit' action button in the table for given tag name. + */ + async clickEdit(tagName: string) { + const tagRow = await this.getRowByName(tagName); + if (tagRow) { + const editButton = await this.testSubjects.findDescendant('tagsTableAction-edit', tagRow); + await editButton?.click(); } + } - /** - * Check if the action for given `actionId` is present in the bulk action menu. - * - * The menu will automatically be opened if not already, but the test must still - * select tags to make the action menu button appear. - */ - async isBulkActionPresent(actionId: string) { - if (!(await this.isActionMenuButtonDisplayed())) { - return false; - } - const menuWasOpened = await this.isActionMenuOpened(); - if (!menuWasOpened) { - await this.openActionMenu(); - } + /** + * Return the raw `WebElementWrapper` of the table's row for given tag name. + */ + async getRowByName(tagName: string) { + const tagNames = await this.getDisplayedTagNames(); + const tagIndex = tagNames.indexOf(tagName); + const rows = await this.testSubjects.findAll('tagsTableRow'); + return rows[tagIndex]; + } + + /** + * Click on the 'connections' link in the table for given tag name. + */ + async clickOnConnectionsLink(tagName: string) { + const tagRow = await this.getRowByName(tagName); + const connectionLink = await this.testSubjects.findDescendant( + 'tagsTableRowConnectionsLink', + tagRow + ); + await connectionLink.click(); + } - const actionExists = await testSubjects.exists(`actionBar-button-${actionId}`); + /** + * Return true if the selection column is displayed on the table, false otherwise. + */ + async isSelectionColumnDisplayed() { + const firstRow = await this.testSubjects.find('tagsTableRow'); + const checkbox = await firstRow.findAllByCssSelector( + '.euiTableRowCellCheckbox .euiCheckbox__input' + ); + return Boolean(checkbox.length); + } - if (!menuWasOpened) { - await this.toggleActionMenu(); - } + /** + * Click on the selection checkbox of the tag matching given tag name. + */ + async selectTagByName(tagName: string) { + const tagRow = await this.getRowByName(tagName); + const checkbox = await tagRow.findByCssSelector('.euiTableRowCellCheckbox .euiCheckbox__input'); + await checkbox.click(); + } + + /** + * Returns true if the tag bulk action menu is displayed, false otherwise. + */ + async isActionMenuButtonDisplayed() { + return this.testSubjects.exists('actionBar-contextMenuButton'); + } - return actionExists; + /** + * Open the bulk action menu if not already opened. + */ + async openActionMenu() { + if (!(await this.isActionMenuOpened())) { + await this.toggleActionMenu(); } + } - /** - * Click on given bulk action button - */ - async clickOnBulkAction(actionId: string) { + /** + * Check if the action for given `actionId` is present in the bulk action menu. + * + * The menu will automatically be opened if not already, but the test must still + * select tags to make the action menu button appear. + */ + async isBulkActionPresent(actionId: string) { + if (!(await this.isActionMenuButtonDisplayed())) { + return false; + } + const menuWasOpened = await this.isActionMenuOpened(); + if (!menuWasOpened) { await this.openActionMenu(); - await testSubjects.click(`actionBar-button-${actionId}`); } - /** - * Toggle (close if opened, open if closed) the bulk action menu. - */ - async toggleActionMenu() { - await testSubjects.click('actionBar-contextMenuButton'); - } + const actionExists = await this.testSubjects.exists(`actionBar-button-${actionId}`); - /** - * Return true if the bulk action menu is opened, false otherwise. - */ - async isActionMenuOpened() { - return testSubjects.exists('actionBar-contextMenuPopover'); + if (!menuWasOpened) { + await this.toggleActionMenu(); } - /** - * Return the info of all the tags currently displayed in the table (in table's order) - */ - async getDisplayedTagsInfo() { - const rows = await testSubjects.findAll('tagsTableRow'); - return Promise.all([...rows.map(parseTableRow)]); - } + return actionExists; + } - async getDisplayedTagInfo(tagName: string) { - const rows = await this.getDisplayedTagsInfo(); - return rows.find((row) => row.name === tagName); - } + /** + * Click on given bulk action button + */ + async clickOnBulkAction(actionId: string) { + await this.openActionMenu(); + await this.testSubjects.click(`actionBar-button-${actionId}`); + } - /** - * Converts the tagName to the format used in test subjects - * @param tagName - */ - testSubjFriendly(tagName: string) { - return tagName.replace(' ', '_'); - } + /** + * Toggle (close if opened, open if closed) the bulk action menu. + */ + async toggleActionMenu() { + await this.testSubjects.click('actionBar-contextMenuButton'); } - const parseTableRow = async (row: WebElementWrapper) => { - const dom = await row.parseDomContent(); + /** + * Return true if the bulk action menu is opened, false otherwise. + */ + async isActionMenuOpened() { + return this.testSubjects.exists('actionBar-contextMenuPopover'); + } - const connectionsText = dom.findTestSubject('tagsTableRowConnectionsLink').text(); - const rawConnectionCount = connectionsText.replace(/[^0-9]/g, ''); - const connectionCount = rawConnectionCount ? parseInt(rawConnectionCount, 10) : undefined; + /** + * Return the info of all the tags currently displayed in the table (in table's order) + */ + async getDisplayedTagsInfo() { + const rows = await this.testSubjects.findAll('tagsTableRow'); + return Promise.all([...rows.map(parseTableRow)]); + } - // ideally we would also return the color, but it can't be easily retrieved from the DOM - return { - name: dom.findTestSubject('tagsTableRowName').find('.euiTableCellContent').text(), - description: dom - .findTestSubject('tagsTableRowDescription') - .find('.euiTableCellContent') - .text(), - connectionCount, - }; - }; + async getDisplayedTagInfo(tagName: string) { + const rows = await this.getDisplayedTagsInfo(); + return rows.find((row) => row.name === tagName); + } - return new TagManagementPage(); + /** + * Converts the tagName to the format used in test subjects + * @param tagName + */ + testSubjFriendly(tagName: string) { + return tagName.replace(' ', '_'); + } } + +const parseTableRow = async (row: WebElementWrapper) => { + const dom = await row.parseDomContent(); + + const connectionsText = dom.findTestSubject('tagsTableRowConnectionsLink').text(); + const rawConnectionCount = connectionsText.replace(/[^0-9]/g, ''); + const connectionCount = rawConnectionCount ? parseInt(rawConnectionCount, 10) : undefined; + + // ideally we would also return the color, but it can't be easily retrieved from the DOM + return { + name: dom.findTestSubject('tagsTableRowName').find('.euiTableCellContent').text(), + description: dom.findTestSubject('tagsTableRowDescription').find('.euiTableCellContent').text(), + connectionCount, + }; +}; diff --git a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts index 1c4a85450a8da..211bcbbd59219 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts +++ b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts @@ -5,76 +5,72 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function UpgradeAssistantPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const retry = getService('retry'); - const log = getService('log'); - const browser = getService('browser'); - const find = getService('find'); - const testSubjects = getService('testSubjects'); - const { common } = getPageObjects(['common']); +export class UpgradeAssistantPageObject extends FtrService { + private readonly retry = this.ctx.getService('retry'); + private readonly log = this.ctx.getService('log'); + 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'); - class UpgradeAssistant { - async initTests() { - log.debug('UpgradeAssistant:initTests'); - } + async initTests() { + this.log.debug('UpgradeAssistant:initTests'); + } - async navigateToPage() { - return await retry.try(async () => { - await common.navigateToApp('settings'); - await testSubjects.click('upgrade_assistant'); - await retry.waitFor('url to contain /upgrade_assistant', async () => { - const url = await browser.getCurrentUrl(); - return url.includes('/upgrade_assistant'); - }); + async navigateToPage() { + return await this.retry.try(async () => { + await this.common.navigateToApp('settings'); + await this.testSubjects.click('upgrade_assistant'); + await this.retry.waitFor('url to contain /upgrade_assistant', async () => { + const url = await this.browser.getCurrentUrl(); + return url.includes('/upgrade_assistant'); }); - } - - async toggleDeprecationLogging() { - log.debug('toggleDeprecationLogging()'); - await testSubjects.click('upgradeAssistantDeprecationToggle'); - } + }); + } - async isDeprecationLoggingEnabled() { - const isDeprecationEnabled = await testSubjects.getAttribute( - 'upgradeAssistantDeprecationToggle', - 'aria-checked' - ); - log.debug(`Deprecation enabled == ${isDeprecationEnabled}`); - return isDeprecationEnabled === 'true'; - } + async toggleDeprecationLogging() { + this.log.debug('toggleDeprecationLogging()'); + await this.testSubjects.click('upgradeAssistantDeprecationToggle'); + } - async deprecationLoggingEnabledLabel() { - const loggingEnabledLabel = await find.byCssSelector( - '[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span' - ); - return await loggingEnabledLabel.getVisibleText(); - } + async isDeprecationLoggingEnabled() { + const isDeprecationEnabled = await this.testSubjects.getAttribute( + 'upgradeAssistantDeprecationToggle', + 'aria-checked' + ); + this.log.debug(`Deprecation enabled == ${isDeprecationEnabled}`); + return isDeprecationEnabled === 'true'; + } - async clickTab(tabId: string) { - return await retry.try(async () => { - log.debug('clickTab()'); - await find.clickByCssSelector(`.euiTabs .euiTab#${tabId}`); - }); - } + async deprecationLoggingEnabledLabel() { + const loggingEnabledLabel = await this.find.byCssSelector( + '[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span' + ); + return await loggingEnabledLabel.getVisibleText(); + } - async waitForTelemetryHidden() { - const self = this; - await retry.waitFor('Telemetry to disappear.', async () => { - return (await self.isTelemetryExists()) === false; - }); - } + async clickTab(tabId: string) { + return await this.retry.try(async () => { + this.log.debug('clickTab()'); + await this.find.clickByCssSelector(`.euiTabs .euiTab#${tabId}`); + }); + } - async issueSummaryText() { - log.debug('expectIssueSummary()'); - return await testSubjects.getVisibleText('upgradeAssistantIssueSummary'); - } + async waitForTelemetryHidden() { + const self = this; + await this.retry.waitFor('Telemetry to disappear.', async () => { + return (await self.isTelemetryExists()) === false; + }); + } - async isTelemetryExists() { - return await testSubjects.exists('upgradeAssistantTelemetryRunning'); - } + async issueSummaryText() { + this.log.debug('expectIssueSummary()'); + return await this.testSubjects.getVisibleText('upgradeAssistantIssueSummary'); } - return new UpgradeAssistant(); + async isTelemetryExists() { + return await this.testSubjects.exists('upgradeAssistantTelemetryRunning'); + } } diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 03e1513eac043..366511f7f43c5 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -6,117 +6,121 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function UptimePageProvider({ getPageObjects, getService }: FtrProviderContext) { - const pageObjects = getPageObjects(['timePicker', 'header']); - const { common: commonService, monitor, navigation } = getService('uptime'); - const retry = getService('retry'); - - return new (class UptimePage { - public async goToRoot(refresh?: boolean) { - await navigation.goToUptime(); - if (refresh) { - await navigation.refreshApp(); - } - } - - public async setDateRange(start: string, end: string) { - const { start: prevStart, end: prevEnd } = await pageObjects.timePicker.getTimeConfig(); - if (start !== prevStart || prevEnd !== end) { - await pageObjects.timePicker.setAbsoluteRange(start, end); - } else { - await navigation.refreshApp(); - } - } - - public async goToUptimeOverviewAndLoadData( - dateStart: string, - dateEnd: string, - monitorIdToCheck?: string - ) { - await navigation.goToUptime(); - await this.setDateRange(dateStart, dateEnd); - if (monitorIdToCheck) { - await commonService.monitorIdExists(monitorIdToCheck); - } - await pageObjects.header.waitUntilLoadingHasFinished(); - } - - public async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { - await pageObjects.header.waitUntilLoadingHasFinished(); - await this.setDateRange(dateStart, dateEnd); - await navigation.goToMonitor(monitorId); - } - - public async inputFilterQuery(filterQuery: string) { - await commonService.setFilterText(filterQuery); - } - - public async pageHasDataMissing() { - return await commonService.pageHasDataMissing(); - } - - public async pageHasExpectedIds(monitorIdsToCheck: string[]): Promise { - return retry.tryForTime(15000, async () => { - await Promise.all(monitorIdsToCheck.map((id) => commonService.monitorPageLinkExists(id))); - }); - } - - public async pageUrlContains(value: string, expected: boolean = true): Promise { - return retry.tryForTime(12000, async () => { - expect(await commonService.urlContains(value)).to.eql(expected); - }); - } - - public async changePage(direction: 'next' | 'prev') { - if (direction === 'next') { - await commonService.goToNextPage(); - } else if (direction === 'prev') { - await commonService.goToPreviousPage(); - } - } - - public async setStatusFilter(value: 'up' | 'down') { - if (value === 'up') { - await commonService.setStatusFilterUp(); - } else if (value === 'down') { - await commonService.setStatusFilterDown(); - } - } - - public async selectFilterItems(filters: Record) { - for (const key in filters) { - if (filters.hasOwnProperty(key)) { - const values = filters[key]; - for (let i = 0; i < values.length; i++) { - await commonService.selectFilterItem(key, values[i]); - } +import { FtrService } from '../ftr_provider_context'; + +export class UptimePageObject extends FtrService { + private readonly timePicker = this.ctx.getPageObject('timePicker'); + private readonly header = this.ctx.getPageObject('header'); + + private readonly commonService = this.ctx.getService('uptime').common; + private readonly monitor = this.ctx.getService('uptime').monitor; + private readonly navigation = this.ctx.getService('uptime').navigation; + private readonly retry = this.ctx.getService('retry'); + + public async goToRoot(refresh?: boolean) { + await this.navigation.goToUptime(); + if (refresh) { + await this.navigation.refreshApp(); + } + } + + public async setDateRange(start: string, end: string) { + const { start: prevStart, end: prevEnd } = await this.timePicker.getTimeConfig(); + if (start !== prevStart || prevEnd !== end) { + await this.timePicker.setAbsoluteRange(start, end); + } else { + await this.navigation.refreshApp(); + } + } + + public async goToUptimeOverviewAndLoadData( + dateStart: string, + dateEnd: string, + monitorIdToCheck?: string + ) { + await this.navigation.goToUptime(); + await this.setDateRange(dateStart, dateEnd); + if (monitorIdToCheck) { + await this.commonService.monitorIdExists(monitorIdToCheck); + } + await this.header.waitUntilLoadingHasFinished(); + } + + public async loadDataAndGoToMonitorPage(dateStart: string, dateEnd: string, monitorId: string) { + await this.header.waitUntilLoadingHasFinished(); + await this.setDateRange(dateStart, dateEnd); + await this.navigation.goToMonitor(monitorId); + } + + public async inputFilterQuery(filterQuery: string) { + await this.commonService.setFilterText(filterQuery); + } + + public async pageHasDataMissing() { + return await this.commonService.pageHasDataMissing(); + } + + public async pageHasExpectedIds(monitorIdsToCheck: string[]): Promise { + return this.retry.tryForTime(15000, async () => { + await Promise.all( + monitorIdsToCheck.map((id) => this.commonService.monitorPageLinkExists(id)) + ); + }); + } + + public async pageUrlContains(value: string, expected: boolean = true): Promise { + return this.retry.tryForTime(12000, async () => { + expect(await this.commonService.urlContains(value)).to.eql(expected); + }); + } + + public async changePage(direction: 'next' | 'prev') { + if (direction === 'next') { + await this.commonService.goToNextPage(); + } else if (direction === 'prev') { + await this.commonService.goToPreviousPage(); + } + } + + public async setStatusFilter(value: 'up' | 'down') { + if (value === 'up') { + await this.commonService.setStatusFilterUp(); + } else if (value === 'down') { + await this.commonService.setStatusFilterDown(); + } + } + + public async selectFilterItems(filters: Record) { + for (const key in filters) { + if (filters.hasOwnProperty(key)) { + const values = filters[key]; + for (let i = 0; i < values.length; i++) { + await this.commonService.selectFilterItem(key, values[i]); } } } + } - public async getSnapshotCount() { - return await commonService.getSnapshotCount(); - } + public async getSnapshotCount() { + return await this.commonService.getSnapshotCount(); + } - public async setAlertKueryBarText(filters: string) { - const { setKueryBarText } = commonService; - await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters); - } + public async setAlertKueryBarText(filters: string) { + const { setKueryBarText } = this.commonService; + await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters); + } - public async setMonitorListPageSize(size: number): Promise { - await commonService.openPageSizeSelectPopover(); - return commonService.clickPageSizeSelectPopoverItem(size); - } + public async setMonitorListPageSize(size: number): Promise { + await this.commonService.openPageSizeSelectPopover(); + return this.commonService.clickPageSizeSelectPopoverItem(size); + } - public async checkPingListInteractions(timestamps: string[]): Promise { - return monitor.checkForPingListTimestamps(timestamps); - } + public async checkPingListInteractions(timestamps: string[]): Promise { + return this.monitor.checkForPingListTimestamps(timestamps); + } - public async resetFilters() { - await this.inputFilterQuery(''); - await commonService.resetStatusFilter(); - } - })(); + public async resetFilters() { + await this.inputFilterQuery(''); + await this.commonService.resetStatusFilter(); + } } diff --git a/x-pack/test/functional/page_objects/watcher_page.ts b/x-pack/test/functional/page_objects/watcher_page.ts index d0db5c8c3b267..5aeaddffff581 100644 --- a/x-pack/test/functional/page_objects/watcher_page.ts +++ b/x-pack/test/functional/page_objects/watcher_page.ts @@ -6,64 +6,61 @@ */ import { map as mapAsync } from 'bluebird'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; -export function WatcherPageProvider({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['header']); - const find = getService('find'); - const testSubjects = getService('testSubjects'); +export class WatcherPageObject extends FtrService { + private readonly header = this.ctx.getPageObject('header'); + private readonly find = this.ctx.getService('find'); + private readonly testSubjects = this.ctx.getService('testSubjects'); - class WatcherPage { - async clearAllWatches() { - const checkBoxExists = await testSubjects.exists('checkboxSelectAll'); - if (checkBoxExists) { - await testSubjects.click('checkboxSelectAll'); - await testSubjects.click('btnDeleteWatches'); - await testSubjects.click('confirmModalConfirmButton'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async clearAllWatches() { + const checkBoxExists = await this.testSubjects.exists('checkboxSelectAll'); + if (checkBoxExists) { + await this.testSubjects.click('checkboxSelectAll'); + await this.testSubjects.click('btnDeleteWatches'); + await this.testSubjects.click('confirmModalConfirmButton'); + await this.header.waitUntilLoadingHasFinished(); } + } - async createWatch(watchName: string, name: string) { - await testSubjects.click('createWatchButton'); - await testSubjects.click('jsonWatchCreateLink'); - await find.setValue('#id', watchName); - await find.setValue('#watchName', name); - await find.clickByCssSelector('[type="submit"]'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } + async createWatch(watchName: string, name: string) { + await this.testSubjects.click('createWatchButton'); + await this.testSubjects.click('jsonWatchCreateLink'); + await this.find.setValue('#id', watchName); + await this.find.setValue('#watchName', name); + await this.find.clickByCssSelector('[type="submit"]'); + await this.header.waitUntilLoadingHasFinished(); + } - async getWatch(watchID: string) { - const watchIdColumn = await testSubjects.find(`watchIdColumn-${watchID}`); - const watchNameColumn = await testSubjects.find(`watchNameColumn-${watchID}`); - const id = await watchIdColumn.getVisibleText(); - const name = await watchNameColumn.getVisibleText(); - return { - id, - name, - }; - } + async getWatch(watchID: string) { + const watchIdColumn = await this.testSubjects.find(`watchIdColumn-${watchID}`); + const watchNameColumn = await this.testSubjects.find(`watchNameColumn-${watchID}`); + const id = await watchIdColumn.getVisibleText(); + const name = await watchNameColumn.getVisibleText(); + return { + id, + name, + }; + } - async deleteWatch() { - await testSubjects.click('checkboxSelectAll'); - await testSubjects.click('btnDeleteWatches'); - } + async deleteWatch() { + await this.testSubjects.click('checkboxSelectAll'); + await this.testSubjects.click('btnDeleteWatches'); + } - // get all the watches in the list - async getWatches() { - const watches = await find.allByCssSelector('.euiTableRow'); - return mapAsync(watches, async (watch) => { - const checkBox = await watch.findByCssSelector('td:nth-child(1)'); - const id = await watch.findByCssSelector('td:nth-child(2)'); - const name = await watch.findByCssSelector('td:nth-child(3)'); + // get all the watches in the list + async getWatches() { + const watches = await this.find.allByCssSelector('.euiTableRow'); + return mapAsync(watches, async (watch) => { + const checkBox = await watch.findByCssSelector('td:nth-child(1)'); + const id = await watch.findByCssSelector('td:nth-child(2)'); + const name = await watch.findByCssSelector('td:nth-child(3)'); - return { - checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'), - id: await id.getVisibleText(), - name: (await name.getVisibleText()).split(',').map((role) => role.trim()), - }; - }); - } + return { + checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'), + id: await id.getVisibleText(), + name: (await name.getVisibleText()).split(',').map((role) => role.trim()), + }; + }); } - return new WatcherPage(); } diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 0da518a5e8967..99293c71676b4 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -58,7 +58,7 @@ import { DashboardDrilldownsManageProvider, DashboardPanelTimeRangeProvider, } from './dashboard'; -import { SearchSessionsProvider } from './search_sessions'; +import { SearchSessionsService } from './search_sessions'; // define the name and providers for services that should be // available to your tests. If you don't specify anything here @@ -107,5 +107,5 @@ export const services = { dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider, dashboardDrilldownsManage: DashboardDrilldownsManageProvider, dashboardPanelTimeRange: DashboardPanelTimeRangeProvider, - searchSessions: SearchSessionsProvider, + searchSessions: SearchSessionsService, }; diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts index b61bc8871482f..31cf17575bdd9 100644 --- a/x-pack/test/functional/services/ml/common_ui.ts +++ b/x-pack/test/functional/services/ml/common_ui.ts @@ -12,7 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import type { CanvasElementColorStats } from '../canvas_element'; -interface SetValueOptions { +export interface SetValueOptions { clearWithKeyboard?: boolean; typeCharByChar?: boolean; } diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts index 2990f70059767..7df09652f8953 100644 --- a/x-pack/test/functional/services/ml/job_wizard_common.ts +++ b/x-pack/test/functional/services/ml/job_wizard_common.ts @@ -11,6 +11,10 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { MlCommonUI } from './common_ui'; import { MlCustomUrls } from './custom_urls'; +export interface SectionOptions { + withAdvancedSection: boolean; +} + export function MachineLearningJobWizardCommonProvider( { getService }: FtrProviderContext, mlCommonUI: MlCommonUI, @@ -20,10 +24,6 @@ export function MachineLearningJobWizardCommonProvider( const retry = getService('retry'); const testSubjects = getService('testSubjects'); - interface SectionOptions { - withAdvancedSection: boolean; - } - function advancedSectionSelector(subSelector?: string) { const subj = 'mlJobWizardAdvancedSection'; return !subSelector ? subj : `${subj} > ${subSelector}`; diff --git a/x-pack/test/functional/services/search_sessions.ts b/x-pack/test/functional/services/search_sessions.ts index 63dc880fda380..4490b87006833 100644 --- a/x-pack/test/functional/services/search_sessions.ts +++ b/x-pack/test/functional/services/search_sessions.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; const SEARCH_SESSION_INDICATOR_TEST_SUBJ = 'searchSessionIndicator'; const SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; @@ -25,155 +25,153 @@ type SessionStateType = | 'restored' | 'canceled'; -export function SearchSessionsProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const log = getService('log'); - const retry = getService('retry'); - const browser = getService('browser'); - const supertest = getService('supertest'); - - return new (class SearchSessionsService { - public async find(): Promise { - return testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - } - - public async exists(): Promise { - return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - } - - public async missingOrFail(): Promise { - return testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - } - - public async disabledOrFail() { - await this.exists(); - await expect(await (await this.find()).getAttribute('data-save-disabled')).to.be('true'); - } - - public async expectState(state: SessionStateType) { - return retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { - const currentState = await ( - await testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ) - ).getAttribute('data-state'); - log.info(`searchSessions state current: ${currentState} expected: ${state}`); - return currentState === state; +export class SearchSessionsService extends FtrService { + private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly log = this.ctx.getService('log'); + private readonly retry = this.ctx.getService('retry'); + private readonly browser = this.ctx.getService('browser'); + private readonly supertest = this.ctx.getService('supertest'); + + public async find(): Promise { + return this.testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async exists(): Promise { + return this.testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async missingOrFail(): Promise { + return this.testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async disabledOrFail() { + await this.exists(); + await expect(await (await this.find()).getAttribute('data-save-disabled')).to.be('true'); + } + + public async expectState(state: SessionStateType) { + return this.retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { + const currentState = await ( + await this.testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ) + ).getAttribute('data-state'); + this.log.info(`searchSessions state current: ${currentState} expected: ${state}`); + return currentState === state; + }); + } + + public async viewSearchSessions() { + this.log.debug('viewSearchSessions'); + await this.ensurePopoverOpened(); + await this.testSubjects.click('searchSessionIndicatorViewSearchSessionsLink'); + } + + public async save({ searchSessionName }: { searchSessionName?: string } = {}) { + this.log.debug('save the search session'); + await this.ensurePopoverOpened(); + await this.testSubjects.click('searchSessionIndicatorSaveBtn'); + + if (searchSessionName) { + await this.testSubjects.click('searchSessionNameEdit'); + await this.testSubjects.setValue('searchSessionNameInput', searchSessionName, { + clearWithKeyboard: true, }); - } - - public async viewSearchSessions() { - log.debug('viewSearchSessions'); - await this.ensurePopoverOpened(); - await testSubjects.click('searchSessionIndicatorViewSearchSessionsLink'); - } - - public async save({ searchSessionName }: { searchSessionName?: string } = {}) { - log.debug('save the search session'); - await this.ensurePopoverOpened(); - await testSubjects.click('searchSessionIndicatorSaveBtn'); - - if (searchSessionName) { - await testSubjects.click('searchSessionNameEdit'); - await testSubjects.setValue('searchSessionNameInput', searchSessionName, { - clearWithKeyboard: true, - }); - await testSubjects.click('searchSessionNameSave'); + await this.testSubjects.click('searchSessionNameSave'); + } + + await this.ensurePopoverClosed(); + } + + public async cancel() { + this.log.debug('cancel the search session'); + await this.ensurePopoverOpened(); + await this.testSubjects.click('searchSessionIndicatorCancelBtn'); + await this.ensurePopoverClosed(); + } + + public async openPopover() { + await this.ensurePopoverOpened(); + } + + public async openedOrFail() { + return this.testSubjects.existOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { + timeout: 15000, // because popover auto opens after search takes 10s + }); + } + + public async closedOrFail() { + return this.testSubjects.missingOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { + timeout: 15000, // because popover auto opens after search takes 10s + }); + } + + private async ensurePopoverOpened() { + this.log.debug('ensurePopoverOpened'); + const isAlreadyOpen = await this.testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); + if (isAlreadyOpen) { + this.log.debug('Popover is already open'); + return; + } + return this.retry.waitFor(`searchSessions popover opened`, async () => { + await this.testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + return await this.testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); + }); + } + + private async ensurePopoverClosed() { + this.log.debug('ensurePopoverClosed'); + const isAlreadyClosed = !(await this.testSubjects.exists( + SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ + )); + if (isAlreadyClosed) return; + return this.retry.waitFor(`searchSessions popover closed`, async () => { + await this.browser.pressKeys(this.browser.keys.ESCAPE); + return !(await this.testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ)); + }); + } + + /* + * This cleanup function should be used by tests that create new search sessions. + * Tests should not end with new search sessions remaining in storage since that interferes with functional tests that check the _find API. + * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. + */ + public async deleteAllSearchSessions() { + this.log.debug('Deleting created search sessions'); + // ignores 409 errs and keeps retrying + await this.retry.tryForTime(10000, async () => { + const { body } = await this.supertest + .post('/internal/session/_find') + .set('kbn-xsrf', 'anything') + .set('kbn-system-request', 'true') + .send({ page: 1, perPage: 10000, sortField: 'created', sortOrder: 'asc' }) + .expect(200); + + const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; + if (savedObjects.length) { + this.log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); } - - await this.ensurePopoverClosed(); - } - - public async cancel() { - log.debug('cancel the search session'); - await this.ensurePopoverOpened(); - await testSubjects.click('searchSessionIndicatorCancelBtn'); - await this.ensurePopoverClosed(); - } - - public async openPopover() { - await this.ensurePopoverOpened(); - } - - public async openedOrFail() { - return testSubjects.existOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { - timeout: 15000, // because popover auto opens after search takes 10s - }); - } - - public async closedOrFail() { - return testSubjects.missingOrFail(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ, { - timeout: 15000, // because popover auto opens after search takes 10s - }); - } - - private async ensurePopoverOpened() { - log.debug('ensurePopoverOpened'); - const isAlreadyOpen = await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); - if (isAlreadyOpen) { - log.debug('Popover is already open'); - return; - } - return retry.waitFor(`searchSessions popover opened`, async () => { - await testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); - return await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); - }); - } - - private async ensurePopoverClosed() { - log.debug('ensurePopoverClosed'); - const isAlreadyClosed = !(await testSubjects.exists( - SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ - )); - if (isAlreadyClosed) return; - return retry.waitFor(`searchSessions popover closed`, async () => { - await browser.pressKeys(browser.keys.ESCAPE); - return !(await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ)); - }); - } - - /* - * This cleanup function should be used by tests that create new search sessions. - * Tests should not end with new search sessions remaining in storage since that interferes with functional tests that check the _find API. - * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. - */ - public async deleteAllSearchSessions() { - log.debug('Deleting created search sessions'); - // ignores 409 errs and keeps retrying - await retry.tryForTime(10000, async () => { - const { body } = await supertest - .post('/internal/session/_find') - .set('kbn-xsrf', 'anything') - .set('kbn-system-request', 'true') - .send({ page: 1, perPage: 10000, sortField: 'created', sortOrder: 'asc' }) - .expect(200); - - const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; - if (savedObjects.length) { - log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); - } - await Promise.all( - savedObjects.map(async (so) => { - log.debug(`Deleting search session: ${so.id}`); - await supertest - .delete(`/internal/session/${so.id}`) - .set(`kbn-xsrf`, `anything`) - .expect(200); - }) - ); - }); - } - - public async markTourDone() { - await Promise.all([ - browser.setLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY, 'true'), - browser.setLocalStorageItem(TOUR_RESTORE_STEP_KEY, 'true'), - ]); - } - - public async markTourUndone() { - await Promise.all([ - browser.removeLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY), - browser.removeLocalStorageItem(TOUR_RESTORE_STEP_KEY), - ]); - } - })(); + await Promise.all( + savedObjects.map(async (so) => { + this.log.debug(`Deleting search session: ${so.id}`); + await this.supertest + .delete(`/internal/session/${so.id}`) + .set(`kbn-xsrf`, `anything`) + .expect(200); + }) + ); + }); + } + + public async markTourDone() { + await Promise.all([ + this.browser.setLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY, 'true'), + this.browser.setLocalStorageItem(TOUR_RESTORE_STEP_KEY, 'true'), + ]); + } + + public async markTourUndone() { + await Promise.all([ + this.browser.removeLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY), + this.browser.removeLocalStorageItem(TOUR_RESTORE_STEP_KEY), + ]); + } } diff --git a/x-pack/test/reporting_api_integration/services/usage.ts b/x-pack/test/reporting_api_integration/services/usage.ts index ababbbf03e4c1..4e8d464467191 100644 --- a/x-pack/test/reporting_api_integration/services/usage.ts +++ b/x-pack/test/reporting_api_integration/services/usage.ts @@ -31,7 +31,7 @@ export interface ReportingUsageStats { [jobType: string]: any; } -interface UsageStats { +export interface UsageStats { reporting: ReportingUsageStats; } diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts index 088e594a41b88..b712c2882ee0f 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts @@ -191,6 +191,64 @@ export const expectResponses = { }, }; +const commonUsers = { + noAccess: { + ...NOT_A_KIBANA_USER, + description: 'user with no access', + authorizedAtSpaces: [], + }, + superuser: { + ...SUPERUSER, + description: 'superuser', + authorizedAtSpaces: ['*'], + }, + legacyAll: { ...KIBANA_LEGACY_USER, description: 'legacy user', authorizedAtSpaces: [] }, + allGlobally: { + ...KIBANA_RBAC_USER, + description: 'rbac user with all globally', + authorizedAtSpaces: ['*'], + }, + readGlobally: { + ...KIBANA_RBAC_DASHBOARD_ONLY_USER, + description: 'rbac user with read globally', + authorizedAtSpaces: ['*'], + }, + dualAll: { + ...KIBANA_DUAL_PRIVILEGES_USER, + description: 'dual-privileges user', + authorizedAtSpaces: ['*'], + }, + dualRead: { + ...KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, + description: 'dual-privileges readonly user', + authorizedAtSpaces: ['*'], + }, +}; + +interface Security { + modifier?: T; + users: Record< + | keyof typeof commonUsers + | 'allAtDefaultSpace' + | 'readAtDefaultSpace' + | 'allAtSpace1' + | 'readAtSpace1', + TestUser + >; +} +interface SecurityAndSpaces { + modifier?: T; + users: Record< + keyof typeof commonUsers | 'allAtSpace' | 'readAtSpace' | 'allAtOtherSpace', + TestUser + >; + spaceId: string; +} +interface Spaces { + modifier?: T; + spaceId: string; +} + /** * Get test scenarios for each type of suite. * @param modifier Use this to generate additional permutations of test scenarios. @@ -203,66 +261,10 @@ export const expectResponses = { * ] */ export const getTestScenarios = (modifiers?: T[]) => { - const commonUsers = { - noAccess: { - ...NOT_A_KIBANA_USER, - description: 'user with no access', - authorizedAtSpaces: [], - }, - superuser: { - ...SUPERUSER, - description: 'superuser', - authorizedAtSpaces: ['*'], - }, - legacyAll: { ...KIBANA_LEGACY_USER, description: 'legacy user', authorizedAtSpaces: [] }, - allGlobally: { - ...KIBANA_RBAC_USER, - description: 'rbac user with all globally', - authorizedAtSpaces: ['*'], - }, - readGlobally: { - ...KIBANA_RBAC_DASHBOARD_ONLY_USER, - description: 'rbac user with read globally', - authorizedAtSpaces: ['*'], - }, - dualAll: { - ...KIBANA_DUAL_PRIVILEGES_USER, - description: 'dual-privileges user', - authorizedAtSpaces: ['*'], - }, - dualRead: { - ...KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, - description: 'dual-privileges readonly user', - authorizedAtSpaces: ['*'], - }, - }; - - interface Security { - modifier?: T; - users: Record< - | keyof typeof commonUsers - | 'allAtDefaultSpace' - | 'readAtDefaultSpace' - | 'allAtSpace1' - | 'readAtSpace1', - TestUser - >; - } - interface SecurityAndSpaces { - modifier?: T; - users: Record< - keyof typeof commonUsers | 'allAtSpace' | 'readAtSpace' | 'allAtOtherSpace', - TestUser - >; - spaceId: string; - } - interface Spaces { - modifier?: T; - spaceId: string; - } - - let spaces: Spaces[] = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID].map((x) => ({ spaceId: x })); - let security: Security[] = [ + let spaces: Array> = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID].map((x) => ({ + spaceId: x, + })); + let security: Array> = [ { users: { ...commonUsers, @@ -289,7 +291,7 @@ export const getTestScenarios = (modifiers?: T[]) => { }, }, ]; - let securityAndSpaces: SecurityAndSpaces[] = [ + let securityAndSpaces: Array> = [ { spaceId: DEFAULT_SPACE_ID, users: { diff --git a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts index dd17548df6e3f..6f26e0b3ad364 100644 --- a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts +++ b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts @@ -5,145 +5,144 @@ * 2.0. */ -import { FtrProviderContext } from '../../../functional/ftr_provider_context'; +import { FtrService } from '../../../functional/ftr_provider_context'; import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; -export function DetectionsPageProvider({ getService, getPageObjects }: FtrProviderContext) { - const find = getService('find'); - const { common } = getPageObjects(['common']); - const testSubjects = getService('testSubjects'); - - class DetectionsPage { - async navigateHome(): Promise { - await this.navigateToDetectionsPage(); - } - - async navigateToRules(): Promise { - await this.navigateToDetectionsPage('rules'); - } - - async navigateToRuleMonitoring(): Promise { - await common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table'); - } - - async navigateToExceptionList(): Promise { - await common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table'); - } - - async navigateToCreateRule(): Promise { - await this.navigateToDetectionsPage('rules/create'); - } - - async replaceIndexPattern(): Promise { - const buttons = await find.allByCssSelector('[data-test-subj="comboBoxInput"] button'); - await buttons.map(async (button: WebElementWrapper) => await button.click()); - await testSubjects.setValue('comboBoxSearchInput', '*'); - } - - async openImportQueryModal(): Promise { - const element = await testSubjects.find('importQueryFromSavedTimeline'); - await element.click(500); - await testSubjects.exists('open-timeline-modal-body-filter-default'); - } - - async viewTemplatesInImportQueryModal(): Promise { - await common.clickAndValidate('open-timeline-modal-body-filter-template', 'timelines-table'); - } - - async closeImportQueryModal(): Promise { - await find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon'); - } - - async selectMachineLearningJob(): Promise { - await find.clickByCssSelector('[data-test-subj="mlJobSelect"] button'); - await find.clickByCssSelector('#high_distinct_count_error_message'); - } - - async openAddFilterPopover(): Promise { - const addButtons = await testSubjects.findAll('addFilter'); - await addButtons[1].click(); - await testSubjects.exists('saveFilter'); - } - - async closeAddFilterPopover(): Promise { - await testSubjects.click('cancelSaveFilter'); - } - - async toggleFilterActions(): Promise { - const filterActions = await testSubjects.findAll('addFilter'); - await filterActions[1].click(); - } - - async toggleSavedQueries(): Promise { - const filterActions = await find.allByCssSelector( - '[data-test-subj="saved-query-management-popover-button"]' - ); - await filterActions[1].click(); - } - - async addNameAndDescription( - name: string = 'test rule name', - description: string = 'test rule description' - ): Promise { - await find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500); - await find.setValue( - `[aria-describedby="detectionEngineStepAboutRuleDescription"]`, - description, - 500 - ); - } - - async goBackToAllRules(): Promise { - await common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule'); - } - - async revealAdvancedSettings(): Promise { - await common.clickAndValidate( - 'advancedSettings', - 'detectionEngineStepAboutRuleReferenceUrls' - ); - } - - async preview(): Promise { - await common.clickAndValidate( - 'queryPreviewButton', - 'queryPreviewCustomHistogram', - undefined, - 500 - ); - } - - async continue(prefix: string): Promise { - await testSubjects.click(`${prefix}-continue`); - } - - async addCustomQuery(query: string): Promise { - await testSubjects.setValue('queryInput', query, undefined, 500); - } - - async selectMLRule(): Promise { - await common.clickAndValidate('machineLearningRuleType', 'mlJobSelect'); - } - - async selectEQLRule(): Promise { - await common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput'); - } - - async selectIndicatorMatchRule(): Promise { - await common.clickAndValidate('threatMatchRuleType', 'comboBoxInput'); - } - - async selectThresholdRule(): Promise { - await common.clickAndValidate('thresholdRuleType', 'input'); - } - - private async navigateToDetectionsPage(path: string = ''): Promise { - const subUrl = `detections${path ? `/${path}` : ''}`; - await common.navigateToUrl('securitySolution', subUrl, { - shouldUseHashForSubUrl: false, - }); - } - } - - return new DetectionsPage(); +export class DetectionsPageObject extends FtrService { + private readonly find = this.ctx.getService('find'); + private readonly common = this.ctx.getPageObject('common'); + private readonly testSubjects = this.ctx.getService('testSubjects'); + + async navigateHome(): Promise { + await this.navigateToDetectionsPage(); + } + + async navigateToRules(): Promise { + await this.navigateToDetectionsPage('rules'); + } + + async navigateToRuleMonitoring(): Promise { + await this.common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table'); + } + + async navigateToExceptionList(): Promise { + await this.common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table'); + } + + async navigateToCreateRule(): Promise { + await this.navigateToDetectionsPage('rules/create'); + } + + async replaceIndexPattern(): Promise { + const buttons = await this.find.allByCssSelector('[data-test-subj="comboBoxInput"] button'); + await buttons.map(async (button: WebElementWrapper) => await button.click()); + await this.testSubjects.setValue('comboBoxSearchInput', '*'); + } + + async openImportQueryModal(): Promise { + const element = await this.testSubjects.find('importQueryFromSavedTimeline'); + await element.click(500); + await this.testSubjects.exists('open-timeline-modal-body-filter-default'); + } + + async viewTemplatesInImportQueryModal(): Promise { + await this.common.clickAndValidate( + 'open-timeline-modal-body-filter-template', + 'timelines-table' + ); + } + + async closeImportQueryModal(): Promise { + await this.find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon'); + } + + async selectMachineLearningJob(): Promise { + await this.find.clickByCssSelector('[data-test-subj="mlJobSelect"] button'); + await this.find.clickByCssSelector('#high_distinct_count_error_message'); + } + + async openAddFilterPopover(): Promise { + const addButtons = await this.testSubjects.findAll('addFilter'); + await addButtons[1].click(); + await this.testSubjects.exists('saveFilter'); + } + + async closeAddFilterPopover(): Promise { + await this.testSubjects.click('cancelSaveFilter'); + } + + async toggleFilterActions(): Promise { + const filterActions = await this.testSubjects.findAll('addFilter'); + await filterActions[1].click(); + } + + async toggleSavedQueries(): Promise { + const filterActions = await this.find.allByCssSelector( + '[data-test-subj="saved-query-management-popover-button"]' + ); + await filterActions[1].click(); + } + + async addNameAndDescription( + name: string = 'test rule name', + description: string = 'test rule description' + ): Promise { + await this.find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500); + await this.find.setValue( + `[aria-describedby="detectionEngineStepAboutRuleDescription"]`, + description, + 500 + ); + } + + async goBackToAllRules(): Promise { + await this.common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule'); + } + + async revealAdvancedSettings(): Promise { + await this.common.clickAndValidate( + 'advancedSettings', + 'detectionEngineStepAboutRuleReferenceUrls' + ); + } + + async preview(): Promise { + await this.common.clickAndValidate( + 'queryPreviewButton', + 'queryPreviewCustomHistogram', + undefined, + 500 + ); + } + + async continue(prefix: string): Promise { + await this.testSubjects.click(`${prefix}-continue`); + } + + async addCustomQuery(query: string): Promise { + await this.testSubjects.setValue('queryInput', query, undefined, 500); + } + + async selectMLRule(): Promise { + await this.common.clickAndValidate('machineLearningRuleType', 'mlJobSelect'); + } + + async selectEQLRule(): Promise { + await this.common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput'); + } + + async selectIndicatorMatchRule(): Promise { + await this.common.clickAndValidate('threatMatchRuleType', 'comboBoxInput'); + } + + async selectThresholdRule(): Promise { + await this.common.clickAndValidate('thresholdRuleType', 'input'); + } + + private async navigateToDetectionsPage(path: string = ''): Promise { + const subUrl = `detections${path ? `/${path}` : ''}`; + await this.common.navigateToUrl('securitySolution', subUrl, { + shouldUseHashForSubUrl: false, + }); + } } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 7c95a53b75ee7..0424891064cd3 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -1,12 +1,24 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - // overhead is too significant - "incremental": false, + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, "types": ["node"] }, - "include": ["**/*", "../../typings/**/*", "../../packages/kbn-test/types/ftr_globals/**/*"], + "include": [ + "**/*", + "./api_integration/apis/logstash/pipeline/fixtures/*.json", + "./api_integration/apis/logstash/pipelines/fixtures/*.json", + "./api_integration/apis/telemetry/fixtures/*.json", + "../../typings/**/*", + "../../packages/kbn-test/types/ftr_globals/**/*", + ], + "exclude": ["target/**/*"], "references": [ + { "path": "../../test/tsconfig.json" }, { "path": "../../src/core/tsconfig.json" }, { "path": "../../src/plugins/bfetch/tsconfig.json" }, { "path": "../../src/plugins/charts/tsconfig.json" }, @@ -84,6 +96,7 @@ { "path": "../plugins/stack_alerts/tsconfig.json" }, { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "../plugins/timelines/tsconfig.json" }, { "path": "../plugins/transform/tsconfig.json" }, { "path": "../plugins/triggers_actions_ui/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" },