mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: introduce Reporter.onExit (#22176)
Fixes https://github.com/microsoft/playwright/issues/22173
This commit is contained in:
parent
966f2392a0
commit
f8f9ee6a25
@ -81,6 +81,7 @@ Here is a typical order of reporter calls:
|
|||||||
* [`method: Reporter.onStepBegin`] and [`method: Reporter.onStepEnd`] are called for each executed step inside the test. When steps are executed, test run has not finished yet.
|
* [`method: Reporter.onStepBegin`] and [`method: Reporter.onStepEnd`] are called for each executed step inside the test. When steps are executed, test run has not finished yet.
|
||||||
* [`method: Reporter.onTestEnd`] is called when test run has finished. By this time, [TestResult] is complete and you can use [`property: TestResult.status`], [`property: TestResult.error`] and more.
|
* [`method: Reporter.onTestEnd`] is called when test run has finished. By this time, [TestResult] is complete and you can use [`property: TestResult.status`], [`property: TestResult.error`] and more.
|
||||||
* [`method: Reporter.onEnd`] is called once after all tests that should run had finished.
|
* [`method: Reporter.onEnd`] is called once after all tests that should run had finished.
|
||||||
|
* [`method: Reporter.onExit`] is called immediately before the test runner exits.
|
||||||
|
|
||||||
Additionally, [`method: Reporter.onStdOut`] and [`method: Reporter.onStdErr`] are called when standard output is produced in the worker process, possibly during a test execution,
|
Additionally, [`method: Reporter.onStdOut`] and [`method: Reporter.onStdErr`] are called when standard output is produced in the worker process, possibly during a test execution,
|
||||||
and [`method: Reporter.onError`] is called when something went wrong outside of the test execution.
|
and [`method: Reporter.onError`] is called when something went wrong outside of the test execution.
|
||||||
@ -131,6 +132,12 @@ Called on some global error, for example unhandled exception in the worker proce
|
|||||||
|
|
||||||
The error.
|
The error.
|
||||||
|
|
||||||
|
## optional async method: Reporter.onExit
|
||||||
|
* since: v1.33
|
||||||
|
|
||||||
|
Called immediately before test runner exists. At this point all the reporters
|
||||||
|
have recived the [`method: Reporter.onEnd`] signal, so all the reports should
|
||||||
|
be build. You can run the code that uploads the reports in this hook.
|
||||||
|
|
||||||
## optional method: Reporter.onStdErr
|
## optional method: Reporter.onStdErr
|
||||||
* since: v1.10
|
* since: v1.10
|
||||||
|
@ -14,12 +14,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Suite, Reporter } from '../../types/testReporter';
|
import type { Suite } from '../../types/testReporter';
|
||||||
import type { FullConfig } from '../common/types';
|
import type { FullConfig } from '../common/types';
|
||||||
|
import type { Multiplexer } from '../reporters/multiplexer';
|
||||||
|
|
||||||
export interface TestRunnerPlugin {
|
export interface TestRunnerPlugin {
|
||||||
name: string;
|
name: string;
|
||||||
setup?(config: FullConfig, configDir: string, reporter: Reporter): Promise<void>;
|
setup?(config: FullConfig, configDir: string, reporter: Multiplexer): Promise<void>;
|
||||||
begin?(suite: Suite): Promise<void>;
|
begin?(suite: Suite): Promise<void>;
|
||||||
end?(): Promise<void>;
|
end?(): Promise<void>;
|
||||||
teardown?(): Promise<void>;
|
teardown?(): Promise<void>;
|
||||||
|
@ -19,10 +19,11 @@ import net from 'net';
|
|||||||
import { debug } from 'playwright-core/lib/utilsBundle';
|
import { debug } from 'playwright-core/lib/utilsBundle';
|
||||||
import { raceAgainstTimeout, launchProcess, httpRequest } from 'playwright-core/lib/utils';
|
import { raceAgainstTimeout, launchProcess, httpRequest } from 'playwright-core/lib/utils';
|
||||||
|
|
||||||
import type { FullConfig, Reporter } from '../../types/testReporter';
|
import type { FullConfig } from '../../types/testReporter';
|
||||||
import type { TestRunnerPlugin } from '.';
|
import type { TestRunnerPlugin } from '.';
|
||||||
import type { FullConfigInternal } from '../common/types';
|
import type { FullConfigInternal } from '../common/types';
|
||||||
import { envWithoutExperimentalLoaderOptions } from '../util';
|
import { envWithoutExperimentalLoaderOptions } from '../util';
|
||||||
|
import type { Multiplexer } from '../reporters/multiplexer';
|
||||||
|
|
||||||
|
|
||||||
export type WebServerPluginOptions = {
|
export type WebServerPluginOptions = {
|
||||||
@ -47,7 +48,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?: Reporter;
|
private _reporter?: Multiplexer;
|
||||||
name = 'playwright:webserver';
|
name = 'playwright:webserver';
|
||||||
|
|
||||||
constructor(options: WebServerPluginOptions, checkPortOnly: boolean) {
|
constructor(options: WebServerPluginOptions, checkPortOnly: boolean) {
|
||||||
@ -55,7 +56,7 @@ export class WebServerPlugin implements TestRunnerPlugin {
|
|||||||
this._checkPortOnly = checkPortOnly;
|
this._checkPortOnly = checkPortOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setup(config: FullConfig, configDir: string, reporter: Reporter) {
|
public async setup(config: FullConfig, configDir: string, reporter: Multiplexer) {
|
||||||
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;
|
||||||
@ -146,7 +147,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: Reporter['onStdErr']) {
|
async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Multiplexer['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);
|
||||||
@ -156,7 +157,7 @@ async function isURLAvailable(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Re
|
|||||||
return statusCode >= 200 && statusCode < 404;
|
return statusCode >= 200 && statusCode < 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Reporter['onStdErr']): Promise<number> {
|
async function httpStatusCode(url: URL, ignoreHTTPSErrors: boolean, onStdErr: Multiplexer['onStdErr']): Promise<number> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
debugWebServer(`HTTP GET: ${url}`);
|
debugWebServer(`HTTP GET: ${url}`);
|
||||||
httpRequest({
|
httpRequest({
|
||||||
@ -189,7 +190,7 @@ async function waitFor(waitFn: () => Promise<boolean>, cancellationToken: { canc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIsAvailableFunction(url: string, checkPortOnly: boolean, ignoreHTTPSErrors: boolean, onStdErr: Reporter['onStdErr']) {
|
function getIsAvailableFunction(url: string, checkPortOnly: boolean, ignoreHTTPSErrors: boolean, onStdErr: Multiplexer['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);
|
||||||
|
@ -112,7 +112,7 @@ class HtmlReporter implements Reporter {
|
|||||||
this._buildResult = await builder.build({ ...this.config.metadata, duration }, reports);
|
this._buildResult = await builder.build({ ...this.config.metadata, duration }, reports);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onExit() {
|
async onExit() {
|
||||||
if (process.env.CI || !this._buildResult)
|
if (process.env.CI || !this._buildResult)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ type StdIOChunk = {
|
|||||||
result?: TestResult;
|
result?: TestResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Multiplexer implements Reporter {
|
export class Multiplexer {
|
||||||
private _reporters: Reporter[];
|
private _reporters: Reporter[];
|
||||||
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
||||||
private _config!: FullConfig;
|
private _config!: FullConfig;
|
||||||
@ -99,7 +99,7 @@ export class Multiplexer implements Reporter {
|
|||||||
await Promise.resolve().then(() => reporter.onEnd?.(result)).catch(e => console.error('Error in reporter', e));
|
await Promise.resolve().then(() => reporter.onEnd?.(result)).catch(e => console.error('Error in reporter', e));
|
||||||
|
|
||||||
for (const reporter of this._reporters)
|
for (const reporter of this._reporters)
|
||||||
await Promise.resolve().then(() => (reporter as any)._onExit?.()).catch(e => console.error('Error in reporter', e));
|
await Promise.resolve().then(() => reporter.onExit?.()).catch(e => console.error('Error in reporter', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(error: TestError) {
|
onError(error: TestError) {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import type { TestBeginPayload, TestEndPayload, DonePayload, TestOutputPayload, StepBeginPayload, StepEndPayload, TeardownErrorsPayload, RunPayload, SerializedConfig } from '../common/ipc';
|
import type { TestBeginPayload, TestEndPayload, DonePayload, TestOutputPayload, StepBeginPayload, StepEndPayload, TeardownErrorsPayload, RunPayload, SerializedConfig } from '../common/ipc';
|
||||||
import { serializeConfig } from '../common/ipc';
|
import { serializeConfig } from '../common/ipc';
|
||||||
import type { TestResult, Reporter, TestStep, TestError } from '../../types/testReporter';
|
import type { TestResult, TestStep, TestError } from '../../types/testReporter';
|
||||||
import type { Suite } from '../common/test';
|
import type { Suite } from '../common/test';
|
||||||
import type { ProcessExitData } from './processHost';
|
import type { ProcessExitData } from './processHost';
|
||||||
import type { TestCase } from '../common/test';
|
import type { TestCase } from '../common/test';
|
||||||
@ -24,6 +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/types';
|
import type { FullConfigInternal } from '../common/types';
|
||||||
|
import type { Multiplexer } from '../reporters/multiplexer';
|
||||||
|
|
||||||
type TestResultData = {
|
type TestResultData = {
|
||||||
result: TestResult;
|
result: TestResult;
|
||||||
@ -45,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: Reporter;
|
private _reporter: Multiplexer;
|
||||||
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: Reporter) {
|
constructor(config: FullConfigInternal, reporter: Multiplexer) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
}
|
}
|
||||||
|
@ -16,22 +16,23 @@
|
|||||||
|
|
||||||
import { debug } from 'playwright-core/lib/utilsBundle';
|
import { debug } from 'playwright-core/lib/utilsBundle';
|
||||||
import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils';
|
import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils';
|
||||||
import type { FullResult, Reporter, 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';
|
||||||
|
|
||||||
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: Reporter;
|
private _reporter: Multiplexer;
|
||||||
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: Reporter, globalTimeoutForError: number) {
|
constructor(reporter: Multiplexer, globalTimeoutForError: number) {
|
||||||
this._reporter = reporter;
|
this._reporter = reporter;
|
||||||
this._globalTimeoutForError = globalTimeoutForError;
|
this._globalTimeoutForError = globalTimeoutForError;
|
||||||
}
|
}
|
||||||
|
@ -372,6 +372,8 @@ export interface FullResult {
|
|||||||
* [testResult.error](https://playwright.dev/docs/api/class-testresult#test-result-error) and more.
|
* [testResult.error](https://playwright.dev/docs/api/class-testresult#test-result-error) and more.
|
||||||
* - [reporter.onEnd(result)](https://playwright.dev/docs/api/class-reporter#reporter-on-end) is called once after
|
* - [reporter.onEnd(result)](https://playwright.dev/docs/api/class-reporter#reporter-on-end) is called once after
|
||||||
* all tests that should run had finished.
|
* all tests that should run had finished.
|
||||||
|
* - [reporter.onExit()](https://playwright.dev/docs/api/class-reporter#reporter-on-exit) is called immediately
|
||||||
|
* before the test runner exits.
|
||||||
*
|
*
|
||||||
* Additionally,
|
* Additionally,
|
||||||
* [reporter.onStdOut(chunk, test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-std-out) and
|
* [reporter.onStdOut(chunk, test, result)](https://playwright.dev/docs/api/class-reporter#reporter-on-std-out) and
|
||||||
@ -410,6 +412,13 @@ export interface Reporter {
|
|||||||
*/
|
*/
|
||||||
onError?(error: TestError): void;
|
onError?(error: TestError): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately before test runner exists. At this point all the reporters have recived the
|
||||||
|
* [reporter.onEnd(result)](https://playwright.dev/docs/api/class-reporter#reporter-on-end) signal, so all the reports
|
||||||
|
* should be build. You can run the code that uploads the reports in this hook.
|
||||||
|
*/
|
||||||
|
onExit?(): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when something has been written to the standard error in the worker process.
|
* Called when something has been written to the standard error in the worker process.
|
||||||
* @param chunk Output chunk.
|
* @param chunk Output chunk.
|
||||||
|
@ -33,6 +33,9 @@ class Reporter {
|
|||||||
onEnd() {
|
onEnd() {
|
||||||
console.log('\\n%%end');
|
console.log('\\n%%end');
|
||||||
}
|
}
|
||||||
|
onExit() {
|
||||||
|
console.log('\\n%%exit');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports = Reporter;
|
module.exports = Reporter;
|
||||||
`;
|
`;
|
||||||
@ -176,6 +179,7 @@ test('should work without a file extension', async ({ runInlineTest }) => {
|
|||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'begin',
|
'begin',
|
||||||
'end',
|
'end',
|
||||||
|
'exit',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,6 +209,7 @@ test('should report onEnd after global teardown', async ({ runInlineTest }) => {
|
|||||||
'begin',
|
'begin',
|
||||||
'global teardown',
|
'global teardown',
|
||||||
'end',
|
'end',
|
||||||
|
'exit',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -227,6 +232,7 @@ test('should load reporter from node_modules', async ({ runInlineTest }) => {
|
|||||||
expect(result.outputLines).toEqual([
|
expect(result.outputLines).toEqual([
|
||||||
'begin',
|
'begin',
|
||||||
'end',
|
'end',
|
||||||
|
'exit',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user