chore: introduce ReporterV2 interface (#23983)
This commit is contained in:
parent
92c738b14a
commit
86c1abd934
|
|
@ -102,7 +102,9 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
// New file, just compress the entries.
|
// New file, just compress the entries.
|
||||||
await fs.promises.mkdir(path.dirname(params.zipFile), { recursive: true });
|
await fs.promises.mkdir(path.dirname(params.zipFile), { recursive: true });
|
||||||
zipFile.end(undefined, () => {
|
zipFile.end(undefined, () => {
|
||||||
zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile)).on('close', () => promise.resolve());
|
zipFile.outputStream.pipe(fs.createWriteStream(params.zipFile))
|
||||||
|
.on('close', () => promise.resolve())
|
||||||
|
.on('error', error => promise.reject(error));
|
||||||
});
|
});
|
||||||
await promise;
|
await promise;
|
||||||
await this._deleteStackSession(params.stacksId);
|
await this._deleteStackSession(params.stacksId);
|
||||||
|
|
|
||||||
|
|
@ -329,7 +329,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
|
||||||
const artifact = new Artifact(this._context, zipFileName);
|
const artifact = new Artifact(this._context, zipFileName);
|
||||||
artifact.reportFinished();
|
artifact.reportFinished();
|
||||||
result.resolve(artifact);
|
result.resolve(artifact);
|
||||||
});
|
}).on('error', error => result.reject(error));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export async function mergeTraceFiles(fileName: string, temporaryTraceFiles: str
|
||||||
Promise.all(temporaryTraceFiles.map(tempFile => fs.promises.unlink(tempFile))).then(() => {
|
Promise.all(temporaryTraceFiles.map(tempFile => fs.promises.unlink(tempFile))).then(() => {
|
||||||
mergePromise.resolve();
|
mergePromise.resolve();
|
||||||
});
|
});
|
||||||
});
|
}).on('error', error => mergePromise.reject(error));
|
||||||
});
|
});
|
||||||
await mergePromise;
|
await mergePromise;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStatus, TestStep } from '../../types/testReporter';
|
import type { FullConfig, FullResult, Location, TestError, TestResult, TestStatus, TestStep } from '../../types/testReporter';
|
||||||
import type { Annotation } from '../common/config';
|
import type { Annotation } from '../common/config';
|
||||||
import type { FullProject, Metadata } from '../../types/test';
|
import type { FullProject, Metadata } from '../../types/test';
|
||||||
import type * as reporterTypes from '../../types/testReporter';
|
import type * as reporterTypes from '../../types/testReporter';
|
||||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||||
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
export type JsonLocation = Location;
|
export type JsonLocation = Location;
|
||||||
export type JsonError = string;
|
export type JsonError = string;
|
||||||
|
|
@ -121,14 +122,16 @@ export type JsonEvent = {
|
||||||
export class TeleReporterReceiver {
|
export class TeleReporterReceiver {
|
||||||
private _rootSuite: TeleSuite;
|
private _rootSuite: TeleSuite;
|
||||||
private _pathSeparator: string;
|
private _pathSeparator: string;
|
||||||
private _reporter: Reporter;
|
private _reporter: ReporterV2;
|
||||||
private _tests = new Map<string, TeleTestCase>();
|
private _tests = new Map<string, TeleTestCase>();
|
||||||
private _rootDir!: string;
|
private _rootDir!: string;
|
||||||
|
private _listOnly = false;
|
||||||
private _clearPreviousResultsWhenTestBegins: boolean = false;
|
private _clearPreviousResultsWhenTestBegins: boolean = false;
|
||||||
private _reuseTestCases: boolean;
|
private _reuseTestCases: boolean;
|
||||||
private _reportConfig: MergeReporterConfig | undefined;
|
private _reportConfig: MergeReporterConfig | undefined;
|
||||||
|
private _config!: FullConfig;
|
||||||
|
|
||||||
constructor(pathSeparator: string, reporter: Reporter, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) {
|
constructor(pathSeparator: string, reporter: ReporterV2, reuseTestCases: boolean, reportConfig?: MergeReporterConfig) {
|
||||||
this._rootSuite = new TeleSuite('', 'root');
|
this._rootSuite = new TeleSuite('', 'root');
|
||||||
this._pathSeparator = pathSeparator;
|
this._pathSeparator = pathSeparator;
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
|
|
@ -136,10 +139,14 @@ export class TeleReporterReceiver {
|
||||||
this._reportConfig = reportConfig;
|
this._reportConfig = reportConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(message: JsonEvent): Promise<void> | undefined {
|
dispatch(message: JsonEvent): Promise<void> | void {
|
||||||
const { method, params } = message;
|
const { method, params } = message;
|
||||||
|
if (method === 'onConfigure') {
|
||||||
|
this._onConfigure(params.config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (method === 'onBegin') {
|
if (method === 'onBegin') {
|
||||||
this._onBegin(params.config, params.projects);
|
this._onBegin(params.projects);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (method === 'onTestBegin') {
|
if (method === 'onTestBegin') {
|
||||||
|
|
@ -176,8 +183,14 @@ export class TeleReporterReceiver {
|
||||||
this._clearPreviousResultsWhenTestBegins = true;
|
this._clearPreviousResultsWhenTestBegins = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onBegin(config: JsonConfig, projects: JsonProject[]) {
|
private _onConfigure(config: JsonConfig) {
|
||||||
this._rootDir = this._reportConfig?.rootDir || config.rootDir;
|
this._rootDir = this._reportConfig?.rootDir || config.rootDir;
|
||||||
|
this._listOnly = config.listOnly;
|
||||||
|
this._config = this._parseConfig(config);
|
||||||
|
this._reporter.onConfigure(this._config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBegin(projects: JsonProject[]) {
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
let projectSuite = this._rootSuite.suites.find(suite => suite.project()!.id === project.id);
|
let projectSuite = this._rootSuite.suites.find(suite => suite.project()!.id === project.id);
|
||||||
if (!projectSuite) {
|
if (!projectSuite) {
|
||||||
|
|
@ -191,7 +204,7 @@ export class TeleReporterReceiver {
|
||||||
|
|
||||||
// Remove deleted tests when listing. Empty suites will be auto-filtered
|
// Remove deleted tests when listing. Empty suites will be auto-filtered
|
||||||
// in the UI layer.
|
// in the UI layer.
|
||||||
if (config.listOnly) {
|
if (this._listOnly) {
|
||||||
const testIds = new Set<string>();
|
const testIds = new Set<string>();
|
||||||
const collectIds = (suite: JsonSuite) => {
|
const collectIds = (suite: JsonSuite) => {
|
||||||
suite.tests.map(t => t.testId).forEach(testId => testIds.add(testId));
|
suite.tests.map(t => t.testId).forEach(testId => testIds.add(testId));
|
||||||
|
|
@ -206,7 +219,7 @@ export class TeleReporterReceiver {
|
||||||
filterTests(projectSuite);
|
filterTests(projectSuite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._reporter.onBegin?.(this._parseConfig(config), this._rootSuite);
|
this._reporter.onBegin?.(this._rootSuite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onTestBegin(testId: string, payload: JsonTestResultStart) {
|
private _onTestBegin(testId: string, payload: JsonTestResultStart) {
|
||||||
|
|
@ -289,11 +302,11 @@ export class TeleReporterReceiver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onEnd(result: FullResult): Promise<void> | undefined {
|
private _onEnd(result: FullResult): Promise<void> | void {
|
||||||
return this._reporter.onEnd?.(result) || undefined;
|
return this._reporter.onEnd?.(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onExit(): Promise<void> | undefined {
|
private _onExit(): Promise<void> | void {
|
||||||
return this._reporter.onExit?.();
|
return this._reporter.onExit?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FullConfig, Suite } from '../../types/testReporter';
|
import type { FullConfig, Suite } from '../../types/testReporter';
|
||||||
import type { InternalReporter } from '../reporters/internalReporter';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
export interface TestRunnerPlugin {
|
export interface TestRunnerPlugin {
|
||||||
name: string;
|
name: string;
|
||||||
setup?(config: FullConfig, configDir: string, reporter: InternalReporter): Promise<void>;
|
setup?(config: FullConfig, configDir: string, reporter: ReporterV2): Promise<void>;
|
||||||
begin?(suite: Suite): Promise<void>;
|
begin?(suite: Suite): Promise<void>;
|
||||||
end?(): Promise<void>;
|
end?(): Promise<void>;
|
||||||
teardown?(): Promise<void>;
|
teardown?(): Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import type { FullConfig } from '../../types/testReporter';
|
||||||
import type { TestRunnerPlugin } from '.';
|
import type { TestRunnerPlugin } from '.';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import { envWithoutExperimentalLoaderOptions } from '../util';
|
import { envWithoutExperimentalLoaderOptions } from '../util';
|
||||||
import type { InternalReporter } from '../reporters/internalReporter';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
|
|
||||||
export type WebServerPluginOptions = {
|
export type WebServerPluginOptions = {
|
||||||
|
|
@ -50,7 +50,7 @@ export class WebServerPlugin implements TestRunnerPlugin {
|
||||||
private _processExitedPromise!: Promise<any>;
|
private _processExitedPromise!: Promise<any>;
|
||||||
private _options: WebServerPluginOptions;
|
private _options: WebServerPluginOptions;
|
||||||
private _checkPortOnly: boolean;
|
private _checkPortOnly: boolean;
|
||||||
private _reporter?: InternalReporter;
|
private _reporter?: ReporterV2;
|
||||||
name = 'playwright:webserver';
|
name = 'playwright:webserver';
|
||||||
|
|
||||||
constructor(options: WebServerPluginOptions, checkPortOnly: boolean) {
|
constructor(options: WebServerPluginOptions, checkPortOnly: boolean) {
|
||||||
|
|
@ -58,7 +58,7 @@ export class WebServerPlugin implements TestRunnerPlugin {
|
||||||
this._checkPortOnly = checkPortOnly;
|
this._checkPortOnly = checkPortOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setup(config: FullConfig, configDir: string, reporter: InternalReporter) {
|
public async setup(config: FullConfig, configDir: string, reporter: ReporterV2) {
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
this._isAvailable = getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter));
|
this._isAvailable = getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter));
|
||||||
this._options.cwd = this._options.cwd ? path.resolve(configDir, this._options.cwd) : configDir;
|
this._options.cwd = this._options.cwd ? path.resolve(configDir, this._options.cwd) : configDir;
|
||||||
|
|
@ -152,7 +152,7 @@ async function isPortUsed(port: number): Promise<boolean> {
|
||||||
return await innerIsPortUsed('127.0.0.1') || await innerIsPortUsed('::1');
|
return await innerIsPortUsed('127.0.0.1') || await innerIsPortUsed('::1');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: InternalReporter['onStdErr']) {
|
async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: ReporterV2['onStdErr']) {
|
||||||
let statusCode = await httpStatusCode(url, ignoreHTTPSErrors, onStdErr);
|
let statusCode = await httpStatusCode(url, ignoreHTTPSErrors, onStdErr);
|
||||||
if (statusCode === 404 && url.pathname === '/') {
|
if (statusCode === 404 && url.pathname === '/') {
|
||||||
const indexUrl = new URL(url);
|
const indexUrl = new URL(url);
|
||||||
|
|
@ -162,7 +162,7 @@ async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: In
|
||||||
return statusCode >= 200 && statusCode < 404;
|
return statusCode >= 200 && statusCode < 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: InternalReporter['onStdErr']): Promise<number> {
|
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: ReporterV2['onStdErr']): Promise<number> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
debugWebServer(`HTTP GET: ${url}`);
|
debugWebServer(`HTTP GET: ${url}`);
|
||||||
httpRequest({
|
httpRequest({
|
||||||
|
|
@ -195,7 +195,7 @@ async function waitFor(waitFn: () => Promise<boolean>, cancellationToken: { canc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIsAvailableFunction(url: string, checkPortOnly: boolean, ignoreHTTPSErrors: boolean, onStdErr: InternalReporter['onStdErr']) {
|
function getIsAvailableFunction(url: string, checkPortOnly: boolean, ignoreHTTPSErrors: boolean, onStdErr: ReporterV2['onStdErr']) {
|
||||||
const urlObject = new URL(url);
|
const urlObject = new URL(url);
|
||||||
if (!checkPortOnly)
|
if (!checkPortOnly)
|
||||||
return () => isURLAvailable(urlObject, ignoreHTTPSErrors, onStdErr);
|
return () => isURLAvailable(urlObject, ignoreHTTPSErrors, onStdErr);
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ export class BaseReporter implements Reporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected generateStartingMessage() {
|
protected generateStartingMessage() {
|
||||||
const jobs = this.config.workers;
|
const jobs = this.config.metadata.actualWorkers ?? this.config.workers;
|
||||||
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : '';
|
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : '';
|
||||||
if (!this.totalTestCount)
|
if (!this.totalTestCount)
|
||||||
return '';
|
return '';
|
||||||
|
|
|
||||||
|
|
@ -57,15 +57,16 @@ export class BlobReporter extends TeleReporterEmitter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
printsToStdio() {
|
override onConfigure(config: FullConfig) {
|
||||||
return false;
|
this._outputDir = path.resolve(this._options.configDir, this._options.outputDir || 'blob-report');
|
||||||
|
this._reportName = this._computeReportName(config);
|
||||||
|
super.onConfigure(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
override onBegin(config: FullConfig<{}, {}>, suite: Suite): void {
|
override onBegin(suite: Suite): void {
|
||||||
this._outputDir = path.resolve(this._options.configDir, this._options.outputDir || 'blob-report');
|
// Note: config.outputDir is cleared betwee onConfigure and onBegin, so we call mkdir here.
|
||||||
fs.mkdirSync(path.join(this._outputDir, 'resources'), { recursive: true });
|
fs.mkdirSync(path.join(this._outputDir, 'resources'), { recursive: true });
|
||||||
this._reportName = this._computeReportName(config);
|
super.onBegin(suite);
|
||||||
super.onBegin(config, suite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async onEnd(result: FullResult): Promise<void> {
|
override async onEnd(result: FullResult): Promise<void> {
|
||||||
|
|
@ -79,14 +80,16 @@ export class BlobReporter extends TeleReporterEmitter {
|
||||||
const zipFileName = path.join(this._outputDir, this._reportName + '.zip');
|
const zipFileName = path.join(this._outputDir, this._reportName + '.zip');
|
||||||
zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', () => {
|
zipFile.outputStream.pipe(fs.createWriteStream(zipFileName)).on('close', () => {
|
||||||
zipFinishPromise.resolve(undefined);
|
zipFinishPromise.resolve(undefined);
|
||||||
});
|
}).on('error', error => zipFinishPromise.reject(error));
|
||||||
zipFile.addReadStream(content, this._reportName + '.jsonl');
|
zipFile.addReadStream(content, this._reportName + '.jsonl');
|
||||||
zipFile.end();
|
zipFile.end();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
...this._copyFilePromises,
|
...this._copyFilePromises,
|
||||||
// Requires Node v14.18.0+
|
// Requires Node v14.18.0+
|
||||||
zipFinishPromise.catch(e => console.error(`Failed to write report ${zipFileName}: ${e}`))
|
zipFinishPromise.catch(e => {
|
||||||
|
throw new Error(`Failed to write report ${zipFileName}: ` + e.message);
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,44 +17,29 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
import { codeFrameColumns } from '../transform/babelBundle';
|
import { codeFrameColumns } from '../transform/babelBundle';
|
||||||
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter } from '../../types/testReporter';
|
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter';
|
||||||
import { Suite } from '../common/test';
|
import { Suite } from '../common/test';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
|
||||||
import { Multiplexer } from './multiplexer';
|
import { Multiplexer } from './multiplexer';
|
||||||
import { prepareErrorStack, relativeFilePath } from './base';
|
import { prepareErrorStack, relativeFilePath } from './base';
|
||||||
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
|
||||||
type StdIOChunk = {
|
export class InternalReporter implements ReporterV2 {
|
||||||
chunk: string | Buffer;
|
|
||||||
test?: TestCase;
|
|
||||||
result?: TestResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class InternalReporter {
|
|
||||||
private _multiplexer: Multiplexer;
|
private _multiplexer: Multiplexer;
|
||||||
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
private _didBegin = false;
|
||||||
private _config!: FullConfigInternal;
|
private _config!: FullConfig;
|
||||||
|
|
||||||
constructor(reporters: Reporter[]) {
|
constructor(reporters: ReporterV2[]) {
|
||||||
this._multiplexer = new Multiplexer(reporters);
|
this._multiplexer = new Multiplexer(reporters);
|
||||||
}
|
}
|
||||||
|
|
||||||
onConfigure(config: FullConfigInternal) {
|
onConfigure(config: FullConfig) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
|
this._multiplexer.onConfigure(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(suite: Suite) {
|
||||||
this._multiplexer.onBegin(config, suite);
|
this._didBegin = true;
|
||||||
|
this._multiplexer.onBegin(suite);
|
||||||
const deferred = this._deferred!;
|
|
||||||
this._deferred = null;
|
|
||||||
for (const item of deferred) {
|
|
||||||
if (item.error)
|
|
||||||
this.onError(item.error);
|
|
||||||
if (item.stdout)
|
|
||||||
this.onStdOut(item.stdout.chunk, item.stdout.test, item.stdout.result);
|
|
||||||
if (item.stderr)
|
|
||||||
this.onStdErr(item.stderr.chunk, item.stderr.test, item.stderr.result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestBegin(test: TestCase, result: TestResult) {
|
onTestBegin(test: TestCase, result: TestResult) {
|
||||||
|
|
@ -62,19 +47,10 @@ export class InternalReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
if (this._deferred) {
|
|
||||||
this._deferred.push({ stdout: { chunk, test, result } });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._multiplexer.onStdOut(chunk, test, result);
|
this._multiplexer.onStdOut(chunk, test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
if (this._deferred) {
|
|
||||||
this._deferred.push({ stderr: { chunk, test, result } });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._multiplexer.onStdErr(chunk, test, result);
|
this._multiplexer.onStdErr(chunk, test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,9 +60,9 @@ export class InternalReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnd(result: FullResult) {
|
async onEnd(result: FullResult) {
|
||||||
if (this._deferred) {
|
if (!this._didBegin) {
|
||||||
// onBegin was not reported, emit it.
|
// onBegin was not reported, emit it.
|
||||||
this.onBegin(this._config.config, new Suite('', 'root'));
|
this.onBegin(new Suite('', 'root'));
|
||||||
}
|
}
|
||||||
await this._multiplexer.onEnd(result);
|
await this._multiplexer.onEnd(result);
|
||||||
}
|
}
|
||||||
|
|
@ -96,11 +72,7 @@ export class InternalReporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(error: TestError) {
|
onError(error: TestError) {
|
||||||
if (this._deferred) {
|
addLocationAndSnippetToError(this._config, error);
|
||||||
this._deferred.push({ error });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addLocationAndSnippetToError(this._config.config, error);
|
|
||||||
this._multiplexer.onError(error);
|
this._multiplexer.onError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,14 +85,18 @@ export class InternalReporter {
|
||||||
this._multiplexer.onStepEnd(test, result, step);
|
this._multiplexer.onStepEnd(test, result, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return this._multiplexer.printsToStdio();
|
||||||
|
}
|
||||||
|
|
||||||
private _addSnippetToTestErrors(test: TestCase, result: TestResult) {
|
private _addSnippetToTestErrors(test: TestCase, result: TestResult) {
|
||||||
for (const error of result.errors)
|
for (const error of result.errors)
|
||||||
addLocationAndSnippetToError(this._config.config, error, test.location.file);
|
addLocationAndSnippetToError(this._config, error, test.location.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addSnippetToStepError(test: TestCase, step: TestStep) {
|
private _addSnippetToStepError(test: TestCase, step: TestStep) {
|
||||||
if (step.error)
|
if (step.error)
|
||||||
addLocationAndSnippetToError(this._config.config, step.error, test.location.file);
|
addLocationAndSnippetToError(this._config, step.error, test.location.file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,16 @@ async function extractReportFromZip(file: string): Promise<Buffer> {
|
||||||
|
|
||||||
async function mergeEvents(dir: string, shardReportFiles: string[]) {
|
async function mergeEvents(dir: string, shardReportFiles: string[]) {
|
||||||
const events: JsonEvent[] = [];
|
const events: JsonEvent[] = [];
|
||||||
|
const configureEvents: JsonEvent[] = [];
|
||||||
const beginEvents: JsonEvent[] = [];
|
const beginEvents: JsonEvent[] = [];
|
||||||
const endEvents: JsonEvent[] = [];
|
const endEvents: JsonEvent[] = [];
|
||||||
for (const reportFile of shardReportFiles) {
|
for (const reportFile of shardReportFiles) {
|
||||||
const reportJsonl = await extractReportFromZip(path.join(dir, reportFile));
|
const reportJsonl = await extractReportFromZip(path.join(dir, reportFile));
|
||||||
const parsedEvents = parseEvents(reportJsonl);
|
const parsedEvents = parseEvents(reportJsonl);
|
||||||
for (const event of parsedEvents) {
|
for (const event of parsedEvents) {
|
||||||
if (event.method === 'onBegin')
|
if (event.method === 'onConfigure')
|
||||||
|
configureEvents.push(event);
|
||||||
|
else if (event.method === 'onBegin')
|
||||||
beginEvents.push(event);
|
beginEvents.push(event);
|
||||||
else if (event.method === 'onEnd')
|
else if (event.method === 'onEnd')
|
||||||
endEvents.push(event);
|
endEvents.push(event);
|
||||||
|
|
@ -89,13 +92,12 @@ async function mergeEvents(dir: string, shardReportFiles: string[]) {
|
||||||
events.push(event);
|
events.push(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [mergeBeginEvents(beginEvents), ...events, mergeEndEvents(endEvents), { method: 'onExit', params: undefined }];
|
return [mergeConfigureEvents(configureEvents), mergeBeginEvents(beginEvents), ...events, mergeEndEvents(endEvents), { method: 'onExit', params: undefined }];
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeBeginEvents(beginEvents: JsonEvent[]): JsonEvent {
|
function mergeConfigureEvents(configureEvents: JsonEvent[]): JsonEvent {
|
||||||
if (!beginEvents.length)
|
if (!configureEvents.length)
|
||||||
throw new Error('No begin events found');
|
throw new Error('No configure events found');
|
||||||
const projects: JsonProject[] = [];
|
|
||||||
let config: JsonConfig = {
|
let config: JsonConfig = {
|
||||||
configFile: undefined,
|
configFile: undefined,
|
||||||
globalTimeout: 0,
|
globalTimeout: 0,
|
||||||
|
|
@ -108,8 +110,21 @@ function mergeBeginEvents(beginEvents: JsonEvent[]): JsonEvent {
|
||||||
workers: 0,
|
workers: 0,
|
||||||
listOnly: false
|
listOnly: false
|
||||||
};
|
};
|
||||||
for (const event of beginEvents) {
|
for (const event of configureEvents)
|
||||||
config = mergeConfigs(config, event.params.config);
|
config = mergeConfigs(config, event.params.config);
|
||||||
|
return {
|
||||||
|
method: 'onConfigure',
|
||||||
|
params: {
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeBeginEvents(beginEvents: JsonEvent[]): JsonEvent {
|
||||||
|
if (!beginEvents.length)
|
||||||
|
throw new Error('No begin events found');
|
||||||
|
const projects: JsonProject[] = [];
|
||||||
|
for (const event of beginEvents) {
|
||||||
const shardProjects: JsonProject[] = event.params.projects;
|
const shardProjects: JsonProject[] = event.params.projects;
|
||||||
for (const shardProject of shardProjects) {
|
for (const shardProject of shardProjects) {
|
||||||
const mergedProject = projects.find(p => p.id === shardProject.id);
|
const mergedProject = projects.find(p => p.id === shardProject.id);
|
||||||
|
|
@ -122,7 +137,6 @@ function mergeBeginEvents(beginEvents: JsonEvent[]): JsonEvent {
|
||||||
return {
|
return {
|
||||||
method: 'onBegin',
|
method: 'onBegin',
|
||||||
params: {
|
params: {
|
||||||
config,
|
|
||||||
projects,
|
projects,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -136,6 +150,7 @@ function mergeConfigs(to: JsonConfig, from: JsonConfig): JsonConfig {
|
||||||
...to.metadata,
|
...to.metadata,
|
||||||
...from.metadata,
|
...from.metadata,
|
||||||
totalTime: to.metadata.totalTime + from.metadata.totalTime,
|
totalTime: to.metadata.totalTime + from.metadata.totalTime,
|
||||||
|
actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0),
|
||||||
},
|
},
|
||||||
workers: to.workers + from.workers,
|
workers: to.workers + from.workers,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -14,64 +14,78 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter } from '../../types/testReporter';
|
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter';
|
||||||
import type { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
|
||||||
export class Multiplexer implements Reporter {
|
export class Multiplexer implements ReporterV2 {
|
||||||
private _reporters: Reporter[];
|
private _reporters: ReporterV2[];
|
||||||
|
|
||||||
constructor(reporters: Reporter[]) {
|
constructor(reporters: ReporterV2[]) {
|
||||||
this._reporters = reporters;
|
this._reporters = reporters;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onConfigure(config: FullConfig) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onBegin?.(config, suite));
|
wrap(() => reporter.onConfigure(config));
|
||||||
|
}
|
||||||
|
|
||||||
|
onBegin(suite: Suite) {
|
||||||
|
for (const reporter of this._reporters)
|
||||||
|
wrap(() => reporter.onBegin(suite));
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestBegin(test: TestCase, result: TestResult) {
|
onTestBegin(test: TestCase, result: TestResult) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onTestBegin?.(test, result));
|
wrap(() => reporter.onTestBegin(test, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onStdOut?.(chunk, test, result));
|
wrap(() => reporter.onStdOut(chunk, test, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onStdErr?.(chunk, test, result));
|
wrap(() => reporter.onStdErr(chunk, test, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestEnd(test: TestCase, result: TestResult) {
|
onTestEnd(test: TestCase, result: TestResult) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onTestEnd?.(test, result));
|
wrap(() => reporter.onTestEnd(test, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnd(result: FullResult) {
|
async onEnd(result: FullResult) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
await wrapAsync(() => reporter.onEnd?.(result));
|
await wrapAsync(() => reporter.onEnd(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
async onExit() {
|
async onExit() {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
await wrapAsync(() => reporter.onExit?.());
|
await wrapAsync(() => reporter.onExit());
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(error: TestError) {
|
onError(error: TestError) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onError?.(error));
|
wrap(() => reporter.onError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onStepBegin?.(test, result, step));
|
wrap(() => reporter.onStepBegin(test, result, step));
|
||||||
}
|
}
|
||||||
|
|
||||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onStepEnd?.(test, result, step));
|
wrap(() => reporter.onStepEnd(test, result, step));
|
||||||
|
}
|
||||||
|
|
||||||
|
printsToStdio(): boolean {
|
||||||
|
return this._reporters.some(r => {
|
||||||
|
let prints = true;
|
||||||
|
wrap(() => prints = r.printsToStdio());
|
||||||
|
return prints;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
119
packages/playwright-test/src/reporters/reporterV2.ts
Normal file
119
packages/playwright-test/src/reporters/reporterV2.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter, Suite } from '../../types/testReporter';
|
||||||
|
|
||||||
|
export interface ReporterV2 {
|
||||||
|
onConfigure(config: FullConfig): void;
|
||||||
|
onBegin(suite: Suite): void;
|
||||||
|
onTestBegin(test: TestCase, result: TestResult): void;
|
||||||
|
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult): void;
|
||||||
|
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult): void;
|
||||||
|
onTestEnd(test: TestCase, result: TestResult): void;
|
||||||
|
onEnd(result: FullResult): void | Promise<void>;
|
||||||
|
onExit(): void | Promise<void>;
|
||||||
|
onError(error: TestError): void;
|
||||||
|
onStepBegin(test: TestCase, result: TestResult, step: TestStep): void;
|
||||||
|
onStepEnd(test: TestCase, result: TestResult, step: TestStep): void;
|
||||||
|
printsToStdio(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StdIOChunk = {
|
||||||
|
chunk: string | Buffer;
|
||||||
|
test?: TestCase;
|
||||||
|
result?: TestResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ReporterV2Wrapper implements ReporterV2 {
|
||||||
|
private _reporter: Reporter;
|
||||||
|
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
||||||
|
private _config!: FullConfig;
|
||||||
|
|
||||||
|
constructor(reporter: Reporter) {
|
||||||
|
this._reporter = reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfigure(config: FullConfig) {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBegin(suite: Suite) {
|
||||||
|
this._reporter.onBegin?.(this._config, suite);
|
||||||
|
|
||||||
|
const deferred = this._deferred!;
|
||||||
|
this._deferred = null;
|
||||||
|
for (const item of deferred) {
|
||||||
|
if (item.error)
|
||||||
|
this.onError(item.error);
|
||||||
|
if (item.stdout)
|
||||||
|
this.onStdOut(item.stdout.chunk, item.stdout.test, item.stdout.result);
|
||||||
|
if (item.stderr)
|
||||||
|
this.onStdErr(item.stderr.chunk, item.stderr.test, item.stderr.result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTestBegin(test: TestCase, result: TestResult) {
|
||||||
|
this._reporter.onTestBegin?.(test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
|
if (this._deferred) {
|
||||||
|
this._deferred.push({ stdout: { chunk, test, result } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._reporter.onStdOut?.(chunk, test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
|
||||||
|
if (this._deferred) {
|
||||||
|
this._deferred.push({ stderr: { chunk, test, result } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._reporter.onStdErr?.(chunk, test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTestEnd(test: TestCase, result: TestResult) {
|
||||||
|
this._reporter.onTestEnd?.(test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onEnd(result: FullResult) {
|
||||||
|
await this._reporter.onEnd?.(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onExit() {
|
||||||
|
await this._reporter.onExit?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(error: TestError) {
|
||||||
|
if (this._deferred) {
|
||||||
|
this._deferred.push({ error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._reporter.onError?.(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
|
this._reporter.onStepBegin?.(test, result, step);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
|
this._reporter.onStepEnd?.(test, result, step);
|
||||||
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,13 +17,14 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { createGuid } from 'playwright-core/lib/utils';
|
import { createGuid } from 'playwright-core/lib/utils';
|
||||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||||
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStep } from '../../types/testReporter';
|
import type { FullConfig, FullResult, Location, TestError, TestResult, TestStep } from '../../types/testReporter';
|
||||||
import { FullConfigInternal, FullProjectInternal } from '../common/config';
|
import { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||||
import type { Suite, TestCase } from '../common/test';
|
import type { Suite, TestCase } from '../common/test';
|
||||||
import type { JsonAttachment, JsonConfig, JsonEvent, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
import type { JsonAttachment, JsonConfig, JsonEvent, JsonProject, JsonStdIOType, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||||
|
import type { ReporterV2 } from './reporterV2';
|
||||||
|
|
||||||
export class TeleReporterEmitter implements Reporter {
|
export class TeleReporterEmitter implements ReporterV2 {
|
||||||
private _messageSink: (message: JsonEvent) => void;
|
private _messageSink: (message: JsonEvent) => void;
|
||||||
private _rootDir!: string;
|
private _rootDir!: string;
|
||||||
private _skipBuffers: boolean;
|
private _skipBuffers: boolean;
|
||||||
|
|
@ -33,10 +34,14 @@ export class TeleReporterEmitter implements Reporter {
|
||||||
this._skipBuffers = skipBuffers;
|
this._skipBuffers = skipBuffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onConfigure(config: FullConfig) {
|
||||||
this._rootDir = config.rootDir;
|
this._rootDir = config.rootDir;
|
||||||
|
this._messageSink({ method: 'onConfigure', params: { config: this._serializeConfig(config) } });
|
||||||
|
}
|
||||||
|
|
||||||
|
onBegin(suite: Suite) {
|
||||||
const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
|
const projects = suite.suites.map(projectSuite => this._serializeProject(projectSuite));
|
||||||
this._messageSink({ method: 'onBegin', params: { config: this._serializeConfig(config), projects } });
|
this._messageSink({ method: 'onBegin', params: { projects } });
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestBegin(test: TestCase, result: TestResult): void {
|
onTestBegin(test: TestCase, result: TestResult): void {
|
||||||
|
|
@ -96,11 +101,11 @@ export class TeleReporterEmitter implements Reporter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void {
|
onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult): void {
|
||||||
this._onStdIO('stdout', chunk, test, result);
|
this._onStdIO('stdout', chunk, test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void {
|
onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult): void {
|
||||||
this._onStdIO('stderr', chunk, test, result);
|
this._onStdIO('stderr', chunk, test, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,6 +125,10 @@ export class TeleReporterEmitter implements Reporter {
|
||||||
async onExit() {
|
async onExit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printsToStdio() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private _serializeConfig(config: FullConfig): JsonConfig {
|
private _serializeConfig(config: FullConfig): JsonConfig {
|
||||||
return {
|
return {
|
||||||
configFile: this._relativePath(config.configFile),
|
configFile: this._relativePath(config.configFile),
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import { ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import { WorkerHost } from './workerHost';
|
import { WorkerHost } from './workerHost';
|
||||||
import type { TestGroup } from './testGroups';
|
import type { TestGroup } from './testGroups';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import type { InternalReporter } from '../reporters/internalReporter';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
type TestResultData = {
|
type TestResultData = {
|
||||||
result: TestResult;
|
result: TestResult;
|
||||||
|
|
@ -46,14 +46,14 @@ export class Dispatcher {
|
||||||
|
|
||||||
private _testById = new Map<string, TestData>();
|
private _testById = new Map<string, TestData>();
|
||||||
private _config: FullConfigInternal;
|
private _config: FullConfigInternal;
|
||||||
private _reporter: InternalReporter;
|
private _reporter: ReporterV2;
|
||||||
private _hasWorkerErrors = false;
|
private _hasWorkerErrors = false;
|
||||||
private _failureCount = 0;
|
private _failureCount = 0;
|
||||||
|
|
||||||
private _extraEnvByProjectId: EnvByProjectId = new Map();
|
private _extraEnvByProjectId: EnvByProjectId = new Map();
|
||||||
private _producedEnvByProjectId: EnvByProjectId = new Map();
|
private _producedEnvByProjectId: EnvByProjectId = new Map();
|
||||||
|
|
||||||
constructor(config: FullConfigInternal, reporter: InternalReporter) {
|
constructor(config: FullConfigInternal, reporter: ReporterV2) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,10 @@ import type { BuiltInReporter, FullConfigInternal } from '../common/config';
|
||||||
import { loadReporter } from './loadUtils';
|
import { loadReporter } from './loadUtils';
|
||||||
import { BlobReporter } from '../reporters/blob';
|
import { BlobReporter } from '../reporters/blob';
|
||||||
import type { ReporterDescription } from '../../types/test';
|
import type { ReporterDescription } from '../../types/test';
|
||||||
|
import { type ReporterV2, ReporterV2Wrapper } from '../reporters/reporterV2';
|
||||||
|
|
||||||
export async function createReporters(config: FullConfigInternal, mode: 'list' | 'run' | 'ui' | 'merge', descriptions?: ReporterDescription[]): Promise<Reporter[]> {
|
export async function createReporters(config: FullConfigInternal, mode: 'list' | 'run' | 'ui' | 'merge', descriptions?: ReporterDescription[]): Promise<ReporterV2[]> {
|
||||||
const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = {
|
const defaultReporters: { [key in Exclude<BuiltInReporter, 'blob'>]: new(arg: any) => Reporter } = {
|
||||||
dot: mode === 'list' ? ListModeReporter : DotReporter,
|
dot: mode === 'list' ? ListModeReporter : DotReporter,
|
||||||
line: mode === 'list' ? ListModeReporter : LineReporter,
|
line: mode === 'list' ? ListModeReporter : LineReporter,
|
||||||
list: mode === 'list' ? ListModeReporter : ListReporter,
|
list: mode === 'list' ? ListModeReporter : ListReporter,
|
||||||
|
|
@ -42,42 +43,40 @@ export async function createReporters(config: FullConfigInternal, mode: 'list' |
|
||||||
junit: JUnitReporter,
|
junit: JUnitReporter,
|
||||||
null: EmptyReporter,
|
null: EmptyReporter,
|
||||||
html: mode === 'ui' ? LineReporter : HtmlReporter,
|
html: mode === 'ui' ? LineReporter : HtmlReporter,
|
||||||
blob: BlobReporter,
|
|
||||||
markdown: MarkdownReporter,
|
markdown: MarkdownReporter,
|
||||||
};
|
};
|
||||||
const reporters: Reporter[] = [];
|
const reporters: ReporterV2[] = [];
|
||||||
descriptions ??= config.config.reporter;
|
descriptions ??= config.config.reporter;
|
||||||
for (const r of descriptions) {
|
for (const r of descriptions) {
|
||||||
const [name, arg] = r;
|
const [name, arg] = r;
|
||||||
const options = { ...arg, configDir: config.configDir };
|
const options = { ...arg, configDir: config.configDir };
|
||||||
if (name in defaultReporters) {
|
if (name === 'blob') {
|
||||||
reporters.push(new defaultReporters[name as keyof typeof defaultReporters](options));
|
reporters.push(new BlobReporter(options));
|
||||||
|
} else if (name in defaultReporters) {
|
||||||
|
reporters.push(new ReporterV2Wrapper(new defaultReporters[name as keyof typeof defaultReporters](options)));
|
||||||
} else {
|
} else {
|
||||||
const reporterConstructor = await loadReporter(config, name);
|
const reporterConstructor = await loadReporter(config, name);
|
||||||
reporters.push(new reporterConstructor(options));
|
reporters.push(new ReporterV2Wrapper(new reporterConstructor(options)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (process.env.PW_TEST_REPORTER) {
|
if (process.env.PW_TEST_REPORTER) {
|
||||||
const reporterConstructor = await loadReporter(config, process.env.PW_TEST_REPORTER);
|
const reporterConstructor = await loadReporter(config, process.env.PW_TEST_REPORTER);
|
||||||
reporters.push(new reporterConstructor());
|
reporters.push(new ReporterV2Wrapper(new reporterConstructor()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const someReporterPrintsToStdio = reporters.some(r => {
|
const someReporterPrintsToStdio = reporters.some(r => r.printsToStdio());
|
||||||
const prints = r.printsToStdio ? r.printsToStdio() : true;
|
|
||||||
return prints;
|
|
||||||
});
|
|
||||||
if (reporters.length && !someReporterPrintsToStdio) {
|
if (reporters.length && !someReporterPrintsToStdio) {
|
||||||
// Add a line/dot/list-mode reporter for convenience.
|
// Add a line/dot/list-mode reporter for convenience.
|
||||||
// Important to put it first, jsut in case some other reporter stalls onEnd.
|
// Important to put it first, jsut in case some other reporter stalls onEnd.
|
||||||
if (mode === 'list')
|
if (mode === 'list')
|
||||||
reporters.unshift(new ListModeReporter());
|
reporters.unshift(new ReporterV2Wrapper(new ListModeReporter()));
|
||||||
else
|
else
|
||||||
reporters.unshift(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter());
|
reporters.unshift(new ReporterV2Wrapper(!process.env.CI ? new LineReporter({ omitFailures: true }) : new DotReporter()));
|
||||||
}
|
}
|
||||||
return reporters;
|
return reporters;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ListModeReporter implements Reporter {
|
class ListModeReporter implements Reporter {
|
||||||
private config!: FullConfig;
|
private config!: FullConfig;
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite): void {
|
onBegin(config: FullConfig, suite: Suite): void {
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class Runner {
|
||||||
: createTaskRunner(config, reporter);
|
: createTaskRunner(config, reporter);
|
||||||
|
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config, reporter);
|
||||||
reporter.onConfigure(config);
|
reporter.onConfigure(config.config);
|
||||||
|
|
||||||
if (!listOnly && config.ignoreSnapshots) {
|
if (!listOnly && config.ignoreSnapshots) {
|
||||||
reporter.onStdOut(colors.dim([
|
reporter.onStdOut(colors.dim([
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,20 @@ import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils';
|
||||||
import type { FullResult, TestError } from '../../reporter';
|
import type { FullResult, TestError } from '../../reporter';
|
||||||
import { SigIntWatcher } from './sigIntWatcher';
|
import { SigIntWatcher } from './sigIntWatcher';
|
||||||
import { serializeError } from '../util';
|
import { serializeError } from '../util';
|
||||||
import type { InternalReporter } from '../reporters/internalReporter';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
type TaskTeardown = () => Promise<any> | undefined;
|
type TaskTeardown = () => Promise<any> | undefined;
|
||||||
export type Task<Context> = (context: Context, errors: TestError[], softErrors: TestError[]) => Promise<TaskTeardown | void> | undefined;
|
export type Task<Context> = (context: Context, errors: TestError[], softErrors: TestError[]) => Promise<TaskTeardown | void> | undefined;
|
||||||
|
|
||||||
export class TaskRunner<Context> {
|
export class TaskRunner<Context> {
|
||||||
private _tasks: { name: string, task: Task<Context> }[] = [];
|
private _tasks: { name: string, task: Task<Context> }[] = [];
|
||||||
private _reporter: InternalReporter;
|
private _reporter: ReporterV2;
|
||||||
private _hasErrors = false;
|
private _hasErrors = false;
|
||||||
private _interrupted = false;
|
private _interrupted = false;
|
||||||
private _isTearDown = false;
|
private _isTearDown = false;
|
||||||
private _globalTimeoutForError: number;
|
private _globalTimeoutForError: number;
|
||||||
|
|
||||||
constructor(reporter: InternalReporter, globalTimeoutForError: number) {
|
constructor(reporter: ReporterV2, globalTimeoutForError: number) {
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
this._globalTimeoutForError = globalTimeoutForError;
|
this._globalTimeoutForError = globalTimeoutForError;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { promisify } from 'util';
|
||||||
import { debug, rimraf } from 'playwright-core/lib/utilsBundle';
|
import { debug, rimraf } from 'playwright-core/lib/utilsBundle';
|
||||||
import { Dispatcher, type EnvByProjectId } from './dispatcher';
|
import { Dispatcher, type EnvByProjectId } from './dispatcher';
|
||||||
import type { TestRunnerPluginRegistration } from '../plugins';
|
import type { TestRunnerPluginRegistration } from '../plugins';
|
||||||
import type { InternalReporter } from '../reporters/internalReporter';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
import { createTestGroups, type TestGroup } from '../runner/testGroups';
|
import { createTestGroups, type TestGroup } from '../runner/testGroups';
|
||||||
import type { Task } from './taskRunner';
|
import type { Task } from './taskRunner';
|
||||||
import { TaskRunner } from './taskRunner';
|
import { TaskRunner } from './taskRunner';
|
||||||
|
|
@ -46,7 +46,7 @@ export type Phase = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TestRun {
|
export class TestRun {
|
||||||
readonly reporter: InternalReporter;
|
readonly reporter: ReporterV2;
|
||||||
readonly config: FullConfigInternal;
|
readonly config: FullConfigInternal;
|
||||||
rootSuite: Suite | undefined = undefined;
|
rootSuite: Suite | undefined = undefined;
|
||||||
readonly phases: Phase[] = [];
|
readonly phases: Phase[] = [];
|
||||||
|
|
@ -55,13 +55,13 @@ export class TestRun {
|
||||||
projectType: Map<FullProjectInternal, 'top-level' | 'dependency'> = new Map();
|
projectType: Map<FullProjectInternal, 'top-level' | 'dependency'> = new Map();
|
||||||
projectSuites: Map<FullProjectInternal, Suite[]> = new Map();
|
projectSuites: Map<FullProjectInternal, Suite[]> = new Map();
|
||||||
|
|
||||||
constructor(config: FullConfigInternal, reporter: InternalReporter) {
|
constructor(config: FullConfigInternal, reporter: ReporterV2) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunner(config: FullConfigInternal, reporter: InternalReporter): TaskRunner<TestRun> {
|
export function createTaskRunner(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
||||||
addGlobalSetupTasks(taskRunner, config);
|
addGlobalSetupTasks(taskRunner, config);
|
||||||
taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true }));
|
taskRunner.addTask('load tests', createLoadTask('in-process', { filterOnly: true, failOnLoadErrors: true }));
|
||||||
|
|
@ -69,13 +69,13 @@ export function createTaskRunner(config: FullConfigInternal, reporter: InternalR
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporter: InternalReporter): TaskRunner<TestRun> {
|
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
||||||
addGlobalSetupTasks(taskRunner, config);
|
addGlobalSetupTasks(taskRunner, config);
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: InternalReporter, additionalFileMatcher?: Matcher): TaskRunner<TestRun> {
|
export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: ReporterV2, additionalFileMatcher?: Matcher): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
|
||||||
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, additionalFileMatcher }));
|
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, additionalFileMatcher }));
|
||||||
addRunTasks(taskRunner, config);
|
addRunTasks(taskRunner, config);
|
||||||
|
|
@ -100,7 +100,7 @@ function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForList(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner<TestRun> {
|
export function createTaskRunnerForList(config: FullConfigInternal, reporter: ReporterV2, mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner<TestRun> {
|
||||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
||||||
taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false }));
|
taskRunner.addTask('load tests', createLoadTask(mode, { ...options, filterOnly: false }));
|
||||||
taskRunner.addTask('report begin', createReportBeginTask());
|
taskRunner.addTask('report begin', createReportBeginTask());
|
||||||
|
|
@ -110,7 +110,7 @@ export function createTaskRunnerForList(config: FullConfigInternal, reporter: In
|
||||||
function createReportBeginTask(): Task<TestRun> {
|
function createReportBeginTask(): Task<TestRun> {
|
||||||
return async ({ config, reporter, rootSuite }) => {
|
return async ({ config, reporter, rootSuite }) => {
|
||||||
const montonicStartTime = monotonicTime();
|
const montonicStartTime = monotonicTime();
|
||||||
reporter.onBegin(config.config, rootSuite!);
|
reporter.onBegin(rootSuite!);
|
||||||
return async () => {
|
return async () => {
|
||||||
config.config.metadata.totalTime = monotonicTime() - montonicStartTime;
|
config.config.metadata.totalTime = monotonicTime() - montonicStartTime;
|
||||||
};
|
};
|
||||||
|
|
@ -228,7 +228,7 @@ function createPhasesTask(): Task<TestRun> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testRun.config.config.workers = Math.min(testRun.config.config.workers, maxConcurrentTestGroups);
|
testRun.config.config.metadata.actualWorkers = Math.min(testRun.config.config.workers, maxConcurrentTestGroups);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import type { FSWatcher } from 'chokidar';
|
||||||
import { open } from 'playwright-core/lib/utilsBundle';
|
import { open } from 'playwright-core/lib/utilsBundle';
|
||||||
import ListReporter from '../reporters/list';
|
import ListReporter from '../reporters/list';
|
||||||
import type { OpenTraceViewerOptions, Transport } from 'playwright-core/lib/server/trace/viewer/traceViewer';
|
import type { OpenTraceViewerOptions, Transport } from 'playwright-core/lib/server/trace/viewer/traceViewer';
|
||||||
|
import { ReporterV2Wrapper } from '../reporters/reporterV2';
|
||||||
|
|
||||||
class UIMode {
|
class UIMode {
|
||||||
private _config: FullConfigInternal;
|
private _config: FullConfigInternal;
|
||||||
|
|
@ -67,9 +68,9 @@ class UIMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
async runGlobalSetup(): Promise<FullResult['status']> {
|
async runGlobalSetup(): Promise<FullResult['status']> {
|
||||||
const reporter = new InternalReporter([new ListReporter()]);
|
const reporter = new InternalReporter([new ReporterV2Wrapper(new ListReporter())]);
|
||||||
const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter);
|
const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter);
|
||||||
reporter.onConfigure(this._config);
|
reporter.onConfigure(this._config.config);
|
||||||
const testRun = new TestRun(this._config, reporter);
|
const testRun = new TestRun(this._config, reporter);
|
||||||
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
||||||
await reporter.onEnd({ status });
|
await reporter.onEnd({ status });
|
||||||
|
|
@ -167,7 +168,7 @@ class UIMode {
|
||||||
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process', { failOnLoadErrors: false });
|
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process', { failOnLoadErrors: false });
|
||||||
const testRun = new TestRun(this._config, reporter);
|
const testRun = new TestRun(this._config, reporter);
|
||||||
clearCompilationCache();
|
clearCompilationCache();
|
||||||
reporter.onConfigure(this._config);
|
reporter.onConfigure(this._config.config);
|
||||||
const status = await taskRunner.run(testRun, 0);
|
const status = await taskRunner.run(testRun, 0);
|
||||||
await reporter.onEnd({ status });
|
await reporter.onEnd({ status });
|
||||||
await reporter.onExit();
|
await reporter.onExit();
|
||||||
|
|
@ -191,7 +192,7 @@ class UIMode {
|
||||||
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
|
const taskRunner = createTaskRunnerForWatch(this._config, reporter);
|
||||||
const testRun = new TestRun(this._config, reporter);
|
const testRun = new TestRun(this._config, reporter);
|
||||||
clearCompilationCache();
|
clearCompilationCache();
|
||||||
reporter.onConfigure(this._config);
|
reporter.onConfigure(this._config.config);
|
||||||
const stop = new ManualPromise();
|
const stop = new ManualPromise();
|
||||||
const run = taskRunner.run(testRun, 0, stop).then(async status => {
|
const run = taskRunner.run(testRun, 0, stop).then(async status => {
|
||||||
await reporter.onEnd({ status });
|
await reporter.onEnd({ status });
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import { enquirer } from '../utilsBundle';
|
||||||
import { separator } from '../reporters/base';
|
import { separator } from '../reporters/base';
|
||||||
import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer';
|
import { PlaywrightServer } from 'playwright-core/lib/remote/playwrightServer';
|
||||||
import ListReporter from '../reporters/list';
|
import ListReporter from '../reporters/list';
|
||||||
|
import { ReporterV2Wrapper } from '../reporters/reporterV2';
|
||||||
|
|
||||||
class FSWatcher {
|
class FSWatcher {
|
||||||
private _dirtyTestFiles = new Map<FullProjectInternal, Set<string>>();
|
private _dirtyTestFiles = new Map<FullProjectInternal, Set<string>>();
|
||||||
|
|
@ -112,10 +113,10 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
||||||
p.project.retries = 0;
|
p.project.retries = 0;
|
||||||
|
|
||||||
// Perform global setup.
|
// Perform global setup.
|
||||||
const reporter = new InternalReporter([new ListReporter()]);
|
const reporter = new InternalReporter([new ReporterV2Wrapper(new ListReporter())]);
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config, reporter);
|
||||||
const taskRunner = createTaskRunnerForWatchSetup(config, reporter);
|
const taskRunner = createTaskRunnerForWatchSetup(config, reporter);
|
||||||
reporter.onConfigure(config);
|
reporter.onConfigure(config.config);
|
||||||
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
const { status, cleanup: globalCleanup } = await taskRunner.runDeferCleanup(testRun, 0);
|
||||||
if (status !== 'passed')
|
if (status !== 'passed')
|
||||||
await globalCleanup();
|
await globalCleanup();
|
||||||
|
|
@ -280,11 +281,11 @@ async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<s
|
||||||
title?: string,
|
title?: string,
|
||||||
}) {
|
}) {
|
||||||
printConfiguration(config, options?.title);
|
printConfiguration(config, options?.title);
|
||||||
const reporter = new InternalReporter([new ListReporter()]);
|
const reporter = new InternalReporter([new ReporterV2Wrapper(new ListReporter())]);
|
||||||
const taskRunner = createTaskRunnerForWatch(config, reporter, options?.additionalFileMatcher);
|
const taskRunner = createTaskRunnerForWatch(config, reporter, options?.additionalFileMatcher);
|
||||||
const testRun = new TestRun(config, reporter);
|
const testRun = new TestRun(config, reporter);
|
||||||
clearCompilationCache();
|
clearCompilationCache();
|
||||||
reporter.onConfigure(config);
|
reporter.onConfigure(config.config);
|
||||||
const taskStatus = await taskRunner.run(testRun, 0);
|
const taskStatus = await taskRunner.run(testRun, 0);
|
||||||
let status: FullResult['status'] = 'passed';
|
let status: FullResult['status'] = 'passed';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -598,7 +598,7 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
||||||
return sendMessage('list', {});
|
return sendMessage('list', {});
|
||||||
|
|
||||||
let rootSuite: Suite;
|
let rootSuite: Suite;
|
||||||
let loadErrors: TestError[];
|
const loadErrors: TestError[] = [];
|
||||||
const progress: Progress = {
|
const progress: Progress = {
|
||||||
passed: 0,
|
passed: 0,
|
||||||
failed: 0,
|
failed: 0,
|
||||||
|
|
@ -606,12 +606,13 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
||||||
};
|
};
|
||||||
let config: FullConfig;
|
let config: FullConfig;
|
||||||
receiver = new TeleReporterReceiver(pathSeparator, {
|
receiver = new TeleReporterReceiver(pathSeparator, {
|
||||||
onBegin: (c: FullConfig, suite: Suite) => {
|
onConfigure: (c: FullConfig) => {
|
||||||
if (!rootSuite) {
|
|
||||||
rootSuite = suite;
|
|
||||||
loadErrors = [];
|
|
||||||
}
|
|
||||||
config = c;
|
config = c;
|
||||||
|
},
|
||||||
|
|
||||||
|
onBegin: (suite: Suite) => {
|
||||||
|
if (!rootSuite)
|
||||||
|
rootSuite = suite;
|
||||||
progress.passed = 0;
|
progress.passed = 0;
|
||||||
progress.failed = 0;
|
progress.failed = 0;
|
||||||
progress.skipped = 0;
|
progress.skipped = 0;
|
||||||
|
|
@ -639,8 +640,18 @@ const refreshRootSuite = (eraseResults: boolean): Promise<void> => {
|
||||||
onError: (error: TestError) => {
|
onError: (error: TestError) => {
|
||||||
xtermDataSource.write((error.stack || error.value || '') + '\n');
|
xtermDataSource.write((error.stack || error.value || '') + '\n');
|
||||||
loadErrors.push(error);
|
loadErrors.push(error);
|
||||||
throttleUpdateRootSuite(config, rootSuite, loadErrors, progress);
|
throttleUpdateRootSuite(config, rootSuite ?? new TeleSuite('', 'root'), loadErrors, progress);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
printsToStdio: () => {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onStdOut: () => {},
|
||||||
|
onStdErr: () => {},
|
||||||
|
onExit: () => {},
|
||||||
|
onStepBegin: () => {},
|
||||||
|
onStepEnd: () => {},
|
||||||
}, true);
|
}, true);
|
||||||
receiver._setClearPreviousResultsWhenTestBegins();
|
receiver._setClearPreviousResultsWhenTestBegins();
|
||||||
return sendMessage('list', {});
|
return sendMessage('list', {});
|
||||||
|
|
|
||||||
|
|
@ -836,8 +836,8 @@ test('preserve config fields', async ({ runInlineTest, mergeReports }) => {
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
await runInlineTest(files, { shard: `1/3` });
|
await runInlineTest(files, { shard: `1/3`, workers: 1 });
|
||||||
await runInlineTest(files, { shard: `3/3` });
|
await runInlineTest(files, { shard: `3/3`, workers: 1 });
|
||||||
|
|
||||||
const mergeConfig = {
|
const mergeConfig = {
|
||||||
reportSlowTests: {
|
reportSlowTests: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue