chore: clean up git commit metadata props and UI (#34867)
This commit is contained in:
parent
b148cbad76
commit
411f938296
|
|
@ -239,7 +239,7 @@ export default defineConfig({
|
||||||
|
|
||||||
Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as key-value pairs, and JSON report will include metadata serialized as json.
|
Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as key-value pairs, and JSON report will include metadata serialized as json.
|
||||||
|
|
||||||
See also [`property: TestConfig.populateGitInfo`] that populates metadata.
|
Providing `'git.commit.info': {}` property will populate it with the git commit details. This is useful for CI/CD environments.
|
||||||
|
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
|
|
@ -326,26 +326,6 @@ This path will serve as the base directory for each test file snapshot directory
|
||||||
## property: TestConfig.snapshotPathTemplate = %%-test-config-snapshot-path-template-%%
|
## property: TestConfig.snapshotPathTemplate = %%-test-config-snapshot-path-template-%%
|
||||||
* since: v1.28
|
* since: v1.28
|
||||||
|
|
||||||
## property: TestConfig.populateGitInfo
|
|
||||||
* since: v1.51
|
|
||||||
- type: ?<[boolean]>
|
|
||||||
|
|
||||||
Whether to populate `'git.commit.info'` field of the [`property: TestConfig.metadata`] with Git commit info and CI/CD information.
|
|
||||||
|
|
||||||
This information will appear in the HTML and JSON reports and is available in the Reporter API.
|
|
||||||
|
|
||||||
On Github Actions, this feature is enabled by default.
|
|
||||||
|
|
||||||
**Usage**
|
|
||||||
|
|
||||||
```js title="playwright.config.ts"
|
|
||||||
import { defineConfig } from '@playwright/test';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
populateGitInfo: !!process.env.CI,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## property: TestConfig.preserveOutput
|
## property: TestConfig.preserveOutput
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
- type: ?<[PreserveOutput]<"always"|"never"|"failures-only">>
|
- type: ?<[PreserveOutput]<"always"|"never"|"failures-only">>
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,6 @@
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metadata-section {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metadata-properties {
|
.metadata-properties {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -57,9 +53,8 @@
|
||||||
border-bottom: 1px solid var(--color-border-default);
|
border-bottom: 1px solid var(--color-border-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
.git-commit-info a {
|
.metadata-view a {
|
||||||
color: var(--color-fg-default);
|
color: var(--color-fg-default);
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.copyable-property {
|
.copyable-property {
|
||||||
|
|
|
||||||
|
|
@ -87,12 +87,12 @@ const InnerMetadataView = () => {
|
||||||
<GitCommitInfoView info={gitCommitInfo}/>
|
<GitCommitInfoView info={gitCommitInfo}/>
|
||||||
{entries.length > 0 && <div className='metadata-separator' />}
|
{entries.length > 0 && <div className='metadata-separator' />}
|
||||||
</>}
|
</>}
|
||||||
<div className='metadata-section metadata-properties'>
|
<div className='metadata-section metadata-properties' role='list'>
|
||||||
{entries.map(([propertyName, value]) => {
|
{entries.map(([propertyName, value]) => {
|
||||||
const valueString = typeof value !== 'object' || value === null || value === undefined ? String(value) : JSON.stringify(value);
|
const valueString = typeof value !== 'object' || value === null || value === undefined ? String(value) : JSON.stringify(value);
|
||||||
const trimmedValue = valueString.length > 1000 ? valueString.slice(0, 1000) + '\u2026' : valueString;
|
const trimmedValue = valueString.length > 1000 ? valueString.slice(0, 1000) + '\u2026' : valueString;
|
||||||
return (
|
return (
|
||||||
<div key={propertyName} className='copyable-property'>
|
<div key={propertyName} className='copyable-property' role='listitem'>
|
||||||
<CopyToClipboardContainer value={valueString}>
|
<CopyToClipboardContainer value={valueString}>
|
||||||
<span style={{ fontWeight: 'bold' }} title={propertyName}>{propertyName}</span>
|
<span style={{ fontWeight: 'bold' }} title={propertyName}>{propertyName}</span>
|
||||||
: <span title={trimmedValue}>{linkifyText(trimmedValue)}</span>
|
: <span title={trimmedValue}>{linkifyText(trimmedValue)}</span>
|
||||||
|
|
@ -105,47 +105,38 @@ const InnerMetadataView = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||||
const email = info['revision.email'] ? ` <${info['revision.email']}>` : '';
|
const email = info.revision?.email ? ` <${info.revision?.email}>` : '';
|
||||||
const author = `${info['revision.author'] || ''}${email}`;
|
const author = `${info.revision?.author || ''}${email}`;
|
||||||
|
|
||||||
let subject = info['revision.subject'] || '';
|
let subject = info.revision?.subject || '';
|
||||||
let link = info['revision.link'];
|
let link = info.revision?.link;
|
||||||
let shortSubject = info['revision.id']?.slice(0, 7) || 'unknown';
|
|
||||||
|
|
||||||
if (info['pull.link'] && info['pull.title']) {
|
if (info.pull_request?.link && info.pull_request?.title) {
|
||||||
subject = info['pull.title'];
|
subject = info.pull_request?.title;
|
||||||
link = info['pull.link'];
|
link = info.pull_request?.link;
|
||||||
shortSubject = link ? 'Pull Request' : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info['revision.timestamp']);
|
const shortTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(info.revision?.timestamp);
|
||||||
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info['revision.timestamp']);
|
const longTimestamp = Intl.DateTimeFormat(undefined, { dateStyle: 'full', timeStyle: 'long' }).format(info.revision?.timestamp);
|
||||||
return <div className='hbox git-commit-info metadata-section'>
|
return <div className='metadata-section' role='list'>
|
||||||
<div className='vbox metadata-properties'>
|
<div role='listitem'>
|
||||||
<div>
|
{link ? (
|
||||||
{link ? (
|
<a href={link} target='_blank' rel='noopener noreferrer' title={subject}>
|
||||||
<a href={link} target='_blank' rel='noopener noreferrer' title={subject}>
|
|
||||||
{subject}
|
|
||||||
</a>
|
|
||||||
) : <span title={subject}>
|
|
||||||
{subject}
|
{subject}
|
||||||
</span>}
|
</a>
|
||||||
</div>
|
) : <span title={subject}>
|
||||||
<div className='hbox'>
|
{subject}
|
||||||
<span className='mr-1'>{author}</span>
|
</span>}
|
||||||
<span title={longTimestamp}> on {shortTimestamp}</span>
|
</div>
|
||||||
{info['ci.link'] && (
|
<div role='listitem' className='hbox'>
|
||||||
<>
|
<span className='mr-1'>{author}</span>
|
||||||
<span className='mx-2'>·</span>
|
<span title={longTimestamp}> on {shortTimestamp}</span>
|
||||||
<a href={info['ci.link']} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
{info.ci?.link && (
|
||||||
</>
|
<>
|
||||||
)}
|
<span className='mx-2'>·</span>
|
||||||
</div>
|
<a href={info.ci?.link} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{link ? (
|
|
||||||
<a href={link} target='_blank' rel='noopener noreferrer' title='View commit details'>
|
|
||||||
{shortSubject}
|
|
||||||
</a>
|
|
||||||
) : !!shortSubject && <span>{shortSubject}</span>}
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ const PromptButton: React.FC<{
|
||||||
const gitCommitInfo = useGitCommitInfo();
|
const gitCommitInfo = useGitCommitInfo();
|
||||||
const prompt = React.useMemo(() => fixTestPrompt(
|
const prompt = React.useMemo(() => fixTestPrompt(
|
||||||
error,
|
error,
|
||||||
gitCommitInfo?.['pull.diff'] ?? gitCommitInfo?.['revision.diff'],
|
gitCommitInfo?.pull_request?.diff ?? gitCommitInfo?.revision?.diff,
|
||||||
result?.attachments.find(a => a.name === 'pageSnapshot')?.body
|
result?.attachments.find(a => a.name === 'pageSnapshot')?.body
|
||||||
), [gitCommitInfo, result, error]);
|
), [gitCommitInfo, result, error]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export class FullConfigInternal {
|
||||||
readonly plugins: TestRunnerPluginRegistration[];
|
readonly plugins: TestRunnerPluginRegistration[];
|
||||||
readonly projects: FullProjectInternal[] = [];
|
readonly projects: FullProjectInternal[] = [];
|
||||||
readonly singleTSConfigPath?: string;
|
readonly singleTSConfigPath?: string;
|
||||||
readonly populateGitInfo: boolean;
|
|
||||||
cliArgs: string[] = [];
|
cliArgs: string[] = [];
|
||||||
cliGrep: string | undefined;
|
cliGrep: string | undefined;
|
||||||
cliGrepInvert: string | undefined;
|
cliGrepInvert: string | undefined;
|
||||||
|
|
@ -78,7 +77,6 @@ export class FullConfigInternal {
|
||||||
const privateConfiguration = (userConfig as any)['@playwright/test'];
|
const privateConfiguration = (userConfig as any)['@playwright/test'];
|
||||||
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
|
this.plugins = (privateConfiguration?.plugins || []).map((p: any) => ({ factory: p }));
|
||||||
this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig);
|
this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig);
|
||||||
this.populateGitInfo = takeFirst(userConfig.populateGitInfo, defaultPopulateGitInfo);
|
|
||||||
|
|
||||||
this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined);
|
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);
|
this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map(s => resolveScript(s, configDir)).filter(script => script !== undefined);
|
||||||
|
|
@ -301,7 +299,6 @@ function resolveScript(id: string | undefined, rootDir: string): string | undefi
|
||||||
|
|
||||||
export const defaultGrep = /.*/;
|
export const defaultGrep = /.*/;
|
||||||
export const defaultReporter = process.env.CI ? 'dot' : 'list';
|
export const defaultReporter = process.env.CI ? 'dot' : 'list';
|
||||||
const defaultPopulateGitInfo = process.env.GITHUB_ACTIONS === 'true';
|
|
||||||
|
|
||||||
const configInternalSymbol = Symbol('configInternalSymbol');
|
const configInternalSymbol = Symbol('configInternalSymbol');
|
||||||
|
|
||||||
|
|
|
||||||
30
packages/playwright/src/isomorphic/types.d.ts
vendored
30
packages/playwright/src/isomorphic/types.d.ts
vendored
|
|
@ -15,16 +15,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface GitCommitInfo {
|
export interface GitCommitInfo {
|
||||||
'revision.id'?: string;
|
revision?: {
|
||||||
'revision.author'?: string;
|
id?: string;
|
||||||
'revision.email'?: string;
|
author?: string;
|
||||||
'revision.subject'?: string;
|
email?: string;
|
||||||
'revision.timestamp'?: number | Date;
|
subject?: string;
|
||||||
'revision.link'?: string;
|
timestamp?: number;
|
||||||
'revision.diff'?: string;
|
link?: string;
|
||||||
'pull.link'?: string;
|
diff?: string;
|
||||||
'pull.diff'?: string;
|
},
|
||||||
'pull.base'?: string;
|
pull_request?: {
|
||||||
'pull.title'?: string;
|
link?: string;
|
||||||
'ci.link'?: string;
|
diff?: string;
|
||||||
|
base?: string;
|
||||||
|
title?: string;
|
||||||
|
},
|
||||||
|
ci?: {
|
||||||
|
link?: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ import type { GitCommitInfo } from '../isomorphic/types';
|
||||||
const GIT_OPERATIONS_TIMEOUT_MS = 1500;
|
const GIT_OPERATIONS_TIMEOUT_MS = 1500;
|
||||||
|
|
||||||
export const addGitCommitInfoPlugin = (fullConfig: FullConfigInternal) => {
|
export const addGitCommitInfoPlugin = (fullConfig: FullConfigInternal) => {
|
||||||
if (fullConfig.populateGitInfo)
|
const commitProperty = fullConfig.config.metadata['git.commit.info'];
|
||||||
|
if (commitProperty && typeof commitProperty === 'object' && Object.keys(commitProperty).length === 0)
|
||||||
fullConfig.plugins.push({ factory: gitCommitInfo });
|
fullConfig.plugins.push({ factory: gitCommitInfo });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -35,10 +36,10 @@ export const gitCommitInfo = (options?: GitCommitInfoPluginOptions): TestRunnerP
|
||||||
name: 'playwright:git-commit-info',
|
name: 'playwright:git-commit-info',
|
||||||
|
|
||||||
setup: async (config: FullConfig, configDir: string) => {
|
setup: async (config: FullConfig, configDir: string) => {
|
||||||
const fromEnv = await linksFromEnv();
|
const commitInfo = await linksFromEnv();
|
||||||
const fromCLI = await gitStatusFromCLI(options?.directory || configDir, fromEnv);
|
await enrichStatusFromCLI(options?.directory || configDir, commitInfo);
|
||||||
config.metadata = config.metadata || {};
|
config.metadata = config.metadata || {};
|
||||||
config.metadata['git.commit.info'] = { ...fromEnv, ...fromCLI };
|
config.metadata['git.commit.info'] = commitInfo;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -47,28 +48,39 @@ interface GitCommitInfoPluginOptions {
|
||||||
directory?: string;
|
directory?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function linksFromEnv() {
|
async function linksFromEnv(): Promise<GitCommitInfo> {
|
||||||
const out: Partial<GitCommitInfo> = {};
|
const out: GitCommitInfo = {};
|
||||||
// Jenkins: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
|
// Jenkins: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
|
||||||
if (process.env.BUILD_URL)
|
if (process.env.BUILD_URL) {
|
||||||
out['ci.link'] = process.env.BUILD_URL;
|
out.ci = out.ci || {};
|
||||||
|
out.ci.link = process.env.BUILD_URL;
|
||||||
|
}
|
||||||
// GitLab: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
|
// GitLab: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
|
||||||
if (process.env.CI_PROJECT_URL && process.env.CI_COMMIT_SHA)
|
if (process.env.CI_PROJECT_URL && process.env.CI_COMMIT_SHA) {
|
||||||
out['revision.link'] = `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`;
|
out.revision = out.revision || {};
|
||||||
if (process.env.CI_JOB_URL)
|
out.revision.link = `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`;
|
||||||
out['ci.link'] = process.env.CI_JOB_URL;
|
}
|
||||||
|
if (process.env.CI_JOB_URL) {
|
||||||
|
out.ci = out.ci || {};
|
||||||
|
out.ci.link = process.env.CI_JOB_URL;
|
||||||
|
}
|
||||||
// GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
|
// GitHub: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
|
||||||
if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_SHA)
|
if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_SHA) {
|
||||||
out['revision.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`;
|
out.revision = out.revision || {};
|
||||||
if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID)
|
out.revision.link = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`;
|
||||||
out['ci.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
}
|
||||||
|
if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID) {
|
||||||
|
out.ci = out.ci || {};
|
||||||
|
out.ci.link = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
||||||
|
}
|
||||||
if (process.env.GITHUB_EVENT_PATH) {
|
if (process.env.GITHUB_EVENT_PATH) {
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, 'utf8'));
|
const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, 'utf8'));
|
||||||
if (json.pull_request) {
|
if (json.pull_request) {
|
||||||
out['pull.title'] = json.pull_request.title;
|
out.pull_request = out.pull_request || {};
|
||||||
out['pull.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${json.pull_request.number}`;
|
out.pull_request.title = json.pull_request.title;
|
||||||
out['pull.base'] = json.pull_request.base.ref;
|
out.pull_request.link = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${json.pull_request.number}`;
|
||||||
|
out.pull_request.base = json.pull_request.base.ref;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
|
|
@ -76,7 +88,7 @@ async function linksFromEnv() {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gitStatusFromCLI(gitDir: string, envInfo: Pick<GitCommitInfo, 'pull.base'>): Promise<GitCommitInfo | undefined> {
|
async function enrichStatusFromCLI(gitDir: string, commitInfo: GitCommitInfo) {
|
||||||
const separator = `:${createGuid().slice(0, 4)}:`;
|
const separator = `:${createGuid().slice(0, 4)}:`;
|
||||||
const commitInfoResult = await spawnAsync(
|
const commitInfoResult = await spawnAsync(
|
||||||
'git',
|
'git',
|
||||||
|
|
@ -90,23 +102,24 @@ async function gitStatusFromCLI(gitDir: string, envInfo: Pick<GitCommitInfo, 'pu
|
||||||
let timestamp: number = Number.parseInt(rawTimestamp, 10);
|
let timestamp: number = Number.parseInt(rawTimestamp, 10);
|
||||||
timestamp = Number.isInteger(timestamp) ? timestamp * 1000 : 0;
|
timestamp = Number.isInteger(timestamp) ? timestamp * 1000 : 0;
|
||||||
|
|
||||||
const result: GitCommitInfo = {
|
commitInfo.revision = {
|
||||||
'revision.id': id,
|
...commitInfo.revision,
|
||||||
'revision.author': author,
|
id,
|
||||||
'revision.email': email,
|
author,
|
||||||
'revision.subject': subject,
|
email,
|
||||||
'revision.timestamp': timestamp,
|
subject,
|
||||||
|
timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
const diffLimit = 1_000_000; // 1MB
|
const diffLimit = 1_000_000; // 1MB
|
||||||
if (envInfo['pull.base']) {
|
if (commitInfo.pull_request?.base) {
|
||||||
const pullDiffResult = await spawnAsync(
|
const pullDiffResult = await spawnAsync(
|
||||||
'git',
|
'git',
|
||||||
['diff', envInfo['pull.base']],
|
['diff', commitInfo.pull_request?.base],
|
||||||
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
||||||
);
|
);
|
||||||
if (!pullDiffResult.code)
|
if (!pullDiffResult.code)
|
||||||
result['pull.diff'] = pullDiffResult.stdout.substring(0, diffLimit);
|
commitInfo.pull_request!.diff = pullDiffResult.stdout.substring(0, diffLimit);
|
||||||
} else {
|
} else {
|
||||||
const diffResult = await spawnAsync(
|
const diffResult = await spawnAsync(
|
||||||
'git',
|
'git',
|
||||||
|
|
@ -114,8 +127,6 @@ async function gitStatusFromCLI(gitDir: string, envInfo: Pick<GitCommitInfo, 'pu
|
||||||
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
||||||
);
|
);
|
||||||
if (!diffResult.code)
|
if (!diffResult.code)
|
||||||
result['revision.diff'] = diffResult.stdout.substring(0, diffLimit);
|
commitInfo.revision!.diff = diffResult.stdout.substring(0, diffLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
packages/playwright/types/test.d.ts
vendored
28
packages/playwright/types/test.d.ts
vendored
|
|
@ -1285,9 +1285,8 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||||
* Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as
|
* Metadata contains key-value pairs to be included in the report. For example, HTML report will display it as
|
||||||
* key-value pairs, and JSON report will include metadata serialized as json.
|
* key-value pairs, and JSON report will include metadata serialized as json.
|
||||||
*
|
*
|
||||||
* See also
|
* Providing `'git.commit.info': {}` property will populate it with the git commit details. This is useful for CI/CD
|
||||||
* [testConfig.populateGitInfo](https://playwright.dev/docs/api/class-testconfig#test-config-populate-git-info) that
|
* environments.
|
||||||
* populates metadata.
|
|
||||||
*
|
*
|
||||||
* **Usage**
|
* **Usage**
|
||||||
*
|
*
|
||||||
|
|
@ -1360,29 +1359,6 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||||
*/
|
*/
|
||||||
outputDir?: string;
|
outputDir?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to populate `'git.commit.info'` field of the
|
|
||||||
* [testConfig.metadata](https://playwright.dev/docs/api/class-testconfig#test-config-metadata) with Git commit info
|
|
||||||
* and CI/CD information.
|
|
||||||
*
|
|
||||||
* This information will appear in the HTML and JSON reports and is available in the Reporter API.
|
|
||||||
*
|
|
||||||
* On Github Actions, this feature is enabled by default.
|
|
||||||
*
|
|
||||||
* **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
|
* Whether to preserve test output in the
|
||||||
* [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Defaults to
|
* [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Defaults to
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ function Error({ message, error, errorId, sdkLanguage, pageSnapshot, revealInSou
|
||||||
const [showLLM, setShowLLM] = React.useState(false);
|
const [showLLM, setShowLLM] = React.useState(false);
|
||||||
const llmAvailable = useIsLLMAvailable();
|
const llmAvailable = useIsLLMAvailable();
|
||||||
const gitCommitInfo = useGitCommitInfo();
|
const gitCommitInfo = useGitCommitInfo();
|
||||||
const diff = gitCommitInfo?.['pull.diff'] ?? gitCommitInfo?.['revision.diff'];
|
const diff = gitCommitInfo?.pull_request?.diff ?? gitCommitInfo?.revision?.diff;
|
||||||
|
|
||||||
let location: string | undefined;
|
let location: string | undefined;
|
||||||
let longLocation: string | undefined;
|
let longLocation: string | undefined;
|
||||||
|
|
|
||||||
|
|
@ -1187,13 +1187,12 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should include metadata with populateGitInfo = true', async ({ runInlineTest, writeFiles, showReport, page }) => {
|
test('should include metadata with git.commit.info', async ({ runInlineTest, writeFiles, showReport, page }) => {
|
||||||
const files = {
|
const files = {
|
||||||
'uncommitted.txt': `uncommitted file`,
|
'uncommitted.txt': `uncommitted file`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
export default {
|
export default {
|
||||||
populateGitInfo: true,
|
metadata: { 'git.commit.info': {}, foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] }
|
||||||
metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] }
|
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
'example.spec.ts': `
|
'example.spec.ts': `
|
||||||
|
|
@ -1230,20 +1229,23 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
await page.getByRole('button', { name: 'Metadata' }).click();
|
await page.getByRole('button', { name: 'Metadata' }).click();
|
||||||
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
|
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
|
||||||
- 'link "chore(html): make this test look nice"'
|
- list:
|
||||||
- text: /^William <shakespeare@example.local> on/
|
- listitem:
|
||||||
- link /^[a-f0-9]{7}$/
|
- 'link "chore(html): make this test look nice"'
|
||||||
- text: 'foo : value1 bar : {"prop":"value2"} baz : ["value3",123]'
|
- listitem: /William <shakespeare@example\\.local>/
|
||||||
|
- list:
|
||||||
|
- listitem: "foo : value1"
|
||||||
|
- listitem: "bar : {\\"prop\\":\\"value2\\"}"
|
||||||
|
- listitem: "baz : [\\"value3\\",123]"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should include metadata with populateGitInfo on GHA', async ({ runInlineTest, writeFiles, showReport, page }) => {
|
test('should include metadata with git.commit.info on GHA', async ({ runInlineTest, writeFiles, showReport, page }) => {
|
||||||
const files = {
|
const files = {
|
||||||
'uncommitted.txt': `uncommitted file`,
|
'uncommitted.txt': `uncommitted file`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
export default {
|
export default {
|
||||||
populateGitInfo: true,
|
metadata: { 'git.commit.info': {}, foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] }
|
||||||
metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] }
|
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
'example.spec.ts': `
|
'example.spec.ts': `
|
||||||
|
|
@ -1291,18 +1293,23 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
await page.getByRole('button', { name: 'Metadata' }).click();
|
await page.getByRole('button', { name: 'Metadata' }).click();
|
||||||
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
|
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
|
||||||
- 'link "My PR"'
|
- list:
|
||||||
- text: /^William <shakespeare@example.local> on/
|
- listitem:
|
||||||
- link "Logs"
|
- link "My PR"
|
||||||
- link "Pull Request"
|
- listitem:
|
||||||
- text: 'foo : value1 bar : {"prop":"value2"} baz : ["value3",123]'
|
- text: /William <shakespeare@example\\.local>/
|
||||||
|
- link "Logs"
|
||||||
|
- list:
|
||||||
|
- listitem: "foo : value1"
|
||||||
|
- listitem: "bar : {\\"prop\\":\\"value2\\"}"
|
||||||
|
- listitem: "baz : [\\"value3\\",123]"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not include git metadata with populateGitInfo = false', async ({ runInlineTest, showReport, page }) => {
|
test('should not include git metadata w/o git.commit.info', async ({ runInlineTest, showReport, page }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
export default { populateGitInfo: false };
|
export default {};
|
||||||
`,
|
`,
|
||||||
'example.spec.ts': `
|
'example.spec.ts': `
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
@ -1323,7 +1330,7 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
export default {
|
export default {
|
||||||
metadata: {
|
metadata: {
|
||||||
'git.commit.info': { 'revision.timestamp': 'hi' }
|
'git.commit.info': { revision: { timestamp: 'hi' } }
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
|
|
@ -2757,8 +2764,12 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
'uncommitted.txt': `uncommitted file`,
|
'uncommitted.txt': `uncommitted file`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
export default {
|
export default {
|
||||||
populateGitInfo: true,
|
metadata: {
|
||||||
metadata: { foo: 'value1', bar: { prop: 'value2' }, baz: ['value3', 123] }
|
'git.commit.info': {},
|
||||||
|
foo: 'value1',
|
||||||
|
bar: { prop: 'value2' },
|
||||||
|
baz: ['value3', 123]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
'example.spec.ts': `
|
'example.spec.ts': `
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ test('should render html report git info metadata', async ({ runUITest }) => {
|
||||||
'reporter.ts': `
|
'reporter.ts': `
|
||||||
module.exports = class Reporter {
|
module.exports = class Reporter {
|
||||||
onBegin(config, suite) {
|
onBegin(config, suite) {
|
||||||
console.log('ci.link:', config.metadata['git.commit.info']['ci.link']);
|
console.log('ci.link:', config.metadata['git.commit.info'].ci.link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
'playwright.config.ts': `
|
'playwright.config.ts': `
|
||||||
import { defineConfig } from '@playwright/test';
|
import { defineConfig } from '@playwright/test';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
populateGitInfo: true,
|
metadata: { 'git.commit.info': {} },
|
||||||
reporter: './reporter.ts',
|
reporter: './reporter.ts',
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue