Skip to content

Commit

Permalink
[scout] migrate more Discover tests (#201842)
Browse files Browse the repository at this point in the history
## 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 <[email protected]>
  • Loading branch information
dmlemeshko and pheyos authored Dec 2, 2024
1 parent a674b9d commit d4094c1
Show file tree
Hide file tree
Showing 28 changed files with 969 additions and 161 deletions.
3 changes: 3 additions & 0 deletions packages/kbn-scout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ export type {
PageObjects,
ScoutTestFixtures,
ScoutWorkerFixtures,
EsArchiverFixture,
} from './src/playwright';

export type { Client, KbnClient, KibanaUrl, SamlSessionManager, ToolingLog } from './src/types';
1 change: 1 addition & 0 deletions packages/kbn-scout/src/playwright/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { scoutTestFixtures } from './test';
export const scoutCoreFixtures = mergeTests(scoutWorkerFixtures, scoutTestFixtures);

export type {
EsArchiverFixture,
ScoutTestFixtures,
ScoutWorkerFixtures,
ScoutPage,
Expand Down
34 changes: 31 additions & 3 deletions packages/kbn-scout/src/playwright/fixtures/test/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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[]) => {
Expand All @@ -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'];
}

/**
Expand Down Expand Up @@ -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);
},
});
47 changes: 47 additions & 0 deletions packages/kbn-scout/src/playwright/fixtures/types/test_scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']>;
Expand Down Expand Up @@ -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>;
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
84 changes: 84 additions & 0 deletions packages/kbn-scout/src/playwright/fixtures/worker/core.ts
Original file line number Diff line number Diff line change
@@ -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' },
],
});
78 changes: 4 additions & 74 deletions packages/kbn-scout/src/playwright/fixtures/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
37 changes: 37 additions & 0 deletions packages/kbn-scout/src/playwright/fixtures/worker/ui_settings.ts
Original file line number Diff line number Diff line change
@@ -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' },
],
});
7 changes: 6 additions & 1 deletion packages/kbn-scout/src/playwright/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Loading

0 comments on commit d4094c1

Please sign in to comment.