[ts] migrate x-pack/test to composite ts project (#101441)

Co-authored-by: spalger <spalger@users.noreply.github.com>
This commit is contained in:
Spencer 2021-06-08 09:54:05 -07:00 committed by GitHub
parent a9e64abe8c
commit bdafd27e19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 3070 additions and 3035 deletions

View file

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

View file

@ -6,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<typeof EsArchiverProvider>;
kibanaServer: ProvidedType<typeof KibanaServerProvider>;
retry: RetryService;
defaults: Record<string, any>;
}
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(

View file

@ -7,5 +7,4 @@
*/
export { KibanaServerProvider } from './kibana_server';
// @ts-ignore
export { extendEsArchiver } from './extend_es_archiver';

View file

@ -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": [

View file

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

View file

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

View file

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

View file

@ -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');
}
async getTopBannerText() {
if (!(await this.isTopBannerVisible())) {
return '';
}
const bannerContainer = await find.byCssSelector(
'.header__topBanner .kbnUserBanner__container'
);
return bannerContainer.getVisibleText();
}
isTopBannerVisible() {
return this.find.existsByCssSelector('.header__topBanner .kbnUserBanner__container');
}
return new BannersPage();
async getTopBannerText() {
if (!(await this.isTopBannerVisible())) {
return '';
}
const bannerContainer = await this.find.byCssSelector(
'.header__topBanner .kbnUserBanner__container'
);
return bannerContainer.getVisibleText();
}
}

File diff suppressed because it is too large Load diff

View file

@ -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');
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');
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');
}
private readonly common = this.ctx.getPageObject('common');
private readonly header = this.ctx.getPageObject('header');
async clickAddField() {
await retry.try(async () => {
await testSubjects.click('graph-add-field-button');
await testSubjects.existOrFail('graph-field-search', { timeout: 3000 });
});
}
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 selectField(field: string) {
await testSubjects.setValue('graph-field-search', field);
await find.clickDisplayedByCssSelector(`[title="${field}"]`);
}
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 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 selectField(field: string) {
await this.testSubjects.setValue('graph-field-search', field);
await this.find.clickDisplayedByCssSelector(`[title="${field}"]`);
}
async query(str: string) {
await testSubjects.click('queryInput');
await testSubjects.setValue('queryInput', str);
await testSubjects.click('graph-explore-button');
}
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 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);
}
}
// invert selection to select all nodes not source or target
await testSubjects.click('graphInvertSelection');
// remove all other nodes
await testSubjects.click('graphRemoveSelection');
}
async stopLayout() {
if (await testSubjects.exists('graphPauseLayout')) {
await testSubjects.click('graphPauseLayout');
}
}
async startLayout() {
if (await testSubjects.exists('graphResumeLayout')) {
await 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<string, number> = {};
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]],
});
}
}
await this.startLayout();
return {
nodes,
edges,
};
}
async createWorkspace() {
await 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 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');
// Confirm that the Graph has been saved.
return await testSubjects.exists('saveGraphSuccess', { timeout: 10000 });
}
async getSearchFilter() {
const searchFilter = await 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 goToListingPage() {
await retry.try(async () => {
await testSubjects.click('breadcrumb graphHomeBreadcrumb first');
await 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 deleteGraph(name: string) {
await testSubjects.click('checkboxSelectAll');
await this.clickDeleteSelectedWorkspaces();
await PageObjects.common.clickConfirmOnModal();
await testSubjects.find('graphCreateGraphPromptButton');
}
async getWorkspaceCount() {
const workspaceTitles = await find.allByCssSelector(
'[data-test-subj^="graphListingTitleLink"]'
);
return workspaceTitles.length;
}
async clickDeleteSelectedWorkspaces() {
await testSubjects.click('deleteSelectedItems');
}
async getVennTerm1() {
const el = await 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 getSmallVennTerm1() {
const el = await 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 getSmallVennTerm2() {
const el = await find.byCssSelector('small.gphLinkSummary__term--2');
return await el.getVisibleText();
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);
}
}
return new GraphPage();
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 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)];
}
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 this.testSubjects.click('graphInvertSelection');
// remove all other nodes
await this.testSubjects.click('graphRemoveSelection');
}
async stopLayout() {
if (await this.testSubjects.exists('graphPauseLayout')) {
await this.testSubjects.click('graphPauseLayout');
}
}
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 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<string, number> = {};
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]],
});
}
}
await this.startLayout();
return {
nodes,
edges,
};
}
async createWorkspace() {
await this.testSubjects.click('graphCreateGraphPromptButton');
}
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 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 this.testSubjects.exists('saveGraphSuccess', { timeout: 10000 });
}
async getSearchFilter() {
const searchFilter = await this.find.allByCssSelector('main .euiFieldSearch');
return searchFilter[0];
}
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 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 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 this.testSubjects.click('checkboxSelectAll');
await this.clickDeleteSelectedWorkspaces();
await this.common.clickConfirmOnModal();
await this.testSubjects.find('graphCreateGraphPromptButton');
}
async getWorkspaceCount() {
const workspaceTitles = await this.find.allByCssSelector(
'[data-test-subj^="graphListingTitleLink"]'
);
return workspaceTitles.length;
}
async clickDeleteSelectedWorkspaces() {
await this.testSubjects.click('deleteSelectedItems');
}
async getVennTerm1() {
const el = await this.find.byCssSelector('span.gphLinkSummary__term--1');
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 this.find.byCssSelector('small.gphLinkSummary__term--1');
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 this.find.byCssSelector('small.gphLinkSummary__term--2');
return await el.getVisibleText();
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -5,45 +5,45 @@
* 2.0.
*/
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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');
}
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 clickBreadcrumb(subj: string) {
return testSubjects.click(subj);
}
async getAccessDeniedMessage() {
return this.testSubjects.getVisibleText('accessDeniedTitle');
}
async assertTableNoData(subj: string) {
if (!(await testSubjects.exists(subj))) {
throw new Error('Expected to find the no data message');
}
}
async clickBreadcrumb(subj: string) {
return this.testSubjects.click(subj);
}
async tableGetRows(subj: string) {
const table = await testSubjects.find(subj);
return table.findAllByTagName('tr');
async assertTableNoData(subj: string) {
if (!(await this.testSubjects.exists(subj))) {
throw new Error('Expected to find the no data message');
}
}
async tableGetRowsFromContainer(subj: string) {
const table = await testSubjects.find(subj);
const tbody = await table.findByTagName('tbody');
return tbody.findAllByTagName('tr');
}
async tableGetRows(subj: string) {
const table = await this.testSubjects.find(subj);
return table.findAllByTagName('tr');
}
async tableSetFilter(subj: string, text: string) {
await testSubjects.setValue(subj, text);
await PageObjects.common.pressEnterKey();
await PageObjects.header.waitUntilLoadingHasFinished();
}
async tableGetRowsFromContainer(subj: string) {
const table = await this.testSubjects.find(subj);
const tbody = await table.findByTagName('tbody');
return tbody.findAllByTagName('tr');
}
async tableClearFilter(subj: string) {
return await testSubjects.setValue(subj, ' \uE003'); // space and backspace to trigger onChange event
}
})();
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
}
}

View file

@ -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 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();
}
async blur() {
await testSubjects.click('helpMenuButton');
await testSubjects.click('helpMenuButton');
await 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 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();
}
async isPopoverDisplayed() {
return await find.existsByCssSelector('.navSearch__panel');
}
async clickOnOption(index: number) {
const options = await testSubjects.findAll('nav-search-option');
await options[index].click();
}
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 getDisplayedResults() {
const resultElements = await testSubjects.findAll('nav-search-option');
return Promise.all(resultElements.map((el) => this.convertResultElement(el)));
}
async isNoResultsPlaceholderDisplayed(checkAfter: number = 3000) {
// see comment in `waitForResultsLoaded`
await delay(checkAfter);
return testSubjects.exists('nav-search-no-results');
}
private async convertResultElement(resultEl: WebElementWrapper): Promise<SearchResult> {
const labelEl = await find.allDescendantDisplayedByCssSelector(
'.euiSelectableTemplateSitewide__listItemTitle',
resultEl
);
const label = await labelEl[0].getVisibleText();
return {
label,
};
const field = await this.testSubjects.find('nav-search-input');
await field.type(term);
if (wait) {
await this.waitForResultsLoaded();
}
}
return new NavigationalSearch();
async getFieldValue() {
const field = await this.testSubjects.find('nav-search-input');
return field.getAttribute('value');
}
async clearField() {
const field = await this.testSubjects.find('nav-search-input');
await field.clearValueWithKeyboard();
}
async isPopoverDisplayed() {
return await this.find.existsByCssSelector('.navSearch__panel');
}
async clickOnOption(index: number) {
const options = await this.testSubjects.findAll('nav-search-option');
await options[index].click();
}
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);
}
async getDisplayedResults() {
const resultElements = await this.testSubjects.findAll('nav-search-option');
return Promise.all(resultElements.map((el) => this.convertResultElement(el)));
}
async isNoResultsPlaceholderDisplayed(checkAfter: number = 3000) {
// see comment in `waitForResultsLoaded`
await delay(checkAfter);
return this.testSubjects.exists('nav-search-no-results');
}
private async convertResultElement(resultEl: WebElementWrapper): Promise<SearchResult> {
const labelEl = await this.find.allDescendantDisplayedByCssSelector(
'.euiSelectableTemplateSitewide__listItemTitle',
resultEl
);
const label = await labelEl[0].getVisibleText();
return {
label,
};
}
}

View file

@ -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';
import { FtrService } 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']);
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');
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";
`);
}
async getReportURL(timeout: number) {
log.debug('getReportURL');
const url = await testSubjects.getAttribute('downloadCompletedReportButton', 'href', timeout);
log.debug(`getReportURL got url: ${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 getResponse(fullUrl: string): Promise<supertestAsPromised.Response> {
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 getRawPdfReportData(url: string): Promise<Buffer> {
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 openPdfReportingPanel() {
log.debug('openPdfReportingPanel');
await PageObjects.share.openShareMenuItem('PDF Reports');
}
async openPngReportingPanel() {
log.debug('openPngReportingPanel');
await PageObjects.share.openShareMenuItem('PNG Reports');
}
async clearToastNotifications() {
const toasts = await testSubjects.findAll('toastCloseButton');
await Promise.all(toasts.map(async (t) => await t.click()));
}
async getQueueReportError() {
return await testSubjects.exists('queueReportError');
}
async getGenerateReportButton() {
return await retry.try(async () => await testSubjects.find('generateReportButton'));
}
async isGenerateReportButtonDisabled() {
const generateReportButton = await this.getGenerateReportButton();
return await retry.try(async () => {
const isDisabled = await generateReportButton.getAttribute('disabled');
return isDisabled;
});
}
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 clickGenerateReportButton() {
await testSubjects.click('generateReportButton');
}
async toggleReportMode() {
await 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 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);
}
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 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) {
this.log.debug('getReportURL');
const url = await this.testSubjects.getAttribute(
'downloadCompletedReportButton',
'href',
timeout
);
this.log.debug(`getReportURL got url: ${url}`);
return url;
}
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<supertestAsPromised.Response> {
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<Buffer> {
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() {
this.log.debug('openCsvReportingPanel');
await this.share.openShareMenuItem('CSV Reports');
}
async openPdfReportingPanel() {
this.log.debug('openPdfReportingPanel');
await this.share.openShareMenuItem('PDF Reports');
}
async openPngReportingPanel() {
this.log.debug('openPngReportingPanel');
await this.share.openShareMenuItem('PNG Reports');
}
async clearToastNotifications() {
const toasts = await this.testSubjects.findAll('toastCloseButton');
await Promise.all(toasts.map(async (t) => await t.click()));
}
async getQueueReportError() {
return await this.testSubjects.exists('queueReportError');
}
async getGenerateReportButton() {
return await this.retry.try(async () => await this.testSubjects.find('generateReportButton'));
}
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 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 this.testSubjects.click('generateReportButton');
}
async toggleReportMode() {
await this.testSubjects.click('reportModeToggle');
}
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');
}
return isToastPresent;
}
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();
}

View file

@ -7,137 +7,130 @@
import expect from '@kbn/expect';
import { map as mapAsync } from 'bluebird';
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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']);
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');
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);
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 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 3: Terms (optional)
await this.verifyStepIsActive(stepNum);
stepNum = await this.moveToNextStep();
// Step 4: Histogram(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 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 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 addRollupNameandIndexPattern(name: string, indexPattern: string) {
log.debug(`Adding name ${name} to form`);
await testSubjects.setValue('rollupJobName', name);
await testSubjects.setValue('rollupIndexPattern', indexPattern);
}
async setRollupDelay(time: string) {
log.debug(`Setting rollup delay to "${time}"`);
await 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 setIndexName(name: string) {
await testSubjects.setValue('rollupIndexName', name);
}
async moveToNextStep(stepNum = 0) {
await testSubjects.click('rollupJobNextButton');
return stepNum + 1;
}
async setJobInterval(time: string) {
await 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 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(),
};
});
}
// Step 6: saveJob and verify the name in the list
await this.verifyStepIsActive(stepNum);
await this.saveJob(startImmediately);
}
async verifyStepIsActive(stepNumber = 0) {
await this.testSubjects.exists(`createRollupStep${stepNumber}--active`);
}
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) {
this.log.debug(`Adding name ${name} to form`);
await this.testSubjects.setValue('rollupJobName', name);
await this.testSubjects.setValue('rollupIndexPattern', indexPattern);
}
async setRollupDelay(time: string) {
this.log.debug(`Setting rollup delay to "${time}"`);
await this.testSubjects.setValue('rollupDelay', time);
}
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 this.testSubjects.setValue('rollupIndexName', name);
}
async moveToNextStep(stepNum = 0) {
await this.testSubjects.click('rollupJobNextButton');
return stepNum + 1;
}
async setJobInterval(time: string) {
await this.testSubjects.setValue('rollupJobInterval', time);
}
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 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();
}

View file

@ -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;
}
interface LoginOptions {
expectSpaceSelector?: boolean;
expectSuccess?: boolean;
expectForbidden?: boolean;
}
type LoginExpectedResult = 'spaceSelector' | 'error' | 'chrome';
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');
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()}`);
}
}
const loginPage = Object.freeze({
async login(username?: string, password?: string, options: LoginOptions = {}) {
if (!(await isLoginFormVisible())) {
await PageObjects.common.navigateToApp('login');
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<string, any>) {
log.debug(`Starting login flow for ${providerType}/${providerName}`);
public loginSelector = Object.freeze({
login: async (providerType: string, providerName: string, options?: Record<string, any>) => {
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);
}
async login(username?: string, password?: string, options: LoginOptions = {}) {
await this.loginPage.login(username, password, options);
if (options.expectSpaceSelector || options.expectForbidden) {
return;
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');
}
);
}
await retry.waitFor('logout button visible', async () => await userMenu.logoutLinkExists());
}
private async isLoginFormVisible() {
return await this.testSubjects.exists('loginForm');
}
async logout() {
log.debug('SecurityPage.logout');
if (!(await userMenu.logoutLinkExists())) {
log.debug('Logout not found');
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 userMenu.clickLogoutButton();
await waitForLoginPage();
}
async getCurrentUser() {
const sidCookie = await browser.getCookie('sid');
if (!sidCookie?.value) {
log.debug('User is not authenticated yet.');
return null;
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');
}
);
}
const { body: user } = await supertest
.get('/internal/security/me')
.set('kbn-xsrf', 'xxx')
.set('Cookie', `sid=${sidCookie.value}`)
.expect(200);
return user as AuthenticatedUser;
}
async forceLogout() {
log.debug('SecurityPage.forceLogout');
if (await find.existsByDisplayedByCssSelector('.login-form', 100)) {
log.debug('Already on the login page, not forcing anything');
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;
}
);
}
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();
}
private async waitForLoginResult(expectedResult?: LoginExpectedResult) {
this.log.debug(`Waiting for login result, expected: ${expectedResult}.`);
async clickRolesSection() {
await testSubjects.click('roles');
}
async clickUsersSection() {
await testSubjects.click('users');
}
async clickCreateNewUser() {
await retry.try(() => testSubjects.click('createUserButton'));
}
async clickCreateNewRole() {
await retry.try(() => testSubjects.click('createRoleButton'));
}
async clickCloneRole(roleName: string) {
await retry.try(() => testSubjects.click(`clone-role-action-${roleName}`));
}
async clickCancelEditUser() {
await find.clickByButtonText('Cancel');
}
async clickCancelEditRole() {
await testSubjects.click('roleFormCancelButton');
}
async clickSaveEditUser() {
await find.clickByButtonText('Update user');
await PageObjects.header.waitUntilLoadingHasFinished();
}
async clickSaveCreateUser() {
await find.clickByButtonText('Create user');
await PageObjects.header.waitUntilLoadingHasFinished();
}
async clickSaveEditRole() {
const saveButton = await retry.try(() => testSubjects.find('roleFormSaveButton'));
await saveButton.moveMouseTo();
await saveButton.click();
await PageObjects.header.waitUntilLoadingHasFinished();
}
async addIndexToRole(index: string) {
log.debug(`Adding index ${index} to role`);
await comboBox.setCustom('indicesInput0', index);
}
async addPrivilegeToRole(privilege: string) {
log.debug(`Adding privilege ${privilege} to role`);
const privilegeInput = await retry.try(() =>
find.byCssSelector('[data-test-subj="privilegesInput0"] input')
// 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()}`
);
await privilegeInput.type(privilege);
const btn = await find.byButtonText(privilege);
await btn.click();
// const options = await find.byCssSelector(`.euiFilterSelectItem`);
// Object.entries(options).forEach(([key, prop]) => {
// console.log({ key, proto: prop.__proto__ });
// });
// await options.click();
return;
}
async assignRoleToUser(role: string) {
await this.selectRole(role);
}
async navigateTo() {
await PageObjects.common.navigateToApp('settings');
}
async clickElasticsearchUsers() {
await this.navigateTo();
await this.clickUsersSection();
}
async clickElasticsearchRoles() {
await this.navigateTo();
await this.clickRolesSection();
}
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,
});
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);
}
return users;
}
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 });
}
return roles;
}
/**
* @deprecated Use `PageObjects.security.clickCreateNewUser` instead
*/
async clickNewUser() {
return await testSubjects.click('createUserButton');
}
/**
* @deprecated Use `PageObjects.security.clickCreateNewUser` instead
*/
async clickNewRole() {
return await testSubjects.click('createRoleButton');
}
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);
}
const rolesToAdd = user.roles || [];
for (let i = 0; i < rolesToAdd.length; i++) {
await this.selectRole(rolesToAdd[i]);
}
}
async submitCreateUserForm() {
await find.clickByButtonText('Create user');
}
async createUser(user: UserFormValues) {
await this.clickElasticsearchUsers();
await this.clickCreateNewUser();
await this.fillUserForm(user);
await this.submitCreateUserForm();
}
async addRole(roleName: string, roleObj: Role) {
const self = this;
await this.clickCreateNewRole();
// 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);
for (const indexName of roleObj.elasticsearch.indices[0].names) {
await comboBox.setCustom('indicesInput0', indexName);
}
if (roleObj.elasticsearch.indices[0].query) {
await testSubjects.click('restrictDocumentsQuery0');
await testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query);
}
const globalPrivileges = (roleObj.kibana as any).global;
if (globalPrivileges) {
for (const privilegeName of globalPrivileges) {
await testSubjects.click('addSpacePrivilegeButton');
await testSubjects.click('spaceSelectorComboBox');
const globalSpaceOption = await find.byCssSelector(`#spaceOption_\\*`);
await globalSpaceOption.click();
await testSubjects.click(`basePrivilege_${privilegeName}`);
await testSubjects.click('createSpacePrivilegeButton');
await this.retry.try(async () => {
if (await this.find.existsByCssSelector(rawDataTabLocator)) {
await this.find.clickByCssSelector(rawDataTabLocator);
}
}
function addPrivilege(privileges: string[]) {
return privileges.reduce(function (promise: Promise<any>, privilegeName: string) {
return promise
.then(() => self.addPrivilegeToRole(privilegeName))
.then(() => PageObjects.common.sleep(250));
}, Promise.resolve());
}
await addPrivilege(roleObj.elasticsearch.indices[0].privileges);
async function addGrantedField(fields: string[]) {
for (const entry of fields) {
await comboBox.setCustom('fieldInput0', entry);
}
}
// clicking the Granted fields and removing the asterix
if (roleObj.elasticsearch.indices[0].field_security) {
// Toggle FLS switch
await testSubjects.click('restrictFieldsQuery0');
// have to remove the '*'
await find.clickByCssSelector(
'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon'
);
await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!);
}
log.debug('click save button');
await testSubjects.click('roleFormSaveButton');
// Signifies that the role management page redirected back to the role grid page,
// and successfully refreshed the grid
await testSubjects.existOrFail('roleRow');
await this.testSubjects.existOrFail('ResetSessionButton');
});
this.log.debug(
`Finished login process, found reset session button message. currentUrl = ${await this.browser.getCurrentUrl()}`
);
return;
}
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');
}
async deleteUser(username: string) {
log.debug('Delete user ' + username);
await find.clickByDisplayedLinkText(username);
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
log.debug('Find delete button and click');
await find.clickByButtonText('Delete user');
await PageObjects.common.sleep(2000);
const confirmText = await testSubjects.getVisibleText('confirmModalBodyText');
log.debug('Delete user alert text = ' + confirmText);
await testSubjects.click('confirmModalConfirmButton');
return confirmText;
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()}`);
}
}
return new SecurityPage();
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);
}
async login(username?: string, password?: string, options: LoginOptions = {}) {
await this.loginPage.login(username, password, options);
if (options.expectSpaceSelector || options.expectForbidden) {
return;
}
await this.retry.waitFor(
'logout button visible',
async () => await this.userMenu.logoutLinkExists()
);
}
async logout() {
this.log.debug('SecurityPage.logout');
if (!(await this.userMenu.logoutLinkExists())) {
this.log.debug('Logout not found');
return;
}
await this.userMenu.clickLogoutButton();
await this.waitForLoginPage();
}
async getCurrentUser() {
const sidCookie = await this.browser.getCookie('sid');
if (!sidCookie?.value) {
this.log.debug('User is not authenticated yet.');
return null;
}
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 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;
}
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 clickRolesSection() {
await this.testSubjects.click('roles');
}
async clickUsersSection() {
await this.testSubjects.click('users');
}
async clickCreateNewUser() {
await this.retry.try(() => this.testSubjects.click('createUserButton'));
}
async clickCreateNewRole() {
await this.retry.try(() => this.testSubjects.click('createRoleButton'));
}
async clickCloneRole(roleName: string) {
await this.retry.try(() => this.testSubjects.click(`clone-role-action-${roleName}`));
}
async clickCancelEditUser() {
await this.find.clickByButtonText('Cancel');
}
async clickCancelEditRole() {
await this.testSubjects.click('roleFormCancelButton');
}
async clickSaveEditUser() {
await this.find.clickByButtonText('Update user');
await this.header.waitUntilLoadingHasFinished();
}
async clickSaveCreateUser() {
await this.find.clickByButtonText('Create user');
await this.header.waitUntilLoadingHasFinished();
}
async clickSaveEditRole() {
const saveButton = await this.retry.try(() => this.testSubjects.find('roleFormSaveButton'));
await saveButton.moveMouseTo();
await saveButton.click();
await this.header.waitUntilLoadingHasFinished();
}
async addIndexToRole(index: string) {
this.log.debug(`Adding index ${index} to role`);
await this.comboBox.setCustom('indicesInput0', index);
}
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);
const btn = await this.find.byButtonText(privilege);
await btn.click();
// const options = await this.find.byCssSelector(`.euiFilterSelectItem`);
// Object.entries(options).forEach(([key, prop]) => {
// console.log({ key, proto: prop.__proto__ });
// });
// await options.click();
}
async assignRoleToUser(role: string) {
await this.selectRole(role);
}
async navigateTo() {
await this.common.navigateToApp('settings');
}
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,
});
}
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 });
}
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);
}
const rolesToAdd = user.roles || [];
for (let i = 0; i < rolesToAdd.length; i++) {
await this.selectRole(rolesToAdd[i]);
}
}
async submitCreateUserForm() {
await this.find.clickByButtonText('Create user');
}
async createUser(user: UserFormValues) {
await this.clickElasticsearchUsers();
await this.clickCreateNewUser();
await this.fillUserForm(user);
await this.submitCreateUserForm();
}
async addRole(roleName: string, roleObj: Role) {
const self = this;
await this.clickCreateNewRole();
// 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);
for (const indexName of roleObj.elasticsearch.indices[0].names) {
await this.comboBox.setCustom('indicesInput0', indexName);
}
if (roleObj.elasticsearch.indices[0].query) {
await this.testSubjects.click('restrictDocumentsQuery0');
await this.testSubjects.setValue('queryInput0', roleObj.elasticsearch.indices[0].query);
}
const globalPrivileges = (roleObj.kibana as any).global;
if (globalPrivileges) {
for (const privilegeName of globalPrivileges) {
await this.testSubjects.click('addSpacePrivilegeButton');
await this.testSubjects.click('spaceSelectorComboBox');
const globalSpaceOption = await this.find.byCssSelector(`#spaceOption_\\*`);
await globalSpaceOption.click();
await this.testSubjects.click(`basePrivilege_${privilegeName}`);
await this.testSubjects.click('createSpacePrivilegeButton');
}
}
const addPrivilege = (privileges: string[]) => {
return privileges.reduce((promise: Promise<any>, privilegeName: string) => {
return promise
.then(() => self.addPrivilegeToRole(privilegeName))
.then(() => this.common.sleep(250));
}, Promise.resolve());
};
await addPrivilege(roleObj.elasticsearch.indices[0].privileges);
const addGrantedField = async (fields: string[]) => {
for (const entry of fields) {
await this.comboBox.setCustom('fieldInput0', entry);
}
};
// clicking the Granted fields and removing the asterix
if (roleObj.elasticsearch.indices[0].field_security) {
// Toggle FLS switch
await this.testSubjects.click('restrictFieldsQuery0');
// have to remove the '*'
await this.find.clickByCssSelector(
'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon'
);
await addGrantedField(roleObj.elasticsearch.indices[0].field_security!.grant!);
}
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');
}
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');
}
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;
}
}

View file

@ -6,191 +6,187 @@
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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']);
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');
class SpaceSelectorPage {
async initTests() {
log.debug('SpaceSelectorPage:initTests');
}
async initTests() {
this.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);
});
}
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 expectHomePage(spaceId: string) {
return await this.expectRoute(spaceId, `/app/home#/`);
}
async expectHomePage(spaceId: string) {
return await this.expectRoute(spaceId, `/app/home#/`);
}
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 openSpacesNav() {
log.debug('openSpacesNav()');
return await testSubjects.click('spacesNavSelector');
}
async clickManageSpaces() {
await testSubjects.click('manageSpaces');
}
async clickCreateSpace() {
await testSubjects.click('createSpace');
}
async clickEnterSpaceName() {
await testSubjects.click('addSpaceName');
}
async addSpaceName(spaceName: string) {
await testSubjects.setValue('addSpaceName', spaceName);
}
async clickCustomizeSpaceAvatar(spaceId: string) {
await testSubjects.click(`space-avatar-${spaceId}`);
}
async clickSpaceInitials() {
await testSubjects.click('spaceLetterInitial');
}
async addSpaceInitials(spaceInitials: string) {
await testSubjects.setValue('spaceLetterInitial', spaceInitials);
}
async clickColorPicker() {
await testSubjects.click('colorPickerAnchor');
}
async setColorinPicker(hexValue: string) {
await testSubjects.setValue('colorPickerAnchor', hexValue);
}
async clickShowFeatures() {
await testSubjects.click('show-hide-section-link');
}
async clickChangeAllPriv() {
await testSubjects.click('changeAllPrivilegesButton');
}
async clickSaveSpaceCreation() {
await testSubjects.click('save-space-button');
}
async clickSpaceEditButton(spaceName: string) {
await testSubjects.click(`${spaceName}-editSpace`);
}
async clickGoToRolesPage() {
await testSubjects.click('rolesManagementPage');
}
async clickCancelSpaceCreation() {
await testSubjects.click('cancel-space-button');
}
async clickOnCustomizeURL() {
await testSubjects.click('CustomizeOrReset');
}
async clickOnSpaceURLDisplay() {
await testSubjects.click('spaceURLDisplay');
}
async setSpaceURL(spaceURL: string) {
await testSubjects.setValue('spaceURLDisplay', spaceURL);
}
async clickHideAllFeatures() {
await testSubjects.click('spc-toggle-all-features-hide');
}
async clickShowAllFeatures() {
await testSubjects.click('spc-toggle-all-features-show');
}
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 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 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 openSpacesNav() {
this.log.debug('openSpacesNav()');
return await this.testSubjects.click('spacesNavSelector');
}
async toggleFeatureCategoryVisibility(categoryName: string) {
await testSubjects.click(`featureCategoryButton_${categoryName}`);
}
async clickManageSpaces() {
await this.testSubjects.click('manageSpaces');
}
async clickOnDescriptionOfSpace() {
await testSubjects.click('descriptionSpaceText');
}
async clickCreateSpace() {
await this.testSubjects.click('createSpace');
}
async setOnDescriptionOfSpace(descriptionSpace: string) {
await testSubjects.setValue('descriptionSpaceText', descriptionSpace);
}
async clickEnterSpaceName() {
await this.testSubjects.click('addSpaceName');
}
async clickOnDeleteSpaceButton(spaceName: string) {
await testSubjects.click(`${spaceName}-deleteSpace`);
}
async addSpaceName(spaceName: string) {
await this.testSubjects.setValue('addSpaceName', spaceName);
}
async cancelDeletingSpace() {
await testSubjects.click('confirmModalCancelButton');
}
async clickCustomizeSpaceAvatar(spaceId: string) {
await this.testSubjects.click(`space-avatar-${spaceId}`);
}
async confirmDeletingSpace() {
await testSubjects.click('confirmModalConfirmButton');
}
async clickSpaceInitials() {
await this.testSubjects.click('spaceLetterInitial');
}
async clickOnSpaceb() {
await testSubjects.click('space-avatar-space_b');
}
async addSpaceInitials(spaceInitials: string) {
await this.testSubjects.setValue('spaceLetterInitial', spaceInitials);
}
async goToSpecificSpace(spaceName: string) {
await testSubjects.click(`${spaceName}-gotoSpace`);
}
async clickColorPicker() {
await this.testSubjects.click('colorPickerAnchor');
}
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 setColorinPicker(hexValue: string) {
await this.testSubjects.setValue('colorPickerAnchor', hexValue);
}
async clickShowFeatures() {
await this.testSubjects.click('show-hide-section-link');
}
async clickChangeAllPriv() {
await this.testSubjects.click('changeAllPrivilegesButton');
}
async clickSaveSpaceCreation() {
await this.testSubjects.click('save-space-button');
}
async clickSpaceEditButton(spaceName: string) {
await this.testSubjects.click(`${spaceName}-editSpace`);
}
async clickGoToRolesPage() {
await this.testSubjects.click('rolesManagementPage');
}
async clickCancelSpaceCreation() {
await this.testSubjects.click('cancel-space-button');
}
async clickOnCustomizeURL() {
await this.testSubjects.click('CustomizeOrReset');
}
async clickOnSpaceURLDisplay() {
await this.testSubjects.click('spaceURLDisplay');
}
async setSpaceURL(spaceURL: string) {
await this.testSubjects.setValue('spaceURLDisplay', spaceURL);
}
async clickHideAllFeatures() {
await this.testSubjects.click('spc-toggle-all-features-hide');
}
async clickShowAllFeatures() {
await this.testSubjects.click('spc-toggle-all-features-show');
}
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();
}
}
return new SpaceSelectorPage();
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 toggleFeatureCategoryVisibility(categoryName: string) {
await this.testSubjects.click(`featureCategoryButton_${categoryName}`);
}
async clickOnDescriptionOfSpace() {
await this.testSubjects.click('descriptionSpaceText');
}
async setOnDescriptionOfSpace(descriptionSpace: string) {
await this.testSubjects.setValue('descriptionSpaceText', descriptionSpace);
}
async clickOnDeleteSpaceButton(spaceName: string) {
await this.testSubjects.click(`${spaceName}-deleteSpace`);
}
async cancelDeletingSpace() {
await this.testSubjects.click('confirmModalCancelButton');
}
async confirmDeletingSpace() {
await this.testSubjects.click('confirmModalConfirmButton');
}
async clickOnSpaceb() {
await this.testSubjects.click('space-avatar-space_b');
}
async goToSpecificSpace(spaceName: string) {
await this.testSubjects.click(`${spaceName}-gotoSpace`);
}
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);
});
}
}

View file

@ -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<void> {
await find.byCssSelector('[data-test-subj="statusPageRoot"]', 20000);
}
async initTests() {
this.log.debug('StatusPage:initTests');
}
return new StatusPage();
async expectStatusPage(): Promise<void> {
await this.find.byCssSelector('[data-test-subj="statusPageRoot"]', 20000);
}
}

View file

@ -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');
/**
* Sub page object to manipulate the create/edit tag modal.
*/
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);
}
/**
* 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);
}
if (submit) {
await this.clickConfirm();
}
}
/**
* Return the values currently displayed in the form.
*/
async getFormValues(): Promise<Required<FillTagFormFields>> {
return {
name: await testSubjects.getAttribute('createModalField-name', 'value'),
color: await testSubjects.getAttribute('colorPickerAnchor', 'value'),
description: await testSubjects.getAttribute('createModalField-description', 'value'),
};
}
/**
* Return the validation errors currently displayed for each field.
*/
async getValidationErrors(): Promise<TagFormValidation> {
const errors: TagFormValidation = {};
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;
};
errors.name = await getError('createModalRow-name');
errors.color = await getError('createModalRow-color');
errors.description = await getError('createModalRow-description');
return errors;
}
/**
* 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 'cancel' button in the create/edit modal.
*/
async clickCancel() {
await testSubjects.click('createModalCancelButton');
}
/**
* Click on the 'confirm' button in the create/edit modal if not disabled.
*/
async clickConfirm() {
await testSubjects.click('createModalConfirmButton');
await PageObjects.header.waitUntilLoadingHasFinished();
}
/**
* Return true if the confirm button is disabled, false otherwise.
*/
async isConfirmDisabled() {
const disabled = await testSubjects.getAttribute('createModalConfirmButton', 'disabled');
return disabled === 'true';
}
/**
* Return true if the modal is currently opened.
*/
async isOpened() {
return await testSubjects.exists('tagModalForm');
}
/**
* Wait until the modal is closed.
*/
async waitUntilClosed() {
await retry.try(async () => {
if (await this.isOpened()) {
throw new Error('save modal still open');
}
});
}
/**
* Close the modal if currently opened.
*/
async closeIfOpened() {
if (await this.isOpened()) {
await this.clickCancel();
}
}
constructor(ctx: FtrProviderContext, private readonly page: TagManagementPageObject) {
super(ctx);
}
/**
* Sub page object to manipulate the assign flyout.
* Open the create tag modal, by clicking on the associated button.
*/
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();
}
/**
* Click on the 'cancel' button in the assign flyout.
*/
async clickCancel() {
await testSubjects.click('assignFlyoutCancelButton');
await this.page.waitUntilTableIsLoaded();
}
/**
* Click on the 'confirm' button in the assign flyout.
*/
async clickConfirm() {
await testSubjects.click('assignFlyoutConfirmButton');
await this.waitForFlyoutToClose();
await this.page.waitUntilTableIsLoaded();
}
/**
* 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}`);
}
/**
* Wait until the assignable object results are displayed in the flyout.
*/
async waitUntilResultsAreLoaded() {
return find.waitForDeletedByCssSelector(
'*[data-test-subj="assignFlyoutResultList"] .euiLoadingSpinner'
);
}
/**
* Wait until the flyout is closed.
*/
async waitForFlyoutToClose() {
return testSubjects.waitForDeleted('assignFlyoutResultList');
}
async openCreate() {
return await this.testSubjects.click('createTagButton');
}
/**
* Tag management page object.
* 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);
}
/**
* Fills the given fields in the form.
*
* @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.
* 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.
*/
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();
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);
}
/**
* 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');
}
});
if (submit) {
await this.clickConfirm();
}
}
/**
* 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();
/**
* Return the values currently displayed in the form.
*/
async getFormValues(): Promise<Required<FillTagFormFields>> {
return {
name: await this.testSubjects.getAttribute('createModalField-name', 'value'),
color: await this.testSubjects.getAttribute('colorPickerAnchor', 'value'),
description: await this.testSubjects.getAttribute('createModalField-description', 'value'),
};
}
/**
* Return the validation errors currently displayed for each field.
*/
async getValidationErrors(): Promise<TagFormValidation> {
const errors: TagFormValidation = {};
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;
};
errors.name = await getError('createModalRow-name');
errors.color = await getError('createModalRow-color');
errors.description = await getError('createModalRow-description');
return errors;
}
/**
* 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 'cancel' button in the create/edit modal.
*/
async clickCancel() {
await this.testSubjects.click('createModalCancelButton');
}
/**
* 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 confirm button is disabled, false otherwise.
*/
async isConfirmDisabled() {
const disabled = await this.testSubjects.getAttribute('createModalConfirmButton', 'disabled');
return disabled === 'true';
}
/**
* Return true if the modal is currently opened.
*/
async isOpened() {
return await this.testSubjects.exists('tagModalForm');
}
/**
* Wait until the modal is closed.
*/
async waitUntilClosed() {
await this.retry.try(async () => {
if (await this.isOpened()) {
throw new Error('save modal still open');
}
});
}
/**
* Close the modal if currently opened.
*/
async closeIfOpened() {
if (await this.isOpened()) {
await this.clickCancel();
}
}
}
/**
* Return true if the `Create new tag` button is visible, false otherwise.
*/
async isCreateButtonVisible() {
return await testSubjects.exists('createTagButton');
/**
* 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');
constructor(ctx: FtrProviderContext, private readonly page: TagManagementPageObject) {
super(ctx);
}
/**
* 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();
}
/**
* 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(
/**
* Click on the 'cancel' button in the assign flyout.
*/
async clickCancel() {
await this.testSubjects.click('assignFlyoutCancelButton');
await this.page.waitUntilTableIsLoaded();
}
/**
* Click on the 'confirm' button in the assign flyout.
*/
async clickConfirm() {
await this.testSubjects.click('assignFlyoutConfirmButton');
await this.waitForFlyoutToClose();
await this.page.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 assignable object results are displayed in the flyout.
*/
async waitUntilResultsAreLoaded() {
return this.find.waitForDeletedByCssSelector(
'*[data-test-subj="assignFlyoutResultList"] .euiLoadingSpinner'
);
}
/**
* Wait until the flyout is closed.
*/
async waitForFlyoutToClose() {
return this.testSubjects.waitForDeleted('assignFlyoutResultList');
}
}
/**
* 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');
public readonly tagModal = new TagModal(this.ctx, this);
public readonly assignFlyout = new TagAssignmentFlyout(this.ctx, this);
/**
* 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();
}
/**
* 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)'
);
if (isLoaded) {
return true;
} else {
throw new Error('Waiting');
}
});
}
/**
* 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();
}
/**
* Return true if the `Create new tag` button is visible, false otherwise.
*/
async isCreateButtonVisible() {
return await this.testSubjects.exists('createTagButton');
}
/**
* 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
);
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}`);
}
}
/**
* 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);
}
/**
* 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;
}
/**
* 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();
}
}
/**
* 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];
}
/**
* 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 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'
);
return Boolean(checkbox.length);
}
/**
* 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 testSubjects.exists('actionBar-contextMenuButton');
}
/**
* Open the bulk action menu if not already opened.
*/
async openActionMenu() {
if (!(await this.isActionMenuOpened())) {
await this.toggleActionMenu();
}
}
/**
* 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();
}
const actionExists = await testSubjects.exists(`actionBar-button-${actionId}`);
if (!menuWasOpened) {
await this.toggleActionMenu();
}
return actionExists;
}
/**
* Click on given bulk action button
*/
async clickOnBulkAction(actionId: string) {
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');
}
/**
* Return true if the bulk action menu is opened, false otherwise.
*/
async isActionMenuOpened() {
return testSubjects.exists('actionBar-contextMenuPopover');
}
/**
* 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)]);
}
async getDisplayedTagInfo(tagName: string) {
const rows = await this.getDisplayedTagsInfo();
return rows.find((row) => row.name === tagName);
}
/**
* Converts the tagName to the format used in test subjects
* @param tagName
*/
testSubjFriendly(tagName: string) {
return tagName.replace(' ', '_');
await actionButton.click();
const actionPresent = await this.testSubjects.exists(`tagsTableAction-${action}`);
await actionButton.click();
return actionPresent;
} else {
return await this.testSubjects.exists(`tagsTableAction-${action}`);
}
}
const parseTableRow = async (row: WebElementWrapper) => {
const dom = await row.parseDomContent();
/**
* 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);
}
const connectionsText = dom.findTestSubject('tagsTableRowConnectionsLink').text();
const rawConnectionCount = connectionsText.replace(/[^0-9]/g, '');
const connectionCount = rawConnectionCount ? parseInt(rawConnectionCount, 10) : undefined;
/**
* 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;
}
// 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,
};
};
/**
* 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();
}
}
return new TagManagementPage();
/**
* 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();
}
/**
* 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);
}
/**
* 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');
}
/**
* Open the bulk action menu if not already opened.
*/
async openActionMenu() {
if (!(await this.isActionMenuOpened())) {
await this.toggleActionMenu();
}
}
/**
* 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();
}
const actionExists = await this.testSubjects.exists(`actionBar-button-${actionId}`);
if (!menuWasOpened) {
await this.toggleActionMenu();
}
return actionExists;
}
/**
* Click on given bulk action button
*/
async clickOnBulkAction(actionId: string) {
await this.openActionMenu();
await this.testSubjects.click(`actionBar-button-${actionId}`);
}
/**
* Toggle (close if opened, open if closed) the bulk action menu.
*/
async toggleActionMenu() {
await this.testSubjects.click('actionBar-contextMenuButton');
}
/**
* Return true if the bulk action menu is opened, false otherwise.
*/
async isActionMenuOpened() {
return this.testSubjects.exists('actionBar-contextMenuPopover');
}
/**
* 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)]);
}
async getDisplayedTagInfo(tagName: string) {
const rows = await this.getDisplayedTagsInfo();
return rows.find((row) => row.name === tagName);
}
/**
* 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,
};
};

View file

@ -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 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 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 deprecationLoggingEnabledLabel() {
const loggingEnabledLabel = await find.byCssSelector(
'[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span'
);
return await loggingEnabledLabel.getVisibleText();
}
async clickTab(tabId: string) {
return await retry.try(async () => {
log.debug('clickTab()');
await find.clickByCssSelector(`.euiTabs .euiTab#${tabId}`);
});
}
async waitForTelemetryHidden() {
const self = this;
await retry.waitFor('Telemetry to disappear.', async () => {
return (await self.isTelemetryExists()) === false;
});
}
async issueSummaryText() {
log.debug('expectIssueSummary()');
return await testSubjects.getVisibleText('upgradeAssistantIssueSummary');
}
async isTelemetryExists() {
return await testSubjects.exists('upgradeAssistantTelemetryRunning');
}
async initTests() {
this.log.debug('UpgradeAssistant:initTests');
}
return new UpgradeAssistant();
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() {
this.log.debug('toggleDeprecationLogging()');
await this.testSubjects.click('upgradeAssistantDeprecationToggle');
}
async isDeprecationLoggingEnabled() {
const isDeprecationEnabled = await this.testSubjects.getAttribute(
'upgradeAssistantDeprecationToggle',
'aria-checked'
);
this.log.debug(`Deprecation enabled == ${isDeprecationEnabled}`);
return isDeprecationEnabled === 'true';
}
async deprecationLoggingEnabledLabel() {
const loggingEnabledLabel = await this.find.byCssSelector(
'[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span'
);
return await loggingEnabledLabel.getVisibleText();
}
async clickTab(tabId: string) {
return await this.retry.try(async () => {
this.log.debug('clickTab()');
await this.find.clickByCssSelector(`.euiTabs .euiTab#${tabId}`);
});
}
async waitForTelemetryHidden() {
const self = this;
await this.retry.waitFor('Telemetry to disappear.', async () => {
return (await self.isTelemetryExists()) === false;
});
}
async issueSummaryText() {
this.log.debug('expectIssueSummary()');
return await this.testSubjects.getVisibleText('upgradeAssistantIssueSummary');
}
async isTelemetryExists() {
return await this.testSubjects.exists('upgradeAssistantTelemetryRunning');
}
}

View file

@ -6,117 +6,121 @@
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
import { FtrService } 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');
export class UptimePageObject extends FtrService {
private readonly timePicker = this.ctx.getPageObject('timePicker');
private readonly header = this.ctx.getPageObject('header');
return new (class UptimePage {
public async goToRoot(refresh?: boolean) {
await navigation.goToUptime();
if (refresh) {
await navigation.refreshApp();
}
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 pageObjects.timePicker.getTimeConfig();
if (start !== prevStart || prevEnd !== end) {
await pageObjects.timePicker.setAbsoluteRange(start, end);
} else {
await 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 navigation.goToUptime();
await this.setDateRange(dateStart, dateEnd);
if (monitorIdToCheck) {
await commonService.monitorIdExists(monitorIdToCheck);
}
await pageObjects.header.waitUntilLoadingHasFinished();
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 pageObjects.header.waitUntilLoadingHasFinished();
await this.setDateRange(dateStart, dateEnd);
await navigation.goToMonitor(monitorId);
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<void> {
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<void> {
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 inputFilterQuery(filterQuery: string) {
await commonService.setFilterText(filterQuery);
public async setStatusFilter(value: 'up' | 'down') {
if (value === 'up') {
await this.commonService.setStatusFilterUp();
} else if (value === 'down') {
await this.commonService.setStatusFilterDown();
}
}
public async pageHasDataMissing() {
return await commonService.pageHasDataMissing();
}
public async pageHasExpectedIds(monitorIdsToCheck: string[]): Promise<void> {
return retry.tryForTime(15000, async () => {
await Promise.all(monitorIdsToCheck.map((id) => commonService.monitorPageLinkExists(id)));
});
}
public async pageUrlContains(value: string, expected: boolean = true): Promise<void> {
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<string, string[]>) {
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]);
}
public async selectFilterItems(filters: Record<string, string[]>) {
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<void> {
await commonService.openPageSizeSelectPopover();
return commonService.clickPageSizeSelectPopoverItem(size);
}
public async setMonitorListPageSize(size: number): Promise<void> {
await this.commonService.openPageSizeSelectPopover();
return this.commonService.clickPageSizeSelectPopoverItem(size);
}
public async checkPingListInteractions(timestamps: string[]): Promise<void> {
return monitor.checkForPingListTimestamps(timestamps);
}
public async checkPingListInteractions(timestamps: string[]): Promise<void> {
return this.monitor.checkForPingListTimestamps(timestamps);
}
public async resetFilters() {
await this.inputFilterQuery('');
await commonService.resetStatusFilter();
}
})();
public async resetFilters() {
await this.inputFilterQuery('');
await this.commonService.resetStatusFilter();
}
}

View file

@ -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 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 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 deleteWatch() {
await testSubjects.click('checkboxSelectAll');
await 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)');
return {
checkBox: (await checkBox.getAttribute('innerHTML')).includes('input'),
id: await id.getVisibleText(),
name: (await name.getVisibleText()).split(',').map((role) => role.trim()),
};
});
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();
}
}
return new WatcherPage();
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 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 this.testSubjects.click('checkboxSelectAll');
await this.testSubjects.click('btnDeleteWatches');
}
// 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()),
};
});
}
}

View file

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

View file

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

View file

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

View file

@ -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');
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');
return new (class SearchSessionsService {
public async find(): Promise<WebElementWrapper> {
return testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ);
}
public async find(): Promise<WebElementWrapper> {
return this.testSubjects.find(SEARCH_SESSION_INDICATOR_TEST_SUBJ);
}
public async exists(): Promise<boolean> {
return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ);
}
public async exists(): Promise<boolean> {
return this.testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ);
}
public async missingOrFail(): Promise<void> {
return testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ);
}
public async missingOrFail(): Promise<void> {
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 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;
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,
});
await this.testSubjects.click('searchSessionNameSave');
}
public async viewSearchSessions() {
log.debug('viewSearchSessions');
await this.ensurePopoverOpened();
await testSubjects.click('searchSessionIndicatorViewSearchSessionsLink');
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);
});
}
public async save({ searchSessionName }: { searchSessionName?: string } = {}) {
log.debug('save the search session');
await this.ensurePopoverOpened();
await testSubjects.click('searchSessionIndicatorSaveBtn');
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));
});
}
if (searchSessionName) {
await testSubjects.click('searchSessionNameEdit');
await testSubjects.setValue('searchSessionNameInput', searchSessionName, {
clearWithKeyboard: true,
});
await testSubjects.click('searchSessionNameSave');
/*
* 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 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);
})
);
});
}
await this.ensurePopoverClosed();
}
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 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),
]);
}
})();
public async markTourUndone() {
await Promise.all([
this.browser.removeLocalStorageItem(TOUR_TAKING_TOO_LONG_STEP_KEY),
this.browser.removeLocalStorageItem(TOUR_RESTORE_STEP_KEY),
]);
}
}

View file

@ -31,7 +31,7 @@ export interface ReportingUsageStats {
[jobType: string]: any;
}
interface UsageStats {
export interface UsageStats {
reporting: ReportingUsageStats;
}

View file

@ -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<T> {
modifier?: T;
users: Record<
| keyof typeof commonUsers
| 'allAtDefaultSpace'
| 'readAtDefaultSpace'
| 'allAtSpace1'
| 'readAtSpace1',
TestUser
>;
}
interface SecurityAndSpaces<T> {
modifier?: T;
users: Record<
keyof typeof commonUsers | 'allAtSpace' | 'readAtSpace' | 'allAtOtherSpace',
TestUser
>;
spaceId: string;
}
interface Spaces<T> {
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 = <T>(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<Spaces<T>> = [DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID].map((x) => ({
spaceId: x,
}));
let security: Array<Security<T>> = [
{
users: {
...commonUsers,
@ -289,7 +291,7 @@ export const getTestScenarios = <T>(modifiers?: T[]) => {
},
},
];
let securityAndSpaces: SecurityAndSpaces[] = [
let securityAndSpaces: Array<SecurityAndSpaces<T>> = [
{
spaceId: DEFAULT_SPACE_ID,
users: {

View file

@ -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');
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');
class DetectionsPage {
async navigateHome(): Promise<void> {
await this.navigateToDetectionsPage();
}
async navigateToRules(): Promise<void> {
await this.navigateToDetectionsPage('rules');
}
async navigateToRuleMonitoring(): Promise<void> {
await common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table');
}
async navigateToExceptionList(): Promise<void> {
await common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table');
}
async navigateToCreateRule(): Promise<void> {
await this.navigateToDetectionsPage('rules/create');
}
async replaceIndexPattern(): Promise<void> {
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<void> {
const element = await testSubjects.find('importQueryFromSavedTimeline');
await element.click(500);
await testSubjects.exists('open-timeline-modal-body-filter-default');
}
async viewTemplatesInImportQueryModal(): Promise<void> {
await common.clickAndValidate('open-timeline-modal-body-filter-template', 'timelines-table');
}
async closeImportQueryModal(): Promise<void> {
await find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon');
}
async selectMachineLearningJob(): Promise<void> {
await find.clickByCssSelector('[data-test-subj="mlJobSelect"] button');
await find.clickByCssSelector('#high_distinct_count_error_message');
}
async openAddFilterPopover(): Promise<void> {
const addButtons = await testSubjects.findAll('addFilter');
await addButtons[1].click();
await testSubjects.exists('saveFilter');
}
async closeAddFilterPopover(): Promise<void> {
await testSubjects.click('cancelSaveFilter');
}
async toggleFilterActions(): Promise<void> {
const filterActions = await testSubjects.findAll('addFilter');
await filterActions[1].click();
}
async toggleSavedQueries(): Promise<void> {
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<void> {
await find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500);
await find.setValue(
`[aria-describedby="detectionEngineStepAboutRuleDescription"]`,
description,
500
);
}
async goBackToAllRules(): Promise<void> {
await common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule');
}
async revealAdvancedSettings(): Promise<void> {
await common.clickAndValidate(
'advancedSettings',
'detectionEngineStepAboutRuleReferenceUrls'
);
}
async preview(): Promise<void> {
await common.clickAndValidate(
'queryPreviewButton',
'queryPreviewCustomHistogram',
undefined,
500
);
}
async continue(prefix: string): Promise<void> {
await testSubjects.click(`${prefix}-continue`);
}
async addCustomQuery(query: string): Promise<void> {
await testSubjects.setValue('queryInput', query, undefined, 500);
}
async selectMLRule(): Promise<void> {
await common.clickAndValidate('machineLearningRuleType', 'mlJobSelect');
}
async selectEQLRule(): Promise<void> {
await common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput');
}
async selectIndicatorMatchRule(): Promise<void> {
await common.clickAndValidate('threatMatchRuleType', 'comboBoxInput');
}
async selectThresholdRule(): Promise<void> {
await common.clickAndValidate('thresholdRuleType', 'input');
}
private async navigateToDetectionsPage(path: string = ''): Promise<void> {
const subUrl = `detections${path ? `/${path}` : ''}`;
await common.navigateToUrl('securitySolution', subUrl, {
shouldUseHashForSubUrl: false,
});
}
async navigateHome(): Promise<void> {
await this.navigateToDetectionsPage();
}
return new DetectionsPage();
async navigateToRules(): Promise<void> {
await this.navigateToDetectionsPage('rules');
}
async navigateToRuleMonitoring(): Promise<void> {
await this.common.clickAndValidate('allRulesTableTab-monitoring', 'monitoring-table');
}
async navigateToExceptionList(): Promise<void> {
await this.common.clickAndValidate('allRulesTableTab-exceptions', 'exceptions-table');
}
async navigateToCreateRule(): Promise<void> {
await this.navigateToDetectionsPage('rules/create');
}
async replaceIndexPattern(): Promise<void> {
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<void> {
const element = await this.testSubjects.find('importQueryFromSavedTimeline');
await element.click(500);
await this.testSubjects.exists('open-timeline-modal-body-filter-default');
}
async viewTemplatesInImportQueryModal(): Promise<void> {
await this.common.clickAndValidate(
'open-timeline-modal-body-filter-template',
'timelines-table'
);
}
async closeImportQueryModal(): Promise<void> {
await this.find.clickByCssSelector('.euiButtonIcon.euiButtonIcon--text.euiModal__closeIcon');
}
async selectMachineLearningJob(): Promise<void> {
await this.find.clickByCssSelector('[data-test-subj="mlJobSelect"] button');
await this.find.clickByCssSelector('#high_distinct_count_error_message');
}
async openAddFilterPopover(): Promise<void> {
const addButtons = await this.testSubjects.findAll('addFilter');
await addButtons[1].click();
await this.testSubjects.exists('saveFilter');
}
async closeAddFilterPopover(): Promise<void> {
await this.testSubjects.click('cancelSaveFilter');
}
async toggleFilterActions(): Promise<void> {
const filterActions = await this.testSubjects.findAll('addFilter');
await filterActions[1].click();
}
async toggleSavedQueries(): Promise<void> {
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<void> {
await this.find.setValue(`[aria-describedby="detectionEngineStepAboutRuleName"]`, name, 500);
await this.find.setValue(
`[aria-describedby="detectionEngineStepAboutRuleDescription"]`,
description,
500
);
}
async goBackToAllRules(): Promise<void> {
await this.common.clickAndValidate('ruleDetailsBackToAllRules', 'create-new-rule');
}
async revealAdvancedSettings(): Promise<void> {
await this.common.clickAndValidate(
'advancedSettings',
'detectionEngineStepAboutRuleReferenceUrls'
);
}
async preview(): Promise<void> {
await this.common.clickAndValidate(
'queryPreviewButton',
'queryPreviewCustomHistogram',
undefined,
500
);
}
async continue(prefix: string): Promise<void> {
await this.testSubjects.click(`${prefix}-continue`);
}
async addCustomQuery(query: string): Promise<void> {
await this.testSubjects.setValue('queryInput', query, undefined, 500);
}
async selectMLRule(): Promise<void> {
await this.common.clickAndValidate('machineLearningRuleType', 'mlJobSelect');
}
async selectEQLRule(): Promise<void> {
await this.common.clickAndValidate('eqlRuleType', 'eqlQueryBarTextInput');
}
async selectIndicatorMatchRule(): Promise<void> {
await this.common.clickAndValidate('threatMatchRuleType', 'comboBoxInput');
}
async selectThresholdRule(): Promise<void> {
await this.common.clickAndValidate('thresholdRuleType', 'input');
}
private async navigateToDetectionsPage(path: string = ''): Promise<void> {
const subUrl = `detections${path ? `/${path}` : ''}`;
await this.common.navigateToUrl('securitySolution', subUrl, {
shouldUseHashForSubUrl: false,
});
}
}

View file

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