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;
|
this._suite = suite;
|
||||||
for (const suite of this._suite.suites) {
|
for (const suite of this._suite.suites) {
|
||||||
suite.findTest(test => {
|
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[] {
|
_filesSortedByWorkerHash(): TestRunnerEntry[] {
|
||||||
const result: TestRunnerEntry[] = [];
|
const result: TestRunnerEntry[] = [];
|
||||||
for (const suite of this._suite.suites) {
|
for (const suite of this._suite.suites) {
|
||||||
const ordinals: number[] = [];
|
const ids: string[] = [];
|
||||||
suite.findTest(test => ordinals.push(test._ordinal) && false);
|
suite.findTest(test => ids.push(test._id) && false);
|
||||||
if (!ordinals.length)
|
if (!ids.length)
|
||||||
continue;
|
continue;
|
||||||
result.push({
|
result.push({
|
||||||
ordinals,
|
ids,
|
||||||
file: suite.file,
|
file: suite.file,
|
||||||
configuration: suite.configuration,
|
configuration: suite.configuration,
|
||||||
configurationString: suite._configurationString,
|
configurationString: suite._configurationString,
|
||||||
@ -98,7 +98,7 @@ export class Runner {
|
|||||||
await Promise.all(jobs);
|
await Promise.all(jobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _runJob(worker, entry) {
|
async _runJob(worker: Worker, entry: TestRunnerEntry) {
|
||||||
worker.run(entry);
|
worker.run(entry);
|
||||||
let doneCallback;
|
let doneCallback;
|
||||||
const result = new Promise(f => doneCallback = f);
|
const result = new Promise(f => doneCallback = f);
|
||||||
@ -112,8 +112,16 @@ export class Runner {
|
|||||||
// When worker encounters error, we will restart it.
|
// When worker encounters error, we will restart it.
|
||||||
this._restartWorker(worker);
|
this._restartWorker(worker);
|
||||||
|
|
||||||
// In case of fatal error, we are done with the entry.
|
// In case of fatal error without test id, we are done with the entry.
|
||||||
if (params.fatalError) {
|
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();
|
doneCallback();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -123,11 +131,11 @@ export class Runner {
|
|||||||
const pair = this._testById.get(params.failedTestId);
|
const pair = this._testById.get(params.failedTestId);
|
||||||
if (pair.test.results.length < this._config.retries + 1) {
|
if (pair.test.results.length < this._config.retries + 1) {
|
||||||
pair.result = pair.test._appendResult();
|
pair.result = pair.test._appendResult();
|
||||||
remaining.unshift(pair.test._ordinal);
|
remaining.unshift(pair.test._id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (remaining.length)
|
if (remaining.length)
|
||||||
this._queue.unshift({ ...entry, ordinals: remaining });
|
this._queue.unshift({ ...entry, ids: remaining });
|
||||||
|
|
||||||
// This job is over, we just scheduled another one.
|
// This job is over, we just scheduled another one.
|
||||||
doneCallback();
|
doneCallback();
|
||||||
|
@ -27,7 +27,7 @@ export class Test {
|
|||||||
fn: Function;
|
fn: Function;
|
||||||
results: TestResult[] = [];
|
results: TestResult[] = [];
|
||||||
|
|
||||||
_ordinal: number;
|
_id: string;
|
||||||
_overriddenFn: Function;
|
_overriddenFn: Function;
|
||||||
_startTime: number;
|
_startTime: number;
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ export class Suite {
|
|||||||
let ordinal = 0;
|
let ordinal = 0;
|
||||||
this.findTest((test: Test) => {
|
this.findTest((test: Test) => {
|
||||||
// All tests are identified with their ordinals.
|
// 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);
|
const revertBabelRequire = spec(suite, file, this._config.timeout);
|
||||||
require(file);
|
require(file);
|
||||||
revertBabelRequire();
|
revertBabelRequire();
|
||||||
suite._renumber();
|
|
||||||
|
|
||||||
const workerGeneratorConfigurations = new Map();
|
const workerGeneratorConfigurations = new Map();
|
||||||
|
|
||||||
@ -104,6 +103,7 @@ export class TestCollector {
|
|||||||
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '') + ' ' + (i ? ` #repeat-${i}#` : '');
|
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '') + ' ' + (i ? ` #repeat-${i}#` : '');
|
||||||
clone.configuration = configuration;
|
clone.configuration = configuration;
|
||||||
clone._configurationString = configurationString + `#repeat-${i}#`;
|
clone._configurationString = configurationString + `#repeat-${i}#`;
|
||||||
|
clone._renumber();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +122,6 @@ export class TestCollector {
|
|||||||
continue;
|
continue;
|
||||||
const testCopy = test._clone();
|
const testCopy = test._clone();
|
||||||
testCopy.only = test.only;
|
testCopy.only = test.only;
|
||||||
testCopy._ordinal = test._ordinal;
|
|
||||||
copy._addTest(testCopy);
|
copy._addTest(testCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export const fixturePool = new FixturePool();
|
|||||||
|
|
||||||
export type TestRunnerEntry = {
|
export type TestRunnerEntry = {
|
||||||
file: string;
|
file: string;
|
||||||
ordinals: number[];
|
ids: string[];
|
||||||
configurationString: string;
|
configurationString: string;
|
||||||
configuration: Configuration;
|
configuration: Configuration;
|
||||||
hash: string;
|
hash: string;
|
||||||
@ -43,9 +43,8 @@ function chunkToParams(chunk: Buffer | string): { text?: string, buffer?: strin
|
|||||||
export class TestRunner extends EventEmitter {
|
export class TestRunner extends EventEmitter {
|
||||||
private _failedTestId: string | undefined;
|
private _failedTestId: string | undefined;
|
||||||
private _fatalError: any | undefined;
|
private _fatalError: any | undefined;
|
||||||
private _file: any;
|
private _ids: Set<string>;
|
||||||
private _ordinals: Set<number>;
|
private _remaining: Set<string>;
|
||||||
private _remaining: Set<number>;
|
|
||||||
private _trialRun: any;
|
private _trialRun: any;
|
||||||
private _configuredFile: any;
|
private _configuredFile: any;
|
||||||
private _parsedGeneratorConfiguration: any = {};
|
private _parsedGeneratorConfiguration: any = {};
|
||||||
@ -55,12 +54,15 @@ export class TestRunner extends EventEmitter {
|
|||||||
private _stdOutBuffer: (string | Buffer)[] = [];
|
private _stdOutBuffer: (string | Buffer)[] = [];
|
||||||
private _stdErrBuffer: (string | Buffer)[] = [];
|
private _stdErrBuffer: (string | Buffer)[] = [];
|
||||||
private _testResult: TestResult | null = null;
|
private _testResult: TestResult | null = null;
|
||||||
|
private _suite: Suite;
|
||||||
|
|
||||||
constructor(entry: TestRunnerEntry, config: RunnerConfig, workerId: number) {
|
constructor(entry: TestRunnerEntry, config: RunnerConfig, workerId: number) {
|
||||||
super();
|
super();
|
||||||
this._file = entry.file;
|
this._suite = new Suite('');
|
||||||
this._ordinals = new Set(entry.ordinals);
|
this._suite.file = entry.file;
|
||||||
this._remaining = new Set(entry.ordinals);
|
this._suite._configurationString = entry.configurationString;
|
||||||
|
this._ids = new Set(entry.ids);
|
||||||
|
this._remaining = new Set(entry.ids);
|
||||||
this._trialRun = config.trialRun;
|
this._trialRun = config.trialRun;
|
||||||
this._timeout = config.timeout;
|
this._timeout = config.timeout;
|
||||||
this._config = config;
|
this._config = config;
|
||||||
@ -68,7 +70,7 @@ export class TestRunner extends EventEmitter {
|
|||||||
for (const {name, value} of entry.configuration)
|
for (const {name, value} of entry.configuration)
|
||||||
this._parsedGeneratorConfiguration[name] = value;
|
this._parsedGeneratorConfiguration[name] = value;
|
||||||
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
||||||
setCurrentTestFile(this._file);
|
setCurrentTestFile(this._suite.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
@ -108,14 +110,13 @@ export class TestRunner extends EventEmitter {
|
|||||||
async run() {
|
async run() {
|
||||||
setParameters(this._parsedGeneratorConfiguration);
|
setParameters(this._parsedGeneratorConfiguration);
|
||||||
|
|
||||||
const suite = new Suite('');
|
const revertBabelRequire = spec(this._suite, this._suite.file, this._timeout);
|
||||||
const revertBabelRequire = spec(suite, this._file, this._timeout);
|
require(this._suite.file);
|
||||||
require(this._file);
|
|
||||||
revertBabelRequire();
|
revertBabelRequire();
|
||||||
suite._renumber();
|
this._suite._renumber();
|
||||||
|
|
||||||
rerunRegistrations(this._file, 'test');
|
rerunRegistrations(this._suite.file, 'test');
|
||||||
await this._runSuite(suite);
|
await this._runSuite(this._suite);
|
||||||
this._reportDone();
|
this._reportDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,11 +145,11 @@ export class TestRunner extends EventEmitter {
|
|||||||
private async _runTest(test: Test) {
|
private async _runTest(test: Test) {
|
||||||
if (this._failedTestId)
|
if (this._failedTestId)
|
||||||
return false;
|
return false;
|
||||||
if (this._ordinals.size && !this._ordinals.has(test._ordinal))
|
if (this._ids.size && !this._ids.has(test._id))
|
||||||
return;
|
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._testId = id;
|
||||||
this.emit('testBegin', { id });
|
this.emit('testBegin', { id });
|
||||||
|
|
||||||
|
@ -29,13 +29,13 @@ global.console = new Console({
|
|||||||
});
|
});
|
||||||
|
|
||||||
process.stdout.write = chunk => {
|
process.stdout.write = chunk => {
|
||||||
if (testRunner && !closed)
|
if (testRunner)
|
||||||
testRunner.stdout(chunk);
|
testRunner.stdout(chunk);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
process.stderr.write = chunk => {
|
process.stderr.write = chunk => {
|
||||||
if (testRunner && !closed)
|
if (testRunner)
|
||||||
testRunner.stderr(chunk);
|
testRunner.stderr(chunk);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -48,12 +48,12 @@ let workerId: number;
|
|||||||
let testRunner: TestRunner;
|
let testRunner: TestRunner;
|
||||||
|
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
if (testRunner && !closed)
|
if (testRunner)
|
||||||
testRunner.fatalError(reason);
|
testRunner.fatalError(reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('uncaughtException', error => {
|
process.on('uncaughtException', error => {
|
||||||
if (testRunner && !closed)
|
if (testRunner)
|
||||||
testRunner.fatalError(error);
|
testRunner.fatalError(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,8 +73,6 @@ process.on('message', async message => {
|
|||||||
testRunner.on(event, sendMessageToParent.bind(null, event));
|
testRunner.on(event, sendMessageToParent.bind(null, event));
|
||||||
await testRunner.run();
|
await testRunner.run();
|
||||||
testRunner = null;
|
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);
|
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 = {}) {
|
async function runTest(filePath: string, params: any = {}) {
|
||||||
const outputDir = path.join(__dirname, 'test-results');
|
const outputDir = path.join(__dirname, 'test-results');
|
||||||
const reportFile = path.join(outputDir, 'results.json');
|
const reportFile = path.join(outputDir, 'results.json');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user