Skip to content

Commit 1b30686

Browse files
dmlemeshkokibanamachinejennypavlova
authored
[scout] use project deps as global hooks for parallel tests (elastic#211409)
## Summary Currently we are using `globalSetup` [script in configuration file](https://playwright.dev/docs/test-global-setup-teardown#option-2-configure-globalsetup-and-globalteardown) to ingest Elasticsearch data before running the tests in parallel against the same ES/Kibana instances. This approach doesn't work well when you need to adjust `globalSetup` logic based on specific condition, e.g. configuration file defining where servers are hosted, its credentials, etc. Not only global hook, but `ScoutConfig` fixture expects an argument to define where servers configuration is defined: https://github.com/elastic/kibana/blob/cd502acea12979979497f62897be663044ade3aa/packages/kbn-scout/src/playwright/fixtures/worker/core_fixtures.ts#L65-L75 `testInfo` is how Playwright exposes currently running configuration in a form of `project` interface: [projects](https://playwright.dev/docs/test-projects) can be used to group tests, e.g. for specific envs or browsers. Unfortunately `testInfo` is not exposed in global scripts, because in Playwright project design `globalSetup` scripts are run before multiple projects and projects can have its own `setup` hooks via [dependencies](https://playwright.dev/docs/test-global-setup-teardown#option-1-project-dependencies): ``` { name: 'setup', testMatch: /global.setup\.ts/, }, { name: 'local', use: { ...devices['Desktop Chrome'], configName: 'local' }, dependencies: 'setup', }, ``` We already use project API to get `serversConfigDir` path, where we plan to store local and cloud server configurations. This PR proposes to define projects as `local` and `cloud` (maybe even separate `cloud-mki`, `cloud-ech`) as a way to provide playwright information about servers configuration. Advantages: 1. we can re-use existing fixtures as-is, without adding custom exported helper functions for ES data ingestion 2. project dependency is displayed as `setup` in Playwright report 3. way better and simpler design for consumers: ``` import { globalSetupHook } from '@kbn/scout'; globalSetupHook('Ingest data to Elasticsearch', async ({ esArchiver, log }) => { // add archives to load, if needed const archives = [ testData.ES_ARCHIVES.LOGSTASH, ]; log.debug('[setup] loading test data (only if indexes do not exist)...'); for (const archive of archives) { await esArchiver.loadIfNeeded(archive); } }); ``` 4. it is supported by VSCode Playwright plugin <img width="1271" alt="Screenshot 2025-02-17 at 11 26 12" src="https://github.com/user-attachments/assets/ba7eeb38-d39d-4785-9c11-18647599ec4a" /> I find it extremely useful because you don't need to change env var when you want to switch b/w local or cloud run, all the configurations are loaded automatically and you just tick the checkbox! Disadvantages: 1. it is important to run `playwright test` with `--project` flag to use the proper configuration 2. we have to define how `projects` are used for local and cloud configuration, and make sure it meets requirements of multiple teams. We can expose a way to pass custom project definitions in `createPlaywrightConfig` function, but it might complicate the support effort when every Team has too many custom projects. 3. `project` term is something we can't change and might be confusing 4. Since it is a Playwright feature, we might not have consistency with API tests runner under Scout For reviewers: Playing with it locally might give a better understanding about the pros/cons, especially with IDE playwright plugin installed. Running servers with tests: ``` node scripts/scout.js run-tests --serverless=oblt --testTarget=local --config x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts node scripts/scout.js run-tests --serverless=oblt --config x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts ``` Running test only requires passing `project` argument: ``` npx playwright test --project=local --config x-pack/platform/plugins/private/discover_enhanced/ui_tests/playwright.config.ts npx playwright test --project=local --config x-pack/platform/plugins/private/discover_enhanced/ui_tests/parallel.playwright.config.ts ``` --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: jennypavlova <[email protected]>
1 parent 9fb25a1 commit 1b30686

File tree

35 files changed

+298
-179
lines changed

35 files changed

+298
-179
lines changed

.eslintrc.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1471,7 +1471,8 @@ module.exports = {
14711471
'playwright/no-slowed-test': 'error',
14721472
'playwright/no-standalone-expect': 'error',
14731473
'playwright/no-unsafe-references': 'error',
1474-
'playwright/no-wait-for-selector': 'warn',
1474+
'playwright/no-useless-await': 'error',
1475+
'playwright/no-wait-for-selector': 'error',
14751476
'playwright/max-nested-describe': ['error', { max: 1 }],
14761477
'playwright/missing-playwright-await': 'error',
14771478
'playwright/prefer-comparison-matcher': 'error',

src/platform/packages/private/kbn-scout-reporting/src/helpers/cli_processing.test.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ import { getRunTarget, stripRunCommand } from './cli_processing';
1212
describe('cli_processing', () => {
1313
describe('stripRunCommand', () => {
1414
it(`should return the correct run command when started with 'npx'`, () => {
15-
const argv = ['npx', 'playwright', 'test', '--config', 'path/to/config', '--grep=@svlSearch'];
15+
const argv = [
16+
'npx',
17+
'playwright',
18+
'test',
19+
'--config',
20+
'path/to/config',
21+
'--project',
22+
'local',
23+
'--grep=@svlSearch',
24+
];
1625

1726
expect(stripRunCommand(argv)).toBe(
18-
'npx playwright test --config path/to/config --grep=@svlSearch'
27+
'npx playwright test --config path/to/config --project local --grep=@svlSearch'
1928
);
2029
});
2130

@@ -26,11 +35,13 @@ describe('cli_processing', () => {
2635
'test',
2736
'--config',
2837
'path/to/config',
38+
'--project',
39+
'local',
2940
'--grep=@svlSearch',
3041
];
3142

3243
expect(stripRunCommand(argv)).toBe(
33-
'npx playwright test --config path/to/config --grep=@svlSearch'
44+
'npx playwright test --config path/to/config --project local --grep=@svlSearch'
3445
);
3546
});
3647

src/platform/packages/shared/kbn-scout/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,11 @@ If the servers are already running, you can execute tests independently using ei
186186
- Command Line: Use the following command to run tests:
187187
188188
```bash
189-
npx playwright test --config <plugin-path>/ui_tests/playwright.config.ts
189+
npx playwright test --config <plugin-path>/ui_tests/playwright.config.ts --project local
190190
```
191191
192+
We use `project` flag to define test target, where tests to be run: local servers or Elastic Cloud. Currently we only support local servers.
193+
192194
### Contributing
193195
194196
We welcome contributions to improve and extend `kbn-scout`. This guide will help you get started, add new features, and align with existing project standards.

src/platform/packages/shared/kbn-scout/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ export {
1212
expect,
1313
test,
1414
spaceTest,
15+
globalSetupHook,
1516
tags,
1617
createPlaywrightConfig,
1718
createLazyPageObject,
18-
ingestTestDataHook,
19-
ingestSynthtraceDataHook,
2019
} from './src/playwright';
2120
export type {
2221
ScoutPlaywrightOptions,

src/platform/packages/shared/kbn-scout/src/playwright/config/create_config.test.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ describe('createPlaywrightConfig', () => {
6464
expect(config.timeout).toBe(60000);
6565
expect(config.expect?.timeout).toBe(10000);
6666
expect(config.outputDir).toBe('./output/test-artifacts');
67-
expect(config.projects![0].name).toEqual('chromium');
67+
expect(config.projects).toHaveLength(1);
68+
expect(config.projects![0].name).toEqual('local');
6869
});
6970

7071
it('should return a Playwright configuration with Scout reporters', () => {
@@ -96,12 +97,17 @@ describe('createPlaywrightConfig', () => {
9697
]);
9798
});
9899

99-
it(`should override 'workers' count in Playwright configuration`, () => {
100+
it(`should override 'workers' count and add 'setup' project dependency`, () => {
100101
const testDir = './my_tests';
101102
const workers = 2;
102103

103104
const config = createPlaywrightConfig({ testDir, workers });
104105
expect(config.workers).toBe(workers);
106+
107+
expect(config.projects).toHaveLength(2);
108+
expect(config.projects![0].name).toEqual('setup');
109+
expect(config.projects![1].name).toEqual('local');
110+
expect(config.projects![1]).toHaveProperty('dependencies', ['setup']);
105111
});
106112

107113
it('should generate and cache runId in process.env.TEST_RUN_ID', () => {

src/platform/packages/shared/kbn-scout/src/playwright/config/create_config.ts

+28-20
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,34 @@ export function createPlaywrightConfig(options: ScoutPlaywrightOptions): Playwri
2727
process.env.TEST_RUN_ID = runId;
2828
}
2929

30+
const scoutProjects: PlaywrightTestConfig<ScoutTestOptions>['projects'] = [
31+
{
32+
name: 'local',
33+
use: { ...devices['Desktop Chrome'], configName: 'local' },
34+
},
35+
];
36+
37+
/**
38+
* For parallel tests, we need to add a setup project that runs before the tests project.
39+
*/
40+
if (options.workers && options.workers > 1) {
41+
const parentProject = scoutProjects.find((p) => p.use?.configName);
42+
43+
scoutProjects.unshift({
44+
name: 'setup',
45+
use: parentProject?.use ? { ...parentProject.use } : {},
46+
testMatch: /global.setup\.ts/,
47+
});
48+
49+
scoutProjects.forEach((project) => {
50+
if (project.name !== 'setup') {
51+
project.dependencies = ['setup'];
52+
}
53+
});
54+
}
55+
3056
return defineConfig<ScoutTestOptions>({
3157
testDir: options.testDir,
32-
globalSetup: options.globalSetup,
3358
/* Run tests in files in parallel */
3459
fullyParallel: false,
3560
/* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -47,6 +72,7 @@ export function createPlaywrightConfig(options: ScoutPlaywrightOptions): Playwri
4772
],
4873
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
4974
use: {
75+
// 'configName' is not defined by default to enforce using '--project' flag when running the tests
5076
testIdAttribute: 'data-test-subj',
5177
serversConfigDir: SCOUT_SERVERS_ROOT,
5278
[VALID_CONFIG_MARKER]: true,
@@ -70,24 +96,6 @@ export function createPlaywrightConfig(options: ScoutPlaywrightOptions): Playwri
7096

7197
outputDir: './output/test-artifacts', // For other test artifacts (screenshots, videos, traces)
7298

73-
/* Configure projects for major browsers */
74-
projects: [
75-
{
76-
name: 'chromium',
77-
use: { ...devices['Desktop Chrome'] },
78-
},
79-
80-
// {
81-
// name: 'firefox',
82-
// use: { ...devices['Desktop Firefox'] },
83-
// },
84-
],
85-
86-
/* Run your local dev server before starting the tests */
87-
// webServer: {
88-
// command: 'npm run start',
89-
// url: 'http://127.0.0.1:3000',
90-
// reuseExistingServer: !process.env.CI,
91-
// },
99+
projects: scoutProjects,
92100
});
93101
}

src/platform/packages/shared/kbn-scout/src/playwright/fixtures/parallel_run_fixtures.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
*/
99

1010
import { mergeTests } from 'playwright/test';
11-
import { apiFixtures, coreWorkerFixtures, scoutSpaceParallelFixture } from './worker';
11+
import {
12+
apiFixtures,
13+
coreWorkerFixtures,
14+
esArchiverFixture,
15+
scoutSpaceParallelFixture,
16+
synthtraceFixture,
17+
} from './worker';
1218
import type {
1319
ApiParallelWorkerFixtures,
1420
EsClient,
@@ -52,3 +58,10 @@ export interface ScoutParallelWorkerFixtures extends ApiParallelWorkerFixtures {
5258
esClient: EsClient;
5359
scoutSpace: ScoutSpaceParallelFixture;
5460
}
61+
62+
export const globalSetup = mergeTests(
63+
coreWorkerFixtures,
64+
esArchiverFixture,
65+
synthtraceFixture,
66+
apiFixtures
67+
);

src/platform/packages/shared/kbn-scout/src/playwright/fixtures/worker/core_fixtures.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,15 @@ export const coreWorkerFixtures = base.extend<
6666
*/
6767
config: [
6868
({ log }, use, workerInfo) => {
69-
const configName = 'local';
7069
const projectUse = workerInfo.project.use as ScoutTestOptions;
70+
if (!projectUse.configName) {
71+
throw new Error(
72+
`Failed to read the 'configName' property. Make sure to run tests with '--project' flag and target enviroment (local or cloud),
73+
e.g. 'npx playwright test --project local --config <path_to_Playwright.config.ts>'`
74+
);
75+
}
7176
const serversConfigDir = projectUse.serversConfigDir;
72-
const configInstance = createScoutConfig(serversConfigDir, configName, log);
77+
const configInstance = createScoutConfig(serversConfigDir, projectUse.configName, log);
7378

7479
use(configInstance);
7580
},

src/platform/packages/shared/kbn-scout/src/playwright/fixtures/worker/synthtrace.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
*/
99

1010
import { Readable } from 'stream';
11-
import type { ApmFields, Fields, InfraDocument, OtelDocument } from '@kbn/apm-synthtrace-client';
11+
import type {
12+
ApmFields,
13+
Fields,
14+
InfraDocument,
15+
OtelDocument,
16+
SynthtraceGenerator,
17+
} from '@kbn/apm-synthtrace-client';
1218
import Url from 'url';
1319
import type { SynthtraceEsClient } from '@kbn/apm-synthtrace/src/lib/shared/base_client';
1420
import {
@@ -17,10 +23,9 @@ import {
1723
getOtelSynthtraceEsClient,
1824
} from '../../../common/services/synthtrace';
1925
import { coreWorkerFixtures } from './core_fixtures';
20-
import type { SynthtraceEvents } from '../../global_hooks/synthtrace_ingestion';
2126

2227
interface SynthtraceFixtureEsClient<TFields extends Fields> {
23-
index: (events: SynthtraceEvents<TFields>) => Promise<void>;
28+
index: (events: SynthtraceGenerator<TFields>) => Promise<void>;
2429
clean: SynthtraceEsClient<TFields>['clean'];
2530
}
2631

@@ -34,15 +39,12 @@ const useSynthtraceClient = async <TFields extends Fields>(
3439
client: SynthtraceEsClient<TFields>,
3540
use: (client: SynthtraceFixtureEsClient<TFields>) => Promise<void>
3641
) => {
37-
const index = async (events: SynthtraceEvents<TFields>) =>
42+
const index = async (events: SynthtraceGenerator<TFields>) =>
3843
await client.index(Readable.from(Array.from(events).flatMap((event) => event.serialize())));
3944

4045
const clean = async () => await client.clean();
4146

4247
await use({ index, clean });
43-
44-
// cleanup function after all tests have ran
45-
await client.clean();
4648
};
4749

4850
export const synthtraceFixture = coreWorkerFixtures.extend<{}, SynthtraceFixture>({

src/platform/packages/shared/kbn-scout/src/playwright/global_hooks/synthtrace_ingestion.ts

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ const getSynthtraceClient = (
6565
}
6666
};
6767

68+
/**
69+
* @deprecated Use `globalSetupHook` and synthtrace fixtures instead
70+
*/
6871
export async function ingestSynthtraceDataHook(config: FullConfig, data: SynthtraceIngestionData) {
6972
const log = getLogger();
7073

src/platform/packages/shared/kbn-scout/src/playwright/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import { scoutFixtures, scoutParallelFixtures } from './fixtures';
10+
import { scoutFixtures, scoutParallelFixtures, globalSetup } from './fixtures';
1111

1212
// Scout core fixtures: worker & test scope
1313
export const test = scoutFixtures;
1414

1515
// Scout core 'space aware' fixtures: worker & test scope
1616
export const spaceTest = scoutParallelFixtures;
1717

18+
export const globalSetupHook = globalSetup;
19+
1820
export { createPlaywrightConfig } from './config';
1921
export { createLazyPageObject } from './page_objects/utils';
2022
export { expect } from './expect';

src/platform/packages/shared/kbn-scout/src/playwright/runner/flags.test.ts

+33
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ describe('parseTestFlags', () => {
7272
expect(result).toEqual({
7373
mode: 'serverless=oblt',
7474
configPath: '/path/to/config',
75+
testTarget: 'local',
7576
headed: false,
7677
esFrom: undefined,
7778
installDir: undefined,
@@ -82,6 +83,7 @@ describe('parseTestFlags', () => {
8283
it(`should parse with correct config and stateful flags`, async () => {
8384
const flags = new FlagsReader({
8485
config: '/path/to/config',
86+
testTarget: 'local',
8587
stateful: true,
8688
logToFile: false,
8789
headed: true,
@@ -93,10 +95,41 @@ describe('parseTestFlags', () => {
9395
expect(result).toEqual({
9496
mode: 'stateful',
9597
configPath: '/path/to/config',
98+
testTarget: 'local',
9699
headed: true,
97100
esFrom: 'snapshot',
98101
installDir: undefined,
99102
logsDir: undefined,
100103
});
101104
});
105+
106+
it(`should throw an error with incorrect '--testTarget' flag`, async () => {
107+
const flags = new FlagsReader({
108+
config: '/path/to/config',
109+
testTarget: 'a',
110+
stateful: true,
111+
logToFile: false,
112+
headed: true,
113+
esFrom: 'snapshot',
114+
});
115+
116+
await expect(parseTestFlags(flags)).rejects.toThrow(
117+
'invalid --testTarget, expected one of "local", "cloud"'
118+
);
119+
});
120+
121+
it(`should throw an error with incorrect '--testTarget' flag set to 'cloud'`, async () => {
122+
const flags = new FlagsReader({
123+
config: '/path/to/config',
124+
testTarget: 'cloud',
125+
stateful: true,
126+
logToFile: false,
127+
headed: true,
128+
esFrom: 'snapshot',
129+
});
130+
validatePlaywrightConfigMock.mockResolvedValueOnce();
131+
await expect(parseTestFlags(flags)).rejects.toThrow(
132+
'Running tests against Cloud / MKI is not supported yet'
133+
);
134+
});
102135
});

src/platform/packages/shared/kbn-scout/src/playwright/runner/flags.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface RunTestsOptions {
1919
configPath: string;
2020
headed: boolean;
2121
mode: CliSupportedServerModes;
22+
testTarget: 'local' | 'cloud';
2223
esFrom: 'serverless' | 'source' | 'snapshot' | undefined;
2324
installDir: string | undefined;
2425
logsDir: string | undefined;
@@ -27,18 +28,24 @@ export interface RunTestsOptions {
2728
export const TEST_FLAG_OPTIONS: FlagOptions = {
2829
...SERVER_FLAG_OPTIONS,
2930
boolean: [...(SERVER_FLAG_OPTIONS.boolean || []), 'headed'],
30-
string: [...(SERVER_FLAG_OPTIONS.string || []), 'config'],
31-
default: { headed: false },
31+
string: [...(SERVER_FLAG_OPTIONS.string || []), 'config', 'testTarget'],
32+
default: { headed: false, testTarget: 'local' },
3233
help: `${SERVER_FLAG_OPTIONS.help}
3334
--config Playwright config file path
3435
--headed Run Playwright with browser head
36+
--testTarget Run tests agaist locally started servers or Cloud deployment / MKI project
3537
`,
3638
};
3739

3840
export async function parseTestFlags(flags: FlagsReader) {
3941
const options = parseServerFlags(flags);
4042
const configPath = flags.string('config');
4143
const headed = flags.boolean('headed');
44+
const testTarget = flags.enum('testTarget', ['local', 'cloud']) || 'local';
45+
46+
if (testTarget === 'cloud') {
47+
throw createFlagError(`Running tests against Cloud / MKI is not supported yet`);
48+
}
4249

4350
if (!configPath) {
4451
throw createFlagError(`Path to playwright config is required: --config <file path>`);
@@ -51,5 +58,6 @@ export async function parseTestFlags(flags: FlagsReader) {
5158
...options,
5259
configPath,
5360
headed,
61+
testTarget,
5462
};
5563
}

0 commit comments

Comments
 (0)