chore(test runner): minor improvements (#11067)

- Types for fixture options and more.
- Refined type for deadline runner.
This commit is contained in:
Dmitry Gozman 2021-12-22 09:59:58 -08:00 committed by GitHub
parent 97a43b4bed
commit f933759ad1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 25 additions and 21 deletions

View file

@ -183,7 +183,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
}); });
const result = await raceAgainstDeadline(createBrowserPromise, deadline); const result = await raceAgainstDeadline(createBrowserPromise, deadline);
if (result.result) { if (!result.timedOut) {
return result.result; return result.result;
} else { } else {
closePipe(); closePipe();

View file

@ -18,18 +18,18 @@ import { monotonicTime } from './utils';
export class DeadlineRunner<T> { export class DeadlineRunner<T> {
private _timer: NodeJS.Timer | undefined; private _timer: NodeJS.Timer | undefined;
readonly result = new ManualPromise<{ result?: T, timedOut?: boolean }>(); readonly result = new ManualPromise<{ timedOut: true } | { result: T, timedOut: false }>();
constructor(promise: Promise<T>, deadline: number) { constructor(promise: Promise<T>, deadline: number) {
promise.then(result => { promise.then(result => {
this._finish({ result }); this._finish({ result, timedOut: false });
}).catch(e => { }).catch(e => {
this._finish(undefined, e); this._finish(undefined, e);
}); });
this.updateDeadline(deadline); this.updateDeadline(deadline);
} }
private _finish(success?: { result?: T, timedOut?: boolean }, error?: any) { private _finish(success?: { timedOut: true } | { result: T, timedOut: false }, error?: any) {
if (this.result.isDone()) if (this.result.isDone())
return; return;
this.updateDeadline(0); this.updateDeadline(0);
@ -58,7 +58,7 @@ export class DeadlineRunner<T> {
} }
} }
export async function raceAgainstDeadline<T>(promise: Promise<T>, deadline: number): Promise<{ result?: T, timedOut?: boolean }> { export async function raceAgainstDeadline<T>(promise: Promise<T>, deadline: number) {
return (new DeadlineRunner(promise, deadline)).result; return (new DeadlineRunner(promise, deadline)).result;
} }

View file

@ -20,15 +20,18 @@ import { FixturesWithLocation, Location, WorkerInfo, TestInfo, TestStepInternal
import { ManualPromise } from 'playwright-core/lib/utils/async'; import { ManualPromise } from 'playwright-core/lib/utils/async';
type FixtureScope = 'test' | 'worker'; type FixtureScope = 'test' | 'worker';
type FixtureOptions = { auto?: boolean, scope?: FixtureScope, option?: boolean };
type FixtureTuple = [ value: any, options: FixtureOptions ];
type FixtureRegistration = { type FixtureRegistration = {
location: Location; location: Location; // Fixutre registration location.
name: string; name: string;
scope: FixtureScope; scope: FixtureScope;
fn: Function | any; // Either a fixture function, or a fixture value. fn: Function | any; // Either a fixture function, or a fixture value.
auto: boolean; auto: boolean;
deps: string[]; option: boolean;
id: string; deps: string[]; // Names of the dependencies, ({ foo, bar }) => {...}
super?: FixtureRegistration; id: string; // Unique id, to differentiate between fixtures with the same name.
super?: FixtureRegistration; // A fixture override can use the previous version of the fixture.
}; };
class Fixture { class Fixture {
@ -105,11 +108,11 @@ class Fixture {
} }
} }
function isFixtureTuple(value: any): value is [any, any] { function isFixtureTuple(value: any): value is FixtureTuple {
return Array.isArray(value) && typeof value[1] === 'object' && ('scope' in value[1] || 'auto' in value[1] || 'option' in value[1]); return Array.isArray(value) && typeof value[1] === 'object' && ('scope' in value[1] || 'auto' in value[1] || 'option' in value[1]);
} }
export function isFixtureOption(value: any): value is [any, any] { export function isFixtureOption(value: any): value is FixtureTuple {
return isFixtureTuple(value) && !!value[1].option; return isFixtureTuple(value) && !!value[1].option;
} }
@ -124,11 +127,12 @@ export class FixturePool {
for (const entry of Object.entries(fixtures)) { for (const entry of Object.entries(fixtures)) {
const name = entry[0]; const name = entry[0];
let value = entry[1]; let value = entry[1];
let options: { auto: boolean, scope: FixtureScope } | undefined; let options: Required<FixtureOptions> | undefined;
if (isFixtureTuple(value)) { if (isFixtureTuple(value)) {
options = { options = {
auto: !!value[1].auto, auto: !!value[1].auto,
scope: value[1].scope || 'test' scope: value[1].scope || 'test',
option: !!value[1].option,
}; };
value = value[0]; value = value[0];
} }
@ -141,9 +145,9 @@ export class FixturePool {
if (previous.auto !== options.auto) if (previous.auto !== options.auto)
throw errorWithLocations(`Fixture "${name}" has already been registered as a { auto: '${previous.scope}' } fixture.`, { location, name }, previous); throw errorWithLocations(`Fixture "${name}" has already been registered as a { auto: '${previous.scope}' } fixture.`, { location, name }, previous);
} else if (previous) { } else if (previous) {
options = { auto: previous.auto, scope: previous.scope }; options = { auto: previous.auto, scope: previous.scope, option: previous.option };
} else if (!options) { } else if (!options) {
options = { auto: false, scope: 'test' }; options = { auto: false, scope: 'test', option: false };
} }
if (options.scope !== 'test' && options.scope !== 'worker') if (options.scope !== 'test' && options.scope !== 'worker')
@ -152,7 +156,7 @@ export class FixturePool {
throw errorWithLocations(`Cannot use({ ${name} }) in a describe group, because it forces a new worker.\nMake it top-level in the test file or put in the configuration file.`, { location, name }); throw errorWithLocations(`Cannot use({ ${name} }) in a describe group, because it forces a new worker.\nMake it top-level in the test file or put in the configuration file.`, { location, name });
const deps = fixtureParameterNames(fn, location); const deps = fixtureParameterNames(fn, location);
const registration: FixtureRegistration = { id: '', name, location, scope: options.scope, fn, auto: options.auto, deps, super: previous }; const registration: FixtureRegistration = { id: '', name, location, scope: options.scope, fn, auto: options.auto, option: options.option, deps, super: previous };
registrationId(registration); registrationId(registration);
this.registrations.set(name, registration); this.registrations.set(name, registration);
} }

View file

@ -100,8 +100,8 @@ export class Runner {
try { try {
const config = this._loader.fullConfig(); const config = this._loader.fullConfig();
const globalDeadline = config.globalTimeout ? config.globalTimeout + monotonicTime() : 0; const globalDeadline = config.globalTimeout ? config.globalTimeout + monotonicTime() : 0;
const { result, timedOut } = await raceAgainstDeadline(this._run(list, filePatternFilters, projectNames), globalDeadline); const result = await raceAgainstDeadline(this._run(list, filePatternFilters, projectNames), globalDeadline);
if (timedOut) { if (result.timedOut) {
const actualResult: FullResult = { status: 'timedout' }; const actualResult: FullResult = { status: 'timedout' };
if (this._didBegin) if (this._didBegin)
await this._reporter.onEnd?.(actualResult); await this._reporter.onEnd?.(actualResult);
@ -109,7 +109,7 @@ export class Runner {
this._reporter.onError?.(createStacklessError(`Timed out waiting ${config.globalTimeout / 1000}s for the entire test run`)); this._reporter.onError?.(createStacklessError(`Timed out waiting ${config.globalTimeout / 1000}s for the entire test run`));
return actualResult; return actualResult;
} }
return result!; return result.result;
} catch (e) { } catch (e) {
const result: FullResult = { status: 'failed' }; const result: FullResult = { status: 'failed' };
try { try {

View file

@ -194,9 +194,9 @@ export class WorkerRunner extends EventEmitter {
if (!this._fatalError) if (!this._fatalError)
this._fatalError = serializeError(new Error(`Timeout of ${this._project.config.timeout}ms exceeded while running ${beforeAllModifier.type} modifier\n at ${formatLocation(beforeAllModifier.location)}`)); this._fatalError = serializeError(new Error(`Timeout of ${this._project.config.timeout}ms exceeded while running ${beforeAllModifier.type} modifier\n at ${formatLocation(beforeAllModifier.location)}`));
this.stop(); this.stop();
} } else if (!!result.result) {
if (!!result.result)
annotations.push({ type: beforeAllModifier.type, description: beforeAllModifier.description }); annotations.push({ type: beforeAllModifier.type, description: beforeAllModifier.description });
}
} }
for (const hook of suite.hooks) { for (const hook of suite.hooks) {