tr-did-timeout-top-level
This commit is contained in:
parent
006ee7f3b0
commit
2bc8126828
|
|
@ -354,13 +354,13 @@ export class TestInfoImpl implements TestInfo {
|
||||||
// Do not overwrite any previous hard errors.
|
// Do not overwrite any previous hard errors.
|
||||||
// Some (but not all) scenarios include:
|
// Some (but not all) scenarios include:
|
||||||
// - expect() that fails after uncaught exception.
|
// - expect() that fails after uncaught exception.
|
||||||
// - fail after the timeout, e.g. due to fixture teardown.
|
// - fail in fixture teardown after the test failure.
|
||||||
if (isHardError && this._hasHardError)
|
if (isHardError && this._hasHardError)
|
||||||
return;
|
return;
|
||||||
if (isHardError)
|
if (isHardError)
|
||||||
this._hasHardError = true;
|
this._hasHardError = true;
|
||||||
if (this.status === 'passed' || this.status === 'skipped')
|
if (this.status === 'passed' || this.status === 'skipped')
|
||||||
this.status = 'failed';
|
this.status = error instanceof TimeoutManagerError ? 'timedOut' : 'failed';
|
||||||
const serialized = serializeError(error);
|
const serialized = serializeError(error);
|
||||||
const step = (error as any)[stepSymbol] as TestStepInternal | undefined;
|
const step = (error as any)[stepSymbol] as TestStepInternal | undefined;
|
||||||
if (step && step.boxedStack)
|
if (step && step.boxedStack)
|
||||||
|
|
@ -397,8 +397,6 @@ export class TestInfoImpl implements TestInfo {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this._timeoutManager.withRunnable(runnable, async () => {
|
await this._timeoutManager.withRunnable(runnable, async () => {
|
||||||
// Note: separate try/catch is here to report errors after timeout.
|
|
||||||
// This way we get a nice "locator.click" error after the test times out and closes the page.
|
|
||||||
try {
|
try {
|
||||||
await cb();
|
await cb();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -406,7 +404,13 @@ export class TestInfoImpl implements TestInfo {
|
||||||
if (this.status === 'passed')
|
if (this.status === 'passed')
|
||||||
this.status = 'skipped';
|
this.status = 'skipped';
|
||||||
} else if (!(e instanceof TimeoutManagerError)) {
|
} else if (!(e instanceof TimeoutManagerError)) {
|
||||||
// Do not consider timeout errors in child stages as a regular "hard error".
|
// Note: we handle timeout errors at the top level, so ignore them here.
|
||||||
|
// Unfortunately, we cannot ignore user errors here. Consider the following scenario:
|
||||||
|
// - locator.click times out
|
||||||
|
// - all stages containing the test function finish with TimeoutManagerError
|
||||||
|
// - test finishes, the page is closed and this triggers locator.click error
|
||||||
|
// - we would like to present the locator.click error to the user
|
||||||
|
// - therefore, we need a try/catch inside the "run with timeout" block and capture the error
|
||||||
this._failWithError(e, true /* isHardError */, true /* retriable */);
|
this._failWithError(e, true /* isHardError */, true /* retriable */);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
|
|
@ -414,17 +418,6 @@ export class TestInfoImpl implements TestInfo {
|
||||||
});
|
});
|
||||||
stage.step?.complete({});
|
stage.step?.complete({});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// When interrupting, we arrive here with a TimeoutManagerError, but we should not
|
|
||||||
// consider it a timeout.
|
|
||||||
if (!this._wasInterrupted && !this._didTimeout && (error instanceof TimeoutManagerError)) {
|
|
||||||
this._didTimeout = true;
|
|
||||||
const serialized = serializeError(error);
|
|
||||||
this.errors.push(serialized);
|
|
||||||
this._tracing.appendForError(serialized);
|
|
||||||
// Do not overwrite existing failure upon hook/teardown timeout.
|
|
||||||
if (this.status === 'passed' || this.status === 'skipped')
|
|
||||||
this.status = 'timedOut';
|
|
||||||
}
|
|
||||||
stage.step?.complete({ error });
|
stage.step?.complete({ error });
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -435,6 +428,15 @@ export class TestInfoImpl implements TestInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handlePossibleTimeoutError(error: Error) {
|
||||||
|
// When interrupting, we arrive here with a TimeoutManagerError, but we should not
|
||||||
|
// consider it a timeout.
|
||||||
|
if (!this._wasInterrupted && !this._didTimeout && (error instanceof TimeoutManagerError)) {
|
||||||
|
// this._didTimeout = true;
|
||||||
|
this._failWithError(error, false /* isHardError */, true /* retriable */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_isFailure() {
|
_isFailure() {
|
||||||
return this.status !== 'skipped' && this.status !== this.expectedStatus;
|
return this.status !== 'skipped' && this.status !== this.expectedStatus;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ export class WorkerMain extends ProcessRunner {
|
||||||
const fn = test.fn; // Extract a variable to get a better stack trace ("myTest" vs "TestCase.myTest [as fn]").
|
const fn = test.fn; // Extract a variable to get a better stack trace ("myTest" vs "TestCase.myTest [as fn]").
|
||||||
await fn(testFunctionParams, testInfo);
|
await fn(testFunctionParams, testInfo);
|
||||||
});
|
});
|
||||||
}).catch(() => {}); // Ignore top-level error, we still have to run after hooks.
|
}).catch(error => testInfo._handlePossibleTimeoutError(error));
|
||||||
|
|
||||||
// Update duration, so it is available in fixture teardown and afterEach hooks.
|
// Update duration, so it is available in fixture teardown and afterEach hooks.
|
||||||
testInfo.duration = testInfo._timeoutManager.defaultSlotTimings().elapsed | 0;
|
testInfo.duration = testInfo._timeoutManager.defaultSlotTimings().elapsed | 0;
|
||||||
|
|
@ -462,11 +462,12 @@ export class WorkerMain extends ProcessRunner {
|
||||||
|
|
||||||
if (firstAfterHooksError)
|
if (firstAfterHooksError)
|
||||||
throw firstAfterHooksError;
|
throw firstAfterHooksError;
|
||||||
}).catch(() => {}); // Ignore top-level error.
|
}).catch(error => testInfo._handlePossibleTimeoutError(error));
|
||||||
|
|
||||||
await testInfo._runAsStage({ title: 'stop tracing' }, async () => {
|
const tracingSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||||
|
await testInfo._runAsStage({ title: 'stop tracing', canTimeout: true, runnableType: 'test', runnableSlot: tracingSlot }, async () => {
|
||||||
await testInfo._tracing.stopIfNeeded();
|
await testInfo._tracing.stopIfNeeded();
|
||||||
}).catch(() => {}); // Ignore top-level error.
|
}).catch(error => testInfo._handlePossibleTimeoutError(error));
|
||||||
|
|
||||||
testInfo.duration = (testInfo._timeoutManager.defaultSlotTimings().elapsed + afterHooksSlot.elapsed) | 0;
|
testInfo.duration = (testInfo._timeoutManager.defaultSlotTimings().elapsed + afterHooksSlot.elapsed) | 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -526,7 +526,7 @@ test('afterAll timeout should be reported, run other afterAll hooks, and continu
|
||||||
test.afterAll(async () => {
|
test.afterAll(async () => {
|
||||||
console.log('\\n%%afterAll2');
|
console.log('\\n%%afterAll2');
|
||||||
});
|
});
|
||||||
test('does not run', () => {
|
test('run in a different worker', () => {
|
||||||
console.log('\\n%%test2');
|
console.log('\\n%%test2');
|
||||||
});
|
});
|
||||||
`,
|
`,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue