playwright/tests/config/ghaMarkdownReporter.ts
2025-06-03 13:52:26 -07:00

149 lines
4.9 KiB
TypeScript

/**
* 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 { context, getOctokit } from '@actions/github';
import * as core from '@actions/core';
import MarkdownReporter from '../../packages/playwright/src/reporters/markdown';
import type { MetadataWithCommitInfo } from 'playwright/src/isomorphic/types';
import type { IssueCommentEdge, Repository } from '@octokit/graphql-schema';
function getGithubToken() {
const token = process.env.GITHUB_TOKEN || core.getInput('github-token');
if (!token) {
core.setFailed('Missing "github-token" input');
throw new Error('Missing "github-token" input');
}
return token;
}
const octokit = getOctokit(getGithubToken());
class GHAMarkdownReporter extends MarkdownReporter {
override async publishReport(report: string) {
core.info('Publishing report to PR.');
const { prNumber, prHref } = this.pullRequestFromMetadata();
if (!prNumber) {
core.info(`No PR number found, skipping GHA comment. PR href: ${prHref}`);
return;
}
core.info(`Posting comment to PR ${prHref}`);
const prNodeId = await this.collapsePreviousComments(prNumber);
if (!prNodeId) {
core.warning(`No PR node ID found, skipping GHA comment. PR href: ${prHref}`);
return;
}
await this.addNewReportComment(prNodeId, report);
}
private async collapsePreviousComments(prNumber: number) {
const { owner, repo } = context.repo;
const data = await octokit.graphql<{ repository: Repository }>(`
query {
repository(owner: "${owner}", name: "${repo}") {
pullRequest(number: ${prNumber}) {
id
comments(last: 100) {
nodes {
id
body
author {
__typename
login
}
}
}
}
}
}
`);
const comments = data.repository.pullRequest?.comments.nodes?.filter(comment =>
comment?.author?.__typename === 'Bot' &&
comment?.author?.login === 'github-actions' &&
comment.body?.includes(this._magicComment()));
const prId = data.repository.pullRequest?.id;
if (!comments?.length)
return prId;
const mutations = comments.map((comment, i) =>
`m${i}: minimizeComment(input: { subjectId: "${comment!.id}", classifier: OUTDATED }) { clientMutationId }`);
await octokit.graphql(`
mutation {
${mutations.join('\n')}
}
`);
return prId;
}
private _magicComment() {
return `<!-- Generated by Playwright markdown reporter for ${this._workflowRunName()} in job ${process.env.GITHUB_JOB} -->`;
}
private _workflowRunName() {
// When used via 'workflow_run' event.
const workflowRunName = context.payload.workflow_run?.name;
if (workflowRunName)
return workflowRunName;
// When used via 'pull_request'/'push' event.
// This is the name of the workflow file, e.g. 'ci.yml' or name if set.
return process.env.GITHUB_WORKFLOW;
}
private async addNewReportComment(prNodeId: string, report: string) {
const reportUrl = process.env.HTML_REPORT_URL;
const mergeWorkflowUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
const body = formatComment([
this._magicComment(),
`### ${reportUrl ? `[Test results](${reportUrl})` : 'Test results'} for "${this._workflowRunName()}"`,
report,
'',
`Merge [workflow run](${mergeWorkflowUrl}).`
]);
const response = await octokit.graphql<{ addComment: { commentEdge: IssueCommentEdge } }>(`
mutation {
addComment(input: {subjectId: "${prNodeId}", body: """${body}"""}) {
commentEdge {
node {
... on IssueComment {
url
}
}
}
}
}
`);
core.info(`Posted comment: ${response.addComment.commentEdge.node?.url}`);
}
private pullRequestFromMetadata() {
const metadata = this._config.metadata as MetadataWithCommitInfo;
const prHref = metadata.ci?.prHref;
return { prNumber: parseInt(prHref?.split('/').pop() ?? '', 10), prHref };
}
}
function formatComment(lines: string[]) {
let body = lines.join('\n');
if (body.length > 65535)
body = body.substring(0, 65000) + `... ${body.length - 65000} more characters`;
return body;
}
export default GHAMarkdownReporter;