feat(reporters): augment non-stdio reporters with dot/line (#10003)
This commit is contained in:
parent
2e1dcaf2ee
commit
9cebe60831
|
|
@ -237,3 +237,9 @@ Test that has been finished.
|
||||||
- `result` <[TestResult]>
|
- `result` <[TestResult]>
|
||||||
|
|
||||||
Result of the test run.
|
Result of the test run.
|
||||||
|
|
||||||
|
|
||||||
|
## method: Reporter.printsToStdio
|
||||||
|
- returns: <[boolean]>
|
||||||
|
|
||||||
|
Whether this reporter uses stdio for reporting. When it does not, Playwright Test could add some output to enhance user experience.
|
||||||
|
|
|
||||||
|
|
@ -305,22 +305,22 @@ npx playwright show-report my-report
|
||||||
|
|
||||||
### JSON reporter
|
### JSON reporter
|
||||||
|
|
||||||
JSON reporter produces an object with all information about the test run. It is usually used together with some terminal reporter like `dot` or `line`.
|
JSON reporter produces an object with all information about the test run.
|
||||||
|
|
||||||
Most likely you want to write the JSON to a file. When running with `--reporter=json`, use `PLAYWRIGHT_JSON_OUTPUT_NAME` environment variable:
|
Most likely you want to write the JSON to a file. When running with `--reporter=json`, use `PLAYWRIGHT_JSON_OUTPUT_NAME` environment variable:
|
||||||
|
|
||||||
```bash bash-flavor=bash
|
```bash bash-flavor=bash
|
||||||
PLAYWRIGHT_JSON_OUTPUT_NAME=results.json npx playwright test --reporter=json,dot
|
PLAYWRIGHT_JSON_OUTPUT_NAME=results.json npx playwright test --reporter=json
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash bash-flavor=batch
|
```bash bash-flavor=batch
|
||||||
set PLAYWRIGHT_JSON_OUTPUT_NAME=results.json
|
set PLAYWRIGHT_JSON_OUTPUT_NAME=results.json
|
||||||
npx playwright test --reporter=json,dot
|
npx playwright test --reporter=json
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash bash-flavor=powershell
|
```bash bash-flavor=powershell
|
||||||
$env:PLAYWRIGHT_JSON_OUTPUT_NAME="results.json"
|
$env:PLAYWRIGHT_JSON_OUTPUT_NAME="results.json"
|
||||||
npx playwright test --reporter=json,dot
|
npx playwright test --reporter=json
|
||||||
```
|
```
|
||||||
|
|
||||||
In configuration file, pass options directly:
|
In configuration file, pass options directly:
|
||||||
|
|
@ -348,22 +348,22 @@ export default config;
|
||||||
|
|
||||||
### JUnit reporter
|
### JUnit reporter
|
||||||
|
|
||||||
JUnit reporter produces a JUnit-style xml report. It is usually used together with some terminal reporter like `dot` or `line`.
|
JUnit reporter produces a JUnit-style xml report.
|
||||||
|
|
||||||
Most likely you want to write the report to an xml file. When running with `--reporter=junit`, use `PLAYWRIGHT_JUNIT_OUTPUT_NAME` environment variable:
|
Most likely you want to write the report to an xml file. When running with `--reporter=junit`, use `PLAYWRIGHT_JUNIT_OUTPUT_NAME` environment variable:
|
||||||
|
|
||||||
```bash bash-flavor=bash
|
```bash bash-flavor=bash
|
||||||
PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml npx playwright test --reporter=junit,line
|
PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml npx playwright test --reporter=junit
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash bash-flavor=batch
|
```bash bash-flavor=batch
|
||||||
set PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml
|
set PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml
|
||||||
npx playwright test --reporter=junit,line
|
npx playwright test --reporter=junit
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash bash-flavor=powershell
|
```bash bash-flavor=powershell
|
||||||
$env:PLAYWRIGHT_JUNIT_OUTPUT_NAME="results.xml"
|
$env:PLAYWRIGHT_JUNIT_OUTPUT_NAME="results.xml"
|
||||||
npx playwright test --reporter=junit,line
|
npx playwright test --reporter=junit
|
||||||
```
|
```
|
||||||
|
|
||||||
In configuration file, pass options directly:
|
In configuration file, pass options directly:
|
||||||
|
|
@ -391,7 +391,7 @@ export default config;
|
||||||
|
|
||||||
### GitHub Actions annotations
|
### GitHub Actions annotations
|
||||||
|
|
||||||
You can use the built in `github` reporter to get automatic failure annotations when running in GitHub actions. Use it with some other reporter, for example `'dot'` and/or `'json'`.
|
You can use the built in `github` reporter to get automatic failure annotations when running in GitHub actions.
|
||||||
|
|
||||||
Note that all other reporters work on GitHub Actions as well, but do not provide annotations.
|
Note that all other reporters work on GitHub Actions as well, but do not provide annotations.
|
||||||
|
|
||||||
|
|
@ -403,7 +403,7 @@ Note that all other reporters work on GitHub Actions as well, but do not provide
|
||||||
const config = {
|
const config = {
|
||||||
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
|
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
|
||||||
// default 'list' when running locally
|
// default 'list' when running locally
|
||||||
reporter: process.env.CI ? [ ['github'], ['dot'] ] : 'list',
|
reporter: process.env.CI ? 'github' : 'list',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
@ -416,7 +416,7 @@ import { PlaywrightTestConfig } from '@playwright/test';
|
||||||
const config: PlaywrightTestConfig = {
|
const config: PlaywrightTestConfig = {
|
||||||
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
|
// 'github' for GitHub Actions CI to generate annotations, plus a concise 'dot'
|
||||||
// default 'list' when running locally
|
// default 'list' when running locally
|
||||||
reporter: process.env.CI ? [ ['github'], ['dot'] ] : 'list',
|
reporter: process.env.CI ? 'github' : 'list',
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@ import { FullResult, TestCase, TestResult } from '../../types/testReporter';
|
||||||
class DotReporter extends BaseReporter {
|
class DotReporter extends BaseReporter {
|
||||||
private _counter = 0;
|
private _counter = 0;
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
override onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
override onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
super.onStdOut(chunk, test, result);
|
super.onStdOut(chunk, test, result);
|
||||||
if (!this.config.quiet)
|
if (!this.config.quiet)
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ class GitHubLogger {
|
||||||
export class GitHubReporter extends BaseReporter {
|
export class GitHubReporter extends BaseReporter {
|
||||||
githubLogger = new GitHubLogger();
|
githubLogger = new GitHubLogger();
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
override async onEnd(result: FullResult) {
|
override async onEnd(result: FullResult) {
|
||||||
super.onEnd(result);
|
super.onEnd(result);
|
||||||
this._printAnnotations();
|
this._printAnnotations();
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import fs from 'fs';
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Transform, TransformCallback } from 'stream';
|
import { Transform, TransformCallback } from 'stream';
|
||||||
import { FullConfig, Suite } from '../../types/testReporter';
|
import { FullConfig, Suite, Reporter } from '../../types/testReporter';
|
||||||
import { HttpServer } from 'playwright-core/lib/utils/httpServer';
|
import { HttpServer } from 'playwright-core/lib/utils/httpServer';
|
||||||
import { calculateSha1, removeFolders } from 'playwright-core/lib/utils/utils';
|
import { calculateSha1, removeFolders } from 'playwright-core/lib/utils/utils';
|
||||||
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
|
import RawReporter, { JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep, JsonAttachment } from './raw';
|
||||||
|
|
@ -104,7 +104,7 @@ type TestEntry = {
|
||||||
testCaseSummary: TestCaseSummary
|
testCaseSummary: TestCaseSummary
|
||||||
};
|
};
|
||||||
|
|
||||||
class HtmlReporter {
|
class HtmlReporter implements Reporter {
|
||||||
private config!: FullConfig;
|
private config!: FullConfig;
|
||||||
private suite!: Suite;
|
private suite!: Suite;
|
||||||
private _outputFolder: string | undefined;
|
private _outputFolder: string | undefined;
|
||||||
|
|
@ -116,6 +116,10 @@ class HtmlReporter {
|
||||||
this._open = options.open || 'on-failure';
|
this._open = options.open || 'on-failure';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.suite = suite;
|
this.suite = suite;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,11 @@ class JSONReporter implements Reporter {
|
||||||
private _outputFile: string | undefined;
|
private _outputFile: string | undefined;
|
||||||
|
|
||||||
constructor(options: { outputFile?: string } = {}) {
|
constructor(options: { outputFile?: string } = {}) {
|
||||||
this._outputFile = options.outputFile;
|
this._outputFile = options.outputFile || process.env[`PLAYWRIGHT_JSON_OUTPUT_NAME`];
|
||||||
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return !this._outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
|
|
@ -270,7 +274,6 @@ class JSONReporter implements Reporter {
|
||||||
|
|
||||||
function outputReport(report: JSONReport, outputFile: string | undefined) {
|
function outputReport(report: JSONReport, outputFile: string | undefined) {
|
||||||
const reportString = JSON.stringify(report, undefined, 2);
|
const reportString = JSON.stringify(report, undefined, 2);
|
||||||
outputFile = outputFile || process.env[`PLAYWRIGHT_JSON_OUTPUT_NAME`];
|
|
||||||
if (outputFile) {
|
if (outputFile) {
|
||||||
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
||||||
fs.writeFileSync(outputFile, reportString);
|
fs.writeFileSync(outputFile, reportString);
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,14 @@ class JUnitReporter implements Reporter {
|
||||||
private stripANSIControlSequences = false;
|
private stripANSIControlSequences = false;
|
||||||
|
|
||||||
constructor(options: { outputFile?: string, stripANSIControlSequences?: boolean } = {}) {
|
constructor(options: { outputFile?: string, stripANSIControlSequences?: boolean } = {}) {
|
||||||
this.outputFile = options.outputFile;
|
this.outputFile = options.outputFile || process.env[`PLAYWRIGHT_JUNIT_OUTPUT_NAME`];
|
||||||
this.stripANSIControlSequences = options.stripANSIControlSequences || false;
|
this.stripANSIControlSequences = options.stripANSIControlSequences || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return !this.outputFile;
|
||||||
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.suite = suite;
|
this.suite = suite;
|
||||||
|
|
@ -69,10 +73,9 @@ class JUnitReporter implements Reporter {
|
||||||
|
|
||||||
serializeXML(root, tokens, this.stripANSIControlSequences);
|
serializeXML(root, tokens, this.stripANSIControlSequences);
|
||||||
const reportString = tokens.join('\n');
|
const reportString = tokens.join('\n');
|
||||||
const outputFile = this.outputFile || process.env[`PLAYWRIGHT_JUNIT_OUTPUT_NAME`];
|
if (this.outputFile) {
|
||||||
if (outputFile) {
|
fs.mkdirSync(path.dirname(this.outputFile), { recursive: true });
|
||||||
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
fs.writeFileSync(this.outputFile, reportString);
|
||||||
fs.writeFileSync(outputFile, reportString);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(reportString);
|
console.log(reportString);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,10 @@ class LineReporter extends BaseReporter {
|
||||||
private _failures = 0;
|
private _failures = 0;
|
||||||
private _lastTest: TestCase | undefined;
|
private _lastTest: TestCase | undefined;
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
override onBegin(config: FullConfig, suite: Suite) {
|
override onBegin(config: FullConfig, suite: Suite) {
|
||||||
super.onBegin(config, suite);
|
super.onBegin(config, suite);
|
||||||
this._total = suite.allTests().length;
|
this._total = suite.allTests().length;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ class ListReporter extends BaseReporter {
|
||||||
this._liveTerminal = process.stdout.isTTY || process.env.PWTEST_SKIP_TEST_OUTPUT || !!this._ttyWidthForTest;
|
this._liveTerminal = process.stdout.isTTY || process.env.PWTEST_SKIP_TEST_OUTPUT || !!this._ttyWidthForTest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
override onBegin(config: FullConfig, suite: Suite) {
|
override onBegin(config: FullConfig, suite: Suite) {
|
||||||
super.onBegin(config, suite);
|
super.onBegin(config, suite);
|
||||||
console.log();
|
console.log();
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ export class Multiplexer implements Reporter {
|
||||||
this._reporters = reporters;
|
this._reporters = reporters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return this._reporters.some(r => r.printsToStdio ? r.printsToStdio() : true);
|
||||||
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
reporter.onBegin?.(config, suite);
|
reporter.onBegin?.(config, suite);
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,6 @@ export class Runner {
|
||||||
html: HtmlReporter,
|
html: HtmlReporter,
|
||||||
};
|
};
|
||||||
const reporters: Reporter[] = [];
|
const reporters: Reporter[] = [];
|
||||||
const reporterConfig = this._loader.fullConfig().reporter;
|
|
||||||
if (reporterConfig.length === 1 && reporterConfig[0][0] === 'html') {
|
|
||||||
// For html reporter, add a line/dot report for convenience.
|
|
||||||
// Important to put html last because it stalls onEnd.
|
|
||||||
reporterConfig.unshift([process.stdout.isTTY && !process.env.CI ? 'line' : 'dot', { omitFailures: true }]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const r of this._loader.fullConfig().reporter) {
|
for (const r of this._loader.fullConfig().reporter) {
|
||||||
const [name, arg] = r;
|
const [name, arg] = r;
|
||||||
if (name in defaultReporters) {
|
if (name in defaultReporters) {
|
||||||
|
|
@ -96,6 +89,15 @@ export class Runner {
|
||||||
reporters.push(new reporterConstructor(arg));
|
reporters.push(new reporterConstructor(arg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const someReporterPrintsToStdio = reporters.some(r => {
|
||||||
|
const prints = r.printsToStdio ? r.printsToStdio() : true;
|
||||||
|
return prints;
|
||||||
|
});
|
||||||
|
if (reporters.length && !someReporterPrintsToStdio) {
|
||||||
|
// Add a line/dot report for convenience.
|
||||||
|
// Important to put it first, jsut in case some other reporter stalls onEnd.
|
||||||
|
reporters.unshift(process.stdout.isTTY && !process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter({ omitFailures: true }));
|
||||||
|
}
|
||||||
return new Multiplexer(reporters);
|
return new Multiplexer(reporters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,11 @@ export interface FullResult {
|
||||||
* went wrong outside of the test execution.
|
* went wrong outside of the test execution.
|
||||||
*/
|
*/
|
||||||
export interface Reporter {
|
export interface Reporter {
|
||||||
|
/**
|
||||||
|
* Whether this reporter uses stdio for reporting. When it does not, Playwright Test could add some output to enhance user
|
||||||
|
* experience.
|
||||||
|
*/
|
||||||
|
printsToStdio?(): boolean;
|
||||||
/**
|
/**
|
||||||
* Called once before running tests. All tests have been already discovered and put into a hierarchy of [Suite]s.
|
* Called once before running tests. All tests have been already discovered and put into a hierarchy of [Suite]s.
|
||||||
* @param config Resolved configuration.
|
* @param config Resolved configuration.
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { test, expect } from './playwright-test-fixtures';
|
import * as fs from 'fs';
|
||||||
|
import { test, expect, stripAscii } from './playwright-test-fixtures';
|
||||||
|
|
||||||
test('should support spec.ok', async ({ runInlineTest }) => {
|
test('should support spec.ok', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
|
|
@ -200,3 +201,20 @@ test('should have error position in results', async ({
|
||||||
expect(result.report.suites[0].specs[0].tests[0].results[0].errorLocation.line).toBe(7);
|
expect(result.report.suites[0].specs[0].tests[0].results[0].errorLocation.line).toBe(7);
|
||||||
expect(result.report.suites[0].specs[0].tests[0].results[0].errorLocation.column).toBe(23);
|
expect(result.report.suites[0].specs[0].tests[0].results[0].errorLocation.column).toBe(23);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should add dot in addition to file json', async ({ runInlineTest }, testInfo) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.ts': `
|
||||||
|
module.exports = { reporter: [['json', { outputFile: 'a.json' }]] };
|
||||||
|
`,
|
||||||
|
'a.test.js': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test('one', async ({}) => {
|
||||||
|
expect(1).toBe(1);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { reporter: '' });
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(stripAscii(result.output)).toContain('·');
|
||||||
|
expect(fs.existsSync(testInfo.outputPath('a.json'))).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ export interface FullResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Reporter {
|
export interface Reporter {
|
||||||
|
printsToStdio?(): boolean;
|
||||||
onBegin?(config: FullConfig, suite: Suite): void;
|
onBegin?(config: FullConfig, suite: Suite): void;
|
||||||
onTestBegin?(test: TestCase, result: TestResult): void;
|
onTestBegin?(test: TestCase, result: TestResult): void;
|
||||||
onStdOut?(chunk: string | Buffer, test?: TestCase, result?: TestResult): void;
|
onStdOut?(chunk: string | Buffer, test?: TestCase, result?: TestResult): void;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue