Revert "chore: refactor timeout manager to use scopes (1) (#24315)" (#24382)

This reverts commit f5df0940c9.
This commit is contained in:
Pavel Feldman 2023-07-24 14:09:20 -07:00 committed by GitHub
parent d144c5e6e3
commit 4ba6d789bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 218 additions and 224 deletions

View file

@ -439,10 +439,9 @@ function formatStackFrame(frame: StackFrame) {
} }
function hookType(testInfo: TestInfoImpl): 'beforeAll' | 'afterAll' | undefined { function hookType(testInfo: TestInfoImpl): 'beforeAll' | 'afterAll' | undefined {
if (testInfo._timeoutManager.hasRunnableType('beforeAll')) const type = testInfo._timeoutManager.currentRunnableType();
return 'beforeAll'; if (type === 'beforeAll' || type === 'afterAll')
if (testInfo._timeoutManager.hasRunnableType('afterAll')) return type;
return 'afterAll';
} }
type StackFrame = { type StackFrame = {

View file

@ -17,7 +17,7 @@
import { formatLocation, debugTest, filterStackFile, serializeError } from '../util'; import { formatLocation, debugTest, filterStackFile, serializeError } from '../util';
import { ManualPromise, zones } from 'playwright-core/lib/utils'; import { ManualPromise, zones } from 'playwright-core/lib/utils';
import type { TestInfoImpl, TestStepInternal } from './testInfo'; import type { TestInfoImpl, TestStepInternal } from './testInfo';
import type { RunnableDescription, TimeoutManager } from './timeoutManager'; import type { FixtureDescription, TimeoutManager } from './timeoutManager';
import { fixtureParameterNames, type FixturePool, type FixtureRegistration, type FixtureScope } from '../common/fixtures'; import { fixtureParameterNames, type FixturePool, type FixtureRegistration, type FixtureScope } from '../common/fixtures';
import type { WorkerInfo } from '../../types/test'; import type { WorkerInfo } from '../../types/test';
import type { Location } from '../../types/testReporter'; import type { Location } from '../../types/testReporter';
@ -31,7 +31,7 @@ class Fixture {
_useFuncFinished: ManualPromise<void> | undefined; _useFuncFinished: ManualPromise<void> | undefined;
_selfTeardownComplete: Promise<void> | undefined; _selfTeardownComplete: Promise<void> | undefined;
_teardownWithDepsComplete: Promise<void> | undefined; _teardownWithDepsComplete: Promise<void> | undefined;
_runnableDescription: RunnableDescription; _runnableDescription: FixtureDescription;
_deps = new Set<Fixture>(); _deps = new Set<Fixture>();
_usages = new Set<Fixture>(); _usages = new Set<Fixture>();
@ -42,7 +42,6 @@ class Fixture {
const title = this.registration.customTitle || this.registration.name; const title = this.registration.customTitle || this.registration.name;
this._runnableDescription = { this._runnableDescription = {
title, title,
type: 'fixture',
phase: 'setup', phase: 'setup',
location: registration.location, location: registration.location,
slot: this.registration.timeout === undefined ? undefined : { slot: this.registration.timeout === undefined ? undefined : {
@ -107,6 +106,7 @@ class Fixture {
const workerInfo: WorkerInfo = { config: testInfo.config, parallelIndex: testInfo.parallelIndex, workerIndex: testInfo.workerIndex, project: testInfo.project }; const workerInfo: WorkerInfo = { config: testInfo.config, parallelIndex: testInfo.parallelIndex, workerIndex: testInfo.workerIndex, project: testInfo.project };
const info = this.registration.scope === 'worker' ? workerInfo : testInfo; const info = this.registration.scope === 'worker' ? workerInfo : testInfo;
testInfo._timeoutManager.setCurrentFixture(this._runnableDescription);
const handleError = (e: any) => { const handleError = (e: any) => {
this.failed = true; this.failed = true;
@ -115,8 +115,6 @@ class Fixture {
else else
throw e; throw e;
}; };
await testInfo._timeoutManager.runRunnable(this._runnableDescription, async () => {
try { try {
const result = zones.preserve(async () => { const result = zones.preserve(async () => {
if (!shouldGenerateStep) if (!shouldGenerateStep)
@ -148,7 +146,7 @@ class Fixture {
afterStep?.complete({ error: serializeError(e) }); afterStep?.complete({ error: serializeError(e) });
}); });
} }
}); testInfo._timeoutManager.setCurrentFixture(undefined);
} }
async teardown(timeoutManager: TimeoutManager) { async teardown(timeoutManager: TimeoutManager) {
@ -156,16 +154,20 @@ class Fixture {
// When we are waiting for the teardown for the second time, // When we are waiting for the teardown for the second time,
// most likely after the first time did timeout, annotate current fixture // most likely after the first time did timeout, annotate current fixture
// for better error messages. // for better error messages.
this._runnableDescription.phase = 'teardown'; this._setTeardownDescription(timeoutManager);
await timeoutManager.runRunnable(this._runnableDescription, async () => {
await this._teardownWithDepsComplete; await this._teardownWithDepsComplete;
}); timeoutManager.setCurrentFixture(undefined);
return; return;
} }
this._teardownWithDepsComplete = this._teardownInternal(timeoutManager); this._teardownWithDepsComplete = this._teardownInternal(timeoutManager);
await this._teardownWithDepsComplete; await this._teardownWithDepsComplete;
} }
private _setTeardownDescription(timeoutManager: TimeoutManager) {
this._runnableDescription.phase = 'teardown';
timeoutManager.setCurrentFixture(this._runnableDescription);
}
private async _teardownInternal(timeoutManager: TimeoutManager) { private async _teardownInternal(timeoutManager: TimeoutManager) {
if (typeof this.registration.fn !== 'function') if (typeof this.registration.fn !== 'function')
return; return;
@ -179,11 +181,10 @@ class Fixture {
} }
if (this._useFuncFinished) { if (this._useFuncFinished) {
debugTest(`teardown ${this.registration.name}`); debugTest(`teardown ${this.registration.name}`);
this._runnableDescription.phase = 'teardown'; this._setTeardownDescription(timeoutManager);
await timeoutManager.runRunnable(this._runnableDescription, async () => { this._useFuncFinished.resolve();
this._useFuncFinished!.resolve();
await this._selfTeardownComplete; await this._selfTeardownComplete;
}); timeoutManager.setCurrentFixture(undefined);
} }
} finally { } finally {
for (const dep of this._deps) for (const dep of this._deps)

View file

@ -24,22 +24,28 @@ export type TimeSlot = {
elapsed: number; elapsed: number;
}; };
export type RunnableDescription = { type RunnableDescription = {
type: 'test' | 'beforeAll' | 'afterAll' | 'beforeEach' | 'afterEach' | 'slow' | 'skip' | 'fail' | 'fixme' | 'teardown' | 'fixture'; type: 'test' | 'beforeAll' | 'afterAll' | 'beforeEach' | 'afterEach' | 'slow' | 'skip' | 'fail' | 'fixme' | 'teardown';
phase?: 'setup' | 'teardown';
title?: string;
location?: Location; location?: Location;
slot?: TimeSlot; // Falls back to test slot. slot?: TimeSlot; // Falls back to test slot.
}; };
export type FixtureDescription = {
title: string;
phase: 'setup' | 'teardown';
location?: Location;
slot?: TimeSlot; // Falls back to current runnable slot.
};
export class TimeoutManager { export class TimeoutManager {
private _defaultSlot: TimeSlot; private _defaultSlot: TimeSlot;
private _runnables: RunnableDescription[] = []; private _runnable: RunnableDescription;
private _fixture: FixtureDescription | undefined;
private _timeoutRunner: TimeoutRunner; private _timeoutRunner: TimeoutRunner;
constructor(timeout: number) { constructor(timeout: number) {
this._defaultSlot = { timeout, elapsed: 0 }; this._defaultSlot = { timeout, elapsed: 0 };
this._runnables = [{ type: 'test', slot: this._defaultSlot }]; this._runnable = { type: 'test', slot: this._defaultSlot };
this._timeoutRunner = new TimeoutRunner(timeout); this._timeoutRunner = new TimeoutRunner(timeout);
} }
@ -47,22 +53,12 @@ export class TimeoutManager {
this._timeoutRunner.interrupt(); this._timeoutRunner.interrupt();
} }
async runRunnable<T>(runnable: RunnableDescription, cb: () => Promise<T>): Promise<T> { setCurrentRunnable(runnable: RunnableDescription) {
let slot = this._currentSlot(); this._updateRunnables(runnable, undefined);
slot.elapsed = this._timeoutRunner.elapsed();
this._runnables.unshift(runnable);
slot = this._currentSlot();
this._timeoutRunner.updateTimeout(slot.timeout, slot.elapsed);
try {
return await cb();
} finally {
let slot = this._currentSlot();
slot.elapsed = this._timeoutRunner.elapsed();
this._runnables.splice(this._runnables.indexOf(runnable), 1);
slot = this._currentSlot();
this._timeoutRunner.updateTimeout(slot.timeout, slot.elapsed);
} }
setCurrentFixture(fixture: FixtureDescription | undefined) {
this._updateRunnables(this._runnable, fixture);
} }
defaultSlotTimings() { defaultSlotTimings() {
@ -95,12 +91,8 @@ export class TimeoutManager {
this._timeoutRunner.updateTimeout(timeout); this._timeoutRunner.updateTimeout(timeout);
} }
hasRunnableType(type: RunnableDescription['type']) { currentRunnableType() {
return this._runnables.some(r => r.type === type); return this._runnable.type;
}
private _runnable(): RunnableDescription {
return this._runnables[0]!;
} }
currentSlotDeadline() { currentSlotDeadline() {
@ -108,44 +100,51 @@ export class TimeoutManager {
} }
private _currentSlot() { private _currentSlot() {
for (const runnable of this._runnables) { return this._fixture?.slot || this._runnable.slot || this._defaultSlot;
if (runnable.slot)
return runnable.slot;
} }
return this._defaultSlot;
private _updateRunnables(runnable: RunnableDescription, fixture: FixtureDescription | undefined) {
let slot = this._currentSlot();
slot.elapsed = this._timeoutRunner.elapsed();
this._runnable = runnable;
this._fixture = fixture;
slot = this._currentSlot();
this._timeoutRunner.updateTimeout(slot.timeout, slot.elapsed);
} }
private _createTimeoutError(): TestInfoError { private _createTimeoutError(): TestInfoError {
let message = ''; let message = '';
const timeout = this._currentSlot().timeout; const timeout = this._currentSlot().timeout;
const runnable = this._runnable(); switch (this._runnable.type) {
switch (runnable.type) {
case 'test': { case 'test': {
message = `Test timeout of ${timeout}ms exceeded.`; if (this._fixture) {
break; if (this._fixture.phase === 'setup') {
} message = `Test timeout of ${timeout}ms exceeded while setting up "${this._fixture.title}".`;
case 'fixture': {
if (this._runnables.some(r => r.type === 'teardown')) {
message = `Worker teardown timeout of ${timeout}ms exceeded while ${runnable.phase === 'setup' ? 'setting up' : 'tearing down'} "${runnable.title}".`;
} else if (runnable.phase === 'setup') {
message = `Test timeout of ${timeout}ms exceeded while setting up "${runnable.title}".`;
} else { } else {
message = [ message = [
`Test finished within timeout of ${timeout}ms, but tearing down "${runnable.title}" ran out of time.`, `Test finished within timeout of ${timeout}ms, but tearing down "${this._fixture.title}" ran out of time.`,
`Please allow more time for the test, since teardown is attributed towards the test timeout budget.`, `Please allow more time for the test, since teardown is attributed towards the test timeout budget.`,
].join('\n'); ].join('\n');
} }
} else {
message = `Test timeout of ${timeout}ms exceeded.`;
}
break; break;
} }
case 'afterEach': case 'afterEach':
case 'beforeEach': case 'beforeEach':
message = `Test timeout of ${timeout}ms exceeded while running "${runnable.type}" hook.`; message = `Test timeout of ${timeout}ms exceeded while running "${this._runnable.type}" hook.`;
break; break;
case 'beforeAll': case 'beforeAll':
case 'afterAll': case 'afterAll':
message = `"${runnable.type}" hook timeout of ${timeout}ms exceeded.`; message = `"${this._runnable.type}" hook timeout of ${timeout}ms exceeded.`;
break; break;
case 'teardown': { case 'teardown': {
if (this._fixture)
message = `Worker teardown timeout of ${timeout}ms exceeded while ${this._fixture.phase === 'setup' ? 'setting up' : 'tearing down'} "${this._fixture.title}".`;
else
message = `Worker teardown timeout of ${timeout}ms exceeded.`; message = `Worker teardown timeout of ${timeout}ms exceeded.`;
break; break;
} }
@ -153,14 +152,14 @@ export class TimeoutManager {
case 'slow': case 'slow':
case 'fixme': case 'fixme':
case 'fail': case 'fail':
message = `"${runnable.type}" modifier timeout of ${timeout}ms exceeded.`; message = `"${this._runnable.type}" modifier timeout of ${timeout}ms exceeded.`;
break; break;
} }
const fixtureWithSlot = runnable.type === 'fixture' && runnable.slot ? runnable : undefined; const fixtureWithSlot = this._fixture?.slot ? this._fixture : undefined;
if (fixtureWithSlot) if (fixtureWithSlot)
message = `Fixture "${fixtureWithSlot.title}" timeout of ${timeout}ms exceeded during ${fixtureWithSlot.phase}.`; message = `Fixture "${fixtureWithSlot.title}" timeout of ${timeout}ms exceeded during ${fixtureWithSlot.phase}.`;
message = colors.red(message); message = colors.red(message);
const location = (fixtureWithSlot || runnable).location; const location = (fixtureWithSlot || this._runnable).location;
return { return {
message, message,
// Include location for hooks, modifiers and fixtures to distinguish between them. // Include location for hooks, modifiers and fixtures to distinguish between them.

View file

@ -153,12 +153,11 @@ export class WorkerMain extends ProcessRunner {
private async _teardownScopes() { private async _teardownScopes() {
// TODO: separate timeout for teardown? // TODO: separate timeout for teardown?
const timeoutManager = new TimeoutManager(this._project.project.timeout); const timeoutManager = new TimeoutManager(this._project.project.timeout);
timeoutManager.setCurrentRunnable({ type: 'teardown' });
const timeoutError = await timeoutManager.runWithTimeout(async () => { const timeoutError = await timeoutManager.runWithTimeout(async () => {
await timeoutManager.runRunnable({ type: 'teardown' }, async () => {
await this._fixtureRunner.teardownScope('test', timeoutManager); await this._fixtureRunner.teardownScope('test', timeoutManager);
await this._fixtureRunner.teardownScope('worker', timeoutManager); await this._fixtureRunner.teardownScope('worker', timeoutManager);
}); });
});
if (timeoutError) if (timeoutError)
this._fatalErrors.push(timeoutError); this._fatalErrors.push(timeoutError);
} }
@ -363,6 +362,7 @@ export class WorkerMain extends ProcessRunner {
await this._runEachHooksForSuites(suites, 'beforeEach', testInfo, undefined); await this._runEachHooksForSuites(suites, 'beforeEach', testInfo, undefined);
// Setup fixtures required by the test. // Setup fixtures required by the test.
testInfo._timeoutManager.setCurrentRunnable({ type: 'test' });
testFunctionParams = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo, 'test'); testFunctionParams = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo, 'test');
}, 'allowSkips'); }, 'allowSkips');
if (beforeHooksError) if (beforeHooksError)
@ -406,8 +406,8 @@ export class WorkerMain extends ProcessRunner {
if (testInfo._didTimeout) { if (testInfo._didTimeout) {
// A timed-out test gets a full additional timeout to run after hooks. // A timed-out test gets a full additional timeout to run after hooks.
afterHooksSlot = { timeout: this._project.project.timeout, elapsed: 0 }; afterHooksSlot = { timeout: this._project.project.timeout, elapsed: 0 };
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterEach', slot: afterHooksSlot });
} }
await testInfo._timeoutManager.runRunnable({ type: 'afterEach', slot: afterHooksSlot }, async () => {
await testInfo._runAsStep({ category: 'hook', title: 'After Hooks' }, async step => { await testInfo._runAsStep({ category: 'hook', title: 'After Hooks' }, async step => {
testInfo._afterHooksStep = step; testInfo._afterHooksStep = step;
let firstAfterHooksError: TestInfoError | undefined; let firstAfterHooksError: TestInfoError | undefined;
@ -429,7 +429,7 @@ export class WorkerMain extends ProcessRunner {
// Teardown test-scoped fixtures. Attribute to 'test' so that users understand // Teardown test-scoped fixtures. Attribute to 'test' so that users understand
// they should probably increase the test timeout to fix this issue. // they should probably increase the test timeout to fix this issue.
await testInfo._timeoutManager.runRunnable({ type: 'test', slot: afterHooksSlot }, async () => { testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: afterHooksSlot });
debugTest(`tearing down test scope started`); debugTest(`tearing down test scope started`);
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager)); const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
debugTest(`tearing down test scope finished`); debugTest(`tearing down test scope finished`);
@ -446,8 +446,6 @@ export class WorkerMain extends ProcessRunner {
} }
}); });
});
if (testInfo._isFailure()) if (testInfo._isFailure())
this._isStopped = true; this._isStopped = true;
@ -461,8 +459,8 @@ export class WorkerMain extends ProcessRunner {
debugTest(`running full cleanup after the failure`); debugTest(`running full cleanup after the failure`);
const teardownSlot = { timeout: this._project.project.timeout, elapsed: 0 }; const teardownSlot = { timeout: this._project.project.timeout, elapsed: 0 };
// Attribute to 'test' so that users understand they should probably increase the test timeout to fix this issue. // Attribute to 'test' so that users understand they should probably increate the test timeout to fix this issue.
await testInfo._timeoutManager.runRunnable({ type: 'test', slot: teardownSlot }, async () => { testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: teardownSlot });
debugTest(`tearing down test scope started`); debugTest(`tearing down test scope started`);
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager)); const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
debugTest(`tearing down test scope finished`); debugTest(`tearing down test scope finished`);
@ -473,18 +471,18 @@ export class WorkerMain extends ProcessRunner {
firstAfterHooksError = firstAfterHooksError || afterAllError; firstAfterHooksError = firstAfterHooksError || afterAllError;
} }
// Attribute to 'teardown' because worker fixtures are not perceived as a part of a test.
testInfo._timeoutManager.setCurrentRunnable({ type: 'teardown', slot: teardownSlot });
debugTest(`tearing down worker scope started`); debugTest(`tearing down worker scope started`);
const workerScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('worker', testInfo._timeoutManager)); const workerScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('worker', testInfo._timeoutManager));
debugTest(`tearing down worker scope finished`); debugTest(`tearing down worker scope finished`);
firstAfterHooksError = firstAfterHooksError || workerScopeError; firstAfterHooksError = firstAfterHooksError || workerScopeError;
}); });
});
} }
if (firstAfterHooksError) if (firstAfterHooksError)
step.complete({ error: firstAfterHooksError }); step.complete({ error: firstAfterHooksError });
}); });
});
this._currentTest = null; this._currentTest = null;
setCurrentTestInfo(null); setCurrentTestInfo(null);
@ -501,8 +499,8 @@ export class WorkerMain extends ProcessRunner {
const actualScope = this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location) ? 'worker' : 'test'; const actualScope = this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location) ? 'worker' : 'test';
if (actualScope !== scope) if (actualScope !== scope)
continue; continue;
await testInfo._timeoutManager.runRunnable({ type: modifier.type, location: modifier.location, slot: timeSlot }, async () => {
debugTest(`modifier at "${formatLocation(modifier.location)}" started`); debugTest(`modifier at "${formatLocation(modifier.location)}" started`);
testInfo._timeoutManager.setCurrentRunnable({ type: modifier.type, location: modifier.location, slot: timeSlot });
const result = await testInfo._runAsStep({ const result = await testInfo._runAsStep({
category: 'hook', category: 'hook',
title: `${modifier.type} modifier`, title: `${modifier.type} modifier`,
@ -512,7 +510,6 @@ export class WorkerMain extends ProcessRunner {
if (result && extraAnnotations) if (result && extraAnnotations)
extraAnnotations.push({ type: modifier.type, description: modifier.description }); extraAnnotations.push({ type: modifier.type, description: modifier.description });
testInfo[modifier.type](!!result, modifier.description); testInfo[modifier.type](!!result, modifier.description);
});
} }
} }
@ -528,7 +525,7 @@ export class WorkerMain extends ProcessRunner {
try { try {
// Separate time slot for each "beforeAll" hook. // Separate time slot for each "beforeAll" hook.
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 }; const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
await testInfo._timeoutManager.runRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot }, async () => { testInfo._timeoutManager.setCurrentRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot });
await testInfo._runAsStep({ await testInfo._runAsStep({
category: 'hook', category: 'hook',
title: `${hook.type} hook`, title: `${hook.type} hook`,
@ -542,7 +539,6 @@ export class WorkerMain extends ProcessRunner {
await this._fixtureRunner.teardownScope('test', testInfo._timeoutManager); await this._fixtureRunner.teardownScope('test', testInfo._timeoutManager);
} }
}); });
});
} catch (e) { } catch (e) {
// Always run all the hooks, and capture the first error. // Always run all the hooks, and capture the first error.
beforeAllError = beforeAllError || e; beforeAllError = beforeAllError || e;
@ -565,7 +561,7 @@ export class WorkerMain extends ProcessRunner {
const afterAllError = await testInfo._runAndFailOnError(async () => { const afterAllError = await testInfo._runAndFailOnError(async () => {
// Separate time slot for each "afterAll" hook. // Separate time slot for each "afterAll" hook.
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 }; const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
await testInfo._timeoutManager.runRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot }, async () => { testInfo._timeoutManager.setCurrentRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot });
await testInfo._runAsStep({ await testInfo._runAsStep({
category: 'hook', category: 'hook',
title: `${hook.type} hook`, title: `${hook.type} hook`,
@ -580,7 +576,6 @@ export class WorkerMain extends ProcessRunner {
} }
}); });
}); });
});
firstError = firstError || afterAllError; firstError = firstError || afterAllError;
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" finished`); debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" finished`);
} }
@ -592,13 +587,12 @@ export class WorkerMain extends ProcessRunner {
let error: Error | undefined; let error: Error | undefined;
for (const hook of hooks) { for (const hook of hooks) {
try { try {
await testInfo._timeoutManager.runRunnable({ type, location: hook.location, slot: timeSlot }, async () => { testInfo._timeoutManager.setCurrentRunnable({ type, location: hook.location, slot: timeSlot });
await testInfo._runAsStep({ await testInfo._runAsStep({
category: 'hook', category: 'hook',
title: `${hook.type} hook`, title: `${hook.type} hook`,
location: hook.location, location: hook.location,
}, () => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'test')); }, () => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'test'));
});
} catch (e) { } catch (e) {
// Always run all the hooks, and capture the first error. // Always run all the hooks, and capture the first error.
error = error || e; error = error || e;

View file

@ -477,7 +477,7 @@ test('should not report fixture teardown error twice', async ({ runInlineTest })
expect(countTimes(result.output, 'Oh my error')).toBe(2); expect(countTimes(result.output, 'Oh my error')).toBe(2);
}); });
test('should report fixture teardown extend', async ({ runInlineTest }) => { test('should not report fixture teardown timeout twice', async ({ runInlineTest }) => {
const result = await runInlineTest({ const result = await runInlineTest({
'a.spec.ts': ` 'a.spec.ts': `
import { test as base, expect } from '@playwright/test'; import { test as base, expect } from '@playwright/test';
@ -494,7 +494,8 @@ test('should report fixture teardown extend', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1); expect(result.failed).toBe(1);
expect(result.output).toContain('Test finished within timeout of 1000ms, but tearing down "fixture" ran out of time.'); expect(result.output).toContain('Test finished within timeout of 1000ms, but tearing down "fixture" ran out of time.');
expect(result.output).toContain('base.extend'); expect(result.output).not.toContain('base.extend'); // Should not point to the location.
// TODO: this should be "not.toContain" actually.
expect(result.output).toContain('Worker teardown timeout of 1000ms exceeded while tearing down "fixture".'); expect(result.output).toContain('Worker teardown timeout of 1000ms exceeded while tearing down "fixture".');
}); });