chore: migrate builtin reporters to ReporterV2 (#23985)
This allows builtin reporters to handle stdio between onConfigure and onBegin. Fixes #23539.
This commit is contained in:
parent
86c1abd934
commit
7e310f79af
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
import { colors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
||||
import path from 'path';
|
||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location, Reporter } from '../../types/testReporter';
|
||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
import { monotonicTime } from 'playwright-core/lib/utils';
|
||||
import type { ReporterV2 } from './reporterV2';
|
||||
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
||||
export const kOutputSymbol = Symbol('output');
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ type TestSummary = {
|
|||
fatalErrors: TestError[];
|
||||
};
|
||||
|
||||
export class BaseReporter implements Reporter {
|
||||
export class BaseReporter implements ReporterV2 {
|
||||
duration = 0;
|
||||
config!: FullConfig;
|
||||
suite!: Suite;
|
||||
|
|
@ -60,9 +61,16 @@ export class BaseReporter implements Reporter {
|
|||
this._ttyWidthForTest = parseInt(process.env.PWTEST_TTY_WIDTH || '', 10);
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
this.monotonicStartTime = monotonicTime();
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
|
||||
onConfigure(config: FullConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
onBegin(suite: Suite) {
|
||||
this.monotonicStartTime = monotonicTime();
|
||||
this.suite = suite;
|
||||
this.totalTestCount = suite.allTests().length;
|
||||
}
|
||||
|
|
@ -82,6 +90,9 @@ export class BaseReporter implements Reporter {
|
|||
(result as any)[kOutputSymbol].push(output);
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase, result: TestResult): void {
|
||||
}
|
||||
|
||||
onTestEnd(test: TestCase, result: TestResult) {
|
||||
// Ignore any tests that are run in parallel.
|
||||
for (let suite: Suite | undefined = test.parent; suite; suite = suite.parent) {
|
||||
|
|
@ -104,6 +115,19 @@ export class BaseReporter implements Reporter {
|
|||
this.result = result;
|
||||
}
|
||||
|
||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep): void {
|
||||
}
|
||||
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep): void {
|
||||
}
|
||||
|
||||
async onExit() {
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected ttyWidth() {
|
||||
return this._ttyWidthForTest || process.stdout.columns || 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@
|
|||
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { BaseReporter, formatError } from './base';
|
||||
import type { FullResult, TestCase, TestResult, FullConfig, Suite, TestError } from '../../types/testReporter';
|
||||
import type { FullResult, TestCase, TestResult, Suite, TestError } from '../../types/testReporter';
|
||||
|
||||
class DotReporter extends BaseReporter {
|
||||
private _counter = 0;
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return true;
|
||||
}
|
||||
|
||||
override onBegin(config: FullConfig, suite: Suite) {
|
||||
super.onBegin(config, suite);
|
||||
override onBegin(suite: Suite) {
|
||||
super.onBegin(suite);
|
||||
console.log(this.generateStartingMessage());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,50 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Reporter } from '../../types/testReporter';
|
||||
import type { ReporterV2 } from './reporterV2';
|
||||
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Suite } from '../../types/testReporter';
|
||||
|
||||
class EmptyReporter implements Reporter {
|
||||
class EmptyReporter implements ReporterV2 {
|
||||
onConfigure(config: FullConfig) {
|
||||
}
|
||||
|
||||
onBegin(suite: Suite) {
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase, result: TestResult) {
|
||||
}
|
||||
|
||||
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||
}
|
||||
|
||||
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||
}
|
||||
|
||||
onTestEnd(test: TestCase, result: TestResult) {
|
||||
}
|
||||
|
||||
async onEnd(result: FullResult) {
|
||||
}
|
||||
|
||||
async onExit() {
|
||||
}
|
||||
|
||||
onError(error: TestError) {
|
||||
}
|
||||
|
||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
}
|
||||
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
}
|
||||
|
||||
export default EmptyReporter;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class GitHubLogger {
|
|||
export class GitHubReporter extends BaseReporter {
|
||||
githubLogger = new GitHubLogger();
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import type { TransformCallback } from 'stream';
|
||||
import { Transform } from 'stream';
|
||||
import type { FullConfig, Reporter, Suite } from '../../types/testReporter';
|
||||
import type { FullConfig, Suite } from '../../types/testReporter';
|
||||
import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, removeFolders } from 'playwright-core/lib/utils';
|
||||
import type { JsonAttachment, JsonReport, JsonSuite, JsonTestCase, JsonTestResult, JsonTestStep } from './raw';
|
||||
import RawReporter from './raw';
|
||||
|
|
@ -31,6 +31,7 @@ import { yazl } from 'playwright-core/lib/zipBundle';
|
|||
import { mime } from 'playwright-core/lib/utilsBundle';
|
||||
import type { HTMLReport, Stats, TestAttachment, TestCase, TestCaseSummary, TestFile, TestFileSummary, TestResult, TestStep } from '@html-reporter/types';
|
||||
import { FullConfigInternal } from '../common/config';
|
||||
import EmptyReporter from './empty';
|
||||
|
||||
type TestEntry = {
|
||||
testCase: TestCase;
|
||||
|
|
@ -47,7 +48,7 @@ type HtmlReporterOptions = {
|
|||
attachmentsBaseURL?: string,
|
||||
};
|
||||
|
||||
class HtmlReporter implements Reporter {
|
||||
class HtmlReporter extends EmptyReporter {
|
||||
private config!: FullConfig;
|
||||
private suite!: Suite;
|
||||
private _options: HtmlReporterOptions;
|
||||
|
|
@ -57,21 +58,25 @@ class HtmlReporter implements Reporter {
|
|||
private _buildResult: { ok: boolean, singleTestId: string | undefined } | undefined;
|
||||
|
||||
constructor(options: HtmlReporterOptions) {
|
||||
super();
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
override onConfigure(config: FullConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
override onBegin(suite: Suite) {
|
||||
const { outputFolder, open, attachmentsBaseURL } = this._resolveOptions();
|
||||
this._outputFolder = outputFolder;
|
||||
this._open = open;
|
||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||
const reportedWarnings = new Set<string>();
|
||||
for (const project of config.projects) {
|
||||
for (const project of this.config.projects) {
|
||||
if (outputFolder.startsWith(project.outputDir) || project.outputDir.startsWith(outputFolder)) {
|
||||
const key = outputFolder + '|' + project.outputDir;
|
||||
if (reportedWarnings.has(key))
|
||||
|
|
@ -100,7 +105,7 @@ class HtmlReporter implements Reporter {
|
|||
};
|
||||
}
|
||||
|
||||
async onEnd() {
|
||||
override async onEnd() {
|
||||
const projectSuites = this.suite.suites;
|
||||
const reports = projectSuites.map(suite => {
|
||||
const rawReporter = new RawReporter();
|
||||
|
|
@ -112,7 +117,7 @@ class HtmlReporter implements Reporter {
|
|||
this._buildResult = await builder.build(this.config.metadata, reports);
|
||||
}
|
||||
|
||||
async onExit() {
|
||||
override async onExit() {
|
||||
if (process.env.CI || !this._buildResult)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,44 +19,47 @@ import { colors } from 'playwright-core/lib/utilsBundle';
|
|||
import { codeFrameColumns } from '../transform/babelBundle';
|
||||
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter';
|
||||
import { Suite } from '../common/test';
|
||||
import { Multiplexer } from './multiplexer';
|
||||
import { prepareErrorStack, relativeFilePath } from './base';
|
||||
import type { ReporterV2 } from './reporterV2';
|
||||
|
||||
export class InternalReporter implements ReporterV2 {
|
||||
private _multiplexer: Multiplexer;
|
||||
private _reporter: ReporterV2;
|
||||
private _didBegin = false;
|
||||
private _config!: FullConfig;
|
||||
|
||||
constructor(reporters: ReporterV2[]) {
|
||||
this._multiplexer = new Multiplexer(reporters);
|
||||
constructor(reporter: ReporterV2) {
|
||||
this._reporter = reporter;
|
||||
}
|
||||
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
|
||||
onConfigure(config: FullConfig) {
|
||||
this._config = config;
|
||||
this._multiplexer.onConfigure(config);
|
||||
this._reporter.onConfigure(config);
|
||||
}
|
||||
|
||||
onBegin(suite: Suite) {
|
||||
this._didBegin = true;
|
||||
this._multiplexer.onBegin(suite);
|
||||
this._reporter.onBegin(suite);
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase, result: TestResult) {
|
||||
this._multiplexer.onTestBegin(test, result);
|
||||
this._reporter.onTestBegin(test, result);
|
||||
}
|
||||
|
||||
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||
this._multiplexer.onStdOut(chunk, test, result);
|
||||
this._reporter.onStdOut(chunk, test, result);
|
||||
}
|
||||
|
||||
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||
this._multiplexer.onStdErr(chunk, test, result);
|
||||
this._reporter.onStdErr(chunk, test, result);
|
||||
}
|
||||
|
||||
onTestEnd(test: TestCase, result: TestResult) {
|
||||
this._addSnippetToTestErrors(test, result);
|
||||
this._multiplexer.onTestEnd(test, result);
|
||||
this._reporter.onTestEnd(test, result);
|
||||
}
|
||||
|
||||
async onEnd(result: FullResult) {
|
||||
|
|
@ -64,29 +67,29 @@ export class InternalReporter implements ReporterV2 {
|
|||
// onBegin was not reported, emit it.
|
||||
this.onBegin(new Suite('', 'root'));
|
||||
}
|
||||
await this._multiplexer.onEnd(result);
|
||||
await this._reporter.onEnd(result);
|
||||
}
|
||||
|
||||
async onExit() {
|
||||
await this._multiplexer.onExit();
|
||||
await this._reporter.onExit();
|
||||
}
|
||||
|
||||
onError(error: TestError) {
|
||||
addLocationAndSnippetToError(this._config, error);
|
||||
this._multiplexer.onError(error);
|
||||
this._reporter.onError(error);
|
||||
}
|
||||
|
||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
this._multiplexer.onStepBegin(test, result, step);
|
||||
this._reporter.onStepBegin(test, result, step);
|
||||
}
|
||||
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
this._addSnippetToStepError(test, step);
|
||||
this._multiplexer.onStepEnd(test, result, step);
|
||||
this._reporter.onStepEnd(test, result, step);
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
return this._multiplexer.printsToStdio();
|
||||
return this._reporter.printsToStdio();
|
||||
}
|
||||
|
||||
private _addSnippetToTestErrors(test: TestCase, result: TestResult) {
|
||||
|
|
|
|||
|
|
@ -16,40 +16,45 @@
|
|||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, Location, Reporter, JSONReport, JSONReportSuite, JSONReportSpec, JSONReportTest, JSONReportTestResult, JSONReportTestStep, JSONReportError } from '../../types/testReporter';
|
||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, FullResult, Location, JSONReport, JSONReportSuite, JSONReportSpec, JSONReportTest, JSONReportTestResult, JSONReportTestStep, JSONReportError } from '../../types/testReporter';
|
||||
import { formatError, prepareErrorStack } from './base';
|
||||
import { MultiMap } from 'playwright-core/lib/utils';
|
||||
import { assert } from 'playwright-core/lib/utils';
|
||||
import { FullProjectInternal } from '../common/config';
|
||||
import EmptyReporter from './empty';
|
||||
|
||||
export function toPosixPath(aPath: string): string {
|
||||
return aPath.split(path.sep).join(path.posix.sep);
|
||||
}
|
||||
|
||||
class JSONReporter implements Reporter {
|
||||
class JSONReporter extends EmptyReporter {
|
||||
config!: FullConfig;
|
||||
suite!: Suite;
|
||||
private _errors: TestError[] = [];
|
||||
private _outputFile: string | undefined;
|
||||
|
||||
constructor(options: { outputFile?: string } = {}) {
|
||||
super();
|
||||
this._outputFile = options.outputFile || reportOutputNameFromEnv();
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return !this._outputFile;
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
override onConfigure(config: FullConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
override onBegin(suite: Suite) {
|
||||
this.suite = suite;
|
||||
}
|
||||
|
||||
onError(error: TestError): void {
|
||||
override onError(error: TestError): void {
|
||||
this._errors.push(error);
|
||||
}
|
||||
|
||||
async onEnd(result: FullResult) {
|
||||
override async onEnd(result: FullResult) {
|
||||
outputReport(this._serializeReport(), this.config, this._outputFile);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,13 @@
|
|||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import type { FullConfig, FullResult, Reporter, Suite, TestCase } from '../../types/testReporter';
|
||||
import type { FullConfig, FullResult, Suite, TestCase } from '../../types/testReporter';
|
||||
import { monotonicTime } from 'playwright-core/lib/utils';
|
||||
import { formatFailure, stripAnsiEscapes } from './base';
|
||||
import { assert } from 'playwright-core/lib/utils';
|
||||
import EmptyReporter from './empty';
|
||||
|
||||
class JUnitReporter implements Reporter {
|
||||
class JUnitReporter extends EmptyReporter {
|
||||
private config!: FullConfig;
|
||||
private suite!: Suite;
|
||||
private timestamp!: Date;
|
||||
|
|
@ -33,22 +34,26 @@ class JUnitReporter implements Reporter {
|
|||
private stripANSIControlSequences = false;
|
||||
|
||||
constructor(options: { outputFile?: string, stripANSIControlSequences?: boolean } = {}) {
|
||||
super();
|
||||
this.outputFile = options.outputFile || reportOutputNameFromEnv();
|
||||
this.stripANSIControlSequences = options.stripANSIControlSequences || false;
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return !this.outputFile;
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
override onConfigure(config: FullConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
override onBegin(suite: Suite) {
|
||||
this.suite = suite;
|
||||
this.timestamp = new Date();
|
||||
this.startTime = monotonicTime();
|
||||
}
|
||||
|
||||
async onEnd(result: FullResult) {
|
||||
override async onEnd(result: FullResult) {
|
||||
const duration = monotonicTime() - this.startTime;
|
||||
const children: XMLEntry[] = [];
|
||||
for (const projectSuite of this.suite.suites) {
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@
|
|||
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { BaseReporter, formatError, formatFailure, formatTestTitle } from './base';
|
||||
import type { FullConfig, TestCase, Suite, TestResult, FullResult, TestStep, TestError } from '../../types/testReporter';
|
||||
import type { TestCase, Suite, TestResult, FullResult, TestStep, TestError } from '../../types/testReporter';
|
||||
|
||||
class LineReporter extends BaseReporter {
|
||||
private _current = 0;
|
||||
private _failures = 0;
|
||||
private _lastTest: TestCase | undefined;
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return true;
|
||||
}
|
||||
|
||||
override onBegin(config: FullConfig, suite: Suite) {
|
||||
super.onBegin(config, suite);
|
||||
override onBegin(suite: Suite) {
|
||||
super.onBegin(suite);
|
||||
console.log(this.generateStartingMessage());
|
||||
console.log();
|
||||
}
|
||||
|
|
@ -62,17 +62,20 @@ class LineReporter extends BaseReporter {
|
|||
console.log();
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase, result: TestResult) {
|
||||
override onTestBegin(test: TestCase, result: TestResult) {
|
||||
super.onTestBegin(test, result);
|
||||
++this._current;
|
||||
this._updateLine(test, result, undefined);
|
||||
}
|
||||
|
||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
override onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
super.onStepBegin(test, result, step);
|
||||
if (step.category === 'test.step')
|
||||
this._updateLine(test, result, step);
|
||||
}
|
||||
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
override onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
super.onStepEnd(test, result, step);
|
||||
if (step.category === 'test.step')
|
||||
this._updateLine(test, result, step.parent);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
/* eslint-disable no-console */
|
||||
import { colors, ms as milliseconds } from 'playwright-core/lib/utilsBundle';
|
||||
import { BaseReporter, formatError, formatTestTitle, stepSuffix, stripAnsiEscapes } from './base';
|
||||
import type { FullConfig, FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||
import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||
|
||||
// Allow it in the Visual Studio Code Terminal and the new Windows Terminal
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION;
|
||||
|
|
@ -41,12 +41,12 @@ class ListReporter extends BaseReporter {
|
|||
this._liveTerminal = process.stdout.isTTY || !!process.env.PWTEST_TTY_WIDTH;
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return true;
|
||||
}
|
||||
|
||||
override onBegin(config: FullConfig, suite: Suite) {
|
||||
super.onBegin(config, suite);
|
||||
override onBegin(suite: Suite) {
|
||||
super.onBegin(suite);
|
||||
const startingMessage = this.generateStartingMessage();
|
||||
if (startingMessage) {
|
||||
console.log(startingMessage);
|
||||
|
|
@ -54,7 +54,8 @@ class ListReporter extends BaseReporter {
|
|||
}
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase, result: TestResult) {
|
||||
override onTestBegin(test: TestCase, result: TestResult) {
|
||||
super.onTestBegin(test, result);
|
||||
if (this._liveTerminal)
|
||||
this._maybeWriteNewLine();
|
||||
this._resultIndex.set(result, String(this._resultIndex.size + 1));
|
||||
|
|
@ -77,7 +78,8 @@ class ListReporter extends BaseReporter {
|
|||
this._dumpToStdio(test, chunk, process.stderr);
|
||||
}
|
||||
|
||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
override onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||
super.onStepBegin(test, result, step);
|
||||
if (step.category !== 'test.step')
|
||||
return;
|
||||
const testIndex = this._resultIndex.get(result)!;
|
||||
|
|
@ -102,7 +104,8 @@ class ListReporter extends BaseReporter {
|
|||
}
|
||||
}
|
||||
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
override onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||
super.onStepEnd(test, result, step);
|
||||
if (step.category !== 'test.step')
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class MarkdownReporter extends BaseReporter {
|
|||
this._options = options;
|
||||
}
|
||||
|
||||
printsToStdio() {
|
||||
override printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ export class Multiplexer implements ReporterV2 {
|
|||
this._reporters = reporters;
|
||||
}
|
||||
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
|
||||
onConfigure(config: FullConfig) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onConfigure(config));
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export interface ReporterV2 {
|
|||
onStepBegin(test: TestCase, result: TestResult, step: TestStep): void;
|
||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep): void;
|
||||
printsToStdio(): boolean;
|
||||
version(): 'v2';
|
||||
}
|
||||
|
||||
type StdIOChunk = {
|
||||
|
|
@ -37,7 +38,16 @@ type StdIOChunk = {
|
|||
result?: TestResult;
|
||||
};
|
||||
|
||||
export class ReporterV2Wrapper implements ReporterV2 {
|
||||
export function wrapReporterAsV2(reporter: Reporter | ReporterV2): ReporterV2 {
|
||||
try {
|
||||
if ('version' in reporter && reporter.version() === 'v2')
|
||||
return reporter as ReporterV2;
|
||||
} catch (e) {
|
||||
}
|
||||
return new ReporterV2Wrapper(reporter as Reporter);
|
||||
}
|
||||
|
||||
class ReporterV2Wrapper implements ReporterV2 {
|
||||
private _reporter: Reporter;
|
||||
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
||||
private _config!: FullConfig;
|
||||
|
|
@ -46,6 +56,10 @@ export class ReporterV2Wrapper implements ReporterV2 {
|
|||
this._reporter = reporter;
|
||||
}
|
||||
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
|
||||
onConfigure(config: FullConfig) {
|
||||
this._config = config;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ export class TeleReporterEmitter implements ReporterV2 {
|
|||
this._skipBuffers = skipBuffers;
|
||||
}
|
||||
|
||||
version(): 'v2' {
|
||||
return 'v2';
|
||||
}
|
||||
|
||||
onConfigure(config: FullConfig) {
|
||||
this._rootDir = config.rootDir;
|
||||
this._messageSink({ method: 'onConfigure', params: { config: this._serializeConfig(config) } });
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type { FullConfig, Reporter, TestError } from '../../types/testReporter';
|
||||
import type { FullConfig, TestError } from '../../types/testReporter';
|
||||
import { formatError } from '../reporters/base';
|
||||
import DotReporter from '../reporters/dot';
|
||||
import EmptyReporter from '../reporters/empty';
|
||||
|
|
@ -31,10 +31,11 @@ import type { BuiltInReporter, FullConfigInternal } from '../common/config';
|
|||
import { loadReporter } from './loadUtils';
|
||||
import { BlobReporter } from '../reporters/blob';
|
||||
import type { ReporterDescription } from '../../types/test';
|
||||
import { type ReporterV2, ReporterV2Wrapper } from '../reporters/reporterV2';
|
||||
import { type ReporterV2, wrapReporterAsV2 } from '../reporters/reporterV2';
|
||||
|
||||
export async function createReporters(config: FullConfigInternal, mode: 'list' | 'run' | 'ui' | 'merge', descriptions?: ReporterDescription[]): Promise<ReporterV2[]> {
|
||||
const defaultReporters: { [key in Exclude<BuiltInReporter, 'blob'>]: new(arg: any) => Reporter } = {
|
||||
const defaultReporters: { [key in BuiltInReporter]: new(arg: any) => ReporterV2 } = {
|
||||
blob: BlobReporter,
|
||||
dot: mode === 'list' ? ListModeReporter : DotReporter,
|
||||
line: mode === 'list' ? ListModeReporter : LineReporter,
|
||||
list: mode === 'list' ? ListModeReporter : ListReporter,
|
||||
|
|
@ -50,18 +51,16 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' |
|
|||
for (const r of descriptions) {
|
||||
const [name, arg] = r;
|
||||
const options = { ...arg, configDir: config.configDir };
|
||||
if (name === 'blob') {
|
||||
reporters.push(new BlobReporter(options));
|
||||
} else if (name in defaultReporters) {
|
||||
reporters.push(new ReporterV2Wrapper(new defaultReporters[name as keyof typeof defaultReporters](options)));
|
||||
if (name in defaultReporters) {
|
||||
reporters.push(new defaultReporters[name as keyof typeof defaultReporters](options));
|
||||
} else {
|
||||
const reporterConstructor = await loadReporter(config, name);
|
||||
reporters.push(new ReporterV2Wrapper(new reporterConstructor(options)));
|
||||
reporters.push(wrapReporterAsV2(new reporterConstructor(options)));
|
||||
}
|
||||
}
|
||||
if (process.env.PW_TEST_REPORTER) {
|
||||
const reporterConstructor = await loadReporter(config, process.env.PW_TEST_REPORTER);
|
||||
reporters.push(new ReporterV2Wrapper(new reporterConstructor()));
|
||||
reporters.push(wrapReporterAsV2(new reporterConstructor()));
|
||||
}
|
||||
|
||||
const someReporterPrintsToStdio = reporters.some(r => r.printsToStdio());
|
||||
|
|
@ -69,18 +68,21 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' |
|
|||
// Add a line/dot/list-mode reporter for convenience.
|
||||
// Important to put it first, jsut in case some other reporter stalls onEnd.
|
||||
if (mode === 'list')
|
||||
reporters.unshift(new ReporterV2Wrapper(new ListModeReporter()));
|
||||
reporters.unshift(new ListModeReporter());
|
||||
else
|
||||
reporters.unshift(new ReporterV2Wrapper(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter()));
|
||||
reporters.unshift(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter());
|
||||
}
|
||||
return reporters;
|
||||
}
|
||||
|
||||
class ListModeReporter implements Reporter {
|
||||
class ListModeReporter extends EmptyReporter {
|
||||
private config!: FullConfig;
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite): void {
|
||||
override onConfigure(config: FullConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
override onBegin(suite: Suite): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Listing tests:`);
|
||||
const tests = suite.allTests();
|
||||
|
|
@ -88,7 +90,7 @@ class ListModeReporter implements Reporter {
|
|||
for (const test of tests) {
|
||||
// root, project, file, ...describes, test
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const location = `${path.relative(config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`;
|
||||
const location = `${path.relative(this.config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`;
|
||||
const projectTitle = projectName ? `[${projectName}] › ` : '';
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(` ${projectTitle}${location} › ${titles.join(' › ')}`);
|
||||
|
|
@ -98,7 +100,7 @@ class ListModeReporter implements Reporter {
|
|||
console.log(`Total: ${tests.length} ${tests.length === 1 ? 'test' : 'tests'} in ${files.size} ${files.size === 1 ? 'file' : 'files'}`);
|
||||
}
|
||||
|
||||
onError(error: TestError) {
|
||||
override onError(error: TestError) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('\n' + formatError(this.config, error, false).message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { colors } from 'playwright-core/lib/utilsBundle';
|
|||
import { runWatchModeLoop } from './watchMode';
|
||||
import { runUIMode } from './uiMode';
|
||||
import { InternalReporter } from '../reporters/internalReporter';
|
||||
import { Multiplexer } from '../reporters/multiplexer';
|
||||
|
||||
type ProjectConfigWithFiles = {
|
||||
name: string;
|
||||
|
|
@ -69,7 +70,7 @@ export class Runner {
|
|||
// Legacy webServer support.
|
||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||
|
||||
const reporter = new InternalReporter(await createReporters(config, listOnly ? 'list' : 'run'));
|
||||
const reporter = new InternalReporter(new Multiplexer(await createReporters(config, listOnly ? 'list' : 'run')));
|
||||
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process', { failOnLoadErrors: true })
|
||||
: createTaskRunner(config, reporter);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import type { FSWatcher } from 'chokidar';
|
|||
import { open } from 'playwright-core/lib/utilsBundle';
|
||||
import ListReporter from '../reporters/list';
|
||||
import type { OpenTraceViewerOptions, Transport } from 'playwright-core/lib/server/trace/viewer/traceViewer';
|
||||
import { ReporterV2Wrapper } from '../reporters/reporterV2';
|
||||
import { Multiplexer } from '../reporters/multiplexer';
|
||||
|
||||
class UIMode {
|
||||
private _config: FullConfigInternal;
|
||||
|
|
@ -68,7 +68,7 @@ class UIMode {
|
|||
}
|
||||
|
||||
async runGlobalSetup(): Promise<FullResult['status']> {
|
||||
const reporter = new InternalReporter([new ReporterV2Wrapper(new ListReporter())]);
|
||||
const reporter = new InternalReporter(new ListReporter());
|
||||
const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter);
|
||||
reporter.onConfigure(this._config.config);
|
||||
const testRun = new TestRun(this._config, reporter);
|
||||
|
|
@ -161,8 +161,7 @@ class UIMode {
|
|||
}
|
||||
|
||||
private async _listTests() {
|
||||
const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true);
|
||||
const reporter = new InternalReporter([listReporter]);
|
||||
const reporter = new InternalReporter(new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true));
|
||||
this._config.cliListOnly = true;
|
||||
this._config.testIdMatcher = undefined;
|
||||
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process', { failOnLoadErrors: false });
|
||||
|
|
@ -188,7 +187,7 @@ class UIMode {
|
|||
|
||||
const reporters = await createReporters(this._config, 'ui');
|
||||
reporters.push(new TeleReporterEmitter(e => this._dispatchEvent(e.method, e.params), true));
|
||||
const reporter = new InternalReporter(reporters);
|
||||
const reporter = new InternalReporter(new Multiplexer(reporters));
|
||||
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
|
||||
const testRun = new TestRun(this._config, reporter);
|
||||
clearCompilationCache();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import { enquirer } from '../utilsBundle';
|
|||
import { separator } from '../reporters/base';
|
||||
import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer';
|
||||
import ListReporter from '../reporters/list';
|
||||
import { ReporterV2Wrapper } from '../reporters/reporterV2';
|
||||
|
||||
class FSWatcher {
|
||||
private _dirtyTestFiles = new Map<FullProjectInternal, Set<string>>();
|
||||
|
|
@ -113,7 +112,7 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
p.project.retries = 0;
|
||||
|
||||
// Perform global setup.
|
||||
const reporter = new InternalReporter([new ReporterV2Wrapper(new ListReporter())]);
|
||||
const reporter = new InternalReporter(new ListReporter());
|
||||
const testRun = new TestRun(config, reporter);
|
||||
const taskRunner = createTaskRunnerForWatchSetup(config, reporter);
|
||||
reporter.onConfigure(config.config);
|
||||
|
|
@ -281,7 +280,7 @@ async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<s
|
|||
title?: string,
|
||||
}) {
|
||||
printConfiguration(config, options?.title);
|
||||
const reporter = new InternalReporter([new ReporterV2Wrapper(new ListReporter())]);
|
||||
const reporter = new InternalReporter(new ListReporter());
|
||||
const taskRunner = createTaskRunnerForWatch(config, reporter, options?.additionalFileMatcher);
|
||||
const testRun = new TestRun(config, reporter);
|
||||
clearCompilationCache();
|
||||
|
|
|
|||
|
|
@ -606,6 +606,8 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
|||
};
|
||||
let config: FullConfig;
|
||||
receiver = new TeleReporterReceiver(pathSeparator, {
|
||||
version: () => 'v2',
|
||||
|
||||
onConfigure: (c: FullConfig) => {
|
||||
config = c;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -704,3 +704,32 @@ test('should be able to ignore "stderr"', async ({ runInlineTest }, { workerInde
|
|||
expect(result.passed).toBe(1);
|
||||
expect(result.output).not.toContain('error from server');
|
||||
});
|
||||
|
||||
test('should forward stdout when set to "pipe" before server is ready', async ({ runInlineTest }, { workerIndex }) => {
|
||||
test.skip(process.platform === 'win32', 'No sending SIGINT on Windows');
|
||||
|
||||
const result = await runInlineTest({
|
||||
'web-server.js': `
|
||||
console.log('output from server');
|
||||
console.log('\\n%%SEND-SIGINT%%');
|
||||
setTimeout(() => {}, 10000000);
|
||||
`,
|
||||
'test.spec.ts': `
|
||||
import { test, expect } from '@playwright/test';
|
||||
test('pass', async ({}) => {});
|
||||
`,
|
||||
'playwright.config.ts': `
|
||||
module.exports = {
|
||||
webServer: {
|
||||
command: 'node web-server.js',
|
||||
port: 12345,
|
||||
stdout: 'pipe',
|
||||
timeout: 3000,
|
||||
},
|
||||
};
|
||||
`,
|
||||
}, { workers: 1 }, {}, { sendSIGINTAfter: 1 });
|
||||
expect(result.passed).toBe(0);
|
||||
expect(result.output).toContain('[WebServer] output from server');
|
||||
expect(result.output).not.toContain('Timed out waiting 3000ms');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue