diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 9cf3a0856b..3f982d22d5 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -179,6 +179,8 @@ async function mergeReports(reportDir: string | undefined, opts: { [key: string] const configLoader = new ConfigLoader(); const config = await (configFile ? configLoader.loadConfigFile(configFile) : configLoader.loadEmptyConfig(process.cwd())); const dir = path.resolve(process.cwd(), reportDir || 'playwright-report'); + if (!(await fs.promises.stat(dir)).isDirectory()) + throw new Error('Directory does not exist: ' + dir); await createMergedReport(config, dir, opts.reporter || 'list'); } diff --git a/packages/playwright-test/src/reporters/DEPS.list b/packages/playwright-test/src/reporters/DEPS.list index f2b0f3c999..4f7cb588dd 100644 --- a/packages/playwright-test/src/reporters/DEPS.list +++ b/packages/playwright-test/src/reporters/DEPS.list @@ -5,4 +5,4 @@ ../utilsBundle.ts [blob.ts] -../runner/loadUtils.ts +../runner/reporters.ts diff --git a/packages/playwright-test/src/reporters/blob.ts b/packages/playwright-test/src/reporters/blob.ts index 8c90548d56..e303b524c2 100644 --- a/packages/playwright-test/src/reporters/blob.ts +++ b/packages/playwright-test/src/reporters/blob.ts @@ -16,25 +16,18 @@ import type { EventEmitter } from 'events'; import fs from 'fs'; -import path from 'path'; import os from 'os'; +import path from 'path'; import { ManualPromise, ZipFile, calculateSha1, removeFolders } from 'playwright-core/lib/utils'; import { mime } from 'playwright-core/lib/utilsBundle'; import { yazl } from 'playwright-core/lib/zipBundle'; import { Readable } from 'stream'; -import type { FullConfig, FullResult, Reporter, TestResult } from '../../types/testReporter'; -import type { BuiltInReporter, FullConfigInternal } from '../common/config'; +import type { FullConfig, FullResult, TestResult } from '../../types/testReporter'; +import type { FullConfigInternal } from '../common/config'; import type { Suite } from '../common/test'; import { TeleReporterReceiver, type JsonEvent, type JsonProject, type JsonSuite, type JsonTestResultEnd } from '../isomorphic/teleReceiver'; -import DotReporter from '../reporters/dot'; -import EmptyReporter from '../reporters/empty'; -import GitHubReporter from '../reporters/github'; -import JSONReporter from '../reporters/json'; -import JUnitReporter from '../reporters/junit'; -import LineReporter from '../reporters/line'; -import ListReporter from '../reporters/list'; -import { loadReporter } from '../runner/loadUtils'; -import HtmlReporter, { defaultReportFolder } from './html'; +import { createReporters } from '../runner/reporters'; +import { defaultReportFolder } from './html'; import { TeleReporterEmitter } from './teleEmitter'; @@ -120,40 +113,15 @@ export async function createMergedReport(config: FullConfigInternal, dir: string const events = mergeEvents(shardReports); patchAttachmentPaths(events, resourceDir); - const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = { - dot: DotReporter, - line: LineReporter, - list: ListReporter, - github: GitHubReporter, - json: JSONReporter, - junit: JUnitReporter, - null: EmptyReporter, - html: HtmlReporter, - blob: BlobReporter, - }; reporterName ??= 'list'; - const arg = config.config.reporter.find(([reporter, arg]) => reporter === reporterName)?.[1]; - const options = { - ...arg, - configDir: process.cwd(), - }; - - let reporter: Reporter | undefined; - if (reporterName in defaultReporters) { - reporter = new defaultReporters[reporterName as keyof typeof defaultReporters](options); - } else { - const reporterConstructor = await loadReporter(config, reporterName); - reporter = new reporterConstructor(options); - } - - const receiver = new TeleReporterReceiver(path.sep, reporter); + const reporters = await createReporters(config, 'merge', [reporterName]); + const receiver = new TeleReporterReceiver(path.sep, reporters[0]); for (const event of events) await receiver.dispatch(event); } finally { await removeFolders([resourceDir]); } - console.log(`Done.`); } async function extractReports(dir: string, shardFiles: string[], resourceDir: string): Promise { diff --git a/packages/playwright-test/src/runner/reporters.ts b/packages/playwright-test/src/runner/reporters.ts index 578a98cbc1..a4bb24e59e 100644 --- a/packages/playwright-test/src/runner/reporters.ts +++ b/packages/playwright-test/src/runner/reporters.ts @@ -25,13 +25,13 @@ import JSONReporter from '../reporters/json'; import JUnitReporter from '../reporters/junit'; import LineReporter from '../reporters/line'; import ListReporter from '../reporters/list'; -import { Multiplexer } from '../reporters/multiplexer'; import type { Suite } from '../common/test'; import type { BuiltInReporter, FullConfigInternal } from '../common/config'; import { loadReporter } from './loadUtils'; import { BlobReporter } from '../reporters/blob'; +import type { ReporterDescription } from '../../types/test'; -export async function createReporter(config: FullConfigInternal, mode: 'list' | 'run' | 'ui', additionalReporters: Reporter[] = []): Promise { +export async function createReporters(config: FullConfigInternal, mode: 'list' | 'run' | 'ui' | 'merge', reporterNames?: string[]): Promise { const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = { dot: mode === 'list' ? ListModeReporter : DotReporter, line: mode === 'list' ? ListModeReporter : LineReporter, @@ -44,7 +44,10 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' | blob: BlobReporter, }; const reporters: Reporter[] = []; - for (const r of config.config.reporter) { + const descriptions: ReporterDescription[] = reporterNames ? + reporterNames.map(name => [name, config.config.reporter.find(([reporterName]) => reporterName === name)]) : + config.config.reporter; + for (const r of descriptions) { const [name, arg] = r; const options = { ...arg, configDir: config.configDir }; if (name in defaultReporters) { @@ -54,7 +57,6 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' | reporters.push(new reporterConstructor(options)); } } - reporters.push(...additionalReporters); if (process.env.PW_TEST_REPORTER) { const reporterConstructor = await loadReporter(config, process.env.PW_TEST_REPORTER); reporters.push(new reporterConstructor()); @@ -64,7 +66,7 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' | const prints = r.printsToStdio ? r.printsToStdio() : true; return prints; }); - if (reporters.length && !someReporterPrintsToStdio) { + if (reporters.length && !someReporterPrintsToStdio && mode !== 'merge') { // 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') @@ -72,7 +74,7 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' | else reporters.unshift(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter()); } - return new Multiplexer(reporters); + return reporters; } export class ListModeReporter implements Reporter { diff --git a/packages/playwright-test/src/runner/runner.ts b/packages/playwright-test/src/runner/runner.ts index a8647eae27..c1e6ff914e 100644 --- a/packages/playwright-test/src/runner/runner.ts +++ b/packages/playwright-test/src/runner/runner.ts @@ -19,12 +19,13 @@ import { monotonicTime } from 'playwright-core/lib/utils'; import type { FullResult } from '../../types/testReporter'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import { collectFilesForProject, filterProjects } from './projectUtils'; -import { createReporter } from './reporters'; +import { createReporters } from './reporters'; import { TestRun, createTaskRunner, createTaskRunnerForList } from './tasks'; import type { FullConfigInternal } from '../common/config'; import { colors } from 'playwright-core/lib/utilsBundle'; import { runWatchModeLoop } from './watchMode'; import { runUIMode } from './uiMode'; +import { Multiplexer } from '../reporters/multiplexer'; export class Runner { private _config: FullConfigInternal; @@ -68,7 +69,7 @@ export class Runner { // Legacy webServer support. webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p })); - const reporter = await createReporter(config, listOnly ? 'list' : 'run'); + const reporter = new Multiplexer(await createReporters(config, listOnly ? 'list' : 'run')); const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process') : createTaskRunner(config, reporter); diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index 40e115e468..96dbdb5096 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -22,7 +22,7 @@ import { clearCompilationCache, collectAffectedTestFiles, dependenciesForTestFil import type { FullConfigInternal } from '../common/config'; import { Multiplexer } from '../reporters/multiplexer'; import { TeleReporterEmitter } from '../reporters/teleEmitter'; -import { createReporter } from './reporters'; +import { createReporters } from './reporters'; import { TestRun, createTaskRunnerForList, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks'; import { chokidar } from '../utilsBundle'; import type { FSWatcher } from 'chokidar'; @@ -167,8 +167,9 @@ class UIMode { this._config.cliListOnly = false; this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id); - const runReporter = new TeleReporterEmitter(e => this._dispatchEvent(e)); - const reporter = await createReporter(this._config, 'ui', [runReporter]); + const reporters = await createReporters(this._config, 'ui'); + reporters.push(new TeleReporterEmitter(e => this._dispatchEvent(e))); + const reporter = new Multiplexer(reporters); const taskRunner = createTaskRunnerForWatch(this._config, reporter); const testRun = new TestRun(this._config, reporter); clearCompilationCache();