chore: move markdown reporter to playwright-dashboard (#35465)

This commit is contained in:
Yury Semikhatsky 2025-04-02 17:26:33 -07:00 committed by GitHub
parent 6c5f3bbe39
commit 19d0d54e66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 151 additions and 93 deletions

2
package-lock.json generated
View File

@ -9052,7 +9052,7 @@
"version": "0.0.0",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.52.0-next"
"@playwright/test": "1.52.0-next"
},
"engines": {
"node": ">=18"

View File

@ -19,6 +19,6 @@
"./package.json": "./package.json"
},
"dependencies": {
"playwright-core": "1.52.0-next"
"@playwright/test": "1.52.0-next"
}
}

View File

@ -14,10 +14,11 @@
* limitations under the License.
*/
import { MarkdownReporter } from 'playwright/lib/internalsForTest';
import { context, getOctokit } from '@actions/github';
import * as core from '@actions/core';
import MarkdownReporter from './markdownReporter';
import type { MetadataWithCommitInfo } from 'playwright/src/isomorphic/types';
import type { FullConfig } from '@playwright/test';

View File

@ -0,0 +1,146 @@
/**
* 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 fs from 'fs';
import path from 'path';
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestError } from '@playwright/test/reporter';
type MarkdownReporterOptions = {
configDir: string, // TODO: make it public?
outputFile?: string;
};
class MarkdownReporter implements Reporter {
private _options: MarkdownReporterOptions;
private _fatalErrors: TestError[] = [];
private _config!: FullConfig;
private _suite!: Suite;
constructor(options: MarkdownReporterOptions) {
this._options = options;
}
printsToStdio() {
return false;
}
onBegin(config: FullConfig, suite: Suite) {
this._config = config;
this._suite = suite;
}
onError(error: TestError) {
this._fatalErrors.push(error);
}
async onEnd(result: FullResult) {
const summary = this._generateSummary();
const lines: string[] = [];
if (this._fatalErrors.length)
lines.push(`**${this._fatalErrors.length} fatal errors, not part of any test**`);
if (summary.unexpected.length) {
lines.push(`**${summary.unexpected.length} failed**`);
this._printTestList(':x:', summary.unexpected, lines);
}
if (summary.flaky.length) {
lines.push(`<details>`);
lines.push(`<summary><b>${summary.flaky.length} flaky</b></summary>`);
this._printTestList(':warning:', summary.flaky, lines, ' <br/>');
lines.push(`</details>`);
lines.push(``);
}
if (summary.interrupted.length) {
lines.push(`<details>`);
lines.push(`<summary><b>${summary.interrupted.length} interrupted</b></summary>`);
this._printTestList(':warning:', summary.interrupted, lines, ' <br/>');
lines.push(`</details>`);
lines.push(``);
}
const skipped = summary.skipped ? `, ${summary.skipped} skipped` : '';
const didNotRun = summary.didNotRun ? `, ${summary.didNotRun} did not run` : '';
lines.push(`**${summary.expected} passed${skipped}${didNotRun}**`);
lines.push(`:heavy_check_mark::heavy_check_mark::heavy_check_mark:`);
lines.push(``);
await this.publishReport(lines.join('\n'));
}
protected async publishReport(report: string): Promise<void> {
const maybeRelativeFile = this._options.outputFile || 'report.md';
const reportFile = path.resolve(this._options.configDir, maybeRelativeFile);
await fs.promises.mkdir(path.dirname(reportFile), { recursive: true });
await fs.promises.writeFile(reportFile, report);
}
protected _generateSummary() {
let didNotRun = 0;
let skipped = 0;
let expected = 0;
const interrupted: TestCase[] = [];
const interruptedToPrint: TestCase[] = [];
const unexpected: TestCase[] = [];
const flaky: TestCase[] = [];
this._suite.allTests().forEach(test => {
switch (test.outcome()) {
case 'skipped': {
if (test.results.some(result => result.status === 'interrupted')) {
if (test.results.some(result => !!result.error))
interruptedToPrint.push(test);
interrupted.push(test);
} else if (!test.results.length || test.expectedStatus !== 'skipped') {
++didNotRun;
} else {
++skipped;
}
break;
}
case 'expected': ++expected; break;
case 'unexpected': unexpected.push(test); break;
case 'flaky': flaky.push(test); break;
}
});
return {
didNotRun,
skipped,
expected,
interrupted,
unexpected,
flaky,
};
}
private _printTestList(prefix: string, tests: TestCase[], lines: string[], suffix?: string) {
for (const test of tests)
lines.push(`${prefix} ${formatTestTitle(this._config.rootDir, test)}${suffix || ''}`);
lines.push(``);
}
}
function formatTestTitle(rootDir: string, test: TestCase): string {
// root, project, file, ...describes, test
const [, projectName, , ...titles] = test.titlePath();
const relativeTestPath = path.relative(rootDir, test.location.file);
const location = `${relativeTestPath}:${test.location.line}:${test.location.column}`;
const projectTitle = projectName ? `[${projectName}] ` : '';
const testTitle = `${projectTitle}${location} ${titles.join(' ')}`;
const extraTags = test.tags.filter(t => !testTitle.includes(t));
return `${testTitle}${extraTags.length ? ' ' + extraTags.join(' ') : ''}`;
}
export default MarkdownReporter;

View File

@ -17,7 +17,6 @@
import path from 'path';
import { fileDependenciesForTest } from './transform/compilationCache';
export { default as MarkdownReporter } from './reporters/markdown';
export function fileDependencies() {
return Object.fromEntries([...fileDependenciesForTest().entries()].map(entry => (

View File

@ -1,88 +0,0 @@
/**
* 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 fs from 'fs';
import path from 'path';
import { resolveReporterOutputPath } from '../util';
import { TerminalReporter } from './base';
import type { FullResult, TestCase } from '../../types/testReporter';
type MarkdownReporterOptions = {
configDir: string,
outputFile?: string;
};
class MarkdownReporter extends TerminalReporter {
private _options: MarkdownReporterOptions;
constructor(options: MarkdownReporterOptions) {
super();
this._options = options;
}
printsToStdio() {
return false;
}
override async onEnd(result: FullResult) {
await super.onEnd(result);
const summary = this.generateSummary();
const lines: string[] = [];
if (summary.fatalErrors.length)
lines.push(`**${summary.fatalErrors.length} fatal errors, not part of any test**`);
if (summary.unexpected.length) {
lines.push(`**${summary.unexpected.length} failed**`);
this._printTestList(':x:', summary.unexpected, lines);
}
if (summary.flaky.length) {
lines.push(`<details>`);
lines.push(`<summary><b>${summary.flaky.length} flaky</b></summary>`);
this._printTestList(':warning:', summary.flaky, lines, ' <br/>');
lines.push(`</details>`);
lines.push(``);
}
if (summary.interrupted.length) {
lines.push(`<details>`);
lines.push(`<summary><b>${summary.interrupted.length} interrupted</b></summary>`);
this._printTestList(':warning:', summary.interrupted, lines, ' <br/>');
lines.push(`</details>`);
lines.push(``);
}
const skipped = summary.skipped ? `, ${summary.skipped} skipped` : '';
const didNotRun = summary.didNotRun ? `, ${summary.didNotRun} did not run` : '';
lines.push(`**${summary.expected} passed${skipped}${didNotRun}**`);
lines.push(`:heavy_check_mark::heavy_check_mark::heavy_check_mark:`);
lines.push(``);
await this.publishReport(lines.join('\n'));
}
protected async publishReport(report: string): Promise<void> {
const reportFile = resolveReporterOutputPath('report.md', this._options.configDir, this._options.outputFile);
await fs.promises.mkdir(path.dirname(reportFile), { recursive: true });
await fs.promises.writeFile(reportFile, report);
}
private _printTestList(prefix: string, tests: TestCase[], lines: string[], suffix?: string) {
for (const test of tests)
lines.push(`${prefix} ${this.formatTestTitle(test)}${suffix || ''}`);
lines.push(``);
}
}
export default MarkdownReporter;

View File

@ -18,7 +18,7 @@ import fs from 'fs';
import path from 'path';
import { expect, test } from './playwright-test-fixtures';
const markdownReporter = require.resolve('../../packages/playwright/lib/reporters/markdown');
const markdownReporter = require.resolve('../../packages/playwright-dashboard/lib/markdownReporter');
test('simple report', async ({ runInlineTest }) => {
const files = {