mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
fix(testrunner): report suite-level errors (#3661)
This commit is contained in:
parent
2b7d79d7fa
commit
6a0f587fae
@ -43,7 +43,7 @@ export class Runner {
|
||||
this._suite = suite;
|
||||
for (const suite of this._suite.suites) {
|
||||
suite.findTest(test => {
|
||||
this._testById.set(`${test._ordinal}@${suite.file}::[${suite._configurationString}]`, { test, result: test._appendResult() });
|
||||
this._testById.set(test._id, { test, result: test._appendResult() });
|
||||
});
|
||||
}
|
||||
|
||||
@ -58,12 +58,12 @@ export class Runner {
|
||||
_filesSortedByWorkerHash(): TestRunnerEntry[] {
|
||||
const result: TestRunnerEntry[] = [];
|
||||
for (const suite of this._suite.suites) {
|
||||
const ordinals: number[] = [];
|
||||
suite.findTest(test => ordinals.push(test._ordinal) && false);
|
||||
if (!ordinals.length)
|
||||
const ids: string[] = [];
|
||||
suite.findTest(test => ids.push(test._id) && false);
|
||||
if (!ids.length)
|
||||
continue;
|
||||
result.push({
|
||||
ordinals,
|
||||
ids,
|
||||
file: suite.file,
|
||||
configuration: suite.configuration,
|
||||
configurationString: suite._configurationString,
|
||||
@ -98,7 +98,7 @@ export class Runner {
|
||||
await Promise.all(jobs);
|
||||
}
|
||||
|
||||
async _runJob(worker, entry) {
|
||||
async _runJob(worker: Worker, entry: TestRunnerEntry) {
|
||||
worker.run(entry);
|
||||
let doneCallback;
|
||||
const result = new Promise(f => doneCallback = f);
|
||||
@ -112,8 +112,16 @@ export class Runner {
|
||||
// When worker encounters error, we will restart it.
|
||||
this._restartWorker(worker);
|
||||
|
||||
// In case of fatal error, we are done with the entry.
|
||||
if (params.fatalError) {
|
||||
// In case of fatal error without test id, we are done with the entry.
|
||||
if (params.fatalError && !params.failedTestId) {
|
||||
// Report all the tests are failing with this error.
|
||||
for (const id of entry.ids) {
|
||||
const { test, result } = this._testById.get(id);
|
||||
this._reporter.onTestBegin(test);
|
||||
result.status = 'failed';
|
||||
result.error = params.fatalError;
|
||||
this._reporter.onTestEnd(test, result);
|
||||
}
|
||||
doneCallback();
|
||||
return;
|
||||
}
|
||||
@ -123,11 +131,11 @@ export class Runner {
|
||||
const pair = this._testById.get(params.failedTestId);
|
||||
if (pair.test.results.length < this._config.retries + 1) {
|
||||
pair.result = pair.test._appendResult();
|
||||
remaining.unshift(pair.test._ordinal);
|
||||
remaining.unshift(pair.test._id);
|
||||
}
|
||||
}
|
||||
if (remaining.length)
|
||||
this._queue.unshift({ ...entry, ordinals: remaining });
|
||||
this._queue.unshift({ ...entry, ids: remaining });
|
||||
|
||||
// This job is over, we just scheduled another one.
|
||||
doneCallback();
|
||||
|
@ -27,7 +27,7 @@ export class Test {
|
||||
fn: Function;
|
||||
results: TestResult[] = [];
|
||||
|
||||
_ordinal: number;
|
||||
_id: string;
|
||||
_overriddenFn: Function;
|
||||
_startTime: number;
|
||||
|
||||
@ -157,7 +157,7 @@ export class Suite {
|
||||
let ordinal = 0;
|
||||
this.findTest((test: Test) => {
|
||||
// All tests are identified with their ordinals.
|
||||
test._ordinal = ordinal++;
|
||||
test._id = `${ordinal++}@${this.file}::[${this._configurationString}]`;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,6 @@ export class TestCollector {
|
||||
const revertBabelRequire = spec(suite, file, this._config.timeout);
|
||||
require(file);
|
||||
revertBabelRequire();
|
||||
suite._renumber();
|
||||
|
||||
const workerGeneratorConfigurations = new Map();
|
||||
|
||||
@ -104,6 +103,7 @@ export class TestCollector {
|
||||
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '') + ' ' + (i ? ` #repeat-${i}#` : '');
|
||||
clone.configuration = configuration;
|
||||
clone._configurationString = configurationString + `#repeat-${i}#`;
|
||||
clone._renumber();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,6 @@ export class TestCollector {
|
||||
continue;
|
||||
const testCopy = test._clone();
|
||||
testCopy.only = test.only;
|
||||
testCopy._ordinal = test._ordinal;
|
||||
copy._addTest(testCopy);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export const fixturePool = new FixturePool();
|
||||
|
||||
export type TestRunnerEntry = {
|
||||
file: string;
|
||||
ordinals: number[];
|
||||
ids: string[];
|
||||
configurationString: string;
|
||||
configuration: Configuration;
|
||||
hash: string;
|
||||
@ -43,9 +43,8 @@ function chunkToParams(chunk: Buffer | string): { text?: string, buffer?: strin
|
||||
export class TestRunner extends EventEmitter {
|
||||
private _failedTestId: string | undefined;
|
||||
private _fatalError: any | undefined;
|
||||
private _file: any;
|
||||
private _ordinals: Set<number>;
|
||||
private _remaining: Set<number>;
|
||||
private _ids: Set<string>;
|
||||
private _remaining: Set<string>;
|
||||
private _trialRun: any;
|
||||
private _configuredFile: any;
|
||||
private _parsedGeneratorConfiguration: any = {};
|
||||
@ -55,12 +54,15 @@ export class TestRunner extends EventEmitter {
|
||||
private _stdOutBuffer: (string | Buffer)[] = [];
|
||||
private _stdErrBuffer: (string | Buffer)[] = [];
|
||||
private _testResult: TestResult | null = null;
|
||||
private _suite: Suite;
|
||||
|
||||
constructor(entry: TestRunnerEntry, config: RunnerConfig, workerId: number) {
|
||||
super();
|
||||
this._file = entry.file;
|
||||
this._ordinals = new Set(entry.ordinals);
|
||||
this._remaining = new Set(entry.ordinals);
|
||||
this._suite = new Suite('');
|
||||
this._suite.file = entry.file;
|
||||
this._suite._configurationString = entry.configurationString;
|
||||
this._ids = new Set(entry.ids);
|
||||
this._remaining = new Set(entry.ids);
|
||||
this._trialRun = config.trialRun;
|
||||
this._timeout = config.timeout;
|
||||
this._config = config;
|
||||
@ -68,7 +70,7 @@ export class TestRunner extends EventEmitter {
|
||||
for (const {name, value} of entry.configuration)
|
||||
this._parsedGeneratorConfiguration[name] = value;
|
||||
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
||||
setCurrentTestFile(this._file);
|
||||
setCurrentTestFile(this._suite.file);
|
||||
}
|
||||
|
||||
stop() {
|
||||
@ -108,14 +110,13 @@ export class TestRunner extends EventEmitter {
|
||||
async run() {
|
||||
setParameters(this._parsedGeneratorConfiguration);
|
||||
|
||||
const suite = new Suite('');
|
||||
const revertBabelRequire = spec(suite, this._file, this._timeout);
|
||||
require(this._file);
|
||||
const revertBabelRequire = spec(this._suite, this._suite.file, this._timeout);
|
||||
require(this._suite.file);
|
||||
revertBabelRequire();
|
||||
suite._renumber();
|
||||
this._suite._renumber();
|
||||
|
||||
rerunRegistrations(this._file, 'test');
|
||||
await this._runSuite(suite);
|
||||
rerunRegistrations(this._suite.file, 'test');
|
||||
await this._runSuite(this._suite);
|
||||
this._reportDone();
|
||||
}
|
||||
|
||||
@ -144,11 +145,11 @@ export class TestRunner extends EventEmitter {
|
||||
private async _runTest(test: Test) {
|
||||
if (this._failedTestId)
|
||||
return false;
|
||||
if (this._ordinals.size && !this._ordinals.has(test._ordinal))
|
||||
if (this._ids.size && !this._ids.has(test._id))
|
||||
return;
|
||||
this._remaining.delete(test._ordinal);
|
||||
this._remaining.delete(test._id);
|
||||
|
||||
const id = `${test._ordinal}@${this._configuredFile}`;
|
||||
const id = test._id;
|
||||
this._testId = id;
|
||||
this.emit('testBegin', { id });
|
||||
|
||||
|
@ -29,13 +29,13 @@ global.console = new Console({
|
||||
});
|
||||
|
||||
process.stdout.write = chunk => {
|
||||
if (testRunner && !closed)
|
||||
if (testRunner)
|
||||
testRunner.stdout(chunk);
|
||||
return true;
|
||||
};
|
||||
|
||||
process.stderr.write = chunk => {
|
||||
if (testRunner && !closed)
|
||||
if (testRunner)
|
||||
testRunner.stderr(chunk);
|
||||
return true;
|
||||
};
|
||||
@ -48,12 +48,12 @@ let workerId: number;
|
||||
let testRunner: TestRunner;
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
if (testRunner && !closed)
|
||||
if (testRunner)
|
||||
testRunner.fatalError(reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
if (testRunner && !closed)
|
||||
if (testRunner)
|
||||
testRunner.fatalError(error);
|
||||
});
|
||||
|
||||
@ -73,8 +73,6 @@ process.on('message', async message => {
|
||||
testRunner.on(event, sendMessageToParent.bind(null, event));
|
||||
await testRunner.run();
|
||||
testRunner = null;
|
||||
// Mocha runner adds these; if we don't remove them, we'll get a leak.
|
||||
process.removeAllListeners('uncaughtException');
|
||||
}
|
||||
});
|
||||
|
||||
|
23
test-runner/test/assets/suite-error.js
Normal file
23
test-runner/test/assets/suite-error.js
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const { parameters } = require('../../');
|
||||
|
||||
if (typeof parameters.parallelIndex === 'number')
|
||||
throw new Error('Suite error');
|
||||
|
||||
it('passes',() => {
|
||||
expect(1 + 1).toBe(2);
|
||||
});
|
@ -93,6 +93,13 @@ it('should repeat each', async () => {
|
||||
expect(suite.tests.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should report suite errors', async () => {
|
||||
const { exitCode, failed, output } = await runTest('suite-error.js');
|
||||
expect(exitCode).toBe(1);
|
||||
expect(failed).toBe(1);
|
||||
expect(output).toContain('Suite error');
|
||||
});
|
||||
|
||||
async function runTest(filePath: string, params: any = {}) {
|
||||
const outputDir = path.join(__dirname, 'test-results');
|
||||
const reportFile = path.join(outputDir, 'results.json');
|
||||
|
Loading…
x
Reference in New Issue
Block a user