mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[scout] migrate more Discover tests (#201842)
## Summary This PR migrates the following FTR tests to `@kbn/scout`: `x-pack/test/functional/apps/discover/error_handling.ts` => `x-pack/plugins/discover_enhanced/ui_tests/tests/error_handling.spec.ts` `x-pack/test/functional/apps/discover/saved_search_embeddable.ts` => `x-pack/plugins/discover_enhanced/ui_tests/tests/saved_search_embeddable.spec.ts` `x-pack/test/functional/apps/discover/saved_searches.ts` => `x-pack/plugins/discover_enhanced/ui_tests/tests/saved_searches.spec.ts` `x-pack/test/functional/apps/discover/value_suggestions.ts` 2nd describe block => `x-pack/plugins/discover_enhanced/ui_tests/tests/value_suggestions_use_time_range_disabled.spec.ts` Some other changes to mention: **packages/kbn-test-subj-selector**: - support of `^foo` syntax similar to `CSS [attribute^=value] Selector` **packages/kbn-scout**: - new worker fixture `uiSettings` to wrap Advanced Settings set/unset capability - extend `ScoutPage` fixture with `typeWithDelay` method required for many Kibana input fields - extend `PageObjects` fixture with `DashboardApp` & `FilterBar`, also extending existing ones. How to test: ```bash // ESS node scripts/scout_start_servers.js --stateful npx playwright test --config x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts --grep @ess // Serverless node scripts/scout_start_servers.js --serverless=es npx playwright test --config x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts --grep @svlSearch ``` --------- Co-authored-by: Robert Oskamp <traeluki@gmail.com>
This commit is contained in:
parent
a674b9d043
commit
d4094c17be
28 changed files with 974 additions and 166 deletions
|
@ -16,4 +16,7 @@ export type {
|
|||
PageObjects,
|
||||
ScoutTestFixtures,
|
||||
ScoutWorkerFixtures,
|
||||
EsArchiverFixture,
|
||||
} from './src/playwright';
|
||||
|
||||
export type { Client, KbnClient, KibanaUrl, SamlSessionManager, ToolingLog } from './src/types';
|
||||
|
|
|
@ -15,6 +15,7 @@ import { scoutTestFixtures } from './test';
|
|||
export const scoutCoreFixtures = mergeTests(scoutWorkerFixtures, scoutTestFixtures);
|
||||
|
||||
export type {
|
||||
EsArchiverFixture,
|
||||
ScoutTestFixtures,
|
||||
ScoutWorkerFixtures,
|
||||
ScoutPage,
|
||||
|
|
|
@ -15,7 +15,7 @@ import { ScoutPage, KibanaUrl } from '../types';
|
|||
* Instead of defining each method individually, we use a list of method names and loop through them, creating methods dynamically.
|
||||
* All methods must have 'selector: string' as the first argument
|
||||
*/
|
||||
function extendPageWithTestSubject(page: Page) {
|
||||
function extendPageWithTestSubject(page: Page): ScoutPage['testSubj'] {
|
||||
const methods: Array<keyof Page> = [
|
||||
'check',
|
||||
'click',
|
||||
|
@ -28,10 +28,15 @@ function extendPageWithTestSubject(page: Page) {
|
|||
'innerText',
|
||||
'isChecked',
|
||||
'isHidden',
|
||||
'isVisible',
|
||||
'locator',
|
||||
'waitForSelector',
|
||||
];
|
||||
|
||||
const extendedMethods: Partial<Record<keyof Page, Function>> = {};
|
||||
const extendedMethods: Partial<Record<keyof Page, Function>> & {
|
||||
typeWithDelay?: ScoutPage['testSubj']['typeWithDelay'];
|
||||
clearInput?: ScoutPage['testSubj']['clearInput'];
|
||||
} = {};
|
||||
|
||||
for (const method of methods) {
|
||||
extendedMethods[method] = (...args: any[]) => {
|
||||
|
@ -41,7 +46,27 @@ function extendPageWithTestSubject(page: Page) {
|
|||
};
|
||||
}
|
||||
|
||||
return extendedMethods as Record<keyof Page, any>;
|
||||
// custom method to types text into an input field character by character with a delay
|
||||
extendedMethods.typeWithDelay = async (
|
||||
selector: string,
|
||||
text: string,
|
||||
options?: { delay: number }
|
||||
) => {
|
||||
const { delay = 25 } = options || {};
|
||||
const testSubjSelector = subj(selector);
|
||||
await page.locator(testSubjSelector).click();
|
||||
for (const char of text) {
|
||||
await page.keyboard.insertText(char);
|
||||
await page.waitForTimeout(delay);
|
||||
}
|
||||
};
|
||||
// custom method to clear an input field
|
||||
extendedMethods.clearInput = async (selector: string) => {
|
||||
const testSubjSelector = subj(selector);
|
||||
await page.locator(testSubjSelector).fill('');
|
||||
};
|
||||
|
||||
return extendedMethods as ScoutPage['testSubj'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,6 +103,9 @@ export const scoutPageFixture = base.extend<{ page: ScoutPage; kbnUrl: KibanaUrl
|
|||
// Method to navigate to specific Kibana apps
|
||||
page.gotoApp = (appName: string) => page.goto(kbnUrl.app(appName));
|
||||
|
||||
page.waitForLoadingIndicatorHidden = () =>
|
||||
page.testSubj.waitForSelector('globalLoadingIndicator-hidden', { state: 'attached' });
|
||||
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -22,8 +22,30 @@ export interface LoginFixture {
|
|||
loginAsPrivilegedUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the Playwright 'Page' interface with methods specific to Kibana.
|
||||
* Reasons to use 'ReturnType' instead of Explicit Typings:
|
||||
* - DRY Principle: automatically stays in sync with the Playwright API, reducing maintenance overhead.
|
||||
* - Future-Proofing: If Playwright changes the return type of methods, these types will update accordingly.
|
||||
* Recommendation: define Explicit Types as return types only if methods (e.g. 'typeWithDelay')
|
||||
* have any additional logic or Kibana-specific behavior.
|
||||
*/
|
||||
export type ScoutPage = Page & {
|
||||
/**
|
||||
* Navigates to the specified Kibana application.
|
||||
* @param appName - The name of the Kibana app (e.g., 'discover', 'dashboard').
|
||||
* @param options - Additional navigation options, passed directly to Playwright's `goto` method.
|
||||
* @returns A Promise resolving to a Playwright `Response` or `null`.
|
||||
*/
|
||||
gotoApp: (appName: string, options?: Parameters<Page['goto']>[1]) => ReturnType<Page['goto']>;
|
||||
/**
|
||||
* Waits for the Kibana loading spinner indicator to disappear.
|
||||
* @returns A Promise resolving when the indicator is hidden.
|
||||
*/
|
||||
waitForLoadingIndicatorHidden: () => ReturnType<Page['waitForSelector']>;
|
||||
/**
|
||||
* Simplified API to interact with elements using Kibana's 'data-test-subj' attribute.
|
||||
*/
|
||||
testSubj: {
|
||||
check: (selector: string, options?: Parameters<Page['check']>[1]) => ReturnType<Page['check']>;
|
||||
click: (selector: string, options?: Parameters<Page['click']>[1]) => ReturnType<Page['click']>;
|
||||
|
@ -59,9 +81,34 @@ export type ScoutPage = Page & {
|
|||
selector: string,
|
||||
options?: Parameters<Page['isHidden']>[1]
|
||||
) => ReturnType<Page['isHidden']>;
|
||||
isVisible: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['isVisible']>[1]
|
||||
) => ReturnType<Page['isVisible']>;
|
||||
locator: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['locator']>[1]
|
||||
) => ReturnType<Page['locator']>;
|
||||
waitForSelector: (
|
||||
selector: string,
|
||||
options?: Parameters<Page['waitForSelector']>[1]
|
||||
) => ReturnType<Page['waitForSelector']>;
|
||||
// custom methods
|
||||
/**
|
||||
* Types text into an input field character by character with a specified delay between each character.
|
||||
*
|
||||
* @param selector - The selector for the input element (supports 'data-test-subj' attributes).
|
||||
* @param text - The text to type into the input field.
|
||||
* @param options - Optional configuration object.
|
||||
* @param options.delay - The delay in milliseconds between typing each character (default: 25ms).
|
||||
* @returns A Promise that resolves once the text has been typed.
|
||||
*/
|
||||
typeWithDelay: (selector: string, text: string, options?: { delay: number }) => Promise<void>;
|
||||
/**
|
||||
* Clears the input field by filling it with an empty string.
|
||||
* @param selector The selector for the input element (supports 'data-test-subj' attributes).
|
||||
* @returns A Promise that resolves once the text has been cleared.
|
||||
*/
|
||||
clearInput: (selector: string) => Promise<void>;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,23 +12,31 @@ import type { ToolingLog } from '@kbn/tooling-log';
|
|||
import type { Client } from '@elastic/elasticsearch';
|
||||
import { LoadActionPerfOptions } from '@kbn/es-archiver';
|
||||
import { IndexStats } from '@kbn/es-archiver/src/lib/stats';
|
||||
import type { UiSettingValues } from '@kbn/test/src/kbn_client/kbn_client_ui_settings';
|
||||
|
||||
import { ScoutServerConfig } from '../../../types';
|
||||
import { KibanaUrl } from '../../../common/services/kibana_url';
|
||||
|
||||
interface EsArchiverFixture {
|
||||
export interface EsArchiverFixture {
|
||||
loadIfNeeded: (
|
||||
name: string,
|
||||
performance?: LoadActionPerfOptions | undefined
|
||||
) => Promise<Record<string, IndexStats>>;
|
||||
}
|
||||
|
||||
export interface UiSettingsFixture {
|
||||
set: (values: UiSettingValues) => Promise<void>;
|
||||
unset: (...values: string[]) => Promise<any>;
|
||||
setDefaultTime: ({ from, to }: { from: string; to: string }) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface ScoutWorkerFixtures {
|
||||
log: ToolingLog;
|
||||
config: ScoutServerConfig;
|
||||
kbnUrl: KibanaUrl;
|
||||
esClient: Client;
|
||||
kbnClient: KbnClient;
|
||||
uiSettings: UiSettingsFixture;
|
||||
esArchiver: EsArchiverFixture;
|
||||
samlAuth: SamlSessionManager;
|
||||
}
|
||||
|
|
84
packages/kbn-scout/src/playwright/fixtures/worker/core.ts
Normal file
84
packages/kbn-scout/src/playwright/fixtures/worker/core.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
import { LoadActionPerfOptions } from '@kbn/es-archiver';
|
||||
import {
|
||||
createKbnUrl,
|
||||
createEsArchiver,
|
||||
createEsClient,
|
||||
createKbnClient,
|
||||
createLogger,
|
||||
createSamlSessionManager,
|
||||
createScoutConfig,
|
||||
} from '../../../common/services';
|
||||
import { ScoutWorkerFixtures } from '../types/worker_scope';
|
||||
import { ScoutTestOptions } from '../../types';
|
||||
|
||||
export const coreWorkerFixtures = base.extend<{}, ScoutWorkerFixtures>({
|
||||
log: [
|
||||
({}, use) => {
|
||||
use(createLogger());
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
config: [
|
||||
({ log }, use, testInfo) => {
|
||||
const configName = 'local';
|
||||
const projectUse = testInfo.project.use as ScoutTestOptions;
|
||||
const serversConfigDir = projectUse.serversConfigDir;
|
||||
const configInstance = createScoutConfig(serversConfigDir, configName, log);
|
||||
|
||||
use(configInstance);
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
kbnUrl: [
|
||||
({ config, log }, use) => {
|
||||
use(createKbnUrl(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
esClient: [
|
||||
({ config, log }, use) => {
|
||||
use(createEsClient(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
kbnClient: [
|
||||
({ log, config }, use) => {
|
||||
use(createKbnClient(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
esArchiver: [
|
||||
({ log, esClient, kbnClient }, use) => {
|
||||
const esArchiverInstance = createEsArchiver(esClient, kbnClient, log);
|
||||
// to speedup test execution we only allow to ingest the data indexes and only if index doesn't exist
|
||||
const loadIfNeeded = async (name: string, performance?: LoadActionPerfOptions | undefined) =>
|
||||
esArchiverInstance!.loadIfNeeded(name, performance);
|
||||
|
||||
use({ loadIfNeeded });
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
samlAuth: [
|
||||
({ log, config }, use) => {
|
||||
use(createSamlSessionManager(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
});
|
|
@ -7,78 +7,8 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
import { mergeTests } from 'playwright/test';
|
||||
import { uiSettingsFixture } from './ui_settings';
|
||||
import { coreWorkerFixtures } from './core';
|
||||
|
||||
import { LoadActionPerfOptions } from '@kbn/es-archiver';
|
||||
import {
|
||||
createKbnUrl,
|
||||
createEsArchiver,
|
||||
createEsClient,
|
||||
createKbnClient,
|
||||
createLogger,
|
||||
createSamlSessionManager,
|
||||
createScoutConfig,
|
||||
} from '../../../common/services';
|
||||
import { ScoutWorkerFixtures } from '../types/worker_scope';
|
||||
import { ScoutTestOptions } from '../../types';
|
||||
|
||||
export const scoutWorkerFixtures = base.extend<{}, ScoutWorkerFixtures>({
|
||||
log: [
|
||||
({}, use) => {
|
||||
use(createLogger());
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
config: [
|
||||
({ log }, use, testInfo) => {
|
||||
const configName = 'local';
|
||||
const projectUse = testInfo.project.use as ScoutTestOptions;
|
||||
const serversConfigDir = projectUse.serversConfigDir;
|
||||
const configInstance = createScoutConfig(serversConfigDir, configName, log);
|
||||
|
||||
use(configInstance);
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
kbnUrl: [
|
||||
({ config, log }, use) => {
|
||||
use(createKbnUrl(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
esClient: [
|
||||
({ config, log }, use) => {
|
||||
use(createEsClient(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
kbnClient: [
|
||||
({ log, config }, use) => {
|
||||
use(createKbnClient(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
esArchiver: [
|
||||
({ log, esClient, kbnClient }, use) => {
|
||||
const esArchiverInstance = createEsArchiver(esClient, kbnClient, log);
|
||||
// to speedup test execution we only allow to ingest the data indexes and only if index doesn't exist
|
||||
const loadIfNeeded = async (name: string, performance?: LoadActionPerfOptions | undefined) =>
|
||||
esArchiverInstance!.loadIfNeeded(name, performance);
|
||||
|
||||
use({ loadIfNeeded });
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
|
||||
samlAuth: [
|
||||
({ log, config }, use) => {
|
||||
use(createSamlSessionManager(config, log));
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
});
|
||||
export const scoutWorkerFixtures = mergeTests(coreWorkerFixtures, uiSettingsFixture);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
import { UiSettingValues } from '@kbn/test/src/kbn_client/kbn_client_ui_settings';
|
||||
import { ScoutWorkerFixtures } from '../types';
|
||||
import { isValidUTCDate, formatTime } from '../../utils';
|
||||
|
||||
export const uiSettingsFixture = base.extend<{}, ScoutWorkerFixtures>({
|
||||
uiSettings: [
|
||||
({ kbnClient }, use) => {
|
||||
const kbnClientUiSettings = {
|
||||
set: async (values: UiSettingValues) => kbnClient.uiSettings.update(values),
|
||||
|
||||
unset: async (...keys: string[]) =>
|
||||
Promise.all(keys.map((key) => kbnClient.uiSettings.unset(key))),
|
||||
|
||||
setDefaultTime: async ({ from, to }: { from: string; to: string }) => {
|
||||
const utcFrom = isValidUTCDate(from) ? from : formatTime(from);
|
||||
const untcTo = isValidUTCDate(to) ? to : formatTime(to);
|
||||
await kbnClient.uiSettings.update({
|
||||
'timepicker:timeDefaults': `{ "from": "${utcFrom}", "to": "${untcTo}"}`,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
use(kbnClientUiSettings);
|
||||
},
|
||||
{ scope: 'worker' },
|
||||
],
|
||||
});
|
|
@ -19,4 +19,9 @@ export { expect } from './expect';
|
|||
|
||||
export type { ScoutPlaywrightOptions, ScoutTestOptions } from './types';
|
||||
export type { PageObjects } from './page_objects';
|
||||
export type { ScoutTestFixtures, ScoutWorkerFixtures, ScoutPage } from './fixtures';
|
||||
export type {
|
||||
ScoutTestFixtures,
|
||||
ScoutWorkerFixtures,
|
||||
ScoutPage,
|
||||
EsArchiverFixture,
|
||||
} from './fixtures';
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutPage } from '../fixtures/types';
|
||||
|
||||
type CommonlyUsedTimeRange =
|
||||
| 'Today'
|
||||
| 'Last_15 minutes'
|
||||
| 'Last_1 hour'
|
||||
| 'Last_24 hours'
|
||||
| 'Last_30 days'
|
||||
| 'Last_90 days'
|
||||
| 'Last_1 year';
|
||||
|
||||
export class DashboardApp {
|
||||
constructor(private readonly page: ScoutPage) {}
|
||||
|
||||
async goto() {
|
||||
await this.page.gotoApp('dashboards');
|
||||
}
|
||||
|
||||
async openNewDashboard() {
|
||||
await this.page.testSubj.click('newItemButton');
|
||||
await this.page.testSubj.waitForSelector('emptyDashboardWidget', { state: 'visible' });
|
||||
}
|
||||
|
||||
async saveDashboard(name: string) {
|
||||
await this.page.testSubj.click('dashboardInteractiveSaveMenuItem');
|
||||
await this.page.testSubj.fill('savedObjectTitle', name);
|
||||
await this.page.testSubj.click('confirmSaveSavedObjectButton');
|
||||
await this.page.testSubj.waitForSelector('confirmSaveSavedObjectButton', { state: 'hidden' });
|
||||
}
|
||||
|
||||
async addPanelFromLibrary(...names: string[]) {
|
||||
await this.page.testSubj.click('dashboardAddFromLibraryButton');
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
// clear search input after the first panel is added
|
||||
if (i > 0) {
|
||||
await this.page.testSubj.clearInput('savedObjectFinderSearchInput');
|
||||
}
|
||||
await this.page.testSubj.typeWithDelay('savedObjectFinderSearchInput', names[i]);
|
||||
await this.page.testSubj.click(`savedObjectTitle${names[i].replace(/ /g, '-')}`);
|
||||
// wait for the panel to be added
|
||||
await this.page.testSubj.waitForSelector(
|
||||
`embeddablePanelHeading-${names[i].replace(/[- ]/g, '')}`,
|
||||
{
|
||||
state: 'visible',
|
||||
}
|
||||
);
|
||||
}
|
||||
// close the flyout
|
||||
await this.page.testSubj.click('euiFlyoutCloseButton');
|
||||
await this.page.testSubj.waitForSelector('euiFlyoutCloseButton', { state: 'hidden' });
|
||||
}
|
||||
|
||||
async customizePanel(options: {
|
||||
name: string;
|
||||
customTimeRageCommonlyUsed?: {
|
||||
value: CommonlyUsedTimeRange;
|
||||
};
|
||||
}) {
|
||||
await this.page.testSubj.hover(`embeddablePanelHeading-${options.name.replace(/ /g, '')}`);
|
||||
await this.page.testSubj.click('embeddablePanelAction-ACTION_CUSTOMIZE_PANEL');
|
||||
if (options.customTimeRageCommonlyUsed) {
|
||||
await this.page.testSubj.click('customizePanelShowCustomTimeRange');
|
||||
await this.page.testSubj.click(
|
||||
'customizePanelTimeRangeDatePicker > superDatePickerToggleQuickMenuButton'
|
||||
);
|
||||
await this.page.testSubj.click(
|
||||
`superDatePickerCommonlyUsed_${options.customTimeRageCommonlyUsed.value}`
|
||||
);
|
||||
}
|
||||
|
||||
await this.page.testSubj.click('saveCustomizePanelButton');
|
||||
await this.page.testSubj.waitForSelector('saveCustomizePanelButton', { state: 'hidden' });
|
||||
}
|
||||
|
||||
async removePanel(name: string | 'embeddableError') {
|
||||
const panelHeaderTestSubj =
|
||||
name === 'embeddableError' ? name : `embeddablePanelHeading-${name.replace(/ /g, '')}`;
|
||||
await this.page.testSubj.locator(panelHeaderTestSubj).scrollIntoViewIfNeeded();
|
||||
await this.page.testSubj.locator(panelHeaderTestSubj).hover();
|
||||
await this.page.testSubj.click('embeddablePanelToggleMenuIcon');
|
||||
await this.page.testSubj.click('embeddablePanelAction-deletePanel');
|
||||
await this.page.testSubj.waitForSelector(panelHeaderTestSubj, {
|
||||
state: 'hidden',
|
||||
});
|
||||
}
|
||||
}
|
|
@ -13,8 +13,36 @@ import { expect } from '..';
|
|||
export class DatePicker {
|
||||
constructor(private readonly page: ScoutPage) {}
|
||||
|
||||
private async showStartEndTimes() {
|
||||
// This first await makes sure the superDatePicker has loaded before we check for the ShowDatesButton
|
||||
await this.page.testSubj.waitForSelector('superDatePickerToggleQuickMenuButton', {
|
||||
timeout: 10000,
|
||||
});
|
||||
const isShowDateBtnVisible = await this.page.testSubj.isVisible(
|
||||
'superDatePickerShowDatesButton',
|
||||
{
|
||||
timeout: 5000,
|
||||
}
|
||||
);
|
||||
|
||||
if (isShowDateBtnVisible) {
|
||||
await this.page.testSubj.click('superDatePickerShowDatesButton');
|
||||
}
|
||||
|
||||
const isStartDatePopoverBtnVisible = await this.page.testSubj.isVisible(
|
||||
'superDatePickerstartDatePopoverButton',
|
||||
{
|
||||
timeout: 5000,
|
||||
}
|
||||
);
|
||||
|
||||
if (isStartDatePopoverBtnVisible) {
|
||||
await this.page.testSubj.click('superDatePickerstartDatePopoverButton');
|
||||
}
|
||||
}
|
||||
|
||||
async setAbsoluteRange({ from, to }: { from: string; to: string }) {
|
||||
await this.page.testSubj.click('superDatePickerShowDatesButton');
|
||||
await this.showStartEndTimes();
|
||||
// we start with end date
|
||||
await this.page.testSubj.click('superDatePickerendDatePopoverButton');
|
||||
await this.page.testSubj.click('superDatePickerAbsoluteTab');
|
||||
|
@ -32,10 +60,14 @@ export class DatePicker {
|
|||
await this.page.testSubj.click('parseAbsoluteDateFormat');
|
||||
await this.page.keyboard.press('Escape');
|
||||
|
||||
await expect(this.page.testSubj.locator('superDatePickerstartDatePopoverButton')).toHaveText(
|
||||
from
|
||||
);
|
||||
await expect(this.page.testSubj.locator('superDatePickerendDatePopoverButton')).toHaveText(to);
|
||||
await expect(
|
||||
this.page.testSubj.locator('superDatePickerstartDatePopoverButton'),
|
||||
`Date picker 'start date' should be set correctly`
|
||||
).toHaveText(from);
|
||||
await expect(
|
||||
this.page.testSubj.locator('superDatePickerendDatePopoverButton'),
|
||||
`Date picker 'end date' should be set correctly`
|
||||
).toHaveText(to);
|
||||
await this.page.testSubj.click('querySubmitButton');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,4 +15,35 @@ export class DiscoverApp {
|
|||
async goto() {
|
||||
await this.page.gotoApp('discover');
|
||||
}
|
||||
|
||||
async selectDataView(name: string) {
|
||||
const currentValue = await this.page.testSubj.innerText('*dataView-switch-link');
|
||||
if (currentValue === name) {
|
||||
return;
|
||||
}
|
||||
await this.page.testSubj.click('*dataView-switch-link');
|
||||
await this.page.testSubj.waitForSelector('indexPattern-switcher');
|
||||
await this.page.testSubj.typeWithDelay('indexPattern-switcher--input', name);
|
||||
await this.page.testSubj.locator('indexPattern-switcher').locator(`[title="${name}"]`).click();
|
||||
await this.page.testSubj.waitForSelector('indexPattern-switcher', { state: 'hidden' });
|
||||
await this.page.waitForLoadingIndicatorHidden();
|
||||
}
|
||||
|
||||
async clickNewSearch() {
|
||||
await this.page.testSubj.hover('discoverNewButton');
|
||||
await this.page.testSubj.click('discoverNewButton');
|
||||
await this.page.testSubj.hover('unifiedFieldListSidebar__toggle-collapse'); // cancel tooltips
|
||||
}
|
||||
|
||||
async saveSearch(name: string) {
|
||||
await this.page.testSubj.click('discoverSaveButton');
|
||||
await this.page.testSubj.fill('savedObjectTitle', name);
|
||||
await this.page.testSubj.click('confirmSaveSavedObjectButton');
|
||||
await this.page.testSubj.waitForSelector('savedObjectSaveModal', { state: 'hidden' });
|
||||
await this.page.waitForLoadingIndicatorHidden();
|
||||
}
|
||||
|
||||
async waitForHistogramRendered() {
|
||||
await this.page.testSubj.waitForSelector('unifiedHistogramRendered');
|
||||
}
|
||||
}
|
||||
|
|
70
packages/kbn-scout/src/playwright/page_objects/fiter_bar.ts
Normal file
70
packages/kbn-scout/src/playwright/page_objects/fiter_bar.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { ScoutPage } from '../fixtures/types';
|
||||
import { expect } from '..';
|
||||
|
||||
interface FilterCreationOptions {
|
||||
field: string;
|
||||
operator: 'is' | 'is not' | 'is one of' | 'is not one of' | 'exists' | 'does not exist';
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface FilterStateOptions {
|
||||
field: string;
|
||||
value: string;
|
||||
enabled?: boolean;
|
||||
pinned?: boolean;
|
||||
negated?: boolean;
|
||||
}
|
||||
|
||||
export class FilterBar {
|
||||
constructor(private readonly page: ScoutPage) {}
|
||||
|
||||
async addFilter(options: FilterCreationOptions) {
|
||||
await this.page.testSubj.click('addFilter');
|
||||
await this.page.testSubj.waitForSelector('addFilterPopover');
|
||||
// set field name
|
||||
await this.page.testSubj.typeWithDelay(
|
||||
'filterFieldSuggestionList > comboBoxSearchInput',
|
||||
options.field
|
||||
);
|
||||
await this.page.click(`.euiFilterSelectItem[title="${options.field}"]`);
|
||||
// set operator
|
||||
await this.page.testSubj.typeWithDelay(
|
||||
'filterOperatorList > comboBoxSearchInput',
|
||||
options.operator
|
||||
);
|
||||
await this.page.click(`.euiFilterSelectItem[title="${options.operator}"]`);
|
||||
// set value
|
||||
await this.page.testSubj.locator('filterParams').locator('input').fill(options.value);
|
||||
// save filter
|
||||
await this.page.testSubj.click('saveFilter');
|
||||
|
||||
await expect(
|
||||
this.page.testSubj.locator('^filter-badge'),
|
||||
'New filter badge should be displayed'
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async hasFilter(options: FilterStateOptions) {
|
||||
const testSubjLocator = [
|
||||
'~filter',
|
||||
options.enabled !== undefined && `~filter-${options.enabled ? 'enabled' : 'disabled'}`,
|
||||
options.field && `~filter-key-${options.field}`,
|
||||
options.value && `~filter-value-${options.value}`,
|
||||
options.pinned !== undefined && `~filter-${options.pinned ? 'pinned' : 'unpinned'}`,
|
||||
options.negated !== undefined && (options.negated ? '~filter-negated' : ''),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' & ');
|
||||
|
||||
return this.page.testSubj.isVisible(testSubjLocator, { strict: true });
|
||||
}
|
||||
}
|
|
@ -8,13 +8,17 @@
|
|||
*/
|
||||
|
||||
import { ScoutPage } from '../fixtures/types';
|
||||
import { DashboardApp } from './dashboard_app';
|
||||
import { DatePicker } from './date_picker';
|
||||
import { DiscoverApp } from './discover_app';
|
||||
import { FilterBar } from './fiter_bar';
|
||||
import { createLazyPageObject } from './utils';
|
||||
|
||||
export interface PageObjects {
|
||||
datePicker: DatePicker;
|
||||
discover: DiscoverApp;
|
||||
dashboard: DashboardApp;
|
||||
filterBar: FilterBar;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +30,9 @@ export interface PageObjects {
|
|||
export function createCorePageObjects(page: ScoutPage): PageObjects {
|
||||
return {
|
||||
datePicker: createLazyPageObject(DatePicker, page),
|
||||
dashboard: createLazyPageObject(DashboardApp, page),
|
||||
discover: createLazyPageObject(DiscoverApp, page),
|
||||
filterBar: createLazyPageObject(FilterBar, page),
|
||||
// Add new page objects here
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,4 +7,14 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
export const serviceLoadedMsg = (name: string) => `scout service loaded: ${name}`;
|
||||
|
||||
export const isValidUTCDate = (date: string): boolean => {
|
||||
return !isNaN(Date.parse(date)) && new Date(date).toISOString() === date;
|
||||
};
|
||||
|
||||
export function formatTime(date: string, fmt: string = 'MMM D, YYYY @ HH:mm:ss.SSS') {
|
||||
return moment.utc(date, fmt).format();
|
||||
}
|
||||
|
|
|
@ -10,3 +10,4 @@
|
|||
export * from './config';
|
||||
export * from './cli';
|
||||
export * from './servers';
|
||||
export * from './services';
|
||||
|
|
13
packages/kbn-scout/src/types/services.d.ts
vendored
Normal file
13
packages/kbn-scout/src/types/services.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export type { KbnClient, SamlSessionManager } from '@kbn/test';
|
||||
export type { Client } from '@elastic/elasticsearch';
|
||||
export type { KibanaUrl } from '../common/services';
|
||||
export type { ToolingLog } from '@kbn/tooling-log';
|
|
@ -22,6 +22,8 @@ function termToCssSelector(term: string) {
|
|||
return '[data-test-subj~="' + term.substring(1).replace(/\s/g, '') + '"]';
|
||||
} else if (term.startsWith('*')) {
|
||||
return '[data-test-subj*="' + term.substring(1).replace(/\s/g, '') + '"]';
|
||||
} else if (term.startsWith('^')) {
|
||||
return '[data-test-subj^="' + term.substring(1).replace(/\s/g, '') + '"]';
|
||||
} else {
|
||||
return '[data-test-subj="' + term + '"]';
|
||||
}
|
||||
|
@ -40,25 +42,30 @@ function termToCssSelector(term: string) {
|
|||
* - prefixing a value with `*` will allow matching a `data-test-subj` attribute containing at least one occurrence of value within the string.
|
||||
* - example: `*foo`
|
||||
* - css equivalent: `[data-test-subj*="foo"]`
|
||||
* - DOM match example: <div> data-test-subj="bar-foo" </div>
|
||||
* - DOM match example: <div data-test-subj="bar-foo"> </div>
|
||||
*
|
||||
* - prefixing a value with `^` will allow matching a `data-test-subj` attribute beginning with the specified value.
|
||||
* - example: `^foo`
|
||||
* - css equivalent: `[data-test-subj^="foo"]`
|
||||
* - DOM match example: <div data-test-subj="foo_bar"> </div>
|
||||
*
|
||||
* - prefixing a value with `~` will allow matching a `data-test-subj` attribute represented as a whitespace-separated list of words, one of which is exactly value
|
||||
* - example: `~foo`
|
||||
* - css equivalent: `[data-test-subj~="foo"]`
|
||||
* - DOM match example: <div> data-test-subj="foo bar" </div>
|
||||
* - DOM match example: <div data-test-subj="foo bar"> </div>
|
||||
*
|
||||
* - the `>` character is used between two values to indicate that the value on the right must match an element inside an element matched by the value on the left
|
||||
* - example: `foo > bar`
|
||||
* - css equivalent: `[data-test-subj=foo] [data-test-subj=bar]`
|
||||
* - DOM match example:
|
||||
* <div> data-test-subj="foo"
|
||||
* <div> data-test-subj="bar" </div>
|
||||
* <div data-test-subj="foo">
|
||||
* <div data-test-subj="bar"> </div>
|
||||
* </div>
|
||||
*
|
||||
* - the `&` character is used between two values to indicate that the value on both sides must both match the element
|
||||
* - example: `foo & bar`
|
||||
* - css equivalent: `[data-test-subj=foo][data-test-subj=bar]`
|
||||
* - DOM match example: <div> data-test-subj="foo bar" </div>
|
||||
* - DOM match example: <div data-test-subj="foo bar"> </div>
|
||||
*/
|
||||
export function subj(selector: string) {
|
||||
return selectorToTerms(selector)
|
||||
|
|
|
@ -11,7 +11,10 @@ node scripts/scout_start_servers.js --serverless=es
|
|||
Then you can run the tests multiple times in another terminal with:
|
||||
|
||||
```bash
|
||||
npx playwright test --config x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts
|
||||
// ESS
|
||||
npx playwright test --config x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts --grep @ess
|
||||
// Serverless
|
||||
npx playwright test --config x-pack/plugins/discover_enhanced/ui_tests/playwright.config.ts --grep @svlSearch // @svlOblt, @svlSecurity
|
||||
```
|
||||
|
||||
Test results are available in `x-pack/plugins/discover_enhanced/ui_tests/output`
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 const QUERY_BAR_VALIDATION = {
|
||||
SUGGESTIONS_COUNT: 'The query bar suggestions count should be',
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 const LOGSTASH_DEFAULT_START_TIME = '2015-09-19T06:31:44.000Z';
|
||||
export const LOGSTASH_DEFAULT_END_TIME = '2015-09-23T18:31:44.000Z';
|
||||
|
||||
export const DATA_VIEW_ID = {
|
||||
ECOMMERCE: '5193f870-d861-11e9-a311-0fa548c5f953',
|
||||
LOGSTASH: 'logstash-*',
|
||||
};
|
||||
|
||||
export const DATA_VIEW = {
|
||||
ECOMMERCE: 'ecommerce',
|
||||
LOGSTASH: 'logstash-*',
|
||||
};
|
||||
|
||||
export const LOGSTASH_OUT_OF_RANGE_DATES = {
|
||||
from: 'Mar 1, 2020 @ 00:00:00.000',
|
||||
to: 'Nov 1, 2020 @ 00:00:00.000',
|
||||
};
|
||||
|
||||
export const LOGSTASH_IN_RANGE_DATES = {
|
||||
from: 'Sep 19, 2015 @ 06:31:44.000',
|
||||
to: 'Sep 23, 2015 @ 18:31:44.000',
|
||||
};
|
||||
|
||||
export const ES_ARCHIVES = {
|
||||
LOGSTASH: 'x-pack/test/functional/es_archives/logstash_functional',
|
||||
NO_TIME_FIELD: 'test/functional/fixtures/es_archiver/index_pattern_without_timefield',
|
||||
ECOMMERCE: 'x-pack/test/functional/es_archives/reporting/ecommerce',
|
||||
};
|
||||
|
||||
export const KBN_ARCHIVES = {
|
||||
INVALID_SCRIPTED_FIELD: 'test/functional/fixtures/kbn_archiver/invalid_scripted_field',
|
||||
NO_TIME_FIELD: 'test/functional/fixtures/kbn_archiver/index_pattern_without_timefield',
|
||||
DASHBOARD_DRILLDOWNS:
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/dashboard_drilldowns/drilldowns',
|
||||
DISCOVER: 'test/functional/fixtures/kbn_archiver/discover',
|
||||
ECOMMERCE: 'x-pack/test/functional/fixtures/kbn_archiver/reporting/ecommerce.json',
|
||||
};
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from '@kbn/scout';
|
||||
import { DemoPage } from './page_objects';
|
||||
|
||||
interface ExtendedScoutTestFixtures extends ScoutTestFixtures {
|
||||
export interface ExtendedScoutTestFixtures extends ScoutTestFixtures {
|
||||
pageObjects: PageObjects & {
|
||||
demo: DemoPage;
|
||||
};
|
||||
|
@ -30,3 +30,6 @@ export const test = base.extend<ExtendedScoutTestFixtures, ScoutWorkerFixtures>(
|
|||
await use(extendedPageObjects);
|
||||
},
|
||||
});
|
||||
|
||||
export * as testData from './constants';
|
||||
export * as assertionMessages from './assertion_messages';
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { expect } from '@kbn/scout';
|
||||
import { test, testData } from '../fixtures';
|
||||
|
||||
test.describe('Discover app - errors', { tag: ['@ess'] }, () => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
|
||||
await kbnClient.savedObjects.clean({ types: ['search', 'index-pattern'] });
|
||||
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.INVALID_SCRIPTED_FIELD);
|
||||
await uiSettings.setDefaultTime({
|
||||
from: testData.LOGSTASH_DEFAULT_START_TIME,
|
||||
to: testData.LOGSTASH_DEFAULT_END_TIME,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient }) => {
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsViewer();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('should render invalid scripted field error', async ({ page }) => {
|
||||
await page.testSubj.locator('discoverErrorCalloutTitle').waitFor({ state: 'visible' });
|
||||
await expect(
|
||||
page.testSubj.locator('painlessStackTrace'),
|
||||
'Painless error stacktrace should be displayed'
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { ScoutWorkerFixtures, expect } from '@kbn/scout';
|
||||
import { test, testData } from '../fixtures';
|
||||
|
||||
const createSavedSearch = async (
|
||||
kbnClient: ScoutWorkerFixtures['kbnClient'],
|
||||
searchId: string,
|
||||
searchTitle: string,
|
||||
dataViewId: string
|
||||
) =>
|
||||
await kbnClient.savedObjects.create({
|
||||
type: 'search',
|
||||
id: searchId,
|
||||
overwrite: false,
|
||||
attributes: {
|
||||
title: searchTitle,
|
||||
description: '',
|
||||
columns: ['agent', 'bytes', 'clientip'],
|
||||
sort: [['@timestamp', 'desc']],
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON:
|
||||
'{"highlightAll":true,"version":true,"query":{"language":"lucene","query":""},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}',
|
||||
},
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: dataViewId,
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test.describe(
|
||||
'Discover app - saved search embeddable',
|
||||
{ tag: ['@ess', '@svlSecurity', '@svlOblt', '@svlSearch'] },
|
||||
() => {
|
||||
const SAVED_SEARCH_TITLE = 'TempSearch';
|
||||
const SAVED_SEARCH_ID = '90943e30-9a47-11e8-b64d-95841ca0b247';
|
||||
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
|
||||
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.DASHBOARD_DRILLDOWNS);
|
||||
await uiSettings.set({
|
||||
defaultIndex: testData.DATA_VIEW_ID.LOGSTASH, // TODO: investigate why it is required for `node scripts/playwright_test.js` run
|
||||
'timepicker:timeDefaults': `{ "from": "${testData.LOGSTASH_DEFAULT_START_TIME}", "to": "${testData.LOGSTASH_DEFAULT_END_TIME}"}`,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient, uiSettings }) => {
|
||||
await uiSettings.unset('defaultIndex', 'timepicker:timeDefaults');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsPrivilegedUser();
|
||||
await pageObjects.dashboard.goto();
|
||||
});
|
||||
|
||||
test('should allow removing the dashboard panel after the underlying saved search has been deleted', async ({
|
||||
kbnClient,
|
||||
page,
|
||||
pageObjects,
|
||||
}) => {
|
||||
await pageObjects.dashboard.openNewDashboard();
|
||||
await createSavedSearch(
|
||||
kbnClient,
|
||||
SAVED_SEARCH_ID,
|
||||
SAVED_SEARCH_TITLE,
|
||||
testData.DATA_VIEW_ID.LOGSTASH
|
||||
);
|
||||
await pageObjects.dashboard.addPanelFromLibrary(SAVED_SEARCH_TITLE);
|
||||
await page.testSubj.locator('savedSearchTotalDocuments').waitFor({
|
||||
state: 'visible',
|
||||
});
|
||||
|
||||
await pageObjects.dashboard.saveDashboard('Dashboard with deleted saved search');
|
||||
await kbnClient.savedObjects.delete({
|
||||
type: 'search',
|
||||
id: SAVED_SEARCH_ID,
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
await page.waitForLoadingIndicatorHidden();
|
||||
await expect(
|
||||
page.testSubj.locator('embeddableError'),
|
||||
'Embeddable error should be displayed'
|
||||
).toBeVisible();
|
||||
|
||||
await pageObjects.dashboard.removePanel('embeddableError');
|
||||
await expect(
|
||||
page.testSubj.locator('embeddableError'),
|
||||
'Embeddable error should not be displayed'
|
||||
).toBeHidden();
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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 { expect } from '@kbn/scout';
|
||||
import { test, testData } from '../fixtures';
|
||||
import type { ExtendedScoutTestFixtures } from '../fixtures';
|
||||
|
||||
const assertNoFilterAndEmptyQuery = async (
|
||||
filterBadge: { field: string; value: string },
|
||||
pageObjects: ExtendedScoutTestFixtures['pageObjects'],
|
||||
page: ExtendedScoutTestFixtures['page']
|
||||
) => {
|
||||
expect(
|
||||
// checking if filter exists, enabled or disabled
|
||||
await pageObjects.filterBar.hasFilter(filterBadge),
|
||||
`Filter ${JSON.stringify(filterBadge)} should not exist`
|
||||
).toBe(false);
|
||||
await expect(
|
||||
page.testSubj.locator('queryInput'),
|
||||
'Query Bar input field should be empty'
|
||||
).toHaveText('');
|
||||
};
|
||||
|
||||
const assertDataViewIsSelected = async (page: ExtendedScoutTestFixtures['page'], name: string) =>
|
||||
await expect(
|
||||
page.testSubj.locator('*dataView-switch-link'),
|
||||
'Incorrect data view is selected'
|
||||
).toHaveText(name);
|
||||
|
||||
test.describe(
|
||||
'Discover app - saved searches',
|
||||
{ tag: ['@ess', '@svlSecurity', '@svlOblt', '@svlSearch'] },
|
||||
() => {
|
||||
const START_TIME = '2019-04-27T23:56:51.374Z';
|
||||
const END_TIME = '2019-08-23T16:18:51.821Z';
|
||||
const PANEL_NAME = 'Ecommerce Data';
|
||||
const SEARCH_QUERY = 'customer_gender:MALE';
|
||||
const SAVED_SEARCH_NAME = 'test-unselect-saved-search';
|
||||
const filterFieldAndValue = {
|
||||
field: 'category',
|
||||
value: `Men's Shoes`,
|
||||
};
|
||||
|
||||
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
|
||||
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.ECOMMERCE);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.DISCOVER);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.ECOMMERCE);
|
||||
await uiSettings.set({
|
||||
defaultIndex: testData.DATA_VIEW_ID.ECOMMERCE,
|
||||
'doc_table:legacy': false,
|
||||
'timepicker:timeDefaults': `{ "from": "${START_TIME}", "to": "${END_TIME}"}`,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient, uiSettings }) => {
|
||||
await uiSettings.unset('doc_table:legacy', 'defaultIndex', 'timepicker:timeDefaults');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth }) => {
|
||||
await browserAuth.loginAsPrivilegedUser();
|
||||
});
|
||||
|
||||
test('should customize time range on dashboards', async ({ pageObjects, page }) => {
|
||||
await pageObjects.dashboard.goto();
|
||||
await pageObjects.dashboard.openNewDashboard();
|
||||
await pageObjects.dashboard.addPanelFromLibrary(PANEL_NAME);
|
||||
await page.testSubj.locator('savedSearchTotalDocuments').waitFor({
|
||||
state: 'visible',
|
||||
});
|
||||
|
||||
await pageObjects.dashboard.customizePanel({
|
||||
name: PANEL_NAME,
|
||||
customTimeRageCommonlyUsed: { value: 'Last_90 days' },
|
||||
});
|
||||
await expect(
|
||||
page.testSubj.locator('embeddedSavedSearchDocTable').locator('.euiDataGrid__noResults'),
|
||||
'No results message in Saved Search panel should be visible'
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test(`should unselect saved search when navigating to a 'new'`, async ({
|
||||
pageObjects,
|
||||
page,
|
||||
}) => {
|
||||
await pageObjects.discover.goto();
|
||||
await assertDataViewIsSelected(page, testData.DATA_VIEW.ECOMMERCE);
|
||||
await pageObjects.filterBar.addFilter({
|
||||
...filterFieldAndValue,
|
||||
operator: 'is',
|
||||
});
|
||||
await page.testSubj.fill('queryInput', SEARCH_QUERY);
|
||||
await page.testSubj.click('querySubmitButton');
|
||||
await pageObjects.discover.waitForHistogramRendered();
|
||||
|
||||
await pageObjects.discover.saveSearch(SAVED_SEARCH_NAME);
|
||||
await pageObjects.discover.waitForHistogramRendered();
|
||||
|
||||
expect(
|
||||
await pageObjects.filterBar.hasFilter({
|
||||
...filterFieldAndValue,
|
||||
enabled: true, // Filter is enabled by default
|
||||
})
|
||||
).toBe(true);
|
||||
await expect(page.testSubj.locator('queryInput')).toHaveText(SEARCH_QUERY);
|
||||
|
||||
// create new search
|
||||
await pageObjects.discover.clickNewSearch();
|
||||
await assertDataViewIsSelected(page, testData.DATA_VIEW.ECOMMERCE);
|
||||
await assertNoFilterAndEmptyQuery(filterFieldAndValue, pageObjects, page);
|
||||
|
||||
// change data view
|
||||
await pageObjects.discover.selectDataView(testData.DATA_VIEW.LOGSTASH);
|
||||
await assertNoFilterAndEmptyQuery(filterFieldAndValue, pageObjects, page);
|
||||
|
||||
// change data view again
|
||||
await pageObjects.discover.selectDataView(testData.DATA_VIEW.ECOMMERCE);
|
||||
await assertNoFilterAndEmptyQuery(filterFieldAndValue, pageObjects, page);
|
||||
|
||||
// create new search again
|
||||
await pageObjects.discover.clickNewSearch();
|
||||
await assertDataViewIsSelected(page, testData.DATA_VIEW.ECOMMERCE);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -6,51 +6,55 @@
|
|||
*/
|
||||
|
||||
import { expect } from '@kbn/scout';
|
||||
import { test } from '../fixtures';
|
||||
import { test, testData, assertionMessages } from '../fixtures';
|
||||
|
||||
test.describe('Discover app - value suggestions', () => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient }) => {
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
|
||||
await kbnClient.importExport.load(
|
||||
'x-pack/test/functional/fixtures/kbn_archiver/dashboard_drilldowns/drilldowns'
|
||||
);
|
||||
await kbnClient.uiSettings.update({
|
||||
defaultIndex: 'logstash-*', // TODO: investigate why it is required for `node scripts/playwright_test.js` run
|
||||
'doc_table:legacy': false,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient }) => {
|
||||
await kbnClient.uiSettings.unset('doc_table:legacy');
|
||||
await kbnClient.uiSettings.unset('defaultIndex');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsPrivilegedUser();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('dont show up if outside of range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange({
|
||||
from: 'Mar 1, 2020 @ 00:00:00.000',
|
||||
to: 'Nov 1, 2020 @ 00:00:00.000',
|
||||
test.describe(
|
||||
'Discover app - value suggestions: useTimeRange enabled',
|
||||
{ tag: ['@ess', '@svlSecurity', '@svlOblt', '@svlSearch'] },
|
||||
() => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
|
||||
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.DASHBOARD_DRILLDOWNS);
|
||||
await uiSettings.set({
|
||||
defaultIndex: testData.DATA_VIEW_ID.LOGSTASH, // TODO: investigate why it is required for `node scripts/playwright_test.js` run
|
||||
'doc_table:legacy': false,
|
||||
'timepicker:timeDefaults': `{ "from": "${testData.LOGSTASH_DEFAULT_START_TIME}", "to": "${testData.LOGSTASH_DEFAULT_END_TIME}"}`,
|
||||
});
|
||||
});
|
||||
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('show up if in range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange({
|
||||
from: 'Sep 19, 2015 @ 06:31:44.000',
|
||||
to: 'Sep 23, 2015 @ 18:31:44.000',
|
||||
test.afterAll(async ({ kbnClient, uiSettings }) => {
|
||||
await uiSettings.unset('doc_table:legacy', 'defaultIndex', 'timepicker:timeDefaults');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(5);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('jpg');
|
||||
});
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsViewer();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('dont show up if outside of range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_OUT_OF_RANGE_DATES);
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('show up if in range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_IN_RANGE_DATES);
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(
|
||||
page.testSubj.locator('autoCompleteSuggestionText'),
|
||||
assertionMessages.QUERY_BAR_VALIDATION.SUGGESTIONS_COUNT
|
||||
).toHaveCount(5);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('jpg');
|
||||
});
|
||||
|
||||
test('also displays descriptions for operators', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_IN_RANGE_DATES);
|
||||
await page.testSubj.fill('queryInput', 'extension.raw');
|
||||
await expect(page.testSubj.locator('^autocompleteSuggestion-operator')).toHaveCount(2);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,39 +6,43 @@
|
|||
*/
|
||||
|
||||
import { expect } from '@kbn/scout';
|
||||
import { test } from '../fixtures';
|
||||
import { test, testData, assertionMessages } from '../fixtures';
|
||||
|
||||
test.describe('Discover app - value suggestions non-time based', () => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient }) => {
|
||||
await esArchiver.loadIfNeeded(
|
||||
'test/functional/fixtures/es_archiver/index_pattern_without_timefield'
|
||||
);
|
||||
await kbnClient.importExport.load(
|
||||
'test/functional/fixtures/kbn_archiver/index_pattern_without_timefield'
|
||||
);
|
||||
await kbnClient.uiSettings.update({
|
||||
defaultIndex: 'without-timefield',
|
||||
'doc_table:legacy': false,
|
||||
test.describe(
|
||||
'Discover app - value suggestions non-time based',
|
||||
{ tag: ['@ess', '@svlSecurity', '@svlOblt', '@svlSearch'] },
|
||||
() => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
|
||||
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.NO_TIME_FIELD);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.NO_TIME_FIELD);
|
||||
await uiSettings.set({
|
||||
defaultIndex: 'without-timefield',
|
||||
'doc_table:legacy': false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ kbnClient }) => {
|
||||
await kbnClient.uiSettings.unset('doc_table:legacy');
|
||||
await kbnClient.uiSettings.unset('defaultIndex');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
test.afterAll(async ({ kbnClient, uiSettings }) => {
|
||||
await uiSettings.unset('doc_table:legacy', 'defaultIndex');
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsPrivilegedUser();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsViewer();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('shows all auto-suggest options for a filter in discover context app', async ({ page }) => {
|
||||
await page.testSubj.fill('queryInput', 'type.keyword : ');
|
||||
await expect(page.testSubj.locator('autoCompleteSuggestionText')).toHaveCount(1);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('"apache"');
|
||||
});
|
||||
});
|
||||
test('shows all auto-suggest options for a filter in discover context app', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.testSubj.fill('queryInput', 'type.keyword : ');
|
||||
await expect(
|
||||
page.testSubj.locator('autoCompleteSuggestionText'),
|
||||
assertionMessages.QUERY_BAR_VALIDATION.SUGGESTIONS_COUNT
|
||||
).toHaveCount(1);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('"apache"');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { expect } from '@kbn/scout';
|
||||
import { test, testData, assertionMessages } from '../fixtures';
|
||||
|
||||
test.describe(
|
||||
'Discover app - value suggestions: useTimeRange disabled',
|
||||
{ tag: ['@ess', '@svlSecurity', '@svlOblt', '@svlSearch'] },
|
||||
() => {
|
||||
test.beforeAll(async ({ esArchiver, kbnClient, uiSettings }) => {
|
||||
await esArchiver.loadIfNeeded(testData.ES_ARCHIVES.LOGSTASH);
|
||||
await kbnClient.importExport.load(testData.KBN_ARCHIVES.DASHBOARD_DRILLDOWNS);
|
||||
await uiSettings.set({
|
||||
defaultIndex: testData.DATA_VIEW_ID.LOGSTASH, // TODO: investigate why it is required for `node scripts/playwright_test.js` run
|
||||
'doc_table:legacy': false,
|
||||
'timepicker:timeDefaults': `{ "from": "${testData.LOGSTASH_DEFAULT_START_TIME}", "to": "${testData.LOGSTASH_DEFAULT_END_TIME}"}`,
|
||||
'autocomplete:useTimeRange': false,
|
||||
});
|
||||
});
|
||||
|
||||
test.afterAll(async ({ uiSettings, kbnClient }) => {
|
||||
await uiSettings.unset('doc_table:legacy', 'defaultIndex', 'timepicker:timeDefaults');
|
||||
await uiSettings.set({ 'autocomplete:useTimeRange': true });
|
||||
await kbnClient.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browserAuth, pageObjects }) => {
|
||||
await browserAuth.loginAsViewer();
|
||||
await pageObjects.discover.goto();
|
||||
});
|
||||
|
||||
test('show up if outside of range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_OUT_OF_RANGE_DATES);
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(
|
||||
page.testSubj.locator('autoCompleteSuggestionText'),
|
||||
assertionMessages.QUERY_BAR_VALIDATION.SUGGESTIONS_COUNT
|
||||
).toHaveCount(5);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('jpg');
|
||||
});
|
||||
|
||||
test('show up if in range', async ({ page, pageObjects }) => {
|
||||
await pageObjects.datePicker.setAbsoluteRange(testData.LOGSTASH_IN_RANGE_DATES);
|
||||
await page.testSubj.fill('queryInput', 'extension.raw : ');
|
||||
await expect(
|
||||
page.testSubj.locator('autoCompleteSuggestionText'),
|
||||
assertionMessages.QUERY_BAR_VALIDATION.SUGGESTIONS_COUNT
|
||||
).toHaveCount(5);
|
||||
const actualSuggestions = await page.testSubj
|
||||
.locator('autoCompleteSuggestionText')
|
||||
.allTextContents();
|
||||
expect(actualSuggestions.join(',')).toContain('jpg');
|
||||
});
|
||||
}
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue