diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts index 990fa63100..176b52933e 100644 --- a/packages/playwright-core/src/client/browserType.ts +++ b/packages/playwright-core/src/client/browserType.ts @@ -183,7 +183,7 @@ export class BrowserType extends ChannelOwner imple }); const result = await raceAgainstDeadline(createBrowserPromise, deadline); - if (result.result) { + if (!result.timedOut) { return result.result; } else { closePipe(); diff --git a/packages/playwright-core/src/utils/async.ts b/packages/playwright-core/src/utils/async.ts index 718899288c..aad4794bb1 100644 --- a/packages/playwright-core/src/utils/async.ts +++ b/packages/playwright-core/src/utils/async.ts @@ -18,18 +18,18 @@ import { monotonicTime } from './utils'; export class DeadlineRunner { 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, deadline: number) { promise.then(result => { - this._finish({ result }); + this._finish({ result, timedOut: false }); }).catch(e => { this._finish(undefined, e); }); 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()) return; this.updateDeadline(0); @@ -58,7 +58,7 @@ export class DeadlineRunner { } } -export async function raceAgainstDeadline(promise: Promise, deadline: number): Promise<{ result?: T, timedOut?: boolean }> { +export async function raceAgainstDeadline(promise: Promise, deadline: number) { return (new DeadlineRunner(promise, deadline)).result; } diff --git a/packages/playwright-test/src/fixtures.ts b/packages/playwright-test/src/fixtures.ts index c26fb9069e..4391e2aa64 100644 --- a/packages/playwright-test/src/fixtures.ts +++ b/packages/playwright-test/src/fixtures.ts @@ -20,15 +20,18 @@ import { FixturesWithLocation, Location, WorkerInfo, TestInfo, TestStepInternal import { ManualPromise } from 'playwright-core/lib/utils/async'; type FixtureScope = 'test' | 'worker'; +type FixtureOptions = { auto?: boolean, scope?: FixtureScope, option?: boolean }; +type FixtureTuple = [ value: any, options: FixtureOptions ]; type FixtureRegistration = { - location: Location; + location: Location; // Fixutre registration location. name: string; scope: FixtureScope; fn: Function | any; // Either a fixture function, or a fixture value. auto: boolean; - deps: string[]; - id: string; - super?: FixtureRegistration; + option: boolean; + deps: string[]; // Names of the dependencies, ({ foo, bar }) => {...} + 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 { @@ -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]); } -export function isFixtureOption(value: any): value is [any, any] { +export function isFixtureOption(value: any): value is FixtureTuple { return isFixtureTuple(value) && !!value[1].option; } @@ -124,11 +127,12 @@ export class FixturePool { for (const entry of Object.entries(fixtures)) { const name = entry[0]; let value = entry[1]; - let options: { auto: boolean, scope: FixtureScope } | undefined; + let options: Required | undefined; if (isFixtureTuple(value)) { options = { auto: !!value[1].auto, - scope: value[1].scope || 'test' + scope: value[1].scope || 'test', + option: !!value[1].option, }; value = value[0]; } @@ -141,9 +145,9 @@ export class FixturePool { if (previous.auto !== options.auto) throw errorWithLocations(`Fixture "${name}" has already been registered as a { auto: '${previous.scope}' } fixture.`, { location, name }, previous); } else if (previous) { - options = { auto: previous.auto, scope: previous.scope }; + options = { auto: previous.auto, scope: previous.scope, option: previous.option }; } else if (!options) { - options = { auto: false, scope: 'test' }; + options = { auto: false, scope: 'test', option: false }; } 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 }); 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); this.registrations.set(name, registration); } diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index d121c87f65..cb479d05c3 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -100,8 +100,8 @@ export class Runner { try { const config = this._loader.fullConfig(); const globalDeadline = config.globalTimeout ? config.globalTimeout + monotonicTime() : 0; - const { result, timedOut } = await raceAgainstDeadline(this._run(list, filePatternFilters, projectNames), globalDeadline); - if (timedOut) { + const result = await raceAgainstDeadline(this._run(list, filePatternFilters, projectNames), globalDeadline); + if (result.timedOut) { const actualResult: FullResult = { status: 'timedout' }; if (this._didBegin) 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`)); return actualResult; } - return result!; + return result.result; } catch (e) { const result: FullResult = { status: 'failed' }; try { diff --git a/packages/playwright-test/src/workerRunner.ts b/packages/playwright-test/src/workerRunner.ts index 71b83e5fa4..d2c286764a 100644 --- a/packages/playwright-test/src/workerRunner.ts +++ b/packages/playwright-test/src/workerRunner.ts @@ -194,9 +194,9 @@ export class WorkerRunner extends EventEmitter { 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.stop(); - } - if (!!result.result) + } else if (!!result.result) { annotations.push({ type: beforeAllModifier.type, description: beforeAllModifier.description }); + } } for (const hook of suite.hooks) {