From 6635fe501c307d567afa71a346fc91a7675e3cc5 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 5 Feb 2025 16:35:26 +0100 Subject: [PATCH] [scout] unique runId for reporting, disabled failed test reporter locally (#209507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR makes few changes to scout reporter: Recently I found out that Playwright load configuration file _multiple times_: - first time after you run `npx playwright test -c ...` - on every worker start log before: ``` [main][~/github/kibana]$ npx playwright test --config x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts [createPlaywrightConfig] called with runId: 18f35f735a10155c Running 1 test using 1 worker [createPlaywrightConfig] called with runId: 2633b4e4c20afa15 [chromium] › full_screen_mode.spec.ts:28:9 › Maps › Full screen mode @svlSecurity @svlOblt @svlSearch @ess ``` With our current logic unique `runId` will be generated on each configuration load, meaning for parallel run we will report failures in different directories instead of the same one. Playwright doesn't expose any unique identifier for the run, so we have do something similar described in https://github.com/microsoft/playwright/issues/28941#issuecomment-1888008102 log after fix: ``` [main][~/github/kibana]$ npx playwright test --config x-pack/platform/plugins/shared/maps/ui_tests/playwright.config.ts [createPlaywrightConfig] called with runId: 310a576f32d3b8a5 Running 1 test using 1 worker [createPlaywrightConfig] called with runId: 310a576f32d3b8a5 [chromium] › full_screen_mode.spec.ts:28:9 › Maps › Full screen mode @svlSecurity @svlOblt @svlSearch @ess ``` We also had a chat with @dolaru and agreed that Scout reporters to be disabled for local test run. Few reasons: - Scout custom reporting targets CI execution: events-based reporter was already disabled - Failed test reporter purpose is to provide html boilerplate to be annotated in pipeline build - When you run tests with IDE playwright plugin it provides its own reporter / history, should be enough. --- .../src/reporting/index.ts | 4 +- .../playwright/config/create_config.test.ts | 70 +++++++++++++++---- .../src/playwright/config/create_config.ts | 10 ++- 3 files changed, 69 insertions(+), 15 deletions(-) diff --git a/packages/kbn-scout-reporting/src/reporting/index.ts b/packages/kbn-scout-reporting/src/reporting/index.ts index 824d852a141fa..3daf574cc7d2f 100644 --- a/packages/kbn-scout-reporting/src/reporting/index.ts +++ b/packages/kbn-scout-reporting/src/reporting/index.ts @@ -26,5 +26,7 @@ export const scoutPlaywrightReporter = ( export const scoutFailedTestsReporter = ( options?: ScoutPlaywrightReporterOptions ): ReporterDescription => { - return ['@kbn/scout-reporting/src/reporting/playwright/failed_test', options]; + return SCOUT_REPORTER_ENABLED + ? ['@kbn/scout-reporting/src/reporting/playwright/failed_test', options] + : ['null']; }; diff --git a/packages/kbn-scout/src/playwright/config/create_config.test.ts b/packages/kbn-scout/src/playwright/config/create_config.test.ts index 1b2c53fd99bc7..08eab1ab15cb1 100644 --- a/packages/kbn-scout/src/playwright/config/create_config.test.ts +++ b/packages/kbn-scout/src/playwright/config/create_config.test.ts @@ -7,7 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { SCOUT_REPORTER_ENABLED, SCOUT_SERVERS_ROOT } from '@kbn/scout-info'; +import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info'; +import { scoutPlaywrightReporter, scoutFailedTestsReporter } from '@kbn/scout-reporting'; import { createPlaywrightConfig } from './create_config'; import { VALID_CONFIG_MARKER } from '../types'; import { generateTestRunId } from '@kbn/scout-reporting'; @@ -15,18 +16,26 @@ import { generateTestRunId } from '@kbn/scout-reporting'; jest.mock('@kbn/scout-reporting', () => ({ ...jest.requireActual('@kbn/scout-reporting'), generateTestRunId: jest.fn(), + scoutPlaywrightReporter: jest.fn(), + scoutFailedTestsReporter: jest.fn(), })); describe('createPlaywrightConfig', () => { + const mockedRunId = 'mocked-run-id'; const mockGenerateTestRunId = generateTestRunId as jest.Mock; + const mockedScoutPlaywrightReporter = scoutPlaywrightReporter as jest.Mock; + const mockedScoutFailedTestsReporter = scoutFailedTestsReporter as jest.Mock; beforeEach(() => { jest.clearAllMocks(); + delete process.env.TEST_RUN_ID; }); it('should return a valid default Playwright configuration', () => { - const testRunId = 'test-run-id'; - mockGenerateTestRunId.mockImplementationOnce(() => testRunId); + mockGenerateTestRunId.mockImplementationOnce(() => mockedRunId); + // Scout reporters are disabled by default + mockedScoutPlaywrightReporter.mockReturnValueOnce(['null']); + mockedScoutFailedTestsReporter.mockReturnValueOnce(['null']); const testDir = './my_tests'; const config = createPlaywrightConfig({ testDir }); @@ -49,16 +58,8 @@ describe('createPlaywrightConfig', () => { expect(config.reporter).toEqual([ ['html', { open: 'never', outputFolder: './output/reports' }], ['json', { outputFile: './output/reports/test-results.json' }], - SCOUT_REPORTER_ENABLED - ? [ - '@kbn/scout-reporting/src/reporting/playwright/events', - { name: 'scout-playwright', runId: testRunId }, - ] - : ['null'], - [ - '@kbn/scout-reporting/src/reporting/playwright/failed_test', - { name: 'scout-playwright-failed-tests', runId: testRunId }, - ], + ['null'], + ['null'], ]); expect(config.timeout).toBe(60000); expect(config.expect?.timeout).toBe(10000); @@ -66,6 +67,35 @@ describe('createPlaywrightConfig', () => { expect(config.projects![0].name).toEqual('chromium'); }); + it('should return a Playwright configuration with Scout reporters', () => { + mockGenerateTestRunId.mockImplementationOnce(() => mockedRunId); + mockedScoutPlaywrightReporter.mockReturnValueOnce([ + '@kbn/scout-reporting/src/reporting/playwright/events', + { name: 'scout-playwright', runId: mockedRunId }, + ]); + mockedScoutFailedTestsReporter.mockReturnValueOnce([ + '@kbn/scout-reporting/src/reporting/playwright/failed_test', + { name: 'scout-playwright-failed-tests', runId: mockedRunId }, + ]); + + const testDir = './my_tests'; + const config = createPlaywrightConfig({ testDir }); + + expect(mockGenerateTestRunId).toHaveBeenCalledTimes(1); + expect(config.reporter).toEqual([ + ['html', { open: 'never', outputFolder: './output/reports' }], + ['json', { outputFile: './output/reports/test-results.json' }], + [ + '@kbn/scout-reporting/src/reporting/playwright/events', + { name: 'scout-playwright', runId: mockedRunId }, + ], + [ + '@kbn/scout-reporting/src/reporting/playwright/failed_test', + { name: 'scout-playwright-failed-tests', runId: mockedRunId }, + ], + ]); + }); + it(`should override 'workers' count in Playwright configuration`, () => { const testDir = './my_tests'; const workers = 2; @@ -73,4 +103,18 @@ describe('createPlaywrightConfig', () => { const config = createPlaywrightConfig({ testDir, workers }); expect(config.workers).toBe(workers); }); + + it('should generate and cache runId in process.env.TEST_RUN_ID', () => { + mockGenerateTestRunId.mockReturnValue(mockedRunId); + + // First call to create config + createPlaywrightConfig({ testDir: 'tests' }); + expect(process.env.TEST_RUN_ID).toBe(mockedRunId); + + // Second call (should use the cached value) + createPlaywrightConfig({ testDir: 'tests' }); + + expect(generateTestRunId).toHaveBeenCalledTimes(1); + expect(process.env.TEST_RUN_ID).toBe(mockedRunId); + }); }); diff --git a/packages/kbn-scout/src/playwright/config/create_config.ts b/packages/kbn-scout/src/playwright/config/create_config.ts index 4518f58a72288..452f09a64cd66 100644 --- a/packages/kbn-scout/src/playwright/config/create_config.ts +++ b/packages/kbn-scout/src/playwright/config/create_config.ts @@ -17,7 +17,15 @@ import { SCOUT_SERVERS_ROOT } from '@kbn/scout-info'; import { ScoutPlaywrightOptions, ScoutTestOptions, VALID_CONFIG_MARKER } from '../types'; export function createPlaywrightConfig(options: ScoutPlaywrightOptions): PlaywrightTestConfig { - const runId = generateTestRunId(); + /** + * Playwright loads the config file multiple times, so we need to generate a unique run id + * and store it in the environment to be used across all config function calls. + */ + let runId = process.env.TEST_RUN_ID; + if (!runId) { + runId = generateTestRunId(); + process.env.TEST_RUN_ID = runId; + } return defineConfig({ testDir: options.testDir,