Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [scout] migrate more Discover tests (#201842) #204748

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, ScoutTestFixtures, ScoutWorkerFixtures } from '..
* 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 @@ -81,6 +106,9 @@ export const scoutPageFixture = base.extend<ScoutTestFixtures, ScoutWorkerFixtur
// Method to navigate to specific Kibana apps
extendedPage.gotoApp = (appName: string) => page.goto(kbnUrl.app(appName));

extendedPage.waitForLoadingIndicatorHidden = () =>
extendedPage.testSubj.waitForSelector('globalLoadingIndicator-hidden', { state: 'attached' });

await use(extendedPage);
},
});
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);
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
Loading