mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -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';
|
Loading…
Add table
Add a link
Reference in a new issue