mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-13 17:14:02 +03:00
feat(test runner): show last tests in the worker on teardown error (#13139)
This commit is contained in:
parent
8758cf8cbf
commit
de0af27837
@ -103,8 +103,11 @@ async function gracefullyCloseAndExit() {
|
|||||||
if (workerIndex !== undefined)
|
if (workerIndex !== undefined)
|
||||||
await stopProfiling(workerIndex);
|
await stopProfiling(workerIndex);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const payload: TeardownErrorsPayload = { fatalErrors: [serializeError(e)] };
|
try {
|
||||||
process.send!({ method: 'teardownErrors', params: payload });
|
const payload: TeardownErrorsPayload = { fatalErrors: [serializeError(e)] };
|
||||||
|
process.send!({ method: 'teardownErrors', params: payload });
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import colors from 'colors/safe';
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
import util from 'util';
|
import util from 'util';
|
||||||
import { EventEmitter } from 'events';
|
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 { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerInitParams, StepBeginPayload, StepEndPayload, TeardownErrorsPayload } from './ipc';
|
||||||
import { setCurrentTestInfo } from './globals';
|
import { setCurrentTestInfo } from './globals';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
@ -49,6 +50,8 @@ export class WorkerRunner extends EventEmitter {
|
|||||||
// This promise resolves once the single "run test group" call finishes.
|
// This promise resolves once the single "run test group" call finishes.
|
||||||
private _runFinished = new ManualPromise<void>();
|
private _runFinished = new ManualPromise<void>();
|
||||||
_currentTest: TestInfoImpl | null = null;
|
_currentTest: TestInfoImpl | null = null;
|
||||||
|
private _lastRunningTests: TestInfoImpl[] = [];
|
||||||
|
private _totalRunningTests = 0;
|
||||||
// Dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
|
// Dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`.
|
||||||
private _extraSuiteAnnotations = new Map<Suite, Annotation[]>();
|
private _extraSuiteAnnotations = new Map<Suite, Annotation[]>();
|
||||||
// Suites that had their beforeAll hooks, but not afterAll hooks executed.
|
// 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._loadIfNeeded();
|
||||||
await this._teardownScopes();
|
await this._teardownScopes();
|
||||||
if (this._fatalErrors.length) {
|
if (this._fatalErrors.length) {
|
||||||
|
const diagnostics = this._createWorkerTeardownDiagnostics();
|
||||||
|
if (diagnostics)
|
||||||
|
this._fatalErrors.unshift(diagnostics);
|
||||||
const payload: TeardownErrorsPayload = { fatalErrors: this._fatalErrors };
|
const payload: TeardownErrorsPayload = { fatalErrors: this._fatalErrors };
|
||||||
this.emit('teardownErrors', payload);
|
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() {
|
private async _teardownScopes() {
|
||||||
// TODO: separate timeout for teardown?
|
// TODO: separate timeout for teardown?
|
||||||
const timeoutManager = new TimeoutManager(this._project.config.timeout);
|
const timeoutManager = new TimeoutManager(this._project.config.timeout);
|
||||||
@ -268,6 +288,10 @@ export class WorkerRunner extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._totalRunningTests++;
|
||||||
|
this._lastRunningTests.push(testInfo);
|
||||||
|
if (this._lastRunningTests.length > 10)
|
||||||
|
this._lastRunningTests.shift();
|
||||||
let didFailBeforeAllForSuite: Suite | undefined;
|
let didFailBeforeAllForSuite: Suite | undefined;
|
||||||
let shouldRunAfterEachHooks = false;
|
let shouldRunAfterEachHooks = false;
|
||||||
|
|
||||||
@ -539,3 +563,11 @@ function getSuites(test: TestCase | undefined): Suite[] {
|
|||||||
suites.reverse(); // Put root suite first.
|
suites.reverse(); // Put root suite first.
|
||||||
return suites;
|
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(' › ')}`;
|
||||||
|
}
|
||||||
|
@ -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('Timeout of 100ms exceeded');
|
||||||
expect(result.output).toContain('Error: Oh my error');
|
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'));
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user