chore: call onEnd(result) on InternalReporter (#23972)

Drive-by: fix watch mode not running global teardown.
This commit is contained in:
Dmitry Gozman 2023-06-29 17:03:10 -07:00 committed by GitHub
parent a07626b804
commit e28312ba63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 23 deletions

View File

@ -22,7 +22,6 @@ import { Suite } from '../common/test';
import type { FullConfigInternal } from '../common/config'; 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 { monotonicTime } from 'playwright-core/lib/utils';
type StdIOChunk = { type StdIOChunk = {
chunk: string | Buffer; chunk: string | Buffer;
@ -34,7 +33,6 @@ export class InternalReporter {
private _multiplexer: Multiplexer; private _multiplexer: Multiplexer;
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = []; private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
private _config!: FullConfigInternal; private _config!: FullConfigInternal;
private _montonicStartTime: number = 0;
constructor(reporters: Reporter[]) { constructor(reporters: Reporter[]) {
this._multiplexer = new Multiplexer(reporters); this._multiplexer = new Multiplexer(reporters);
@ -45,7 +43,6 @@ export class InternalReporter {
} }
onBegin(config: FullConfig, suite: Suite) { onBegin(config: FullConfig, suite: Suite) {
this._montonicStartTime = monotonicTime();
this._multiplexer.onBegin(config, suite); this._multiplexer.onBegin(config, suite);
const deferred = this._deferred!; const deferred = this._deferred!;
@ -86,16 +83,15 @@ export class InternalReporter {
this._multiplexer.onTestEnd(test, result); this._multiplexer.onTestEnd(test, result);
} }
async onEnd() { async onEnd(result: FullResult) {
this._config.config.metadata.totalTime = monotonicTime() - this._montonicStartTime;
}
async onExit(result: FullResult) {
if (this._deferred) { if (this._deferred) {
// onBegin was not reported, emit it. // onBegin was not reported, emit it.
this.onBegin(this._config.config, new Suite('', 'root')); this.onBegin(this._config.config, new Suite('', 'root'));
} }
await this._multiplexer.onEnd(result); await this._multiplexer.onEnd(result);
}
async onExit() {
await this._multiplexer.onExit(); await this._multiplexer.onExit();
} }

View File

@ -91,7 +91,8 @@ export class Runner {
status = 'failed'; status = 'failed';
if (status === 'passed' && taskStatus !== 'passed') if (status === 'passed' && taskStatus !== 'passed')
status = taskStatus; status = taskStatus;
await reporter.onExit({ status }); await reporter.onEnd({ status });
await reporter.onExit();
// Calling process.exit() might truncate large stdout/stderr output. // Calling process.exit() might truncate large stdout/stderr output.
// See https://github.com/nodejs/node/issues/6456. // See https://github.com/nodejs/node/issues/6456.

View File

@ -29,6 +29,7 @@ import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGloba
import type { Matcher } from '../util'; import type { Matcher } from '../util';
import type { Suite } from '../common/test'; import type { Suite } from '../common/test';
import { buildDependentProjects, buildTeardownToSetupsMap } from './projectUtils'; import { buildDependentProjects, buildTeardownToSetupsMap } from './projectUtils';
import { monotonicTime } from 'playwright-core/lib/utils';
const removeFolderAsync = promisify(rimraf); const removeFolderAsync = promisify(rimraf);
const readDirAsync = promisify(fs.readdir); const readDirAsync = promisify(fs.readdir);
@ -91,10 +92,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', createReportBeginTask());
reporter.onBegin(config.config, rootSuite!);
return () => reporter.onEnd();
});
for (const plugin of config.plugins) for (const plugin of config.plugins)
taskRunner.addTask('plugin begin', createPluginBeginTask(plugin)); taskRunner.addTask('plugin begin', createPluginBeginTask(plugin));
taskRunner.addTask('start workers', createWorkersTask()); taskRunner.addTask('start workers', createWorkersTask());
@ -105,13 +103,20 @@ function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal
export function createTaskRunnerForList(config: FullConfigInternal, reporter: InternalReporter, mode: 'in-process' | 'out-of-process', options: { failOnLoadErrors: boolean }): TaskRunner<TestRun> { export function createTaskRunnerForList(config: FullConfigInternal, reporter: InternalReporter, 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', async ({ reporter, rootSuite }) => { taskRunner.addTask('report begin', createReportBeginTask());
reporter.onBegin(config.config, rootSuite!);
return () => reporter.onEnd();
});
return taskRunner; return taskRunner;
} }
function createReportBeginTask(): Task<TestRun> {
return async ({ config, reporter, rootSuite }) => {
const montonicStartTime = monotonicTime();
reporter.onBegin(config.config, rootSuite!);
return async () => {
config.config.metadata.totalTime = monotonicTime() - montonicStartTime;
};
};
}
function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestRun> { function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestRun> {
return async ({ config, reporter }) => { return async ({ config, reporter }) => {
if (typeof plugin.factory === 'function') if (typeof plugin.factory === 'function')

View File

@ -72,7 +72,8 @@ class UIMode {
reporter.onConfigure(this._config); reporter.onConfigure(this._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.onExit({ status }); await reporter.onEnd({ status });
await reporter.onExit();
if (status !== 'passed') { if (status !== 'passed') {
await globalCleanup(); await globalCleanup();
return status; return status;
@ -168,7 +169,8 @@ class UIMode {
clearCompilationCache(); clearCompilationCache();
reporter.onConfigure(this._config); reporter.onConfigure(this._config);
const status = await taskRunner.run(testRun, 0); const status = await taskRunner.run(testRun, 0);
await reporter.onExit({ status }); await reporter.onEnd({ status });
await reporter.onExit();
const projectDirs = new Set<string>(); const projectDirs = new Set<string>();
for (const p of this._config.projects) for (const p of this._config.projects)
@ -192,7 +194,8 @@ class UIMode {
reporter.onConfigure(this._config); reporter.onConfigure(this._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.onExit({ status }); await reporter.onEnd({ status });
await reporter.onExit();
this._testRun = undefined; this._testRun = undefined;
this._config.testIdMatcher = undefined; this._config.testIdMatcher = undefined;
return status; return status;

View File

@ -118,7 +118,11 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
reporter.onConfigure(config); reporter.onConfigure(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')
return await globalCleanup(); await globalCleanup();
await reporter.onEnd({ status });
await reporter.onExit();
if (status !== 'passed')
return status;
// Prepare projects that will be watched, set up watcher. // Prepare projects that will be watched, set up watcher.
const failedTestIdCollector = new Set<string>(); const failedTestIdCollector = new Set<string>();
@ -248,7 +252,8 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
} }
} }
return result === 'passed' ? await globalCleanup() : result; const cleanupStatus = await globalCleanup();
return result === 'passed' ? cleanupStatus : result;
} }
async function runChangedTests(config: FullConfigInternal, failedTestIdCollector: Set<string>, filesByProject: Map<FullProjectInternal, Set<string>>, title?: string) { async function runChangedTests(config: FullConfigInternal, failedTestIdCollector: Set<string>, filesByProject: Map<FullProjectInternal, Set<string>>, title?: string) {
@ -297,7 +302,8 @@ async function runTests(config: FullConfigInternal, failedTestIdCollector: Set<s
status = 'failed'; status = 'failed';
if (status === 'passed' && taskStatus !== 'passed') if (status === 'passed' && taskStatus !== 'passed')
status = taskStatus; status = taskStatus;
await reporter.onExit({ status }); await reporter.onEnd({ status });
await reporter.onExit();
} }
function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected: FullProjectInternal[]): Set<FullProjectInternal> { function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected: FullProjectInternal[]): Set<FullProjectInternal> {

View File

@ -696,3 +696,27 @@ test('should run CT on indirect deps change ESM mode', async ({ runWatchTest, wr
expect(testProcess.output).not.toContain(`src${path.sep}link.spec.tsx`); expect(testProcess.output).not.toContain(`src${path.sep}link.spec.tsx`);
await testProcess.waitForOutput('Waiting for file changes.'); await testProcess.waitForOutput('Waiting for file changes.');
}); });
test('should run global teardown before exiting', async ({ runWatchTest }) => {
const testProcess = await runWatchTest({
'playwright.config.ts': `
export default {
globalTeardown: './global-teardown.ts',
};
`,
'global-teardown.ts': `
export default async function() {
console.log('running teardown');
};
`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('passes', async () => {
});
`,
}, {});
await testProcess.waitForOutput('a.test.ts:3:11 passes');
await testProcess.waitForOutput('Waiting for file changes.');
testProcess.write('\x1B');
await testProcess.waitForOutput('running teardown');
});