diff --git a/packages/playwright-test/src/reporters/base.ts b/packages/playwright-test/src/reporters/base.ts index 7481965c1e..cb11f1997c 100644 --- a/packages/playwright-test/src/reporters/base.ts +++ b/packages/playwright-test/src/reporters/base.ts @@ -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; } diff --git a/packages/playwright-test/src/reporters/dot.ts b/packages/playwright-test/src/reporters/dot.ts index 849211fd96..e52f3c02b5 100644 --- a/packages/playwright-test/src/reporters/dot.ts +++ b/packages/playwright-test/src/reporters/dot.ts @@ -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()); } diff --git a/packages/playwright-test/src/reporters/empty.ts b/packages/playwright-test/src/reporters/empty.ts index e5d310868e..09bd63668c 100644 --- a/packages/playwright-test/src/reporters/empty.ts +++ b/packages/playwright-test/src/reporters/empty.ts @@ -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; diff --git a/packages/playwright-test/src/reporters/github.ts b/packages/playwright-test/src/reporters/github.ts index 802b24fa07..9fe888edfe 100644 --- a/packages/playwright-test/src/reporters/github.ts +++ b/packages/playwright-test/src/reporters/github.ts @@ -59,7 +59,7 @@ class GitHubLogger { export class GitHubReporter extends BaseReporter { githubLogger = new GitHubLogger(); - printsToStdio() { + override printsToStdio() { return false; } diff --git a/packages/playwright-test/src/reporters/html.ts b/packages/playwright-test/src/reporters/html.ts index 091da19fb3..38a19d343e 100644 --- a/packages/playwright-test/src/reporters/html.ts +++ b/packages/playwright-test/src/reporters/html.ts @@ -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(); - 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; diff --git a/packages/playwright-test/src/reporters/internalReporter.ts b/packages/playwright-test/src/reporters/internalReporter.ts index e7fbff9827..a796803a47 100644 --- a/packages/playwright-test/src/reporters/internalReporter.ts +++ b/packages/playwright-test/src/reporters/internalReporter.ts @@ -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) { diff --git a/packages/playwright-test/src/reporters/json.ts b/packages/playwright-test/src/reporters/json.ts index 33fd3a58cd..574ad28340 100644 --- a/packages/playwright-test/src/reporters/json.ts +++ b/packages/playwright-test/src/reporters/json.ts @@ -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); } diff --git a/packages/playwright-test/src/reporters/junit.ts b/packages/playwright-test/src/reporters/junit.ts index fa457b4b05..41bea06d96 100644 --- a/packages/playwright-test/src/reporters/junit.ts +++ b/packages/playwright-test/src/reporters/junit.ts @@ -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) { diff --git a/packages/playwright-test/src/reporters/line.ts b/packages/playwright-test/src/reporters/line.ts index 9b68d9ecfa..822009372b 100644 --- a/packages/playwright-test/src/reporters/line.ts +++ b/packages/playwright-test/src/reporters/line.ts @@ -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); } diff --git a/packages/playwright-test/src/reporters/list.ts b/packages/playwright-test/src/reporters/list.ts index 5d394f3c2f..480ddf35d3 100644 --- a/packages/playwright-test/src/reporters/list.ts +++ b/packages/playwright-test/src/reporters/list.ts @@ -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; diff --git a/packages/playwright-test/src/reporters/markdown.ts b/packages/playwright-test/src/reporters/markdown.ts index 8152b45549..d0a4731ecb 100644 --- a/packages/playwright-test/src/reporters/markdown.ts +++ b/packages/playwright-test/src/reporters/markdown.ts @@ -33,7 +33,7 @@ class MarkdownReporter extends BaseReporter { this._options = options; } - printsToStdio() { + override printsToStdio() { return false; } diff --git a/packages/playwright-test/src/reporters/multiplexer.ts b/packages/playwright-test/src/reporters/multiplexer.ts index 48c89f4cee..eae794470a 100644 --- a/packages/playwright-test/src/reporters/multiplexer.ts +++ b/packages/playwright-test/src/reporters/multiplexer.ts @@ -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)); diff --git a/packages/playwright-test/src/reporters/reporterV2.ts b/packages/playwright-test/src/reporters/reporterV2.ts index 6a59c729d5..914534441d 100644 --- a/packages/playwright-test/src/reporters/reporterV2.ts +++ b/packages/playwright-test/src/reporters/reporterV2.ts @@ -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; } diff --git a/packages/playwright-test/src/reporters/teleEmitter.ts b/packages/playwright-test/src/reporters/teleEmitter.ts index 159800c317..204acf9da1 100644 --- a/packages/playwright-test/src/reporters/teleEmitter.ts +++ b/packages/playwright-test/src/reporters/teleEmitter.ts @@ -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) } }); diff --git a/packages/playwright-test/src/runner/reporters.ts b/packages/playwright-test/src/runner/reporters.ts index b19769415c..c7851896d5 100644 --- a/packages/playwright-test/src/runner/reporters.ts +++ b/packages/playwright-test/src/runner/reporters.ts @@ -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 { - const defaultReporters: { [key in Exclude]: 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); } diff --git a/packages/playwright-test/src/runner/runner.ts b/packages/playwright-test/src/runner/runner.ts index 082b2b9529..6fa8bb3c6e 100644 --- a/packages/playwright-test/src/runner/runner.ts +++ b/packages/playwright-test/src/runner/runner.ts @@ -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); diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index d5b8cc181a..79923728bd 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -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 { - 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(); diff --git a/packages/playwright-test/src/runner/watchMode.ts b/packages/playwright-test/src/runner/watchMode.ts index 4bd8ffa52c..690ba58a3e 100644 --- a/packages/playwright-test/src/runner/watchMode.ts +++ b/packages/playwright-test/src/runner/watchMode.ts @@ -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>(); @@ -113,7 +112,7 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise => { }; let config: FullConfig; receiver = new TeleReporterReceiver(pathSeparator, { + version: () => 'v2', + onConfigure: (c: FullConfig) => { config = c; }, diff --git a/tests/playwright-test/web-server.spec.ts b/tests/playwright-test/web-server.spec.ts index 6c4689c6cf..3e9bbae462 100644 --- a/tests/playwright-test/web-server.spec.ts +++ b/tests/playwright-test/web-server.spec.ts @@ -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'); +});