chore: split InternalReporter and Multiplexer (#22671)
The multiplexer will be reused in the blob report merger.
This commit is contained in:
parent
e809ecdc5d
commit
51aca72c35
|
|
@ -15,11 +15,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FullConfig, Suite } from '../../types/testReporter';
|
import type { FullConfig, Suite } from '../../types/testReporter';
|
||||||
import type { Multiplexer } from '../reporters/multiplexer';
|
import type { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
|
||||||
export interface TestRunnerPlugin {
|
export interface TestRunnerPlugin {
|
||||||
name: string;
|
name: string;
|
||||||
setup?(config: FullConfig, configDir: string, reporter: Multiplexer): Promise<void>;
|
setup?(config: FullConfig, configDir: string, reporter: InternalReporter): Promise<void>;
|
||||||
babelPlugins?(): Promise<[string, any?][]>;
|
babelPlugins?(): Promise<[string, any?][]>;
|
||||||
begin?(suite: Suite): Promise<void>;
|
begin?(suite: Suite): Promise<void>;
|
||||||
end?(): Promise<void>;
|
end?(): 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 { Multiplexer } from '../reporters/multiplexer';
|
import type { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
|
||||||
|
|
||||||
export type WebServerPluginOptions = {
|
export type WebServerPluginOptions = {
|
||||||
|
|
@ -49,7 +49,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?: Multiplexer;
|
private _reporter?: InternalReporter;
|
||||||
name = 'playwright:webserver';
|
name = 'playwright:webserver';
|
||||||
|
|
||||||
constructor(options: WebServerPluginOptions, checkPortOnly: boolean) {
|
constructor(options: WebServerPluginOptions, checkPortOnly: boolean) {
|
||||||
|
|
@ -57,7 +57,7 @@ export class WebServerPlugin implements TestRunnerPlugin {
|
||||||
this._checkPortOnly = checkPortOnly;
|
this._checkPortOnly = checkPortOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setup(config: FullConfig, configDir: string, reporter: Multiplexer) {
|
public async setup(config: FullConfig, configDir: string, reporter: InternalReporter) {
|
||||||
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;
|
||||||
|
|
@ -148,7 +148,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: Multiplexer['onStdErr']) {
|
async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: InternalReporter['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);
|
||||||
|
|
@ -158,7 +158,7 @@ async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Mu
|
||||||
return statusCode >= 200 && statusCode < 404;
|
return statusCode >= 200 && statusCode < 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Multiplexer['onStdErr']): Promise<number> {
|
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: InternalReporter['onStdErr']): Promise<number> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
debugWebServer(`HTTP GET: ${url}`);
|
debugWebServer(`HTTP GET: ${url}`);
|
||||||
httpRequest({
|
httpRequest({
|
||||||
|
|
@ -191,7 +191,7 @@ async function waitFor(waitFn: () => Promise<boolean>, cancellationToken: { canc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIsAvailableFunction(url: string, checkPortOnly: boolean, ignoreHTTPSErrors: boolean, onStdErr: Multiplexer['onStdErr']) {
|
function getIsAvailableFunction(url: string, checkPortOnly: boolean, ignoreHTTPSErrors: boolean, onStdErr: InternalReporter['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);
|
||||||
|
|
|
||||||
121
packages/playwright-test/src/reporters/internalReporter.ts
Normal file
121
packages/playwright-test/src/reporters/internalReporter.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
* 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 } from '../../types/testReporter';
|
||||||
|
import { Suite } from '../common/test';
|
||||||
|
import type { FullConfigInternal } from '../common/config';
|
||||||
|
import { addSnippetToError } from './base';
|
||||||
|
import { Multiplexer } from './multiplexer';
|
||||||
|
|
||||||
|
type StdIOChunk = {
|
||||||
|
chunk: string | Buffer;
|
||||||
|
test?: TestCase;
|
||||||
|
result?: TestResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InternalReporter {
|
||||||
|
private _multiplexer: Multiplexer;
|
||||||
|
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
||||||
|
private _config!: FullConfigInternal;
|
||||||
|
|
||||||
|
constructor(reporters: Reporter[]) {
|
||||||
|
this._multiplexer = new Multiplexer(reporters);
|
||||||
|
}
|
||||||
|
|
||||||
|
onConfigure(config: FullConfigInternal) {
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
|
this._multiplexer.onBegin(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._multiplexer.onTestBegin(test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTestEnd(test: TestCase, result: TestResult) {
|
||||||
|
this._addSnippetToTestErrors(test, result);
|
||||||
|
this._multiplexer.onTestEnd(test, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onEnd() { }
|
||||||
|
|
||||||
|
async onExit(result: FullResult) {
|
||||||
|
if (this._deferred) {
|
||||||
|
// onBegin was not reported, emit it.
|
||||||
|
this.onBegin(this._config.config, new Suite('', 'root'));
|
||||||
|
}
|
||||||
|
await this._multiplexer.onEnd(result);
|
||||||
|
await this._multiplexer.onExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(error: TestError) {
|
||||||
|
if (this._deferred) {
|
||||||
|
this._deferred.push({ error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addSnippetToError(this._config.config, error);
|
||||||
|
this._multiplexer.onError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStepBegin(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
|
this._multiplexer.onStepBegin(test, result, step);
|
||||||
|
}
|
||||||
|
|
||||||
|
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
|
this._addSnippetToStepError(test, step);
|
||||||
|
this._multiplexer.onStepEnd(test, result, step);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addSnippetToTestErrors(test: TestCase, result: TestResult) {
|
||||||
|
for (const error of result.errors)
|
||||||
|
addSnippetToError(this._config.config, error, test.location.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addSnippetToStepError(test: TestCase, step: TestStep) {
|
||||||
|
if (step.error)
|
||||||
|
addSnippetToError(this._config.config, step.error, test.location.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,47 +15,18 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter } from '../../types/testReporter';
|
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter } from '../../types/testReporter';
|
||||||
import { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
|
||||||
import { addSnippetToError } from './base';
|
|
||||||
|
|
||||||
type StdIOChunk = {
|
export class Multiplexer implements Reporter {
|
||||||
chunk: string | Buffer;
|
|
||||||
test?: TestCase;
|
|
||||||
result?: TestResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Multiplexer {
|
|
||||||
private _reporters: Reporter[];
|
private _reporters: Reporter[];
|
||||||
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
|
||||||
private _config!: FullConfigInternal;
|
|
||||||
|
|
||||||
constructor(reporters: Reporter[]) {
|
constructor(reporters: Reporter[]) {
|
||||||
this._reporters = reporters;
|
this._reporters = reporters;
|
||||||
}
|
}
|
||||||
|
|
||||||
printsToStdio() {
|
|
||||||
return this._reporters.some(r => r.printsToStdio ? r.printsToStdio() : true);
|
|
||||||
}
|
|
||||||
|
|
||||||
onConfigure(config: FullConfigInternal) {
|
|
||||||
this._config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onBegin?.(config, suite));
|
wrap(() => reporter.onBegin?.(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) {
|
onTestBegin(test: TestCase, result: TestResult) {
|
||||||
|
|
@ -64,76 +35,52 @@ export class Multiplexer {
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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) {
|
||||||
if (this._deferred) {
|
|
||||||
this._deferred.push({ stderr: { chunk, test, result } });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
this._addSnippetToTestErrors(test, result);
|
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => reporter.onTestEnd?.(test, result));
|
wrap(() => reporter.onTestEnd?.(test, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
async onEnd() { }
|
async onEnd(result: FullResult) {
|
||||||
|
|
||||||
async onExit(result: FullResult) {
|
|
||||||
if (this._deferred) {
|
|
||||||
// onBegin was not reported, emit it.
|
|
||||||
this.onBegin(this._config.config, new Suite('', 'root'));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
await Promise.resolve().then(() => reporter.onEnd?.(result)).catch(e => console.error('Error in reporter', e));
|
await wrapAsync(() => reporter.onEnd?.(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
async onExit() {
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
await Promise.resolve().then(() => reporter.onExit?.()).catch(e => console.error('Error in reporter', e));
|
await wrapAsync(() => reporter.onExit?.());
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(error: TestError) {
|
onError(error: TestError) {
|
||||||
if (this._deferred) {
|
|
||||||
this._deferred.push({ error });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addSnippetToError(this._config.config, error);
|
|
||||||
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 as any).onStepBegin?.(test, result, step));
|
wrap(() => reporter.onStepBegin?.(test, result, step));
|
||||||
}
|
}
|
||||||
|
|
||||||
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
|
||||||
this._addSnippetToStepError(test, step);
|
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
wrap(() => (reporter as any).onStepEnd?.(test, result, step));
|
wrap(() => reporter.onStepEnd?.(test, result, step));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _addSnippetToTestErrors(test: TestCase, result: TestResult) {
|
async function wrapAsync(callback: () => void | Promise<void>) {
|
||||||
for (const error of result.errors)
|
try {
|
||||||
addSnippetToError(this._config.config, error, test.location.file);
|
await callback();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error in reporter', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addSnippetToStepError(test: TestCase, step: TestStep) {
|
|
||||||
if (step.error)
|
|
||||||
addSnippetToError(this._config.config, step.error, test.location.file);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrap(callback: () => void) {
|
function wrap(callback: () => void) {
|
||||||
|
|
|
||||||
|
|
@ -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 { Multiplexer } from '../reporters/multiplexer';
|
import type { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
|
||||||
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: Multiplexer;
|
private _reporter: InternalReporter;
|
||||||
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: Multiplexer) {
|
constructor(config: FullConfigInternal, reporter: InternalReporter) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ export class Dispatcher {
|
||||||
for (const test of group.tests) {
|
for (const test of group.tests) {
|
||||||
const result = test._appendTestResult();
|
const result = test._appendTestResult();
|
||||||
result.status = 'skipped';
|
result.status = 'skipped';
|
||||||
this._reporter.onTestBegin?.(test, result);
|
this._reporter.onTestBegin(test, result);
|
||||||
test.annotations = [...test._staticAnnotations];
|
test.annotations = [...test._staticAnnotations];
|
||||||
this._reportTestEnd(test, result);
|
this._reportTestEnd(test, result);
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +222,7 @@ export class Dispatcher {
|
||||||
result.parallelIndex = worker.parallelIndex;
|
result.parallelIndex = worker.parallelIndex;
|
||||||
result.workerIndex = worker.workerIndex;
|
result.workerIndex = worker.workerIndex;
|
||||||
result.startTime = new Date(params.startWallTime);
|
result.startTime = new Date(params.startWallTime);
|
||||||
this._reporter.onTestBegin?.(data.test, result);
|
this._reporter.onTestBegin(data.test, result);
|
||||||
worker.currentTestId = params.testId;
|
worker.currentTestId = params.testId;
|
||||||
};
|
};
|
||||||
worker.addListener('testBegin', onTestBegin);
|
worker.addListener('testBegin', onTestBegin);
|
||||||
|
|
@ -284,7 +284,7 @@ export class Dispatcher {
|
||||||
};
|
};
|
||||||
steps.set(params.stepId, step);
|
steps.set(params.stepId, step);
|
||||||
(parentStep || result).steps.push(step);
|
(parentStep || result).steps.push(step);
|
||||||
this._reporter.onStepBegin?.(data.test, result, step);
|
this._reporter.onStepBegin(data.test, result, step);
|
||||||
};
|
};
|
||||||
worker.on('stepBegin', onStepBegin);
|
worker.on('stepBegin', onStepBegin);
|
||||||
|
|
||||||
|
|
@ -298,14 +298,14 @@ export class Dispatcher {
|
||||||
const { result, steps } = runData;
|
const { result, steps } = runData;
|
||||||
const step = steps.get(params.stepId);
|
const step = steps.get(params.stepId);
|
||||||
if (!step) {
|
if (!step) {
|
||||||
this._reporter.onStdErr?.('Internal error: step end without step begin: ' + params.stepId, data.test, result);
|
this._reporter.onStdErr('Internal error: step end without step begin: ' + params.stepId, data.test, result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
step.duration = params.wallTime - step.startTime.getTime();
|
step.duration = params.wallTime - step.startTime.getTime();
|
||||||
if (params.error)
|
if (params.error)
|
||||||
step.error = params.error;
|
step.error = params.error;
|
||||||
steps.delete(params.stepId);
|
steps.delete(params.stepId);
|
||||||
this._reporter.onStepEnd?.(data.test, result, step);
|
this._reporter.onStepEnd(data.test, result, step);
|
||||||
};
|
};
|
||||||
worker.on('stepEnd', onStepEnd);
|
worker.on('stepEnd', onStepEnd);
|
||||||
|
|
||||||
|
|
@ -342,7 +342,7 @@ export class Dispatcher {
|
||||||
if (onlyStartedTests)
|
if (onlyStartedTests)
|
||||||
return true;
|
return true;
|
||||||
result = data.test._appendTestResult();
|
result = data.test._appendTestResult();
|
||||||
this._reporter.onTestBegin?.(test, result);
|
this._reporter.onTestBegin(test, result);
|
||||||
}
|
}
|
||||||
result.errors = [...errors];
|
result.errors = [...errors];
|
||||||
result.error = result.errors[0];
|
result.error = result.errors[0];
|
||||||
|
|
@ -358,7 +358,7 @@ export class Dispatcher {
|
||||||
// Let's just fail the test run.
|
// Let's just fail the test run.
|
||||||
this._hasWorkerErrors = true;
|
this._hasWorkerErrors = true;
|
||||||
for (const error of params.fatalErrors)
|
for (const error of params.fatalErrors)
|
||||||
this._reporter.onError?.(error);
|
this._reporter.onError(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -410,7 +410,7 @@ export class Dispatcher {
|
||||||
|
|
||||||
// Emulate a "skipped" run, and drop this test from remaining.
|
// Emulate a "skipped" run, and drop this test from remaining.
|
||||||
const result = test._appendTestResult();
|
const result = test._appendTestResult();
|
||||||
this._reporter.onTestBegin?.(test, result);
|
this._reporter.onTestBegin(test, result);
|
||||||
result.status = 'skipped';
|
result.status = 'skipped';
|
||||||
this._reportTestEnd(test, result);
|
this._reportTestEnd(test, result);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -470,17 +470,17 @@ export class Dispatcher {
|
||||||
worker.on('stdOut', (params: TestOutputPayload) => {
|
worker.on('stdOut', (params: TestOutputPayload) => {
|
||||||
const { chunk, test, result } = handleOutput(params);
|
const { chunk, test, result } = handleOutput(params);
|
||||||
result?.stdout.push(chunk);
|
result?.stdout.push(chunk);
|
||||||
this._reporter.onStdOut?.(chunk, test, result);
|
this._reporter.onStdOut(chunk, test, result);
|
||||||
});
|
});
|
||||||
worker.on('stdErr', (params: TestOutputPayload) => {
|
worker.on('stdErr', (params: TestOutputPayload) => {
|
||||||
const { chunk, test, result } = handleOutput(params);
|
const { chunk, test, result } = handleOutput(params);
|
||||||
result?.stderr.push(chunk);
|
result?.stderr.push(chunk);
|
||||||
this._reporter.onStdErr?.(chunk, test, result);
|
this._reporter.onStdErr(chunk, test, result);
|
||||||
});
|
});
|
||||||
worker.on('teardownErrors', (params: TeardownErrorsPayload) => {
|
worker.on('teardownErrors', (params: TeardownErrorsPayload) => {
|
||||||
this._hasWorkerErrors = true;
|
this._hasWorkerErrors = true;
|
||||||
for (const error of params.fatalErrors)
|
for (const error of params.fatalErrors)
|
||||||
this._reporter.onError?.(error);
|
this._reporter.onError(error);
|
||||||
});
|
});
|
||||||
worker.on('exit', () => {
|
worker.on('exit', () => {
|
||||||
const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {};
|
const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {};
|
||||||
|
|
@ -509,7 +509,7 @@ export class Dispatcher {
|
||||||
private _reportTestEnd(test: TestCase, result: TestResult) {
|
private _reportTestEnd(test: TestCase, result: TestResult) {
|
||||||
if (result.status !== 'skipped' && result.status !== test.expectedStatus)
|
if (result.status !== 'skipped' && result.status !== test.expectedStatus)
|
||||||
++this._failureCount;
|
++this._failureCount;
|
||||||
this._reporter.onTestEnd?.(test, result);
|
this._reporter.onTestEnd(test, result);
|
||||||
const maxFailures = this._config.config.maxFailures;
|
const maxFailures = this._config.config.maxFailures;
|
||||||
if (maxFailures && this._failureCount === maxFailures)
|
if (maxFailures && this._failureCount === maxFailures)
|
||||||
this.stop().catch(e => {});
|
this.stop().catch(e => {});
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import type { FullConfigInternal } from '../common/config';
|
||||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||||
import { runWatchModeLoop } from './watchMode';
|
import { runWatchModeLoop } from './watchMode';
|
||||||
import { runUIMode } from './uiMode';
|
import { runUIMode } from './uiMode';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
import { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
|
||||||
export class Runner {
|
export class Runner {
|
||||||
private _config: FullConfigInternal;
|
private _config: FullConfigInternal;
|
||||||
|
|
@ -69,7 +69,7 @@ export class Runner {
|
||||||
// Legacy webServer support.
|
// Legacy webServer support.
|
||||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||||
|
|
||||||
const reporter = new Multiplexer(await createReporters(config, listOnly ? 'list' : 'run'));
|
const reporter = new InternalReporter(await createReporters(config, listOnly ? 'list' : 'run'));
|
||||||
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process')
|
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process')
|
||||||
: createTaskRunner(config, reporter);
|
: createTaskRunner(config, reporter);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 { Multiplexer } from '../reporters/multiplexer';
|
import type { InternalReporter } from '../reporters/internalReporter';
|
||||||
|
|
||||||
type TaskTeardown = () => Promise<any> | undefined;
|
type TaskTeardown = () => Promise<any> | undefined;
|
||||||
export type Task<Context> = (context: Context, errors: TestError[]) => Promise<TaskTeardown | void> | undefined;
|
export type Task<Context> = (context: Context, errors: 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: Multiplexer;
|
private _reporter: InternalReporter;
|
||||||
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: Multiplexer, globalTimeoutForError: number) {
|
constructor(reporter: InternalReporter, 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 { Multiplexer } from '../reporters/multiplexer';
|
import type { InternalReporter } from '../reporters/internalReporter';
|
||||||
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';
|
||||||
|
|
@ -44,7 +44,7 @@ export type Phase = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TestRun {
|
export class TestRun {
|
||||||
readonly reporter: Multiplexer;
|
readonly reporter: InternalReporter;
|
||||||
readonly config: FullConfigInternal;
|
readonly config: FullConfigInternal;
|
||||||
rootSuite: Suite | undefined = undefined;
|
rootSuite: Suite | undefined = undefined;
|
||||||
readonly phases: Phase[] = [];
|
readonly phases: Phase[] = [];
|
||||||
|
|
@ -53,13 +53,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: Multiplexer) {
|
constructor(config: FullConfigInternal, reporter: InternalReporter) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.reporter = reporter;
|
this.reporter = reporter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunner(config: FullConfigInternal, reporter: Multiplexer): TaskRunner<TestRun> {
|
export function createTaskRunner(config: FullConfigInternal, reporter: InternalReporter): 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', true));
|
taskRunner.addTask('load tests', createLoadTask('in-process', true));
|
||||||
|
|
@ -67,13 +67,13 @@ export function createTaskRunner(config: FullConfigInternal, reporter: Multiplex
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporter: Multiplexer): TaskRunner<TestRun> {
|
export function createTaskRunnerForWatchSetup(config: FullConfigInternal, reporter: InternalReporter): 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: Multiplexer, additionalFileMatcher?: Matcher): TaskRunner<TestRun> {
|
export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: InternalReporter, 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', true, additionalFileMatcher));
|
taskRunner.addTask('load tests', createLoadTask('out-of-process', true, additionalFileMatcher));
|
||||||
addRunTasks(taskRunner, config);
|
addRunTasks(taskRunner, config);
|
||||||
|
|
@ -91,7 +91,7 @@ function addGlobalSetupTasks(taskRunner: TaskRunner<TestRun>, config: FullConfig
|
||||||
function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
|
function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
|
||||||
taskRunner.addTask('create phases', createPhasesTask());
|
taskRunner.addTask('create phases', createPhasesTask());
|
||||||
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
||||||
reporter.onBegin?.(config.config, rootSuite!);
|
reporter.onBegin(config.config, rootSuite!);
|
||||||
return () => reporter.onEnd();
|
return () => reporter.onEnd();
|
||||||
});
|
});
|
||||||
for (const plugin of config.plugins)
|
for (const plugin of config.plugins)
|
||||||
|
|
@ -101,11 +101,11 @@ function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskRunnerForList(config: FullConfigInternal, reporter: Multiplexer, mode: 'in-process' | 'out-of-process'): TaskRunner<TestRun> {
|
export function createTaskRunnerForList(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process'): 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, false));
|
taskRunner.addTask('load tests', createLoadTask(mode, false));
|
||||||
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
||||||
reporter.onBegin?.(config.config, rootSuite!);
|
reporter.onBegin(config.config, rootSuite!);
|
||||||
return () => reporter.onEnd();
|
return () => reporter.onEnd();
|
||||||
});
|
});
|
||||||
return taskRunner;
|
return taskRunner;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import { isUnderTest, ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import type { FullResult } from '../../reporter';
|
import type { FullResult } from '../../reporter';
|
||||||
import { clearCompilationCache, collectAffectedTestFiles, dependenciesForTestFile } from '../common/compilationCache';
|
import { clearCompilationCache, collectAffectedTestFiles, dependenciesForTestFile } from '../common/compilationCache';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
import { InternalReporter } from '../reporters/internalReporter';
|
||||||
import { TeleReporterEmitter } from '../reporters/teleEmitter';
|
import { TeleReporterEmitter } from '../reporters/teleEmitter';
|
||||||
import { createReporters } from './reporters';
|
import { createReporters } from './reporters';
|
||||||
import { TestRun, createTaskRunnerForList, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks';
|
import { TestRun, createTaskRunnerForList, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks';
|
||||||
|
|
@ -65,7 +65,7 @@ class UIMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
async runGlobalSetup(): Promise<FullResult['status']> {
|
async runGlobalSetup(): Promise<FullResult['status']> {
|
||||||
const reporter = new Multiplexer([new ListReporter()]);
|
const reporter = new InternalReporter([new ListReporter()]);
|
||||||
const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter);
|
const taskRunner = createTaskRunnerForWatchSetup(this._config, reporter);
|
||||||
reporter.onConfigure(this._config);
|
reporter.onConfigure(this._config);
|
||||||
const testRun = new TestRun(this._config, reporter);
|
const testRun = new TestRun(this._config, reporter);
|
||||||
|
|
@ -144,7 +144,7 @@ class UIMode {
|
||||||
|
|
||||||
private async _listTests() {
|
private async _listTests() {
|
||||||
const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e));
|
const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e));
|
||||||
const reporter = new Multiplexer([listReporter]);
|
const reporter = new InternalReporter([listReporter]);
|
||||||
this._config.cliListOnly = true;
|
this._config.cliListOnly = true;
|
||||||
this._config.testIdMatcher = undefined;
|
this._config.testIdMatcher = undefined;
|
||||||
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process');
|
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process');
|
||||||
|
|
@ -169,7 +169,7 @@ class UIMode {
|
||||||
|
|
||||||
const reporters = await createReporters(this._config, 'ui');
|
const reporters = await createReporters(this._config, 'ui');
|
||||||
reporters.push(new TeleReporterEmitter(e => this._dispatchEvent(e)));
|
reporters.push(new TeleReporterEmitter(e => this._dispatchEvent(e)));
|
||||||
const reporter = new Multiplexer(reporters);
|
const reporter = new InternalReporter(reporters);
|
||||||
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();
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
import readline from 'readline';
|
import readline from 'readline';
|
||||||
import { createGuid, ManualPromise } from 'playwright-core/lib/utils';
|
import { createGuid, ManualPromise } from 'playwright-core/lib/utils';
|
||||||
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||||
import { Multiplexer } from '../reporters/multiplexer';
|
import { InternalReporter } from '../reporters/internalReporter';
|
||||||
import { createFileMatcher, createFileMatcherFromArguments } from '../util';
|
import { createFileMatcher, createFileMatcherFromArguments } from '../util';
|
||||||
import type { Matcher } from '../util';
|
import type { Matcher } from '../util';
|
||||||
import { TestRun, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks';
|
import { TestRun, createTaskRunnerForWatch, createTaskRunnerForWatchSetup } from './tasks';
|
||||||
|
|
@ -112,7 +112,7 @@ 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 Multiplexer([new ListReporter()]);
|
const reporter = new InternalReporter([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);
|
||||||
|
|
@ -275,7 +275,7 @@ async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<s
|
||||||
title?: string,
|
title?: string,
|
||||||
}) {
|
}) {
|
||||||
printConfiguration(config, options?.title);
|
printConfiguration(config, options?.title);
|
||||||
const reporter = new Multiplexer([new ListReporter()]);
|
const reporter = new InternalReporter([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();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue