feat: add new config prop populateGitInfo (#34329)

Co-authored-by: Yury Semikhatsky <yurys@chromium.org>
This commit is contained in:
Vitaliy Potapov 2025-01-28 11:54:31 +04:00 committed by GitHub
parent cea5dad686
commit 61d595ae48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 121 additions and 5 deletions

View File

@ -321,6 +321,22 @@ This path will serve as the base directory for each test file snapshot directory
## property: TestConfig.snapshotPathTemplate = %%-test-config-snapshot-path-template-%%
* since: v1.28
## property: TestConfig.populateGitInfo
* since: v1.51
- type: ?<[boolean]>
Whether to populate [`property: TestConfig.metadata`] with Git info. The metadata will automatically appear in the HTML report and is available in Reporter API.
**Usage**
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
export default defineConfig({
populateGitInfo: !!process.env.CI,
});
```
## property: TestConfig.preserveOutput
* since: v1.10
- type: ?<[PreserveOutput]<"always"|"never"|"failures-only">>

View File

@ -46,6 +46,7 @@ export class FullConfigInternal {
readonly plugins: TestRunnerPluginRegistration[];
readonly projects: FullProjectInternal[] = [];
readonly singleTSConfigPath?: string;
readonly populateGitInfo: boolean;
cliArgs: string[] = [];
cliGrep: string | undefined;
cliGrepInvert: string | undefined;
@ -75,10 +76,15 @@ export class FullConfigInternal {
const privateConfiguration = (userConfig as any)['@playwright/test'];
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig);
this.populateGitInfo = takeFirst(userConfig.populateGitInfo, false);
this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined);
this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined);
// Make sure we reuse same metadata instance between FullConfigInternal instances,
// so that plugins such as gitCommitInfoPlugin can populate metadata once.
userConfig.metadata = userConfig.metadata || {};
this.config = {
configFile: resolvedConfigFile,
rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
@ -90,7 +96,7 @@ export class FullConfigInternal {
grep: takeFirst(userConfig.grep, defaultGrep),
grepInvert: takeFirst(userConfig.grepInvert, null),
maxFailures: takeFirst(configCLIOverrides.debug ? 1 : undefined, configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
metadata: takeFirst(userConfig.metadata, {}),
metadata: userConfig.metadata,
preserveOutput: takeFirst(userConfig.preserveOutput, 'always'),
reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(userConfig.reporter, configDir), [[defaultReporter]]),
reportSlowTests: takeFirst(userConfig.reportSlowTests, { max: 5, threshold: 15000 }),

View File

@ -17,9 +17,15 @@
import { createGuid, spawnAsync } from 'playwright-core/lib/utils';
import type { TestRunnerPlugin } from './';
import type { FullConfig } from '../../types/testReporter';
import type { FullConfigInternal } from '../common/config';
const GIT_OPERATIONS_TIMEOUT_MS = 1500;
export const addGitCommitInfoPlugin = (fullConfig: FullConfigInternal) => {
if (fullConfig.populateGitInfo)
fullConfig.plugins.push({ factory: gitCommitInfo });
};
export const gitCommitInfo = (options?: GitCommitInfoPluginOptions): TestRunnerPlugin => {
return {
name: 'playwright:git-commit-info',

View File

@ -17,6 +17,7 @@
import type { FullResult, TestError } from '../../types/testReporter';
import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
import { addGitCommitInfoPlugin } from '../plugins/gitCommitInfoPlugin';
import { collectFilesForProject, filterProjects } from './projectUtils';
import { createErrorCollectingReporter, createReporters } from './reporters';
import { TestRun, createApplyRebaselinesTask, createClearCacheTask, createGlobalSetupTasks, createLoadTask, createPluginSetupTasks, createReportBeginTask, createRunTestsTasks, createStartDevServerTask, runTasks } from './tasks';
@ -70,6 +71,8 @@ export class Runner {
const config = this._config;
const listOnly = config.cliListOnly;
addGitCommitInfoPlugin(config);
// Legacy webServer support.
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));

View File

@ -39,6 +39,7 @@ import { baseFullConfig } from '../isomorphic/teleReceiver';
import { InternalReporter } from '../reporters/internalReporter';
import type { ReporterV2 } from '../reporters/reporterV2';
import { internalScreen } from '../reporters/base';
import { addGitCommitInfoPlugin } from '../plugins/gitCommitInfoPlugin';
const originalStdoutWrite = process.stdout.write;
const originalStderrWrite = process.stderr.write;
@ -406,6 +407,7 @@ export class TestServerDispatcher implements TestServerInterface {
// Preserve plugin instances between setup and build.
if (!this._plugins) {
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
addGitCommitInfoPlugin(config);
this._plugins = config.plugins || [];
} else {
config.plugins.splice(0, config.plugins.length, ...this._plugins);

View File

@ -1293,6 +1293,24 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
*/
outputDir?: string;
/**
* Whether to populate [testConfig.metadata](https://playwright.dev/docs/api/class-testconfig#test-config-metadata)
* with Git info. The metadata will automatically appear in the HTML report and is available in Reporter API.
*
* **Usage**
*
* ```js
* // playwright.config.ts
* import { defineConfig } from '@playwright/test';
*
* export default defineConfig({
* populateGitInfo: !!process.env.CI,
* });
* ```
*
*/
populateGitInfo?: boolean;
/**
* Whether to preserve test output in the
* [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Defaults to

View File

@ -1142,14 +1142,12 @@ for (const useIntermediateMergeReport of [true, false] as const) {
});
test.describe('gitCommitInfo plugin', () => {
test('should include metadata', async ({ runInlineTest, writeFiles, showReport, page }) => {
test('should include metadata with populateGitInfo = true', async ({ runInlineTest, writeFiles, showReport, page }) => {
const files = {
'uncommitted.txt': `uncommitted file`,
'playwright.config.ts': `
import { gitCommitInfo } from 'playwright/lib/plugins';
import { test, expect } from '@playwright/test';
const plugins = [gitCommitInfo()];
export default { '@playwright/test': { plugins } };
export default { populateGitInfo: true };
`,
'example.spec.ts': `
import { test, expect } from '@playwright/test';
@ -1195,6 +1193,25 @@ for (const useIntermediateMergeReport of [true, false] as const) {
await expect.soft(page.getByTestId('metadata-error')).not.toBeVisible();
});
test('should not include metadata with populateGitInfo = false', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({
'uncommitted.txt': `uncommitted file`,
'playwright.config.ts': `
export default { populateGitInfo: false };
`,
'example.spec.ts': `
import { test, expect } from '@playwright/test';
test('my sample test', async ({}) => { expect(2).toBe(2); });
`,
}, { reporter: 'dot,html' }, { PLAYWRIGHT_HTML_OPEN: 'never' }, undefined);
await showReport();
expect(result.exitCode).toBe(0);
await expect.soft(page.locator('text="my sample test"')).toBeVisible();
await expect.soft(page.getByTestId('metadata-error')).not.toBeVisible();
await expect.soft(page.getByTestId('metadata-chip')).not.toBeVisible();
});
test('should use explicitly supplied metadata', async ({ runInlineTest, showReport, page }) => {
const result = await runInlineTest({

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { test, expect } from './ui-mode-fixtures';
test('should render html report git info metadata', async ({ runUITest }) => {
const { page } = await runUITest({
'reporter.ts': `
module.exports = class Reporter {
onBegin(config, suite) {
console.log('ci.link:', config.metadata['ci.link']);
}
}
`,
'playwright.config.ts': `
import { defineConfig } from '@playwright/test';
export default defineConfig({
populateGitInfo: true,
reporter: './reporter.ts',
});
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('should work', async ({}) => {});
`
}, {
BUILD_URL: 'https://playwright.dev',
});
await page.getByTitle('Run all').click();
await expect(page.getByTestId('status-line')).toHaveText('1/1 passed (100%)');
await page.getByTitle('Toggle output').click();
await expect(page.getByTestId('output')).toContainText('ci.link: https://playwright.dev');
});