This reverts commit f5df0940c9.
This commit is contained in:
parent
d144c5e6e3
commit
4ba6d789bc
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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,40 +115,38 @@ class Fixture {
|
||||||
else
|
else
|
||||||
throw e;
|
throw e;
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
|
const result = zones.preserve(async () => {
|
||||||
|
if (!shouldGenerateStep)
|
||||||
|
return await this.registration.fn(params, useFunc, info);
|
||||||
|
|
||||||
await testInfo._timeoutManager.runRunnable(this._runnableDescription, async () => {
|
await testInfo._runAsStep({
|
||||||
try {
|
title: `fixture: ${this.registration.name}`,
|
||||||
const result = zones.preserve(async () => {
|
category: 'fixture',
|
||||||
if (!shouldGenerateStep)
|
location: isInternalFixture ? this.registration.location : undefined,
|
||||||
return await this.registration.fn(params, useFunc, info);
|
}, async step => {
|
||||||
|
mutableStepOnStack = step;
|
||||||
await testInfo._runAsStep({
|
return await this.registration.fn(params, useFunc, info);
|
||||||
title: `fixture: ${this.registration.name}`,
|
|
||||||
category: 'fixture',
|
|
||||||
location: isInternalFixture ? this.registration.location : undefined,
|
|
||||||
}, async step => {
|
|
||||||
mutableStepOnStack = step;
|
|
||||||
return await this.registration.fn(params, useFunc, info);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (result instanceof Promise)
|
if (result instanceof Promise)
|
||||||
this._selfTeardownComplete = result.catch(handleError);
|
this._selfTeardownComplete = result.catch(handleError);
|
||||||
else
|
else
|
||||||
this._selfTeardownComplete = Promise.resolve();
|
this._selfTeardownComplete = Promise.resolve();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
}
|
}
|
||||||
await useFuncStarted;
|
await useFuncStarted;
|
||||||
if (shouldGenerateStep) {
|
if (shouldGenerateStep) {
|
||||||
mutableStepOnStack?.complete({});
|
mutableStepOnStack?.complete({});
|
||||||
this._selfTeardownComplete?.then(() => {
|
this._selfTeardownComplete?.then(() => {
|
||||||
afterStep?.complete({});
|
afterStep?.complete({});
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
setCurrentFixture(fixture: FixtureDescription | undefined) {
|
||||||
return await cb();
|
this._updateRunnables(this._runnable, fixture);
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,59 +100,66 @@ 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;
|
|
||||||
}
|
private _updateRunnables(runnable: RunnableDescription, fixture: FixtureDescription | undefined) {
|
||||||
return this._defaultSlot;
|
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': {
|
} else {
|
||||||
if (this._runnables.some(r => r.type === 'teardown')) {
|
message = [
|
||||||
message = `Worker teardown timeout of ${timeout}ms exceeded while ${runnable.phase === 'setup' ? 'setting up' : 'tearing down'} "${runnable.title}".`;
|
`Test finished within timeout of ${timeout}ms, but tearing down "${this._fixture.title}" ran out of time.`,
|
||||||
} else if (runnable.phase === 'setup') {
|
`Please allow more time for the test, since teardown is attributed towards the test timeout budget.`,
|
||||||
message = `Test timeout of ${timeout}ms exceeded while setting up "${runnable.title}".`;
|
].join('\n');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
message = [
|
message = `Test timeout of ${timeout}ms exceeded.`;
|
||||||
`Test finished within timeout of ${timeout}ms, but tearing down "${runnable.title}" ran out of time.`,
|
|
||||||
`Please allow more time for the test, since teardown is attributed towards the test timeout budget.`,
|
|
||||||
].join('\n');
|
|
||||||
}
|
}
|
||||||
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': {
|
||||||
message = `Worker teardown timeout of ${timeout}ms exceeded.`;
|
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.`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'skip':
|
case 'skip':
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -153,11 +153,10 @@ 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,84 +406,82 @@ 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;
|
await testInfo._runWithTimeout(async () => {
|
||||||
await testInfo._runWithTimeout(async () => {
|
// Note: do not wrap all teardown steps together, because failure in any of them
|
||||||
// Note: do not wrap all teardown steps together, because failure in any of them
|
// does not prevent further teardown steps from running.
|
||||||
// does not prevent further teardown steps from running.
|
|
||||||
|
|
||||||
// Run "immediately upon test function finish" callback.
|
// Run "immediately upon test function finish" callback.
|
||||||
debugTest(`on-test-function-finish callback started`);
|
debugTest(`on-test-function-finish callback started`);
|
||||||
const didFinishTestFunctionError = await testInfo._runAndFailOnError(async () => testInfo._onDidFinishTestFunction?.());
|
const didFinishTestFunctionError = await testInfo._runAndFailOnError(async () => testInfo._onDidFinishTestFunction?.());
|
||||||
firstAfterHooksError = firstAfterHooksError || didFinishTestFunctionError;
|
firstAfterHooksError = firstAfterHooksError || didFinishTestFunctionError;
|
||||||
debugTest(`on-test-function-finish callback finished`);
|
debugTest(`on-test-function-finish callback finished`);
|
||||||
|
|
||||||
// Run "afterEach" hooks, unless we failed at beforeAll stage.
|
// Run "afterEach" hooks, unless we failed at beforeAll stage.
|
||||||
if (shouldRunAfterEachHooks) {
|
if (shouldRunAfterEachHooks) {
|
||||||
const afterEachError = await testInfo._runAndFailOnError(() => this._runEachHooksForSuites(reversedSuites, 'afterEach', testInfo, afterHooksSlot));
|
const afterEachError = await testInfo._runAndFailOnError(() => this._runEachHooksForSuites(reversedSuites, 'afterEach', testInfo, afterHooksSlot));
|
||||||
firstAfterHooksError = firstAfterHooksError || afterEachError;
|
firstAfterHooksError = firstAfterHooksError || afterEachError;
|
||||||
}
|
|
||||||
|
|
||||||
// Teardown test-scoped fixtures. Attribute to 'test' so that users understand
|
|
||||||
// they should probably increase the test timeout to fix this issue.
|
|
||||||
await testInfo._timeoutManager.runRunnable({ type: 'test', slot: afterHooksSlot }, async () => {
|
|
||||||
debugTest(`tearing down test scope started`);
|
|
||||||
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
|
|
||||||
debugTest(`tearing down test scope finished`);
|
|
||||||
firstAfterHooksError = firstAfterHooksError || testScopeError;
|
|
||||||
|
|
||||||
// Run "afterAll" hooks for suites that are not shared with the next test.
|
|
||||||
// In case of failure the worker will be stopped and we have to make sure that afterAll
|
|
||||||
// hooks run before worker fixtures teardown.
|
|
||||||
for (const suite of reversedSuites) {
|
|
||||||
if (!nextSuites.has(suite) || testInfo._isFailure()) {
|
|
||||||
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
|
||||||
firstAfterHooksError = firstAfterHooksError || afterAllError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
if (testInfo._isFailure())
|
|
||||||
this._isStopped = true;
|
|
||||||
|
|
||||||
if (this._isStopped) {
|
|
||||||
// Run all remaining "afterAll" hooks and teardown all fixtures when worker is shutting down.
|
|
||||||
// Mark as "cleaned up" early to avoid running cleanup twice.
|
|
||||||
this._didRunFullCleanup = true;
|
|
||||||
|
|
||||||
// Give it more time for the full cleanup.
|
|
||||||
await testInfo._runWithTimeout(async () => {
|
|
||||||
debugTest(`running full cleanup after the failure`);
|
|
||||||
|
|
||||||
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.
|
|
||||||
await testInfo._timeoutManager.runRunnable({ type: 'test', slot: teardownSlot }, async () => {
|
|
||||||
debugTest(`tearing down test scope started`);
|
|
||||||
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
|
|
||||||
debugTest(`tearing down test scope finished`);
|
|
||||||
firstAfterHooksError = firstAfterHooksError || testScopeError;
|
|
||||||
|
|
||||||
for (const suite of reversedSuites) {
|
|
||||||
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
|
||||||
firstAfterHooksError = firstAfterHooksError || afterAllError;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugTest(`tearing down worker scope started`);
|
|
||||||
const workerScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('worker', testInfo._timeoutManager));
|
|
||||||
debugTest(`tearing down worker scope finished`);
|
|
||||||
firstAfterHooksError = firstAfterHooksError || workerScopeError;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstAfterHooksError)
|
// Teardown test-scoped fixtures. Attribute to 'test' so that users understand
|
||||||
step.complete({ error: firstAfterHooksError });
|
// they should probably increase the test timeout to fix this issue.
|
||||||
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: afterHooksSlot });
|
||||||
|
debugTest(`tearing down test scope started`);
|
||||||
|
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
|
||||||
|
debugTest(`tearing down test scope finished`);
|
||||||
|
firstAfterHooksError = firstAfterHooksError || testScopeError;
|
||||||
|
|
||||||
|
// Run "afterAll" hooks for suites that are not shared with the next test.
|
||||||
|
// In case of failure the worker will be stopped and we have to make sure that afterAll
|
||||||
|
// hooks run before worker fixtures teardown.
|
||||||
|
for (const suite of reversedSuites) {
|
||||||
|
if (!nextSuites.has(suite) || testInfo._isFailure()) {
|
||||||
|
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
||||||
|
firstAfterHooksError = firstAfterHooksError || afterAllError;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (testInfo._isFailure())
|
||||||
|
this._isStopped = true;
|
||||||
|
|
||||||
|
if (this._isStopped) {
|
||||||
|
// Run all remaining "afterAll" hooks and teardown all fixtures when worker is shutting down.
|
||||||
|
// Mark as "cleaned up" early to avoid running cleanup twice.
|
||||||
|
this._didRunFullCleanup = true;
|
||||||
|
|
||||||
|
// Give it more time for the full cleanup.
|
||||||
|
await testInfo._runWithTimeout(async () => {
|
||||||
|
debugTest(`running full cleanup after the failure`);
|
||||||
|
|
||||||
|
const teardownSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||||
|
// Attribute to 'test' so that users understand they should probably increate the test timeout to fix this issue.
|
||||||
|
testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: teardownSlot });
|
||||||
|
debugTest(`tearing down test scope started`);
|
||||||
|
const testScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('test', testInfo._timeoutManager));
|
||||||
|
debugTest(`tearing down test scope finished`);
|
||||||
|
firstAfterHooksError = firstAfterHooksError || testScopeError;
|
||||||
|
|
||||||
|
for (const suite of reversedSuites) {
|
||||||
|
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
||||||
|
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`);
|
||||||
|
const workerScopeError = await testInfo._runAndFailOnError(() => this._fixtureRunner.teardownScope('worker', testInfo._timeoutManager));
|
||||||
|
debugTest(`tearing down worker scope finished`);
|
||||||
|
firstAfterHooksError = firstAfterHooksError || workerScopeError;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstAfterHooksError)
|
||||||
|
step.complete({ error: firstAfterHooksError });
|
||||||
});
|
});
|
||||||
|
|
||||||
this._currentTest = null;
|
this._currentTest = null;
|
||||||
|
|
@ -501,18 +499,17 @@ 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`,
|
||||||
location: modifier.location,
|
location: modifier.location,
|
||||||
}, () => this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo, scope));
|
}, () => this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, testInfo, scope));
|
||||||
debugTest(`modifier at "${formatLocation(modifier.location)}" finished`);
|
debugTest(`modifier at "${formatLocation(modifier.location)}" finished`);
|
||||||
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,20 +525,19 @@ 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`,
|
||||||
location: hook.location,
|
location: hook.location,
|
||||||
}, async () => {
|
}, async () => {
|
||||||
try {
|
try {
|
||||||
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only');
|
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only');
|
||||||
} finally {
|
} finally {
|
||||||
// Each beforeAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
// Each beforeAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
||||||
// Note: we must teardown even after beforeAll fails, because we'll run more beforeAlls.
|
// Note: we must teardown even after beforeAll fails, because we'll run more beforeAlls.
|
||||||
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.
|
||||||
|
|
@ -565,20 +561,19 @@ 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`,
|
||||||
location: hook.location,
|
location: hook.location,
|
||||||
}, async () => {
|
}, async () => {
|
||||||
try {
|
try {
|
||||||
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only');
|
await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only');
|
||||||
} finally {
|
} finally {
|
||||||
// Each afterAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
// Each afterAll hook has its own scope for test fixtures. Attribute to the same runnable and timeSlot.
|
||||||
// Note: we must teardown even after afterAll fails, because we'll run more afterAlls.
|
// Note: we must teardown even after afterAll fails, because we'll run more afterAlls.
|
||||||
await this._fixtureRunner.teardownScope('test', testInfo._timeoutManager);
|
await this._fixtureRunner.teardownScope('test', testInfo._timeoutManager);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
firstError = firstError || afterAllError;
|
firstError = firstError || afterAllError;
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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".');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue