chore(test runner): refactor worker runner parts (#11316)
This will make it easier to change in the future.
This commit is contained in:
parent
62095b000b
commit
da1f39fb29
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
import { formatLocation, wrapInPromise, debugTest } from './util';
|
import { formatLocation, wrapInPromise, debugTest } from './util';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { FixturesWithLocation, Location, WorkerInfo, TestInfo, TestStepInternal } from './types';
|
import { FixturesWithLocation, Location, WorkerInfo, TestInfo } from './types';
|
||||||
import { ManualPromise } from 'playwright-core/lib/utils/async';
|
import { ManualPromise } from 'playwright-core/lib/utils/async';
|
||||||
|
|
||||||
type FixtureScope = 'test' | 'worker';
|
type FixtureScope = 'test' | 'worker';
|
||||||
|
|
@ -258,7 +258,7 @@ export class FixtureRunner {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveParametersAndRunHookOrTest(fn: Function, workerInfo: WorkerInfo, testInfo: TestInfo | undefined, paramsStepCallback?: TestStepInternal) {
|
async resolveParametersForFunction(fn: Function, workerInfo: WorkerInfo, testInfo: TestInfo | undefined): Promise<object> {
|
||||||
// Install all automatic fixtures.
|
// Install all automatic fixtures.
|
||||||
for (const registration of this.pool!.registrations.values()) {
|
for (const registration of this.pool!.registrations.values()) {
|
||||||
const shouldSkip = !testInfo && registration.scope === 'test';
|
const shouldSkip = !testInfo && registration.scope === 'test';
|
||||||
|
|
@ -274,10 +274,11 @@ export class FixtureRunner {
|
||||||
const fixture = await this.setupFixtureForRegistration(registration, workerInfo, testInfo);
|
const fixture = await this.setupFixtureForRegistration(registration, workerInfo, testInfo);
|
||||||
params[name] = fixture.value;
|
params[name] = fixture.value;
|
||||||
}
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
// Report fixture hooks step as completed.
|
async resolveParametersAndRunFunction(fn: Function, workerInfo: WorkerInfo, testInfo: TestInfo | undefined) {
|
||||||
paramsStepCallback?.complete();
|
const params = await this.resolveParametersForFunction(fn, workerInfo, testInfo);
|
||||||
|
|
||||||
return fn(params, testInfo || workerInfo);
|
return fn(params, testInfo || workerInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
if (!this._fixtureRunner.dependsOnWorkerFixturesOnly(beforeAllModifier.fn, beforeAllModifier.location))
|
if (!this._fixtureRunner.dependsOnWorkerFixturesOnly(beforeAllModifier.fn, beforeAllModifier.location))
|
||||||
continue;
|
continue;
|
||||||
// TODO: separate timeout for beforeAll modifiers?
|
// TODO: separate timeout for beforeAll modifiers?
|
||||||
const result = await raceAgainstDeadline(this._fixtureRunner.resolveParametersAndRunHookOrTest(beforeAllModifier.fn, this._workerInfo, undefined), this._deadline());
|
const result = await raceAgainstDeadline(this._fixtureRunner.resolveParametersAndRunFunction(beforeAllModifier.fn, this._workerInfo, undefined), this._deadline());
|
||||||
if (result.timedOut) {
|
if (result.timedOut) {
|
||||||
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)}`));
|
||||||
|
|
@ -446,32 +446,17 @@ export class WorkerRunner extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
|
private async _runBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
|
||||||
try {
|
const beforeEachModifiers: Modifier[] = [];
|
||||||
const beforeEachModifiers: Modifier[] = [];
|
for (let s: Suite | undefined = test.parent; s; s = s.parent) {
|
||||||
for (let s: Suite | undefined = test.parent; s; s = s.parent) {
|
const modifiers = s._modifiers.filter(modifier => !this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location));
|
||||||
const modifiers = s._modifiers.filter(modifier => !this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location));
|
beforeEachModifiers.push(...modifiers.reverse());
|
||||||
beforeEachModifiers.push(...modifiers.reverse());
|
|
||||||
}
|
|
||||||
beforeEachModifiers.reverse();
|
|
||||||
for (const modifier of beforeEachModifiers) {
|
|
||||||
const result = await this._fixtureRunner.resolveParametersAndRunHookOrTest(modifier.fn, this._workerInfo, testInfo);
|
|
||||||
testInfo[modifier.type](!!result, modifier.description!);
|
|
||||||
}
|
|
||||||
await this._runHooks(test.parent!, 'beforeEach', testInfo);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof SkipError) {
|
|
||||||
if (testInfo.status === 'passed')
|
|
||||||
testInfo.status = 'skipped';
|
|
||||||
} else {
|
|
||||||
if (testInfo.status === 'passed')
|
|
||||||
testInfo.status = 'failed';
|
|
||||||
// Do not overwrite any uncaught error that happened first.
|
|
||||||
// This is typical if you have some expect() that fails in beforeEach.
|
|
||||||
if (!('error' in testInfo))
|
|
||||||
testInfo.error = serializeError(error);
|
|
||||||
}
|
|
||||||
// Continue running afterEach hooks even after the failure.
|
|
||||||
}
|
}
|
||||||
|
beforeEachModifiers.reverse();
|
||||||
|
for (const modifier of beforeEachModifiers) {
|
||||||
|
const result = await this._fixtureRunner.resolveParametersAndRunFunction(modifier.fn, this._workerInfo, testInfo);
|
||||||
|
testInfo[modifier.type](!!result, modifier.description!);
|
||||||
|
}
|
||||||
|
await this._runHooks(test.parent!, 'beforeEach', testInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runTestWithBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
|
private async _runTestWithBeforeHooks(test: TestCase, testInfo: TestInfoImpl) {
|
||||||
|
|
@ -481,69 +466,41 @@ export class WorkerRunner extends EventEmitter {
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
forceNoParent: true
|
forceNoParent: true
|
||||||
});
|
});
|
||||||
|
let error1: TestError | undefined;
|
||||||
if (test._type === 'test')
|
if (test._type === 'test')
|
||||||
await this._runBeforeHooks(test, testInfo);
|
error1 = await this._runFn(() => this._runBeforeHooks(test, testInfo), testInfo, 'allowSkips');
|
||||||
|
|
||||||
// Do not run the test when beforeEach hook fails.
|
// Do not run the test when beforeEach hook fails.
|
||||||
if (testInfo.status === 'failed' || testInfo.status === 'skipped') {
|
if (testInfo.status === 'failed' || testInfo.status === 'skipped') {
|
||||||
step.complete(testInfo.error);
|
step.complete(error1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const error2 = await this._runFn(async () => {
|
||||||
await this._fixtureRunner.resolveParametersAndRunHookOrTest(test.fn, this._workerInfo, testInfo, step);
|
const params = await this._fixtureRunner.resolveParametersForFunction(test.fn, this._workerInfo, testInfo);
|
||||||
} catch (error) {
|
step.complete(); // Report fixture hooks step as completed.
|
||||||
if (error instanceof SkipError) {
|
const fn = test.fn; // Extract a variable to get a better stack trace ("myTest" vs "TestCase.myTest [as fn]").
|
||||||
if (testInfo.status === 'passed')
|
await fn(params, testInfo);
|
||||||
testInfo.status = 'skipped';
|
}, testInfo, 'allowSkips');
|
||||||
} else {
|
|
||||||
// We might fail after the timeout, e.g. due to fixture teardown.
|
step.complete(error2); // Second complete is a no-op.
|
||||||
// Do not overwrite the timeout status.
|
|
||||||
if (testInfo.status === 'passed')
|
|
||||||
testInfo.status = 'failed';
|
|
||||||
// Keep the error even in the case of timeout, if there was no error before.
|
|
||||||
if (!('error' in testInfo))
|
|
||||||
testInfo.error = serializeError(error);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
step.complete(testInfo.error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runAfterHooks(test: TestCase, testInfo: TestInfoImpl) {
|
private async _runAfterHooks(test: TestCase, testInfo: TestInfoImpl) {
|
||||||
let step: TestStepInternal | undefined;
|
const step = testInfo._addStep({
|
||||||
let teardownError: TestError | undefined;
|
category: 'hook',
|
||||||
try {
|
title: 'After Hooks',
|
||||||
step = testInfo._addStep({
|
canHaveChildren: true,
|
||||||
category: 'hook',
|
forceNoParent: true
|
||||||
title: 'After Hooks',
|
});
|
||||||
canHaveChildren: true,
|
|
||||||
forceNoParent: true
|
let teardownError1: TestError | undefined;
|
||||||
});
|
if (test._type === 'test')
|
||||||
if (test._type === 'test')
|
teardownError1 = await this._runFn(() => this._runHooks(test.parent!, 'afterEach', testInfo), testInfo, 'disallowSkips');
|
||||||
await this._runHooks(test.parent!, 'afterEach', testInfo);
|
// Continue teardown even after the failure.
|
||||||
} catch (error) {
|
|
||||||
if (!(error instanceof SkipError)) {
|
const teardownError2 = await this._runFn(() => this._fixtureRunner.teardownScope('test'), testInfo, 'disallowSkips');
|
||||||
if (testInfo.status === 'passed')
|
step.complete(teardownError1 || teardownError2);
|
||||||
testInfo.status = 'failed';
|
|
||||||
// Do not overwrite test failure error.
|
|
||||||
if (!('error' in testInfo))
|
|
||||||
testInfo.error = serializeError(error);
|
|
||||||
// Continue running even after the failure.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await this._fixtureRunner.teardownScope('test');
|
|
||||||
} catch (error) {
|
|
||||||
if (testInfo.status === 'passed')
|
|
||||||
testInfo.status = 'failed';
|
|
||||||
// Do not overwrite test failure error.
|
|
||||||
if (!('error' in testInfo)) {
|
|
||||||
testInfo.error = serializeError(error);
|
|
||||||
teardownError = testInfo.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
step?.complete(teardownError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runHooks(suite: Suite, type: 'beforeEach' | 'afterEach', testInfo: TestInfo) {
|
private async _runHooks(suite: Suite, type: 'beforeEach' | 'afterEach', testInfo: TestInfo) {
|
||||||
|
|
@ -557,7 +514,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
let error: Error | undefined;
|
let error: Error | undefined;
|
||||||
for (const hook of all) {
|
for (const hook of all) {
|
||||||
try {
|
try {
|
||||||
await this._fixtureRunner.resolveParametersAndRunHookOrTest(hook, this._workerInfo, testInfo);
|
await this._fixtureRunner.resolveParametersAndRunFunction(hook, this._workerInfo, testInfo);
|
||||||
} 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;
|
||||||
|
|
@ -567,6 +524,28 @@ export class WorkerRunner extends EventEmitter {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _runFn(fn: Function, testInfo: TestInfoImpl, skips: 'allowSkips' | 'disallowSkips'): Promise<TestError | undefined> {
|
||||||
|
try {
|
||||||
|
await fn();
|
||||||
|
} catch (error) {
|
||||||
|
if (skips === 'allowSkips' && error instanceof SkipError) {
|
||||||
|
if (testInfo.status === 'passed')
|
||||||
|
testInfo.status = 'skipped';
|
||||||
|
} else {
|
||||||
|
const serialized = serializeError(error);
|
||||||
|
// Do not overwrite any previous error and error status.
|
||||||
|
// Some (but not all) scenarios include:
|
||||||
|
// - expect() that fails after uncaught exception.
|
||||||
|
// - fail after the timeout, e.g. due to fixture teardown.
|
||||||
|
if (testInfo.status === 'passed')
|
||||||
|
testInfo.status = 'failed';
|
||||||
|
if (!('error' in testInfo))
|
||||||
|
testInfo.error = serialized;
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _reportDone() {
|
private _reportDone() {
|
||||||
const donePayload: DonePayload = { fatalError: this._fatalError };
|
const donePayload: DonePayload = { fatalError: this._fatalError };
|
||||||
this.emit('done', donePayload);
|
this.emit('done', donePayload);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue