/** * 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 milliseconds from 'ms'; import path from 'path'; import { BaseReporter, formatFailure } from './base'; import { TestCase, FullResult } from '../../../types/testReporter'; type GitHubLogType = 'debug' | 'notice' | 'warning' | 'error'; type GitHubLogOptions = Partial<{ title: string; file: string; col: number; endColumn: number; line: number; endLine: number; }>; class GitHubLogger { private _isGitHubAction: boolean = !!process.env.GITHUB_ACTION; private _log(message: string, type: GitHubLogType = 'notice', options: GitHubLogOptions = {}) { if (this._isGitHubAction) message = message.replace(/\n/g, '%0A'); const configs = Object.entries(options) .map(([key, option]) => `${key}=${option}`) .join(','); console.log(`::${type} ${configs}::${message}`); } debug(message: string, options?: GitHubLogOptions) { this._log(message, 'debug', options); } error(message: string, options?: GitHubLogOptions) { this._log(message, 'error', options); } notice(message: string, options?: GitHubLogOptions) { this._log(message, 'notice', options); } warning(message: string, options?: GitHubLogOptions) { this._log(message, 'warning', options); } } export class GitHubReporter extends BaseReporter { githubLogger = new GitHubLogger(); override async onEnd(result: FullResult) { super.onEnd(result); this._printAnnotations(); } private _printAnnotations() { const summary = this.generateSummary(); const summaryMessage = this.generateSummaryMessage(summary); if (summary.failuresToPrint.length) this._printFailureAnnotations(summary.failuresToPrint); this._printSlowTestAnnotations(); this._printSummaryAnnotation(summaryMessage); } private _printSlowTestAnnotations() { this.getSlowTests().forEach(([file, duration]) => { const filePath = workspaceRelativePath(path.join(process.cwd(), file)); this.githubLogger.warning(`${filePath} took ${milliseconds(duration)}`, { title: 'Slow Test', file: filePath, }); }); } private _printSummaryAnnotation(summary: string){ this.githubLogger.notice(summary, { title: '🎭 Playwright Run Summary' }); } private _printFailureAnnotations(failures: TestCase[]) { failures.forEach((test, index) => { const filePath = workspaceRelativePath(test.location.file); const { annotations } = formatFailure(this.config, test, { filePath, index: index + 1, includeStdio: true, includeAttachments: false, }); annotations.forEach(({ filePath, title, message, position }) => { const options: GitHubLogOptions = { file: filePath, title, }; if (position) { options.line = position.line; options.col = position.column; } this.githubLogger.error(message, options); }); }); } } function workspaceRelativePath(filePath: string): string { return path.relative(process.env['GITHUB_WORKSPACE'] ?? '', filePath); } export default GitHubReporter;