[8.x] [scout] add script to discover playwright configs in repo (#208733) (#208886)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[scout] add script to discover playwright configs in repo
(#208733)](https://github.com/elastic/kibana/pull/208733)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Dzmitry
Lemechko","email":"dzmitry.lemechko@elastic.co"},"sourceCommit":{"committedDate":"2025-01-30T08:42:15Z","message":"[scout]
add script to discover playwright configs in repo (#208733)\n\n##
Summary\r\n\r\nAdding script to discover Scout playwright tests in
Kibana repo, will be\r\nused to build CI pipeline step (running tests
per plugin in a separate\r\nworker for the start). We can also consider
using it to decide if code\r\nchange should trigger only specific plugin
tests to run.\r\n\r\nUsage:\r\n```\r\nnode scripts/scout.js
discover-playwright-configs --searchPaths
x-pack/platform/plugins/private/discover_enhanced\r\n```\r\n\r\nOutput:\r\n```\r\n
info Searching for playwright config files in the following paths:\r\n
info -
x-pack/platform/plugins/private/discover_enhanced/**/ui_tests/{playwright.config.ts,parallel.playwright.config.ts}\r\n
info\r\n info Discovered playwright config files in '1' plugins\r\n info
[discover_enhanced] plugin:\r\n info -
x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel.playwright.config.ts\r\n
info -
x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts\r\n```\r\n\r\nMore
usage examples:\r\n```\r\nnode scripts/scout.js
discover-playwright-configs // by default will search in
['src/platform/plugins', 'x-pack/**/plugins'] and return all existing
ones\r\nnode scripts/scout.js discover-playwright-configs --searchPaths
x-pack/platform // platform ones under x-pack\r\nnode scripts/scout.js
discover-playwright-configs --searchPaths x-pack/** // all under
x-pack\r\n```","sha":"cf7debdfa3fb4a253ac0ec1b43e588e5163f8a98","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","test:scout"],"title":"[scout]
add script to discover playwright configs in
repo","number":208733,"url":"https://github.com/elastic/kibana/pull/208733","mergeCommit":{"message":"[scout]
add script to discover playwright configs in repo (#208733)\n\n##
Summary\r\n\r\nAdding script to discover Scout playwright tests in
Kibana repo, will be\r\nused to build CI pipeline step (running tests
per plugin in a separate\r\nworker for the start). We can also consider
using it to decide if code\r\nchange should trigger only specific plugin
tests to run.\r\n\r\nUsage:\r\n```\r\nnode scripts/scout.js
discover-playwright-configs --searchPaths
x-pack/platform/plugins/private/discover_enhanced\r\n```\r\n\r\nOutput:\r\n```\r\n
info Searching for playwright config files in the following paths:\r\n
info -
x-pack/platform/plugins/private/discover_enhanced/**/ui_tests/{playwright.config.ts,parallel.playwright.config.ts}\r\n
info\r\n info Discovered playwright config files in '1' plugins\r\n info
[discover_enhanced] plugin:\r\n info -
x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel.playwright.config.ts\r\n
info -
x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts\r\n```\r\n\r\nMore
usage examples:\r\n```\r\nnode scripts/scout.js
discover-playwright-configs // by default will search in
['src/platform/plugins', 'x-pack/**/plugins'] and return all existing
ones\r\nnode scripts/scout.js discover-playwright-configs --searchPaths
x-pack/platform // platform ones under x-pack\r\nnode scripts/scout.js
discover-playwright-configs --searchPaths x-pack/** // all under
x-pack\r\n```","sha":"cf7debdfa3fb4a253ac0ec1b43e588e5163f8a98"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/208733","number":208733,"mergeCommit":{"message":"[scout]
add script to discover playwright configs in repo (#208733)\n\n##
Summary\r\n\r\nAdding script to discover Scout playwright tests in
Kibana repo, will be\r\nused to build CI pipeline step (running tests
per plugin in a separate\r\nworker for the start). We can also consider
using it to decide if code\r\nchange should trigger only specific plugin
tests to run.\r\n\r\nUsage:\r\n```\r\nnode scripts/scout.js
discover-playwright-configs --searchPaths
x-pack/platform/plugins/private/discover_enhanced\r\n```\r\n\r\nOutput:\r\n```\r\n
info Searching for playwright config files in the following paths:\r\n
info -
x-pack/platform/plugins/private/discover_enhanced/**/ui_tests/{playwright.config.ts,parallel.playwright.config.ts}\r\n
info\r\n info Discovered playwright config files in '1' plugins\r\n info
[discover_enhanced] plugin:\r\n info -
x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel.playwright.config.ts\r\n
info -
x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts\r\n```\r\n\r\nMore
usage examples:\r\n```\r\nnode scripts/scout.js
discover-playwright-configs // by default will search in
['src/platform/plugins', 'x-pack/**/plugins'] and return all existing
ones\r\nnode scripts/scout.js discover-playwright-configs --searchPaths
x-pack/platform // platform ones under x-pack\r\nnode scripts/scout.js
discover-playwright-configs --searchPaths x-pack/** // all under
x-pack\r\n```","sha":"cf7debdfa3fb4a253ac0ec1b43e588e5163f8a98"}}]}]
BACKPORT-->

Co-authored-by: Dzmitry Lemechko <dzmitry.lemechko@elastic.co>
This commit is contained in:
Kibana Machine 2025-01-30 21:34:03 +11:00 committed by GitHub
parent 1cf806f83a
commit 019de9a8a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 242 additions and 43 deletions

View file

@ -0,0 +1,51 @@
/*
* 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 { Command } from '@kbn/dev-cli-runner';
import { getScoutPlaywrightConfigs, DEFAULT_TEST_PATH_PATTERNS } from '../config';
import { measurePerformance } from '../common';
/**
* Discover Playwright configuration files with Scout tests
*/
export const discoverPlaywrightConfigs: Command<void> = {
name: 'discover-playwright-configs',
description: `
Discover Playwright configuration files with Scout tests.
Common usage:
node scripts/scout discover-playwright-configs --searchPaths <search_paths>
node scripts/scout discover-playwright-configs
`,
flags: {
string: ['searchPaths'],
default: { searchPaths: DEFAULT_TEST_PATH_PATTERNS },
},
run: ({ flagsReader, log }) => {
const searchPaths = flagsReader.arrayOfStrings('searchPaths')!;
const plugins = measurePerformance(log, 'Discovering playwright config files', () => {
return getScoutPlaywrightConfigs(searchPaths, log);
});
const finalMessage =
plugins.size === 0
? 'No playwright config files found'
: `Found playwright config files in '${plugins.size}' plugins`;
log.info(finalMessage);
plugins.forEach((files, plugin) => {
log.info(`[${plugin}] plugin:`);
files.forEach((file) => {
log.info(`- ${file}`);
});
});
},
};

View file

@ -10,12 +10,19 @@ import { RunWithCommands } from '@kbn/dev-cli-runner';
import { cli as reportingCLI } from '@kbn/scout-reporting';
import { startServer } from './start_server';
import { runTests } from './run_tests';
import { discoverPlaywrightConfigs } from './config_discovery';
export async function run() {
await new RunWithCommands(
{
description: 'Scout CLI',
},
[startServer, runTests, reportingCLI.initializeReportDatastream, reportingCLI.uploadEvents]
[
startServer,
runTests,
discoverPlaywrightConfigs,
reportingCLI.initializeReportDatastream,
reportingCLI.uploadEvents,
]
).execute();
}

View file

@ -18,3 +18,33 @@ export async function silence(log: ToolingLog, milliseconds: number) {
)
);
}
/**
* Measure the performance of a sync function
*/
export const measurePerformance = <T>(log: ToolingLog, label: string, fn: () => T): T => {
const startTime = performance.now();
const result = fn();
const duration = performance.now() - startTime;
log.debug(`${label} took ${duration.toFixed(2)}ms`);
return result;
};
/**
* Measure the performance of an async function
*/
export const measurePerformanceAsync = async <T>(
log: ToolingLog,
label: string,
fn: () => Promise<T>
): Promise<T> => {
const startTime = performance.now();
const result = await fn();
const duration = performance.now() - startTime;
log.debug(`${label} took ${duration.toFixed(2)}ms`);
return result;
};

View file

@ -7,16 +7,4 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { ScoutLogger } from '../fixtures/worker';
export const measurePerformance = async <T>(
log: ScoutLogger,
label: string,
fn: () => Promise<T>
): Promise<T> => {
const startTime = performance.now();
const result = await fn();
const duration = performance.now() - startTime;
log.debug(`${label} took ${duration.toFixed(2)}ms`);
return result;
};
export * from './search_configs';

View file

@ -0,0 +1,67 @@
/*
* 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 { ToolingLog } from '@kbn/tooling-log';
import fastGlob from 'fast-glob';
import { getScoutPlaywrightConfigs } from './search_configs';
jest.mock('fast-glob');
describe('getScoutPlaywrightConfigs', () => {
let mockLog: ToolingLog;
beforeEach(() => {
mockLog = new ToolingLog({ level: 'verbose', writeTo: process.stdout });
jest.spyOn(mockLog, 'info').mockImplementation(() => {});
jest.spyOn(mockLog, 'warning').mockImplementation(() => {});
});
afterEach(() => {
jest.clearAllMocks();
});
it('should return an empty map if no matching files are found', () => {
(fastGlob.sync as jest.Mock).mockReturnValueOnce([]);
const plugins = getScoutPlaywrightConfigs(['x-pack/plugins'], mockLog);
expect(plugins.size).toBe(0);
});
it('should correctly extract plugin names and group config files', () => {
(fastGlob.sync as jest.Mock).mockReturnValue([
'x-pack/platform/plugins/plugin_a/ui_tests/playwright.config.ts',
'x-pack/platform/plugins/plugin_a/ui_tests/parallel.playwright.config.ts',
'x-pack/solutions/security/plugins/plugin_b/ui_tests/playwright.config.ts',
]);
const plugins = getScoutPlaywrightConfigs(['x-pack/'], mockLog);
expect(plugins.size).toBe(2);
expect(plugins.get('plugin_a')).toEqual([
'x-pack/platform/plugins/plugin_a/ui_tests/playwright.config.ts',
'x-pack/platform/plugins/plugin_a/ui_tests/parallel.playwright.config.ts',
]);
expect(plugins.get('plugin_b')).toEqual([
'x-pack/solutions/security/plugins/plugin_b/ui_tests/playwright.config.ts',
]);
});
it('should log a warning if a file path does not match the expected pattern', () => {
(fastGlob.sync as jest.Mock).mockReturnValue([
'x-pack/security/plugins/unknown-path/playwright.config.ts',
]);
const plugins = getScoutPlaywrightConfigs(['x-pack/security'], mockLog);
expect(plugins.size).toBe(0);
expect(mockLog.warning).toHaveBeenCalledWith(
expect.stringContaining('Unable to extract plugin name from path')
);
});
});

View file

@ -0,0 +1,47 @@
/*
* 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 fastGlob from 'fast-glob';
import path from 'path';
import { ToolingLog } from '@kbn/tooling-log';
export const DEFAULT_TEST_PATH_PATTERNS = ['src/platform/plugins', 'x-pack/**/plugins'];
export const getScoutPlaywrightConfigs = (searchPaths: string[], log: ToolingLog) => {
const patterns = searchPaths.map((basePath) =>
path.join(basePath, '**/ui_tests/{playwright.config.ts,parallel.playwright.config.ts}')
);
log.info('Searching for playwright config files in the following paths:');
patterns.forEach((pattern) => log.info(`- ${pattern}`));
log.info(''); // Add a newline for better readability
const files = patterns.flatMap((pattern) => fastGlob.sync(pattern, { onlyFiles: true }));
// Group config files by plugin
const plugins = files.reduce((acc: Map<string, string[]>, filePath: string) => {
const match = filePath.match(
/(?:src\/platform\/plugins|x-pack\/.*?\/plugins)\/(?:.*?\/)?([^\/]+)\/ui_tests\//
);
const pluginName = match ? match[1] : null;
if (pluginName) {
if (!acc.has(pluginName)) {
acc.set(pluginName, []);
}
acc.get(pluginName)!.push(filePath);
} else {
log.warning(`Unable to extract plugin name from path: ${filePath}`);
}
return acc;
}, new Map<string, string[]>());
return plugins;
};

View file

@ -9,4 +9,5 @@
export { readConfigFile } from './loader';
export { getConfigFilePath, loadServersConfig } from './utils';
export { getScoutPlaywrightConfigs, DEFAULT_TEST_PATH_PATTERNS } from './discovery';
export type { Config } from './config';

View file

@ -8,9 +8,10 @@
*/
import { UiSettingValues } from '@kbn/test/src/kbn_client/kbn_client_ui_settings';
import { formatTime, isValidUTCDate, measurePerformance } from '../../../utils';
import { formatTime, isValidUTCDate } from '../../../utils';
import { coreWorkerFixtures } from '..';
import { ImportSavedObjects, ScoutSpaceParallelFixture } from '.';
import { measurePerformanceAsync } from '../../../../common';
export const scoutSpaceParallelFixture = coreWorkerFixtures.extend<
{},
@ -24,7 +25,7 @@ export const scoutSpaceParallelFixture = coreWorkerFixtures.extend<
name: spaceId,
disabledFeatures: [],
};
await measurePerformance(log, `scoutSpace:${spaceId} 'spaces.create'`, async () => {
await measurePerformanceAsync(log, `scoutSpace:${spaceId} 'spaces.create'`, async () => {
return kbnClient.spaces.create(spacePayload);
});
@ -32,26 +33,30 @@ export const scoutSpaceParallelFixture = coreWorkerFixtures.extend<
const savedObjectsCache = new Map<string, string>();
const load = async (path: string) => {
return measurePerformance(log, `scoutSpace:${spaceId} 'savedObjects.load'`, async () => {
const response = await kbnClient.importExport.load(path, {
space: spaceId,
// will create new copies of saved objects with unique ids
createNewCopies: true,
});
return measurePerformanceAsync(
log,
`scoutSpace:${spaceId} 'savedObjects.load'`,
async () => {
const response = await kbnClient.importExport.load(path, {
space: spaceId,
// will create new copies of saved objects with unique ids
createNewCopies: true,
});
const imported = (response.successResults as ImportSavedObjects[]).map(
(r: { type: string; destinationId: string; meta: { title: string } }) => {
return { id: r.destinationId, type: r.type, title: r.meta.title };
}
);
const imported = (response.successResults as ImportSavedObjects[]).map(
(r: { type: string; destinationId: string; meta: { title: string } }) => {
return { id: r.destinationId, type: r.type, title: r.meta.title };
}
);
imported.forEach((so) => savedObjectsCache.set(so.title, so.id));
imported.forEach((so) => savedObjectsCache.set(so.title, so.id));
return imported;
});
return imported;
}
);
};
const cleanStandardList = async () => {
return measurePerformance(
return measurePerformanceAsync(
log,
`scoutSpace:${spaceId} 'savedObjects.cleanStandardList'`,
async () => {
@ -63,7 +68,7 @@ export const scoutSpaceParallelFixture = coreWorkerFixtures.extend<
);
};
const setDefaultIndex = async (dataViewName: string) => {
return measurePerformance(
return measurePerformanceAsync(
log,
`scoutSpace:${spaceId} 'savedObjects.setDefaultIndex'`,
async () => {
@ -81,19 +86,23 @@ export const scoutSpaceParallelFixture = coreWorkerFixtures.extend<
);
};
const set = async (values: UiSettingValues) => {
return measurePerformance(log, `scoutSpace:${spaceId} 'uiSettings.set'`, async () => {
return measurePerformanceAsync(log, `scoutSpace:${spaceId} 'uiSettings.set'`, async () => {
return kbnClient.uiSettings.update(values, { space: spaceId });
});
};
const unset = async (...keys: string[]) => {
return measurePerformance(log, `scoutSpace:${spaceId} 'uiSettings.unset'`, async () => {
return Promise.all(
keys.map((key) => kbnClient.uiSettings.unset(key, { space: spaceId }))
);
});
return measurePerformanceAsync(
log,
`scoutSpace:${spaceId} 'uiSettings.unset'`,
async () => {
return Promise.all(
keys.map((key) => kbnClient.uiSettings.unset(key, { space: spaceId }))
);
}
);
};
const setDefaultTime = async ({ from, to }: { from: string; to: string }) => {
return measurePerformance(
return measurePerformanceAsync(
log,
`scoutSpace:${spaceId} 'uiSettings.setDefaultTime'`,
async () => {
@ -125,7 +134,7 @@ export const scoutSpaceParallelFixture = coreWorkerFixtures.extend<
await use({ savedObjects, uiSettings, id: spaceId });
// Cleanup space after tests via API call
await measurePerformance(log, `scoutSpace:${spaceId} 'space.delete'`, async () => {
await measurePerformanceAsync(log, `scoutSpace:${spaceId} 'space.delete'`, async () => {
return kbnClient.spaces.delete(spaceId);
});
},

View file

@ -12,11 +12,11 @@ import {
getLogger,
getEsArchiver,
createScoutConfig,
measurePerformanceAsync,
getEsClient,
getKbnClient,
} from '../../common';
import { ScoutTestOptions } from '../types';
import { measurePerformance } from '../utils';
export async function ingestTestDataHook(config: FullConfig, archives: string[]) {
const log = getLogger();
@ -26,7 +26,7 @@ export async function ingestTestDataHook(config: FullConfig, archives: string[])
return;
}
return measurePerformance(log, '[setup]: ingestTestDataHook', async () => {
return measurePerformanceAsync(log, '[setup]: ingestTestDataHook', async () => {
// TODO: This should be configurable local vs cloud
const configName = 'local';
const projectUse = config.projects[0].use as ScoutTestOptions;

View file

@ -8,4 +8,3 @@
*/
export { isValidUTCDate, formatTime, getPlaywrightGrepTag } from './runner_utils';
export { measurePerformance } from './performance';