mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ts] migrate x-pack/test
to composite ts project (#101441)
Co-authored-by: spalger <spalger@users.noreply.github.com>
This commit is contained in:
parent
a9e64abe8c
commit
bdafd27e19
33 changed files with 3070 additions and 3035 deletions
|
@ -8,7 +8,14 @@
|
|||
"declarationMap": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["public/**/**/*", "server/**/**/*", "common/**/*", "../../../typings/**/*"],
|
||||
"include": [
|
||||
"public/**/**/*",
|
||||
"server/**/**/*",
|
||||
"common/**/*",
|
||||
"../../../typings/**/*",
|
||||
"schema/oss_plugins.json",
|
||||
"schema/oss_root.json",
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../core/tsconfig.json" },
|
||||
{ "path": "../../plugins/kibana_react/tsconfig.json" },
|
||||
|
|
|
@ -6,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(
|
|
@ -7,5 +7,4 @@
|
|||
*/
|
||||
|
||||
export { KibanaServerProvider } from './kibana_server';
|
||||
// @ts-ignore
|
||||
export { extendEsArchiver } from './extend_es_archiver';
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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" },
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
})();
|
||||
}
|
25
x-pack/test/functional/page_objects/logstash_page.ts
Normal file
25
x-pack/test/functional/page_objects/logstash_page.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}`;
|
||||
|
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ export interface ReportingUsageStats {
|
|||
[jobType: string]: any;
|
||||
}
|
||||
|
||||
interface UsageStats {
|
||||
export interface UsageStats {
|
||||
reporting: ReportingUsageStats;
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue