mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
test: remove mocha dependency (#3576)
This commit is contained in:
parent
93d8839947
commit
b909924a61
@ -14,14 +14,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Mocha from 'mocha';
|
|
||||||
import { Test, Suite } from './test';
|
import { Test, Suite } from './test';
|
||||||
import { installTransform } from './transform';
|
import { installTransform } from './transform';
|
||||||
|
|
||||||
Error.stackTraceLimit = 15;
|
Error.stackTraceLimit = 15;
|
||||||
|
|
||||||
let revertBabelRequire: () => void;
|
|
||||||
|
|
||||||
function specBuilder(modifiers, specCallback) {
|
function specBuilder(modifiers, specCallback) {
|
||||||
function builder(specs, last) {
|
function builder(specs, last) {
|
||||||
const callable = (...args) => {
|
const callable = (...args) => {
|
||||||
@ -52,65 +49,54 @@ function specBuilder(modifiers, specCallback) {
|
|||||||
return builder({}, null);
|
return builder({}, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fixturesUI(options, mochaSuite: any) {
|
export function fixturesUI(suite: Suite, file: string, timeout: number): () => void {
|
||||||
const suites = [mochaSuite.__nomocha as Suite];
|
const suites = [suite];
|
||||||
|
|
||||||
mochaSuite.on(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, function(context, file) {
|
const it = specBuilder(['skip', 'fail', 'slow', 'only'], (specs, title, fn) => {
|
||||||
const it = specBuilder(['skip', 'fail', 'slow', 'only'], (specs, title, fn) => {
|
const suite = suites[0];
|
||||||
const suite = suites[0];
|
const test = new Test(title, fn);
|
||||||
const test = new Test(title, fn);
|
test.file = file;
|
||||||
test.file = file;
|
test.slow = specs.slow && specs.slow[0];
|
||||||
test.slow = specs.slow && specs.slow[0];
|
test.timeout = timeout;
|
||||||
test.timeout = options.timeout;
|
|
||||||
|
|
||||||
const only = specs.only && specs.only[0];
|
const only = specs.only && specs.only[0];
|
||||||
if (only)
|
if (only)
|
||||||
test.only = true;
|
test.only = true;
|
||||||
if (!only && specs.skip && specs.skip[0])
|
if (!only && specs.skip && specs.skip[0])
|
||||||
test.pending = true;
|
test.pending = true;
|
||||||
if (!only && specs.fail && specs.fail[0])
|
if (!only && specs.fail && specs.fail[0])
|
||||||
test.pending = true;
|
test.pending = true;
|
||||||
|
test.pending = test.pending || suite.isPending();
|
||||||
test.pending = test.pending || suite.isPending();
|
suite.addTest(test);
|
||||||
if (test.pending)
|
return test;
|
||||||
fn = null;
|
|
||||||
const wrapper = fn ? options.testWrapper(test, fn) : undefined;
|
|
||||||
if (wrapper)
|
|
||||||
wrapper.toString = () => fn.toString();
|
|
||||||
test._materialize(wrapper);
|
|
||||||
suite.addTest(test);
|
|
||||||
return test;
|
|
||||||
});
|
|
||||||
|
|
||||||
const describe = specBuilder(['skip', 'fail', 'only'], (specs, title, fn) => {
|
|
||||||
const child = new Suite(title, suites[0]);
|
|
||||||
suites[0].addSuite(child);
|
|
||||||
child.file = file;
|
|
||||||
const only = specs.only && specs.only[0];
|
|
||||||
if (only)
|
|
||||||
child.only = true;
|
|
||||||
if (!only && specs.skip && specs.skip[0])
|
|
||||||
child.pending = true;
|
|
||||||
if (!only && specs.fail && specs.fail[0])
|
|
||||||
child.pending = true;
|
|
||||||
suites.unshift(child);
|
|
||||||
fn();
|
|
||||||
suites.shift();
|
|
||||||
});
|
|
||||||
|
|
||||||
context.beforeEach = fn => options.hookWrapper(mochaSuite.beforeEach.bind(mochaSuite), fn);
|
|
||||||
context.afterEach = fn => options.hookWrapper(mochaSuite.afterEach.bind(mochaSuite), fn);
|
|
||||||
context.describe = describe;
|
|
||||||
(context as any).fdescribe = describe.only(true);
|
|
||||||
context.xdescribe = describe.skip(true);
|
|
||||||
context.it = it;
|
|
||||||
(context as any).fit = it.only(true);
|
|
||||||
context.xit = it.skip(true);
|
|
||||||
|
|
||||||
revertBabelRequire = installTransform();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mochaSuite.on(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, function(context, file, mocha) {
|
const describe = specBuilder(['skip', 'fail', 'only'], (specs, title, fn) => {
|
||||||
revertBabelRequire();
|
const child = new Suite(title, suites[0]);
|
||||||
|
suites[0].addSuite(child);
|
||||||
|
child.file = file;
|
||||||
|
const only = specs.only && specs.only[0];
|
||||||
|
if (only)
|
||||||
|
child.only = true;
|
||||||
|
if (!only && specs.skip && specs.skip[0])
|
||||||
|
child.pending = true;
|
||||||
|
if (!only && specs.fail && specs.fail[0])
|
||||||
|
child.pending = true;
|
||||||
|
suites.unshift(child);
|
||||||
|
fn();
|
||||||
|
suites.shift();
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
(global as any).beforeEach = fn => suite._addHook('beforeEach', fn);
|
||||||
|
(global as any).afterEach = fn => suite._addHook('afterEach', fn);
|
||||||
|
(global as any).beforeAll = fn => suite._addHook('beforeAll', fn);
|
||||||
|
(global as any).afterAll = fn => suite._addHook('afterAll', fn);
|
||||||
|
(global as any).describe = describe;
|
||||||
|
(global as any).fdescribe = describe.only(true);
|
||||||
|
(global as any).xdescribe = describe.skip(true);
|
||||||
|
(global as any).it = it;
|
||||||
|
(global as any).fit = it.only(true);
|
||||||
|
(global as any).xit = it.skip(true);
|
||||||
|
|
||||||
|
return installTransform();
|
||||||
|
}
|
||||||
|
@ -18,15 +18,10 @@ const child_process = require('child_process');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { EventEmitter } = require('events');
|
const { EventEmitter } = require('events');
|
||||||
const Mocha = require('mocha');
|
|
||||||
const builtinReporters = require('mocha/lib/reporters');
|
const builtinReporters = require('mocha/lib/reporters');
|
||||||
const DotRunner = require('./dotReporter');
|
const DotRunner = require('./dotReporter');
|
||||||
const { lookupRegistrations } = require('./fixtures');
|
const { lookupRegistrations } = require('./fixtures');
|
||||||
|
|
||||||
const constants = Mocha.Runner.constants;
|
|
||||||
// Mocha runner does not remove uncaughtException listeners.
|
|
||||||
process.setMaxListeners(0);
|
|
||||||
|
|
||||||
class Runner extends EventEmitter {
|
class Runner extends EventEmitter {
|
||||||
constructor(suite, total, options) {
|
constructor(suite, total, options) {
|
||||||
super();
|
super();
|
||||||
@ -81,12 +76,12 @@ class Runner extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
this.emit(constants.EVENT_RUN_BEGIN, {});
|
this.emit('start', {});
|
||||||
this._queue = this._filesSortedByWorkerHash();
|
this._queue = this._filesSortedByWorkerHash();
|
||||||
// Loop in case job schedules more jobs
|
// Loop in case job schedules more jobs
|
||||||
while (this._queue.length)
|
while (this._queue.length)
|
||||||
await this._dispatchQueue();
|
await this._dispatchQueue();
|
||||||
this.emit(constants.EVENT_RUN_END, {});
|
this.emit('end', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _dispatchQueue() {
|
async _dispatchQueue() {
|
||||||
@ -109,16 +104,11 @@ class Runner extends EventEmitter {
|
|||||||
let doneCallback;
|
let doneCallback;
|
||||||
const result = new Promise(f => doneCallback = f);
|
const result = new Promise(f => doneCallback = f);
|
||||||
worker.once('done', params => {
|
worker.once('done', params => {
|
||||||
this.stats.duration += params.stats.duration;
|
|
||||||
this.stats.failures += params.stats.failures;
|
|
||||||
this.stats.passes += params.stats.passes;
|
|
||||||
this.stats.pending += params.stats.pending;
|
|
||||||
this.stats.tests += params.stats.passes + params.stats.pending + params.stats.failures;
|
|
||||||
// When worker encounters error, we will restart it.
|
// When worker encounters error, we will restart it.
|
||||||
if (params.error) {
|
if (params.error || params.fatalError) {
|
||||||
this._restartWorker(worker);
|
this._restartWorker(worker);
|
||||||
// If there are remaining tests, we will queue them.
|
// If there are remaining tests, we will queue them.
|
||||||
if (params.remaining.length)
|
if (params.remaining.length && !params.fatalError)
|
||||||
this._queue.unshift({ ...entry, ordinals: params.remaining });
|
this._queue.unshift({ ...entry, ordinals: params.remaining });
|
||||||
} else {
|
} else {
|
||||||
this._workerAvailable(worker);
|
this._workerAvailable(worker);
|
||||||
@ -150,17 +140,28 @@ class Runner extends EventEmitter {
|
|||||||
|
|
||||||
_createWorker() {
|
_createWorker() {
|
||||||
const worker = this._options.debug ? new InProcessWorker(this) : new OopWorker(this);
|
const worker = this._options.debug ? new InProcessWorker(this) : new OopWorker(this);
|
||||||
worker.on('test', params => this.emit(constants.EVENT_TEST_BEGIN, this._updateTest(params.test)));
|
worker.on('test', params => {
|
||||||
worker.on('pending', params => this.emit(constants.EVENT_TEST_PENDING, this._updateTest(params.test)));
|
++this.stats.tests;
|
||||||
worker.on('pass', params => this.emit(constants.EVENT_TEST_PASS, this._updateTest(params.test)));
|
this.emit('test', this._updateTest(params.test));
|
||||||
|
});
|
||||||
|
worker.on('pending', params => {
|
||||||
|
++this.stats.tests;
|
||||||
|
++this.stats.pending;
|
||||||
|
this.emit('pending', this._updateTest(params.test));
|
||||||
|
});
|
||||||
|
worker.on('pass', params => {
|
||||||
|
++this.stats.passes;
|
||||||
|
this.emit('pass', this._updateTest(params.test));
|
||||||
|
});
|
||||||
worker.on('fail', params => {
|
worker.on('fail', params => {
|
||||||
|
++this.stats.failures;
|
||||||
const out = worker.takeOut();
|
const out = worker.takeOut();
|
||||||
if (out.length)
|
if (out.length)
|
||||||
params.error.stack += '\n\x1b[33mstdout: ' + out.join('\n') + '\x1b[0m';
|
params.error.stack += '\n\x1b[33mstdout: ' + out.join('\n') + '\x1b[0m';
|
||||||
const err = worker.takeErr();
|
const err = worker.takeErr();
|
||||||
if (err.length)
|
if (err.length)
|
||||||
params.error.stack += '\n\x1b[33mstderr: ' + err.join('\n') + '\x1b[0m';
|
params.error.stack += '\n\x1b[33mstderr: ' + err.join('\n') + '\x1b[0m';
|
||||||
this.emit(constants.EVENT_TEST_FAIL, this._updateTest(params.test), params.error);
|
this.emit('fail', this._updateTest(params.test), params.error);
|
||||||
});
|
});
|
||||||
worker.on('exit', () => {
|
worker.on('exit', () => {
|
||||||
this._workers.delete(worker);
|
this._workers.delete(worker);
|
||||||
|
@ -14,10 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Mocha from 'mocha';
|
|
||||||
import { fixturesUI } from './fixturesUI';
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
|
|
||||||
export type Configuration = { name: string, value: string }[];
|
export type Configuration = { name: string, value: string }[];
|
||||||
|
|
||||||
export class Test {
|
export class Test {
|
||||||
@ -34,18 +30,13 @@ export class Test {
|
|||||||
_configurationObject: Configuration;
|
_configurationObject: Configuration;
|
||||||
_configurationString: string;
|
_configurationString: string;
|
||||||
_overriddenFn: Function;
|
_overriddenFn: Function;
|
||||||
_impl: any;
|
_startTime: number;
|
||||||
|
|
||||||
constructor(title: string, fn: Function) {
|
constructor(title: string, fn: Function) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.fn = fn;
|
this.fn = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
_materialize(overriddenFn: Function) {
|
|
||||||
this._impl = new Mocha.Test(this.title, overriddenFn);
|
|
||||||
this._impl.pending = this.pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
clone(): Test {
|
clone(): Test {
|
||||||
const test = new Test(this.title, this.fn);
|
const test = new Test(this.title, this.fn);
|
||||||
test.suite = this.suite;
|
test.suite = this.suite;
|
||||||
@ -54,7 +45,6 @@ export class Test {
|
|||||||
test.pending = this.pending;
|
test.pending = this.pending;
|
||||||
test.timeout = this.timeout;
|
test.timeout = this.timeout;
|
||||||
test._overriddenFn = this._overriddenFn;
|
test._overriddenFn = this._overriddenFn;
|
||||||
test._materialize(this._overriddenFn);
|
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +70,12 @@ export class Suite {
|
|||||||
pending = false;
|
pending = false;
|
||||||
file: string;
|
file: string;
|
||||||
|
|
||||||
_impl: any;
|
_hooks: { type: string, fn: Function } [] = [];
|
||||||
|
_entries: (Suite | Test)[] = [];
|
||||||
|
|
||||||
constructor(title: string, parent?: Suite) {
|
constructor(title: string, parent?: Suite) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this._impl = new Mocha.Suite(title, new Mocha.Context());
|
|
||||||
this._impl.__nomocha = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
titlePath(): string[] {
|
titlePath(): string[] {
|
||||||
@ -97,7 +86,9 @@ export class Suite {
|
|||||||
|
|
||||||
total(): number {
|
total(): number {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
this.eachTest(fn => ++count);
|
this.eachTest(fn => {
|
||||||
|
++count;
|
||||||
|
});
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,20 +99,25 @@ export class Suite {
|
|||||||
addTest(test: Test) {
|
addTest(test: Test) {
|
||||||
test.suite = this;
|
test.suite = this;
|
||||||
this.tests.push(test);
|
this.tests.push(test);
|
||||||
this._impl.addTest(test._impl);
|
this._entries.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
addSuite(suite: Suite) {
|
addSuite(suite: Suite) {
|
||||||
suite.parent = this;
|
suite.parent = this;
|
||||||
this.suites.push(suite);
|
this.suites.push(suite);
|
||||||
this._impl.addSuite(suite._impl);
|
this._entries.push(suite);
|
||||||
}
|
}
|
||||||
|
|
||||||
eachTest(fn: (test: Test) => void) {
|
eachTest(fn: (test: Test) => boolean | void): boolean {
|
||||||
for (const suite of this.suites)
|
for (const suite of this.suites) {
|
||||||
suite.eachTest(fn);
|
if (suite.eachTest(fn))
|
||||||
for (const test of this.tests)
|
return true;
|
||||||
fn(test);
|
}
|
||||||
|
for (const test of this.tests) {
|
||||||
|
if (fn(test))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
clone(): Suite {
|
clone(): Suite {
|
||||||
@ -129,84 +125,29 @@ export class Suite {
|
|||||||
suite.only = this.only;
|
suite.only = this.only;
|
||||||
suite.file = this.file;
|
suite.file = this.file;
|
||||||
suite.pending = this.pending;
|
suite.pending = this.pending;
|
||||||
suite._impl = this._impl.clone();
|
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class NullReporter {
|
_renumber() {
|
||||||
stats = {
|
let ordinal = 0;
|
||||||
suites: 0,
|
this.eachTest((test: Test) => {
|
||||||
tests: 0,
|
// All tests are identified with their ordinals.
|
||||||
passes: 0,
|
test._ordinal = ordinal++;
|
||||||
pending: 0,
|
|
||||||
failures: 0
|
|
||||||
};
|
|
||||||
runner = null;
|
|
||||||
failures = [];
|
|
||||||
epilogue: () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoMockaOptions = {
|
|
||||||
forbidOnly?: boolean;
|
|
||||||
timeout: number;
|
|
||||||
testWrapper: (test: Test, fn: Function) => Function;
|
|
||||||
hookWrapper: (hook: any, fn: Function) => Function;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PatchedMocha extends Mocha {
|
|
||||||
suite: any;
|
|
||||||
static pendingSuite: Suite;
|
|
||||||
|
|
||||||
constructor(suite, options) {
|
|
||||||
PatchedMocha.pendingSuite = suite;
|
|
||||||
super(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
grep(...args) {
|
|
||||||
this.suite = new Mocha.Suite('', new Mocha.Context());
|
|
||||||
this.suite.__nomocha = PatchedMocha.pendingSuite;
|
|
||||||
PatchedMocha.pendingSuite._impl = this.suite;
|
|
||||||
return super.grep(...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Runner extends EventEmitter {
|
|
||||||
private _mochaRunner: any;
|
|
||||||
|
|
||||||
constructor(mochaRunner: any) {
|
|
||||||
super();
|
|
||||||
const constants = Mocha.Runner.constants;
|
|
||||||
this._mochaRunner = mochaRunner;
|
|
||||||
this._mochaRunner.on(constants.EVENT_TEST_BEGIN, test => this.emit('test', test));
|
|
||||||
this._mochaRunner.on(constants.EVENT_TEST_PENDING, test => this.emit('pending', test));
|
|
||||||
this._mochaRunner.on(constants.EVENT_TEST_PASS, test => this.emit('pass', test));
|
|
||||||
this._mochaRunner.on(constants.EVENT_TEST_FAIL, (test, err) => this.emit('fail', test, err));
|
|
||||||
this._mochaRunner.on(constants.EVENT_RUN_END, () => this.emit('done'));
|
|
||||||
}
|
|
||||||
|
|
||||||
duration(): number {
|
|
||||||
return this._mochaRunner.stats.duration || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NoMocha {
|
|
||||||
suite: Suite;
|
|
||||||
private _mocha: Mocha;
|
|
||||||
|
|
||||||
constructor(file: string, options: NoMockaOptions) {
|
|
||||||
this.suite = new Suite('');
|
|
||||||
this._mocha = new PatchedMocha(this.suite, {
|
|
||||||
forbidOnly: options.forbidOnly,
|
|
||||||
reporter: NullReporter,
|
|
||||||
timeout: options.timeout,
|
|
||||||
ui: fixturesUI.bind(null, options)
|
|
||||||
});
|
});
|
||||||
this._mocha.addFile(file);
|
|
||||||
(this._mocha as any).loadFiles();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run(cb: () => void): Runner {
|
_addHook(type: string, fn: any) {
|
||||||
return new Runner(this._mocha.run(cb));
|
this._hooks.push({ type, fn });
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasTestsToRun(): boolean {
|
||||||
|
let found = false;
|
||||||
|
this.eachTest(test => {
|
||||||
|
if (!test.pending) {
|
||||||
|
found = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Mocha from 'mocha';
|
|
||||||
import { fixturesForCallback, registerWorkerFixture } from './fixtures';
|
import { fixturesForCallback, registerWorkerFixture } from './fixtures';
|
||||||
import { Configuration, NoMocha, Test, Suite } from './test';
|
import { Configuration, Test, Suite } from './test';
|
||||||
|
import { fixturesUI } from './fixturesUI';
|
||||||
|
|
||||||
export class TestCollector {
|
export class TestCollector {
|
||||||
suite: Suite;
|
suite: Suite;
|
||||||
@ -50,20 +50,15 @@ export class TestCollector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_addFile(file) {
|
_addFile(file) {
|
||||||
const noMocha = new NoMocha(file, {
|
const suite = new Suite('');
|
||||||
forbidOnly: this._options.forbidOnly,
|
const revertBabelRequire = fixturesUI(suite, file, this._options.timeout);
|
||||||
timeout: this._options.timeout,
|
require(file);
|
||||||
testWrapper: (test, fn) => () => {},
|
revertBabelRequire();
|
||||||
hookWrapper: (hook, fn) => () => {},
|
suite._renumber();
|
||||||
});
|
|
||||||
|
|
||||||
const workerGeneratorConfigurations = new Map();
|
const workerGeneratorConfigurations = new Map();
|
||||||
|
|
||||||
let ordinal = 0;
|
suite.eachTest((test: Test) => {
|
||||||
noMocha.suite.eachTest((test: Test) => {
|
|
||||||
// All tests are identified with their ordinals.
|
|
||||||
test._ordinal = ordinal++;
|
|
||||||
|
|
||||||
// Get all the fixtures that the test needs.
|
// Get all the fixtures that the test needs.
|
||||||
const fixtures = fixturesForCallback(test.fn);
|
const fixtures = fixturesForCallback(test.fn);
|
||||||
|
|
||||||
@ -102,7 +97,7 @@ export class TestCollector {
|
|||||||
// Clone the suite as many times as there are worker hashes.
|
// Clone the suite as many times as there are worker hashes.
|
||||||
// Only include the tests that requested these generations.
|
// Only include the tests that requested these generations.
|
||||||
for (const [hash, {configurationObject, configurationString, tests}] of workerGeneratorConfigurations.entries()) {
|
for (const [hash, {configurationObject, configurationString, tests}] of workerGeneratorConfigurations.entries()) {
|
||||||
const clone = this._cloneSuite(noMocha.suite, configurationObject, configurationString, tests);
|
const clone = this._cloneSuite(suite, configurationObject, configurationString, tests);
|
||||||
this.suite.addSuite(clone);
|
this.suite.addSuite(clone);
|
||||||
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '');
|
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '');
|
||||||
}
|
}
|
||||||
@ -111,19 +106,22 @@ export class TestCollector {
|
|||||||
_cloneSuite(suite: Suite, configurationObject: Configuration, configurationString: string, tests: Set<Test>) {
|
_cloneSuite(suite: Suite, configurationObject: Configuration, configurationString: string, tests: Set<Test>) {
|
||||||
const copy = suite.clone();
|
const copy = suite.clone();
|
||||||
copy.only = suite.only;
|
copy.only = suite.only;
|
||||||
for (const child of suite.suites)
|
for (const entry of suite._entries) {
|
||||||
copy.addSuite(this._cloneSuite(child, configurationObject, configurationString, tests));
|
if (entry instanceof Suite) {
|
||||||
for (const test of suite.tests) {
|
copy.addSuite(this._cloneSuite(entry, configurationObject, configurationString, tests));
|
||||||
if (!tests.has(test))
|
} else {
|
||||||
continue;
|
const test = entry;
|
||||||
if (this._grep && !this._grep.test(test.fullTitle()))
|
if (!tests.has(test))
|
||||||
continue;
|
continue;
|
||||||
const testCopy = test.clone();
|
if (this._grep && !this._grep.test(test.fullTitle()))
|
||||||
testCopy.only = test.only;
|
continue;
|
||||||
testCopy._ordinal = test._ordinal;
|
const testCopy = test.clone();
|
||||||
testCopy._configurationObject = configurationObject;
|
testCopy.only = test.only;
|
||||||
testCopy._configurationString = configurationString;
|
testCopy._ordinal = test._ordinal;
|
||||||
copy.addTest(testCopy);
|
testCopy._configurationObject = configurationObject;
|
||||||
|
testCopy._configurationString = configurationString;
|
||||||
|
copy.addTest(testCopy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ import path from 'path';
|
|||||||
import { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } from './fixtures';
|
import { FixturePool, registerWorkerFixture, rerunRegistrations, setParameters } from './fixtures';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { setCurrentTestFile } from './expect';
|
import { setCurrentTestFile } from './expect';
|
||||||
import { NoMocha, Runner, Test } from './test';
|
import { Test, Suite } from './test';
|
||||||
|
import { fixturesUI } from './fixturesUI';
|
||||||
|
|
||||||
export const fixturePool = new FixturePool();
|
export const fixturePool = new FixturePool();
|
||||||
|
|
||||||
@ -31,19 +32,15 @@ export type TestRunnerEntry = {
|
|||||||
|
|
||||||
export class TestRunner extends EventEmitter {
|
export class TestRunner extends EventEmitter {
|
||||||
private _currentOrdinal = -1;
|
private _currentOrdinal = -1;
|
||||||
private _failedWithError = false;
|
private _failedWithError: Error | undefined;
|
||||||
|
private _fatalError: Error | undefined;
|
||||||
private _file: any;
|
private _file: any;
|
||||||
private _ordinals: Set<number>;
|
private _ordinals: Set<number>;
|
||||||
private _remaining: Set<number>;
|
private _remaining: Set<number>;
|
||||||
private _trialRun: any;
|
private _trialRun: any;
|
||||||
private _passes = 0;
|
|
||||||
private _failures = 0;
|
|
||||||
private _pending = 0;
|
|
||||||
private _configuredFile: any;
|
private _configuredFile: any;
|
||||||
private _configurationObject: any;
|
private _configurationObject: any;
|
||||||
private _parsedGeneratorConfiguration: any = {};
|
private _parsedGeneratorConfiguration: any = {};
|
||||||
private _relativeTestFile: string;
|
|
||||||
private _runner: Runner;
|
|
||||||
private _outDir: string;
|
private _outDir: string;
|
||||||
private _timeout: number;
|
private _timeout: number;
|
||||||
private _testDir: string;
|
private _testDir: string;
|
||||||
@ -65,134 +62,114 @@ export class TestRunner extends EventEmitter {
|
|||||||
registerWorkerFixture(name, async ({}, test) => await test(value));
|
registerWorkerFixture(name, async ({}, test) => await test(value));
|
||||||
}
|
}
|
||||||
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
||||||
this._relativeTestFile = path.relative(options.testDir, this._file);
|
setCurrentTestFile(path.relative(options.testDir, this._file));
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop() {
|
stop() {
|
||||||
this._trialRun = true;
|
this._trialRun = true;
|
||||||
return new Promise(f => this._runner.once('done', f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
let callback;
|
|
||||||
const result = new Promise(f => callback = f);
|
|
||||||
setParameters(this._parsedGeneratorConfiguration);
|
setParameters(this._parsedGeneratorConfiguration);
|
||||||
|
|
||||||
const noMocha = new NoMocha(this._file, {
|
const suite = new Suite('');
|
||||||
timeout: 0,
|
const revertBabelRequire = fixturesUI(suite, this._file, this._timeout);
|
||||||
testWrapper: (test, fn) => this._testWrapper(test, fn),
|
require(this._file);
|
||||||
hookWrapper: (hook, fn) => this._hookWrapper(hook, fn),
|
revertBabelRequire();
|
||||||
});
|
suite._renumber();
|
||||||
|
|
||||||
rerunRegistrations(this._file, 'test');
|
rerunRegistrations(this._file, 'test');
|
||||||
this._runner = noMocha.run(callback);
|
await this._runSuite(suite);
|
||||||
|
this._reportDone();
|
||||||
|
}
|
||||||
|
|
||||||
this._runner.on('test', test => {
|
private async _runSuite(suite: Suite) {
|
||||||
setCurrentTestFile(this._relativeTestFile);
|
try {
|
||||||
if (this._failedWithError)
|
await this._runHooks(suite, 'beforeAll', 'before');
|
||||||
return;
|
} catch (e) {
|
||||||
const ordinal = ++this._currentOrdinal;
|
this._fatalError = e;
|
||||||
if (this._ordinals.size && !this._ordinals.has(ordinal))
|
this._reportDone();
|
||||||
return;
|
}
|
||||||
this._remaining.delete(ordinal);
|
for (const entry of suite._entries) {
|
||||||
this.emit('test', { test: this._serializeTest(test, ordinal) });
|
if (entry instanceof Suite) {
|
||||||
});
|
await this._runSuite(entry);
|
||||||
|
} else {
|
||||||
|
await this._runTest(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._runHooks(suite, 'afterAll', 'after');
|
||||||
|
} catch (e) {
|
||||||
|
this._fatalError = e;
|
||||||
|
this._reportDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._runner.on('pending', test => {
|
private async _runTest(test: Test) {
|
||||||
if (this._failedWithError)
|
if (this._failedWithError)
|
||||||
return;
|
return false;
|
||||||
const ordinal = ++this._currentOrdinal;
|
const ordinal = ++this._currentOrdinal;
|
||||||
if (this._ordinals.size && !this._ordinals.has(ordinal))
|
if (this._ordinals.size && !this._ordinals.has(ordinal))
|
||||||
return;
|
return;
|
||||||
this._remaining.delete(ordinal);
|
this._remaining.delete(ordinal);
|
||||||
++this._pending;
|
if (test.pending) {
|
||||||
this.emit('pending', { test: this._serializeTest(test, ordinal) });
|
this.emit('pending', { test: this._serializeTest(test) });
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._runner.on('pass', test => {
|
this.emit('test', { test: this._serializeTest(test) });
|
||||||
if (this._failedWithError)
|
try {
|
||||||
return;
|
await this._runHooks(test.suite, 'beforeEach', 'before');
|
||||||
|
test._startTime = Date.now();
|
||||||
const ordinal = this._currentOrdinal;
|
if (!this._trialRun)
|
||||||
if (this._ordinals.size && !this._ordinals.has(ordinal))
|
await this._testWrapper(test)();
|
||||||
return;
|
this.emit('pass', { test: this._serializeTest(test) });
|
||||||
++this._passes;
|
await this._runHooks(test.suite, 'afterEach', 'after');
|
||||||
this.emit('pass', { test: this._serializeTest(test, ordinal) });
|
} catch (error) {
|
||||||
});
|
|
||||||
|
|
||||||
this._runner.on('fail', (test, error) => {
|
|
||||||
if (this._failedWithError)
|
|
||||||
return;
|
|
||||||
++this._failures;
|
|
||||||
this._failedWithError = error;
|
this._failedWithError = error;
|
||||||
this.emit('fail', {
|
this.emit('fail', {
|
||||||
test: this._serializeTest(test, this._currentOrdinal),
|
test: this._serializeTest(test),
|
||||||
error: serializeError(error),
|
error: serializeError(error),
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
this._runner.once('done', async () => {
|
|
||||||
this.emit('done', {
|
|
||||||
stats: this._serializeStats(),
|
|
||||||
error: this._failedWithError,
|
|
||||||
remaining: [...this._remaining],
|
|
||||||
total: this._passes + this._failures + this._pending
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_shouldRunTest(hook = false) {
|
|
||||||
if (this._trialRun || this._failedWithError)
|
|
||||||
return false;
|
|
||||||
if (hook) {
|
|
||||||
// Hook starts before we bump the test ordinal.
|
|
||||||
if (!this._ordinals.has(this._currentOrdinal + 1))
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if (!this._ordinals.has(this._currentOrdinal))
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_testWrapper(test: Test, fn: Function) {
|
private async _runHooks(suite: Suite, type: string, dir: 'before' | 'after') {
|
||||||
|
if (!suite._hasTestsToRun())
|
||||||
|
return;
|
||||||
|
const all = [];
|
||||||
|
for (let s = suite; s; s = s.parent) {
|
||||||
|
const funcs = s._hooks.filter(e => e.type === type).map(e => e.fn);
|
||||||
|
all.push(...funcs.reverse());
|
||||||
|
}
|
||||||
|
if (dir === 'before')
|
||||||
|
all.reverse();
|
||||||
|
for (const hook of all)
|
||||||
|
await fixturePool.resolveParametersAndRun(hook, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _reportDone() {
|
||||||
|
this.emit('done', {
|
||||||
|
error: this._failedWithError,
|
||||||
|
fatalError: this._fatalError,
|
||||||
|
remaining: [...this._remaining],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _testWrapper(test: Test) {
|
||||||
const timeout = test.slow ? this._timeout * 3 : this._timeout;
|
const timeout = test.slow ? this._timeout * 3 : this._timeout;
|
||||||
const wrapped = fixturePool.wrapTestCallback(fn, timeout, test, {
|
return fixturePool.wrapTestCallback(test.fn, timeout, test, {
|
||||||
outputDir: this._outDir,
|
outputDir: this._outDir,
|
||||||
testDir: this._testDir,
|
testDir: this._testDir,
|
||||||
});
|
});
|
||||||
return wrapped ? (done, ...args) => {
|
|
||||||
if (!this._shouldRunTest()) {
|
|
||||||
done();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wrapped(...args).then(done).catch(done);
|
|
||||||
} : undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_hookWrapper(hook, fn) {
|
private _serializeTest(test) {
|
||||||
if (!this._shouldRunTest(true))
|
|
||||||
return;
|
|
||||||
return hook(async () => {
|
|
||||||
return await fixturePool.resolveParametersAndRun(fn, 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_serializeTest(test, ordinal) {
|
|
||||||
return {
|
return {
|
||||||
id: `${ordinal}@${this._configuredFile}`,
|
id: `${test._ordinal}@${this._configuredFile}`,
|
||||||
duration: test.duration,
|
duration: Date.now() - test._startTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_serializeStats() {
|
|
||||||
return {
|
|
||||||
passes: this._passes,
|
|
||||||
failures: this._failures,
|
|
||||||
pending: this._pending,
|
|
||||||
duration: this._runner.duration(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function trimCycles(obj) {
|
function trimCycles(obj) {
|
||||||
|
@ -15,14 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
import { options } from './playwright.fixtures';
|
import { options } from './playwright.fixtures';
|
||||||
|
|
||||||
it.skip(options.FIREFOX)('should work', async function({page, server}) {
|
it.skip(options.FIREFOX)('should work', async function({page}) {
|
||||||
await page.setContent(`<div id=d1 tabIndex=0></div>`);
|
await page.setContent(`<div id=d1 tabIndex=0></div>`);
|
||||||
expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
|
expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
|
||||||
await page.focus('#d1');
|
await page.focus('#d1');
|
||||||
expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
|
expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit focus event', async function({page, server}) {
|
it('should emit focus event', async function({page}) {
|
||||||
await page.setContent(`<div id=d1 tabIndex=0></div>`);
|
await page.setContent(`<div id=d1 tabIndex=0></div>`);
|
||||||
let focused = false;
|
let focused = false;
|
||||||
await page.exposeFunction('focusEvent', () => focused = true);
|
await page.exposeFunction('focusEvent', () => focused = true);
|
||||||
@ -31,7 +31,7 @@ it('should emit focus event', async function({page, server}) {
|
|||||||
expect(focused).toBe(true);
|
expect(focused).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit blur event', async function({page, server}) {
|
it('should emit blur event', async function({page}) {
|
||||||
await page.setContent(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
|
await page.setContent(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
|
||||||
await page.focus('#d1');
|
await page.focus('#d1');
|
||||||
let focused = false;
|
let focused = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user