feat(test runner): show last tests in the worker on teardown error (#13139)

This commit is contained in:
Dmitry Gozman 2022-03-28 19:58:24 -07:00 committed by GitHub
parent 8758cf8cbf
commit de0af27837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 3 deletions

View File

@ -103,8 +103,11 @@ async function gracefullyCloseAndExit() {
if (workerIndex !== undefined)
await stopProfiling(workerIndex);
} catch (e) {
const payload: TeardownErrorsPayload = { fatalErrors: [serializeError(e)] };
process.send!({ method: 'teardownErrors', params: payload });
try {
const payload: TeardownErrorsPayload = { fatalErrors: [serializeError(e)] };
process.send!({ method: 'teardownErrors', params: payload });
} catch {
}
}
process.exit(0);
}

View File

@ -14,10 +14,11 @@
* limitations under the License.
*/
import colors from 'colors/safe';
import rimraf from 'rimraf';
import util from 'util';
import { EventEmitter } from 'events';
import { serializeError } from './util';
import { relativeFilePath, serializeError } from './util';
import { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, StepBeginPayload, StepEndPayload, TeardownErrorsPayload } from './ipc';
import { setCurrentTestInfo } from './globals';
import { Loader } from './loader';
@ -49,6 +50,8 @@ export class WorkerRunner extends EventEmitter {
// This promise resolves once the single "run test group" call finishes.
private _runFinished = new ManualPromise<void>();
_currentTest: TestInfoImpl | null = null;
private _lastRunningTests: TestInfoImpl[] = [];
private _totalRunningTests = 0;
// Dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
private _extraSuiteAnnotations = new Map<Suite, Annotation[]>();
// Suites that had their beforeAll hooks, but not afterAll hooks executed.
@ -84,11 +87,28 @@ export class WorkerRunner extends EventEmitter {
await this._loadIfNeeded();
await this._teardownScopes();
if (this._fatalErrors.length) {
const diagnostics = this._createWorkerTeardownDiagnostics();
if (diagnostics)
this._fatalErrors.unshift(diagnostics);
const payload: TeardownErrorsPayload = { fatalErrors: this._fatalErrors };
this.emit('teardownErrors', payload);
}
}
private _createWorkerTeardownDiagnostics(): TestError | undefined {
if (!this._lastRunningTests.length)
return;
const count = this._totalRunningTests === 1 ? '1 test' : `${this._totalRunningTests} tests`;
let lastMessage = '';
if (this._lastRunningTests.length < this._totalRunningTests)
lastMessage = `, last ${this._lastRunningTests.length} tests were`;
const message = [
colors.red(`Worker teardown error. This worker ran ${count}${lastMessage}:`),
...this._lastRunningTests.map(testInfo => formatTestTitle(testInfo._test, testInfo.project.name)),
].join('\n');
return { message };
}
private async _teardownScopes() {
// TODO: separate timeout for teardown?
const timeoutManager = new TimeoutManager(this._project.config.timeout);
@ -268,6 +288,10 @@ export class WorkerRunner extends EventEmitter {
return;
}
this._totalRunningTests++;
this._lastRunningTests.push(testInfo);
if (this._lastRunningTests.length > 10)
this._lastRunningTests.shift();
let didFailBeforeAllForSuite: Suite | undefined;
let shouldRunAfterEachHooks = false;
@ -539,3 +563,11 @@ function getSuites(test: TestCase | undefined): Suite[] {
suites.reverse(); // Put root suite first.
return suites;
}
function formatTestTitle(test: TestCase, projectName: string) {
// file, ...describes, test
const [, ...titles] = test.titlePath();
const location = `${relativeFilePath(test.location.file)}:${test.location.line}:${test.location.column}`;
const projectTitle = projectName ? `[${projectName}] ` : '';
return `${projectTitle}${location} ${titles.join(' ')}`;
}

View File

@ -500,3 +500,35 @@ test('should handle fixture teardown error after test timeout and continue', asy
expect(result.output).toContain('Timeout of 100ms exceeded');
expect(result.output).toContain('Error: Oh my error');
});
test('should report worker fixture teardown with debug info', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.spec.ts': `
const test = pwt.test.extend({
fixture: [ async ({ }, use) => {
await use();
await new Promise(() => {});
}, { scope: 'worker' } ],
});
for (let i = 0; i < 20; i++)
test('good' + i, async ({ fixture }) => {});
`,
}, { reporter: 'list', timeout: 1000 });
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(20);
expect(stripAnsi(result.output)).toContain([
'Worker teardown error. This worker ran 20 tests, last 10 tests were:',
'a.spec.ts:12:9 good10',
'a.spec.ts:12:9 good11',
'a.spec.ts:12:9 good12',
'a.spec.ts:12:9 good13',
'a.spec.ts:12:9 good14',
'a.spec.ts:12:9 good15',
'a.spec.ts:12:9 good16',
'a.spec.ts:12:9 good17',
'a.spec.ts:12:9 good18',
'a.spec.ts:12:9 good19',
'',
'Timeout of 1000ms exceeded in fixtures teardown.',
].join('\n'));
});