feat: provide commit diff to HTML reporter (#34653)
Signed-off-by: Simon Knott <info@simonknott.de> Co-authored-by: Max Schmitt <max@schmitt.mx>
This commit is contained in:
parent
fd24521f2e
commit
bc8d6ce344
|
|
@ -95,7 +95,18 @@ const GitCommitInfoView: React.FC<{ info: GitCommitInfo }> = ({ info }) => {
|
||||||
<div className='hbox m-2 mt-1'>
|
<div className='hbox m-2 mt-1'>
|
||||||
<div className='mr-1'>{author}</div>
|
<div className='mr-1'>{author}</div>
|
||||||
<div title={longTimestamp}> on {shortTimestamp}</div>
|
<div title={longTimestamp}> on {shortTimestamp}</div>
|
||||||
{info['ci.link'] && <><span className='mx-2'>·</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>
|
||||||
|
<a href={info['ci.link']} target='_blank' rel='noopener noreferrer' title='CI/CD logs'>Logs</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{info['pull.link'] && (
|
||||||
|
<>
|
||||||
|
<span className='mx-2'>·</span>
|
||||||
|
<a href={info['pull.link']} target='_blank' rel='noopener noreferrer'>Pull Request</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!info['revision.link'] && <a href={info['revision.link']} target='_blank' rel='noopener noreferrer'>
|
{!!info['revision.link'] && <a href={info['revision.link']} target='_blank' rel='noopener noreferrer'>
|
||||||
|
|
|
||||||
|
|
@ -21,5 +21,9 @@ export interface GitCommitInfo {
|
||||||
'revision.subject'?: string;
|
'revision.subject'?: string;
|
||||||
'revision.timestamp'?: number | Date;
|
'revision.timestamp'?: number | Date;
|
||||||
'revision.link'?: string;
|
'revision.link'?: string;
|
||||||
|
'revision.diff'?: string;
|
||||||
|
'pull.link'?: string;
|
||||||
|
'pull.diff'?: string;
|
||||||
|
'pull.base'?: string;
|
||||||
'ci.link'?: string;
|
'ci.link'?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export const gitCommitInfo = (options?: GitCommitInfoPluginOptions): TestRunnerP
|
||||||
|
|
||||||
setup: async (config: FullConfig, configDir: string) => {
|
setup: async (config: FullConfig, configDir: string) => {
|
||||||
const fromEnv = linksFromEnv();
|
const fromEnv = linksFromEnv();
|
||||||
const fromCLI = await gitStatusFromCLI(options?.directory || configDir);
|
const fromCLI = await gitStatusFromCLI(options?.directory || configDir, fromEnv);
|
||||||
config.metadata = config.metadata || {};
|
config.metadata = config.metadata || {};
|
||||||
config.metadata['git.commit.info'] = { ...fromEnv, ...fromCLI };
|
config.metadata['git.commit.info'] = { ...fromEnv, ...fromCLI };
|
||||||
},
|
},
|
||||||
|
|
@ -44,8 +44,8 @@ interface GitCommitInfoPluginOptions {
|
||||||
directory?: string;
|
directory?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function linksFromEnv(): Pick<GitCommitInfo, 'revision.link' | 'ci.link'> {
|
function linksFromEnv() {
|
||||||
const out: { 'revision.link'?: string; 'ci.link'?: string; } = {};
|
const out: Partial<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.link'] = process.env.BUILD_URL;
|
||||||
|
|
@ -59,28 +59,54 @@ function linksFromEnv(): Pick<GitCommitInfo, 'revision.link' | 'ci.link'> {
|
||||||
out['revision.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`;
|
out['revision.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`;
|
||||||
if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID)
|
if (process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID)
|
||||||
out['ci.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
out['ci.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
||||||
|
if (process.env.GITHUB_REF_NAME && process.env.GITHUB_REF_NAME.endsWith('/merge')) {
|
||||||
|
const pullId = process.env.GITHUB_REF_NAME.substring(0, process.env.GITHUB_REF_NAME.indexOf('/merge'));
|
||||||
|
out['pull.link'] = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${pullId}`;
|
||||||
|
out['pull.base'] = process.env.GITHUB_BASE_REF;
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gitStatusFromCLI(gitDir: string): Promise<GitCommitInfo | undefined> {
|
async function gitStatusFromCLI(gitDir: string, envInfo: Pick<GitCommitInfo, 'pull.base'>): Promise<GitCommitInfo | undefined> {
|
||||||
const separator = `:${createGuid().slice(0, 4)}:`;
|
const separator = `:${createGuid().slice(0, 4)}:`;
|
||||||
const { code, stdout } = await spawnAsync(
|
const commitInfoResult = await spawnAsync(
|
||||||
'git',
|
'git',
|
||||||
['show', '-s', `--format=%H${separator}%s${separator}%an${separator}%ae${separator}%ct`, 'HEAD'],
|
['show', '-s', `--format=%H${separator}%s${separator}%an${separator}%ae${separator}%ct`, 'HEAD'],
|
||||||
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
||||||
);
|
);
|
||||||
if (code)
|
if (commitInfoResult.code)
|
||||||
return;
|
return;
|
||||||
const showOutput = stdout.trim();
|
const showOutput = commitInfoResult.stdout.trim();
|
||||||
const [id, subject, author, email, rawTimestamp] = showOutput.split(separator);
|
const [id, subject, author, email, rawTimestamp] = showOutput.split(separator);
|
||||||
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;
|
||||||
|
|
||||||
return {
|
const result: GitCommitInfo = {
|
||||||
'revision.id': id,
|
'revision.id': id,
|
||||||
'revision.author': author,
|
'revision.author': author,
|
||||||
'revision.email': email,
|
'revision.email': email,
|
||||||
'revision.subject': subject,
|
'revision.subject': subject,
|
||||||
'revision.timestamp': timestamp,
|
'revision.timestamp': timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const diffLimit = 1_000_000; // 1MB
|
||||||
|
if (envInfo['pull.base']) {
|
||||||
|
const pullDiffResult = await spawnAsync(
|
||||||
|
'git',
|
||||||
|
['diff', envInfo['pull.base']],
|
||||||
|
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
||||||
|
);
|
||||||
|
if (!pullDiffResult.code)
|
||||||
|
result['pull.diff'] = pullDiffResult.stdout.substring(0, diffLimit);
|
||||||
|
} else {
|
||||||
|
const diffResult = await spawnAsync(
|
||||||
|
'git',
|
||||||
|
['diff', 'HEAD~1'],
|
||||||
|
{ stdio: 'pipe', cwd: gitDir, timeout: GIT_OPERATIONS_TIMEOUT_MS }
|
||||||
|
);
|
||||||
|
if (!diffResult.code)
|
||||||
|
result['revision.diff'] = diffResult.stdout.substring(0, diffLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1213,6 +1213,8 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
await execGit(['init']);
|
await execGit(['init']);
|
||||||
await execGit(['config', '--local', 'user.email', 'shakespeare@example.local']);
|
await execGit(['config', '--local', 'user.email', 'shakespeare@example.local']);
|
||||||
await execGit(['config', '--local', 'user.name', 'William']);
|
await execGit(['config', '--local', 'user.name', 'William']);
|
||||||
|
await execGit(['add', 'playwright.config.ts']);
|
||||||
|
await execGit(['commit', '-m', 'init']);
|
||||||
await execGit(['add', '*.ts']);
|
await execGit(['add', '*.ts']);
|
||||||
await execGit(['commit', '-m', 'chore(html): make this test look nice']);
|
await execGit(['commit', '-m', 'chore(html): make this test look nice']);
|
||||||
|
|
||||||
|
|
@ -1222,6 +1224,8 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
GITHUB_RUN_ID: 'example-run-id',
|
GITHUB_RUN_ID: 'example-run-id',
|
||||||
GITHUB_SERVER_URL: 'https://playwright.dev',
|
GITHUB_SERVER_URL: 'https://playwright.dev',
|
||||||
GITHUB_SHA: 'example-sha',
|
GITHUB_SHA: 'example-sha',
|
||||||
|
GITHUB_REF_NAME: '42/merge',
|
||||||
|
GITHUB_BASE_REF: 'HEAD~1',
|
||||||
});
|
});
|
||||||
|
|
||||||
await showReport();
|
await showReport();
|
||||||
|
|
@ -1231,7 +1235,8 @@ for (const useIntermediateMergeReport of [true, false] as const) {
|
||||||
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
|
await expect(page.locator('.metadata-view')).toMatchAriaSnapshot(`
|
||||||
- 'link "chore(html): make this test look nice"'
|
- 'link "chore(html): make this test look nice"'
|
||||||
- text: /^William <shakespeare@example.local> on/
|
- text: /^William <shakespeare@example.local> on/
|
||||||
- link "logs"
|
- link "Logs"
|
||||||
|
- link "Pull Request"
|
||||||
- link /^[a-f0-9]{7}$/
|
- link /^[a-f0-9]{7}$/
|
||||||
- text: 'foo: value1 bar: {"prop":"value2"} baz: ["value3",123]'
|
- text: 'foo: value1 bar: {"prop":"value2"} baz: ["value3",123]'
|
||||||
`);
|
`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue