chore(test runner): prepare to per-fixture timeout (#11605)
This reworks DeadlineRunner to use exception to signal timeout. This way, we'll be able to run fixtures against a shared deadline vs their own deadline and still get an easy control-flow timeout handling.
This commit is contained in:
parent
a8a81eba11
commit
800b813d4b
|
|
@ -26,7 +26,7 @@ import { envObjectToArray } from './clientHelper';
|
||||||
import { assert, headersObjectToArray, monotonicTime } from '../utils/utils';
|
import { assert, headersObjectToArray, monotonicTime } from '../utils/utils';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import { kBrowserClosedError } from '../utils/errors';
|
import { kBrowserClosedError } from '../utils/errors';
|
||||||
import { raceAgainstDeadline } from '../utils/async';
|
import { raceAgainstTimeout } from '../utils/async';
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
|
|
||||||
export interface BrowserServerLauncher {
|
export interface BrowserServerLauncher {
|
||||||
|
|
@ -157,32 +157,25 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const createBrowserPromise = new Promise<Browser>(async (fulfill, reject) => {
|
const result = await raceAgainstTimeout(async () => {
|
||||||
try {
|
// For tests.
|
||||||
// For tests.
|
if ((params as any).__testHookBeforeCreateBrowser)
|
||||||
if ((params as any).__testHookBeforeCreateBrowser)
|
await (params as any).__testHookBeforeCreateBrowser();
|
||||||
await (params as any).__testHookBeforeCreateBrowser();
|
|
||||||
|
|
||||||
const playwright = await connection!.initializePlaywright();
|
const playwright = await connection!.initializePlaywright();
|
||||||
if (!playwright._initializer.preLaunchedBrowser) {
|
if (!playwright._initializer.preLaunchedBrowser) {
|
||||||
reject(new Error('Malformed endpoint. Did you use launchServer method?'));
|
closePipe();
|
||||||
closePipe();
|
throw new Error('Malformed endpoint. Did you use launchServer method?');
|
||||||
return;
|
|
||||||
}
|
|
||||||
playwright._setSelectors(this._playwright.selectors);
|
|
||||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
|
||||||
browser._logger = logger;
|
|
||||||
browser._shouldCloseConnectionOnClose = true;
|
|
||||||
browser._setBrowserType((playwright as any)[browser._name]);
|
|
||||||
browser._localUtils = this._playwright._utils;
|
|
||||||
browser.on(Events.Browser.Disconnected, closePipe);
|
|
||||||
fulfill(browser);
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
}
|
||||||
});
|
playwright._setSelectors(this._playwright.selectors);
|
||||||
|
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||||
const result = await raceAgainstDeadline(createBrowserPromise, deadline);
|
browser._logger = logger;
|
||||||
|
browser._shouldCloseConnectionOnClose = true;
|
||||||
|
browser._setBrowserType((playwright as any)[browser._name]);
|
||||||
|
browser._localUtils = this._playwright._utils;
|
||||||
|
browser.on(Events.Browser.Disconnected, closePipe);
|
||||||
|
return browser;
|
||||||
|
}, deadline ? deadline - monotonicTime() : 0);
|
||||||
if (!result.timedOut) {
|
if (!result.timedOut) {
|
||||||
return result.result;
|
return result.result;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -16,50 +16,85 @@
|
||||||
|
|
||||||
import { monotonicTime } from './utils';
|
import { monotonicTime } from './utils';
|
||||||
|
|
||||||
export class DeadlineRunner<T> {
|
export class TimeoutRunnerError extends Error {}
|
||||||
private _timer: NodeJS.Timer | undefined;
|
|
||||||
readonly result = new ManualPromise<{ timedOut: true } | { result: T, timedOut: false }>();
|
|
||||||
|
|
||||||
constructor(promise: Promise<T>, deadline: number) {
|
type TimeoutRunnerData = {
|
||||||
promise.then(result => {
|
start: number,
|
||||||
this._finish({ result, timedOut: false });
|
timer: NodeJS.Timer | undefined,
|
||||||
}).catch(e => {
|
timeoutPromise: ManualPromise<any>,
|
||||||
this._finish(undefined, e);
|
};
|
||||||
});
|
export class TimeoutRunner {
|
||||||
this.updateDeadline(deadline);
|
private _running: TimeoutRunnerData | undefined;
|
||||||
|
private _timeout: number;
|
||||||
|
private _elapsed: number;
|
||||||
|
|
||||||
|
constructor(timeout: number) {
|
||||||
|
this._timeout = timeout;
|
||||||
|
this._elapsed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _finish(success?: { timedOut: true } | { result: T, timedOut: false }, error?: any) {
|
async run<T>(cb: () => Promise<T>): Promise<T> {
|
||||||
if (this.result.isDone())
|
const running = this._running = {
|
||||||
return;
|
start: monotonicTime(),
|
||||||
this.updateDeadline(0);
|
timer: undefined,
|
||||||
if (success)
|
timeoutPromise: new ManualPromise(),
|
||||||
this.result.resolve(success);
|
};
|
||||||
else
|
try {
|
||||||
this.result.reject(error);
|
const resultPromise = Promise.race([
|
||||||
|
cb(),
|
||||||
|
running.timeoutPromise
|
||||||
|
]);
|
||||||
|
this._updateTimeout(running, this._timeout);
|
||||||
|
return await resultPromise;
|
||||||
|
} finally {
|
||||||
|
this._elapsed += monotonicTime() - running.start;
|
||||||
|
this._updateTimeout(running, 0);
|
||||||
|
if (this._running === running)
|
||||||
|
this._running = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interrupt() {
|
interrupt() {
|
||||||
this.updateDeadline(-1);
|
if (this._running)
|
||||||
|
this._updateTimeout(this._running, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDeadline(deadline: number) {
|
updateTimeout(timeout: number) {
|
||||||
if (this._timer) {
|
this._timeout = timeout;
|
||||||
clearTimeout(this._timer);
|
if (this._running)
|
||||||
this._timer = undefined;
|
this._updateTimeout(this._running, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTimeout(timeout: number) {
|
||||||
|
this._elapsed = 0;
|
||||||
|
this.updateTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateTimeout(running: TimeoutRunnerData, timeout: number) {
|
||||||
|
if (running.timer) {
|
||||||
|
clearTimeout(running.timer);
|
||||||
|
running.timer = undefined;
|
||||||
}
|
}
|
||||||
if (deadline === 0)
|
if (timeout === 0)
|
||||||
return;
|
return;
|
||||||
const timeout = deadline - monotonicTime();
|
const elapsed = (monotonicTime() - running.start) + this._elapsed;
|
||||||
|
timeout = timeout - elapsed;
|
||||||
if (timeout <= 0)
|
if (timeout <= 0)
|
||||||
this._finish({ timedOut: true });
|
running.timeoutPromise.reject(new TimeoutRunnerError());
|
||||||
else
|
else
|
||||||
this._timer = setTimeout(() => this._finish({ timedOut: true }), timeout);
|
running.timer = setTimeout(() => running.timeoutPromise.reject(new TimeoutRunnerError()), timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function raceAgainstDeadline<T>(promise: Promise<T>, deadline: number) {
|
export async function raceAgainstTimeout<T>(cb: () => Promise<T>, timeout: number): Promise<{ result: T, timedOut: false } | { timedOut: true }> {
|
||||||
return (new DeadlineRunner(promise, deadline)).result;
|
const runner = new TimeoutRunner(timeout);
|
||||||
|
try {
|
||||||
|
return { result: await runner.run(cb), timedOut: false };
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TimeoutRunnerError)
|
||||||
|
return { timedOut: true };
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ManualPromise<T> extends Promise<T> {
|
export class ManualPromise<T> extends Promise<T> {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
import { Dispatcher, TestGroup } from './dispatcher';
|
import { Dispatcher, TestGroup } from './dispatcher';
|
||||||
import { createFileMatcher, createTitleMatcher, FilePatternFilter, monotonicTime, serializeError } from './util';
|
import { createFileMatcher, createTitleMatcher, FilePatternFilter, serializeError } from './util';
|
||||||
import { TestCase, Suite } from './test';
|
import { TestCase, Suite } from './test';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
import { FullResult, Reporter, TestError } from '../types/testReporter';
|
import { FullResult, Reporter, TestError } from '../types/testReporter';
|
||||||
|
|
@ -37,7 +37,7 @@ import { ProjectImpl } from './project';
|
||||||
import { Minimatch } from 'minimatch';
|
import { Minimatch } from 'minimatch';
|
||||||
import { Config, FullConfig } from './types';
|
import { Config, FullConfig } from './types';
|
||||||
import { WebServer } from './webServer';
|
import { WebServer } from './webServer';
|
||||||
import { raceAgainstDeadline } from 'playwright-core/lib/utils/async';
|
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
||||||
|
|
||||||
const removeFolderAsync = promisify(rimraf);
|
const removeFolderAsync = promisify(rimraf);
|
||||||
const readDirAsync = promisify(fs.readdir);
|
const readDirAsync = promisify(fs.readdir);
|
||||||
|
|
@ -136,8 +136,7 @@ export class Runner {
|
||||||
this._reporter = await this._createReporter(!!options.listOnly);
|
this._reporter = await this._createReporter(!!options.listOnly);
|
||||||
try {
|
try {
|
||||||
const config = this._loader.fullConfig();
|
const config = this._loader.fullConfig();
|
||||||
const globalDeadline = config.globalTimeout ? config.globalTimeout + monotonicTime() : 0;
|
const result = await raceAgainstTimeout(() => this._run(!!options.listOnly, options.filePatternFilter || [], options.projectFilter), config.globalTimeout);
|
||||||
const result = await raceAgainstDeadline(this._run(!!options.listOnly, options.filePatternFilter || [], options.projectFilter), globalDeadline);
|
|
||||||
if (result.timedOut) {
|
if (result.timedOut) {
|
||||||
const actualResult: FullResult = { status: 'timedout' };
|
const actualResult: FullResult = { status: 'timedout' };
|
||||||
if (this._didBegin)
|
if (this._didBegin)
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,7 @@ import net from 'net';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import stream from 'stream';
|
import stream from 'stream';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { monotonicTime } from './util';
|
import { raceAgainstTimeout } from 'playwright-core/lib/utils/async';
|
||||||
import { raceAgainstDeadline } from 'playwright-core/lib/utils/async';
|
|
||||||
import { WebServerConfig } from './types';
|
import { WebServerConfig } from './types';
|
||||||
import { launchProcess } from 'playwright-core/lib/utils/processLauncher';
|
import { launchProcess } from 'playwright-core/lib/utils/processLauncher';
|
||||||
|
|
||||||
|
|
@ -95,7 +94,7 @@ export class WebServer {
|
||||||
const launchTimeout = this.config.timeout || 60 * 1000;
|
const launchTimeout = this.config.timeout || 60 * 1000;
|
||||||
const cancellationToken = { canceled: false };
|
const cancellationToken = { canceled: false };
|
||||||
const { timedOut } = (await Promise.race([
|
const { timedOut } = (await Promise.race([
|
||||||
raceAgainstDeadline(waitForSocket(this.config.port, 100, cancellationToken), launchTimeout + monotonicTime()),
|
raceAgainstTimeout(() => waitForSocket(this.config.port, 100, cancellationToken), launchTimeout),
|
||||||
this._processExitedPromise,
|
this._processExitedPromise,
|
||||||
]));
|
]));
|
||||||
cancellationToken.canceled = true;
|
cancellationToken.canceled = true;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import { Modifier, Suite, TestCase } from './test';
|
||||||
import { Annotations, TestCaseType, TestError, TestInfo, TestInfoImpl, TestStepInternal, WorkerInfo } from './types';
|
import { Annotations, TestCaseType, TestError, TestInfo, TestInfoImpl, TestStepInternal, WorkerInfo } from './types';
|
||||||
import { ProjectImpl } from './project';
|
import { ProjectImpl } from './project';
|
||||||
import { FixtureRunner } from './fixtures';
|
import { FixtureRunner } from './fixtures';
|
||||||
import { DeadlineRunner, raceAgainstDeadline } from 'playwright-core/lib/utils/async';
|
import { TimeoutRunner, raceAgainstTimeout, TimeoutRunnerError } from 'playwright-core/lib/utils/async';
|
||||||
import { calculateSha1 } from 'playwright-core/lib/utils/utils';
|
import { calculateSha1 } from 'playwright-core/lib/utils/utils';
|
||||||
|
|
||||||
const removeFolderAsync = util.promisify(rimraf);
|
const removeFolderAsync = util.promisify(rimraf);
|
||||||
|
|
@ -50,7 +50,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
private _entries = new Map<string, TestEntry>();
|
private _entries = new Map<string, TestEntry>();
|
||||||
private _isStopped = false;
|
private _isStopped = false;
|
||||||
private _runFinished = Promise.resolve();
|
private _runFinished = Promise.resolve();
|
||||||
private _currentDeadlineRunner: DeadlineRunner<any> | undefined;
|
private _currentTimeoutRunner: TimeoutRunner | undefined;
|
||||||
_currentTest: TestData | null = null;
|
_currentTest: TestData | null = null;
|
||||||
|
|
||||||
constructor(params: WorkerInitParams) {
|
constructor(params: WorkerInitParams) {
|
||||||
|
|
@ -64,7 +64,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
this._isStopped = true;
|
this._isStopped = true;
|
||||||
|
|
||||||
// Interrupt current action.
|
// Interrupt current action.
|
||||||
this._currentDeadlineRunner?.interrupt();
|
this._currentTimeoutRunner?.interrupt();
|
||||||
|
|
||||||
// TODO: mark test as 'interrupted' instead.
|
// TODO: mark test as 'interrupted' instead.
|
||||||
if (this._currentTest && this._currentTest.testInfo.status === 'passed')
|
if (this._currentTest && this._currentTest.testInfo.status === 'passed')
|
||||||
|
|
@ -83,10 +83,10 @@ export class WorkerRunner extends EventEmitter {
|
||||||
|
|
||||||
private async _teardownScopes() {
|
private async _teardownScopes() {
|
||||||
// TODO: separate timeout for teardown?
|
// TODO: separate timeout for teardown?
|
||||||
const result = await raceAgainstDeadline((async () => {
|
const result = await raceAgainstTimeout(async () => {
|
||||||
await this._fixtureRunner.teardownScope('test');
|
await this._fixtureRunner.teardownScope('test');
|
||||||
await this._fixtureRunner.teardownScope('worker');
|
await this._fixtureRunner.teardownScope('worker');
|
||||||
})(), this._deadline());
|
}, this._project.config.timeout);
|
||||||
if (result.timedOut && !this._fatalError)
|
if (result.timedOut && !this._fatalError)
|
||||||
this._fatalError = { message: colors.red(`Timeout of ${this._project.config.timeout}ms exceeded while shutting down environment`) };
|
this._fatalError = { message: colors.red(`Timeout of ${this._project.config.timeout}ms exceeded while shutting down environment`) };
|
||||||
}
|
}
|
||||||
|
|
@ -115,10 +115,6 @@ export class WorkerRunner extends EventEmitter {
|
||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deadline() {
|
|
||||||
return this._project.config.timeout ? monotonicTime() + this._project.config.timeout : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadIfNeeded() {
|
private async _loadIfNeeded() {
|
||||||
if (this._loader)
|
if (this._loader)
|
||||||
return;
|
return;
|
||||||
|
|
@ -189,7 +185,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.resolveParametersAndRunFunction(beforeAllModifier.fn, this._workerInfo, undefined), this._deadline());
|
const result = await raceAgainstTimeout(() => this._fixtureRunner.resolveParametersAndRunFunction(beforeAllModifier.fn, this._workerInfo, undefined), this._project.config.timeout);
|
||||||
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)}`));
|
||||||
|
|
@ -224,7 +220,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
private async _runTestOrAllHook(test: TestCase, annotations: Annotations, retry: number) {
|
private async _runTestOrAllHook(test: TestCase, annotations: Annotations, retry: number) {
|
||||||
const startTime = monotonicTime();
|
const startTime = monotonicTime();
|
||||||
const startWallTime = Date.now();
|
const startWallTime = Date.now();
|
||||||
let deadlineRunner: DeadlineRunner<any> | undefined;
|
let timeoutRunner: TimeoutRunner;
|
||||||
const testId = test._id;
|
const testId = test._id;
|
||||||
|
|
||||||
const baseOutputDir = (() => {
|
const baseOutputDir = (() => {
|
||||||
|
|
@ -313,8 +309,8 @@ export class WorkerRunner extends EventEmitter {
|
||||||
if (!testInfo.timeout)
|
if (!testInfo.timeout)
|
||||||
return; // Zero timeout means some debug mode - do not set a timeout.
|
return; // Zero timeout means some debug mode - do not set a timeout.
|
||||||
testInfo.timeout = timeout;
|
testInfo.timeout = timeout;
|
||||||
if (deadlineRunner)
|
if (timeoutRunner)
|
||||||
deadlineRunner.updateDeadline(deadline());
|
timeoutRunner.updateTimeout(timeout);
|
||||||
},
|
},
|
||||||
_addStep: data => {
|
_addStep: data => {
|
||||||
const stepId = `${data.category}@${data.title}@${++lastStepId}`;
|
const stepId = `${data.category}@${data.title}@${++lastStepId}`;
|
||||||
|
|
@ -381,10 +377,6 @@ export class WorkerRunner extends EventEmitter {
|
||||||
this._currentTest = testData;
|
this._currentTest = testData;
|
||||||
setCurrentTestInfo(testInfo);
|
setCurrentTestInfo(testInfo);
|
||||||
|
|
||||||
const deadline = () => {
|
|
||||||
return testInfo.timeout ? startTime + testInfo.timeout : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.emit('testBegin', buildTestBeginPayload(testData, startWallTime));
|
this.emit('testBegin', buildTestBeginPayload(testData, startWallTime));
|
||||||
|
|
||||||
if (testInfo.expectedStatus === 'skipped') {
|
if (testInfo.expectedStatus === 'skipped') {
|
||||||
|
|
@ -396,29 +388,18 @@ export class WorkerRunner extends EventEmitter {
|
||||||
// Update the fixture pool - it may differ between tests, but only in test-scoped fixtures.
|
// Update the fixture pool - it may differ between tests, but only in test-scoped fixtures.
|
||||||
this._fixtureRunner.setPool(test._pool!);
|
this._fixtureRunner.setPool(test._pool!);
|
||||||
|
|
||||||
this._currentDeadlineRunner = deadlineRunner = new DeadlineRunner(this._runTestWithBeforeHooks(test, testInfo), deadline());
|
this._currentTimeoutRunner = timeoutRunner = new TimeoutRunner(testInfo.timeout);
|
||||||
const result = await deadlineRunner.result;
|
await this._runWithTimeout(timeoutRunner, () => this._runTestWithBeforeHooks(test, testInfo), testInfo);
|
||||||
// Do not overwrite test failure upon hook timeout.
|
|
||||||
if (result.timedOut && testInfo.status === 'passed')
|
|
||||||
testInfo.status = 'timedOut';
|
|
||||||
testInfo.duration = monotonicTime() - startTime;
|
testInfo.duration = monotonicTime() - startTime;
|
||||||
|
|
||||||
if (!result.timedOut) {
|
if (testInfo.status === 'timedOut') {
|
||||||
this._currentDeadlineRunner = deadlineRunner = new DeadlineRunner(this._runAfterHooks(test, testInfo), deadline());
|
|
||||||
deadlineRunner.updateDeadline(deadline());
|
|
||||||
const hooksResult = await deadlineRunner.result;
|
|
||||||
// Do not overwrite test failure upon hook timeout.
|
|
||||||
if (hooksResult.timedOut && testInfo.status === 'passed')
|
|
||||||
testInfo.status = 'timedOut';
|
|
||||||
} else {
|
|
||||||
// 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.
|
||||||
const newDeadline = this._deadline();
|
timeoutRunner.resetTimeout(testInfo.timeout);
|
||||||
this._currentDeadlineRunner = deadlineRunner = new DeadlineRunner(this._runAfterHooks(test, testInfo), newDeadline);
|
|
||||||
await deadlineRunner.result;
|
|
||||||
}
|
}
|
||||||
|
await this._runWithTimeout(timeoutRunner, () => this._runAfterHooks(test, testInfo), testInfo);
|
||||||
|
|
||||||
testInfo.duration = monotonicTime() - startTime;
|
testInfo.duration = monotonicTime() - startTime;
|
||||||
this._currentDeadlineRunner = undefined;
|
this._currentTimeoutRunner = undefined;
|
||||||
this._currentTest = null;
|
this._currentTest = null;
|
||||||
setCurrentTestInfo(null);
|
setCurrentTestInfo(null);
|
||||||
|
|
||||||
|
|
@ -496,10 +477,10 @@ export class WorkerRunner extends EventEmitter {
|
||||||
|
|
||||||
let teardownError1: TestError | undefined;
|
let teardownError1: TestError | undefined;
|
||||||
if (test._type === 'test')
|
if (test._type === 'test')
|
||||||
teardownError1 = await this._runFn(() => this._runHooks(test.parent!, 'afterEach', testInfo), testInfo, 'disallowSkips');
|
teardownError1 = await this._runFn(() => this._runHooks(test.parent!, 'afterEach', testInfo), testInfo);
|
||||||
// Continue teardown even after the failure.
|
// Continue teardown even after the failure.
|
||||||
|
|
||||||
const teardownError2 = await this._runFn(() => this._fixtureRunner.teardownScope('test'), testInfo, 'disallowSkips');
|
const teardownError2 = await this._runFn(() => this._fixtureRunner.teardownScope('test'), testInfo);
|
||||||
step.complete(teardownError1 || teardownError2);
|
step.complete(teardownError1 || teardownError2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,7 +505,19 @@ export class WorkerRunner extends EventEmitter {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runFn(fn: Function, testInfo: TestInfoImpl, skips: 'allowSkips' | 'disallowSkips'): Promise<TestError | undefined> {
|
private async _runWithTimeout(timeoutRunner: TimeoutRunner, cb: () => Promise<any>, testInfo: TestInfoImpl): Promise<void> {
|
||||||
|
try {
|
||||||
|
await timeoutRunner.run(cb);
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof TimeoutRunnerError))
|
||||||
|
throw error;
|
||||||
|
// Do not overwrite existing failure upon hook/teardown timeout.
|
||||||
|
if (testInfo.status === 'passed')
|
||||||
|
testInfo.status = 'timedOut';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _runFn(fn: Function, testInfo: TestInfoImpl, skips?: 'allowSkips'): Promise<TestError | undefined> {
|
||||||
try {
|
try {
|
||||||
await fn();
|
await fn();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue