playwright/utils/testrunner/test/testrunner.spec.js
Andrey Lushnikov 1605cb453c
testrunner: make environment a simple class (#2769)
This patch:
- makes environment a simple class with optional methods `beforeEach`, `afterEach`, `beforeAll`, `afterAll`, `globalSetup` and `globalTeardown`
- removes capability to have multiple hooks of the same name inside suite
- removes default environment for test. (`dit` now adds a `TraceTestEnvironment` to the test)
- extracts all environments that we use in our tests in `//test/environments.js`

Downsides:
- we no longer know hook locations for the environments. This, however, should not be a big deal since stack traces (if any) will still point into it.
- this also regresses hook locations for suites for simplicity. We can get them back, but it shouldn't be pressing since we now have only one hook of each kind in every suite.
2020-06-30 16:51:03 -07:00

1053 lines
34 KiB
JavaScript

const { TestRunner } = require('../TestRunner');
const { TestCollector, FocusedFilter, Repeater } = require('../TestCollector');
const { TestExpectation, Environment } = require('../Test');
class Runner {
constructor(options = {}) {
this._options = options;
this._filter = new FocusedFilter();
this._repeater = new Repeater();
this._collector = new TestCollector(options);
this._collector.addSuiteAttribute('only', s => this._filter.focusSuite(s));
this._collector.addTestAttribute('only', t => this._filter.focusTest(t));
this._collector.addSuiteAttribute('skip', s => s.setSkipped(true));
this._collector.addTestAttribute('skip', t => t.setSkipped(true));
this._collector.addTestAttribute('fail', t => t.setExpectation(t.Expectations.Fail));
this._collector.addSuiteModifier('repeat', (s, count) => this._repeater.repeat(s, count));
this._collector.addTestModifier('repeat', (t, count) => this._repeater.repeat(t, count));
const api = this._collector.api();
for (const [key, value] of Object.entries(api))
this[key] = value;
this.fdescribe = api.describe.only;
this.xdescribe = api.describe.skip;
this.fit = api.it.only;
this.xit = api.it.skip;
this.Expectations = { ...TestExpectation };
}
createTestRuns() {
return this._repeater.createTestRuns(this._filter.filter(this._collector.tests()));
}
run() {
this._testRunner = new TestRunner();
return this._testRunner.run(this.createTestRuns(), this._options);
}
tests() {
return this._collector.tests();
}
parallel() {
return this._options.parallel || 1;
}
focusedTests() {
return this._filter.focusedTests(this._collector.tests());
}
suites() {
return this._collector.suites();
}
focusedSuites() {
return this._filter.focusedSuites(this._collector.suites());
}
terminate() {
this._testRunner.terminate();
}
}
module.exports.addTests = function({describe, fdescribe, xdescribe, it, xit, fit, expect}) {
describe('TestRunner.it', () => {
it('should declare a test', async() => {
const t = new Runner();
t.it('uno', () => {});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('uno');
expect(test.skipped()).toBe(false);
expect(test.location().filePath()).toEqual(__filename);
expect(test.location().fileName()).toEqual('testrunner.spec.js');
expect(test.location().lineNumber()).toBeTruthy();
expect(test.location().columnNumber()).toBeTruthy();
});
it('should run a test', async() => {
const t = new Runner();
t.it('uno', () => {});
const result = await t.run();
expect(result.runs.length).toBe(1);
expect(result.runs[0].test()).toBe(t.tests()[0]);
expect(result.runs[0].result()).toBe('ok');
});
});
describe('TestRunner.xit', () => {
it('should declare a skipped test', async() => {
const t = new Runner();
t.xit('uno', () => {});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('uno');
expect(test.skipped()).toBe(true);
});
it('should not run a skipped test', async() => {
const t = new Runner();
t.xit('uno', () => {});
const result = await t.run();
expect(result.runs.length).toBe(1);
expect(result.runs[0].test()).toBe(t.tests()[0]);
expect(result.runs[0].result()).toBe('skipped');
});
});
describe('TestRunner.fit', () => {
it('should declare a focused test', async() => {
const t = new Runner();
t.fit('uno', () => {});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('uno');
expect(test.skipped()).toBe(false);
expect(t.focusedTests()[0]).toBe(test);
});
it('should run a focused test', async() => {
const t = new Runner();
t.fit('uno', () => {});
const result = await t.run();
expect(result.runs.length).toBe(1);
expect(result.runs[0].test()).toBe(t.tests()[0]);
expect(result.runs[0].result()).toBe('ok');
});
it('should run a failed focused test', async() => {
const t = new Runner();
let run = false;
t.it.only.fail('uno', () => {
run = true; throw new Error('failure');
});
expect(t.focusedTests().length).toBe(1);
expect(t.tests()[0].expectation()).toBe(t.Expectations.Fail);
const result = await t.run();
expect(run).toBe(true);
expect(result.runs.length).toBe(1);
expect(result.runs[0].test()).toBe(t.tests()[0]);
expect(result.runs[0].result()).toBe('failed');
});
});
describe('TestRunner.describe', () => {
it('should declare a suite', async() => {
const t = new Runner();
t.describe('suite', () => {
t.it('uno', () => {});
});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.name()).toBe('uno');
expect(test.fullName()).toBe('suite uno');
expect(test.skipped()).toBe(false);
expect(test.suite().name()).toBe('suite');
expect(test.suite().fullName()).toBe('suite');
expect(test.suite().skipped()).toBe(false);
});
});
describe('TestRunner.xdescribe', () => {
it('should declare a skipped suite', async() => {
const t = new Runner();
t.xdescribe('suite', () => {
t.it('uno', () => {});
});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.skipped()).toBe(false);
expect(test.suite().skipped()).toBe(true);
});
it('focused tests inside a skipped suite are not run', async() => {
const t = new Runner();
let run = false;
t.xdescribe('suite', () => {
t.fit('uno', () => { run = true; });
});
const result = await t.run();
expect(run).toBe(false);
expect(result.runs.length).toBe(1);
expect(result.runs[0].test()).toBe(t.tests()[0]);
expect(result.runs[0].result()).toBe('skipped');
});
});
describe('TestRunner.fdescribe', () => {
it('should declare a focused suite', async() => {
const t = new Runner();
t.fdescribe('suite', () => {
t.it('uno', () => {});
});
expect(t.tests().length).toBe(1);
const test = t.tests()[0];
expect(test.skipped()).toBe(false);
expect(t.focusedSuites()[0]).toBe(test.suite());
expect(test.suite().skipped()).toBe(false);
});
it('skipped tests inside a focused suite should not be run', async() => {
const t = new Runner();
t.fdescribe('suite', () => {
t.xit('uno', () => {});
});
const result = await t.run();
expect(result.runs.length).toBe(1);
expect(result.runs[0].test()).toBe(t.tests()[0]);
expect(result.runs[0].result()).toBe('skipped');
});
it('should run all "run" tests inside a focused suite', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.fdescribe('suite1', () => {
t.it('dos', () => log.push(2));
t.it('tres', () => log.push(3));
});
t.it('cuatro', () => log.push(4));
await t.run();
expect(log.join()).toBe('2,3');
});
it('should run only "focus" tests inside a focused suite', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.fdescribe('suite1', () => {
t.fit('dos', () => log.push(2));
t.it('tres', () => log.push(3));
});
t.it('cuatro', () => log.push(4));
await t.run();
expect(log.join()).toBe('2');
});
it('should run both "run" tests in focused suite and non-descendant focus tests', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.fdescribe('suite1', () => {
t.it('dos', () => log.push(2));
t.it('tres', () => log.push(3));
});
t.fit('cuatro', () => log.push(4));
await t.run();
expect(log.join()).toBe('2,3,4');
});
});
describe('TestRunner attributes', () => {
it('should work', async() => {
const t = new Runner({timeout: 123});
const log = [];
t._collector.addTestModifier('foo', (t, ...args) => {
log.push('foo');
expect(t.skipped()).toBe(false);
expect(t.Expectations.Ok).toBeTruthy();
expect(t.Expectations.Fail).toBeTruthy();
expect(t.expectation()).toBe(t.Expectations.Ok);
expect(t.timeout()).toBe(123);
expect(args.length).toBe(2);
expect(args[0]).toBe('uno');
expect(args[1]).toBe('dos');
t.setExpectation(t.Expectations.Fail);
t.setTimeout(234);
});
t._collector.addTestAttribute('bar', t => {
log.push('bar');
t.setSkipped(true);
expect(t.skipped()).toBe(true);
expect(t.expectation()).toBe(t.Expectations.Fail);
expect(t.timeout()).toBe(234);
});
t.it.foo('uno', 'dos').bar('test', () => { });
expect(log).toEqual(['foo', 'bar']);
});
});
describe('TestRunner hooks', () => {
it('should run all hooks in proper order', async() => {
const log = [];
const t = new Runner();
const e = {
name() { return 'env'; },
beforeAll() { log.push('env:beforeAll'); },
afterAll() { log.push('env:afterAll'); },
beforeEach() { log.push('env:beforeEach'); },
afterEach() { log.push('env:afterEach'); },
};
const e2 = {
name() { return 'env2'; },
beforeAll() { log.push('env2:beforeAll'); },
afterAll() { log.push('env2:afterAll'); },
};
t.beforeAll(() => log.push('root:beforeAll'));
t.beforeEach(() => log.push('root:beforeEach'));
t.it('uno', () => log.push('test #1'));
t.describe('suite1', () => {
t.beforeAll(() => log.push('suite:beforeAll'));
t.beforeEach(() => log.push('suite:beforeEach'));
t.it('dos', () => log.push('test #2'));
t.it('tres', () => log.push('test #3'));
t.afterEach(() => log.push('suite:afterEach'));
t.afterAll(() => log.push('suite:afterAll'));
});
t.it('cuatro', () => log.push('test #4'));
t.tests()[t.tests().length - 1].addEnvironment(e);
t.tests()[t.tests().length - 1].addEnvironment(e2);
t.describe('no hooks suite', () => {
t.describe('suite2', () => {
t.beforeAll(() => log.push('suite2:beforeAll'));
t.afterAll(() => log.push('suite2:afterAll'));
t.describe('no hooks suite 2', () => {
t.it('cinco', () => log.push('test #5'));
});
});
});
t.suites()[t.suites().length - 1].addEnvironment(e);
t.suites()[t.suites().length - 1].addEnvironment(e2);
t.afterEach(() => log.push('root:afterEach'));
t.afterAll(() => log.push('root:afterAll'));
await t.run();
expect(log).toEqual([
'root:beforeAll',
'root:beforeEach',
'test #1',
'root:afterEach',
'suite:beforeAll',
'root:beforeEach',
'suite:beforeEach',
'test #2',
'suite:afterEach',
'root:afterEach',
'root:beforeEach',
'suite:beforeEach',
'test #3',
'suite:afterEach',
'root:afterEach',
'suite:afterAll',
'env:beforeAll',
'env2:beforeAll',
'root:beforeEach',
'env:beforeEach',
'test #4',
'env:afterEach',
'root:afterEach',
'suite2:beforeAll',
'root:beforeEach',
'env:beforeEach',
'test #5',
'env:afterEach',
'root:afterEach',
'suite2:afterAll',
'env2:afterAll',
'env:afterAll',
'root:afterAll',
]);
});
it('should remove environment', async() => {
const log = [];
const t = new Runner();
const e = {
name() { return 'env'; },
beforeAll() { log.push('env:beforeAll'); },
afterAll() { log.push('env:afterAll'); },
beforeEach() { log.push('env:beforeEach'); },
afterEach() { log.push('env:afterEach'); },
};
const e2 = {
name() { return 'env2'; },
beforeAll() { log.push('env2:beforeAll'); },
afterAll() { log.push('env2:afterAll'); },
beforeEach() { log.push('env2:beforeEach'); },
afterEach() { log.push('env2:afterEach'); },
};
t.it('uno', () => log.push('test #1'));
t.tests()[0].addEnvironment(e).addEnvironment(e2).removeEnvironment(e);
await t.run();
expect(log).toEqual([
'env2:beforeAll',
'env2:beforeEach',
'test #1',
'env2:afterEach',
'env2:afterAll',
]);
});
it('should have the same state object in hooks and test', async() => {
const states = [];
const t = new Runner();
t.beforeEach(state => states.push(state));
t.afterEach(state => states.push(state));
t.beforeAll(state => states.push(state));
t.afterAll(state => states.push(state));
t.it('uno', state => states.push(state));
await t.run();
expect(states.length).toBe(5);
for (let i = 1; i < states.length; ++i)
expect(states[i]).toBe(states[0]);
});
it('should unwind hooks properly when terminated', async() => {
const log = [];
const t = new Runner({timeout: 10000});
t.beforeAll(() => log.push('beforeAll'));
t.beforeEach(() => log.push('beforeEach'));
t.afterEach(() => log.push('afterEach'));
t.afterAll(() => log.push('afterAll'));
t.it('uno', () => {
log.push('terminating...');
t.terminate();
});
await t.run();
expect(log).toEqual([
'beforeAll',
'beforeEach',
'terminating...',
'afterEach',
'afterAll',
]);
});
it('should report as terminated even when hook crashes', async() => {
const t = new Runner({timeout: 10000});
t.afterEach(() => { throw new Error('crash!'); });
t.it('uno', () => { t.terminate(); });
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
});
it('should report as terminated when terminated during hook', async() => {
const t = new Runner({timeout: 10000});
t.afterEach(() => { t.terminate(); });
t.it('uno', () => { });
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
});
it('should unwind hooks properly when crashed', async() => {
const log = [];
const t = new Runner({timeout: 10000});
t.beforeAll(() => log.push('root beforeAll'));
t.beforeEach(() => log.push('root beforeEach'));
t.describe('suite', () => {
t.beforeAll(() => log.push('suite beforeAll'));
t.beforeEach(() => log.push('suite beforeEach'));
t.it('uno', () => log.push('uno'));
t.afterEach(() => {
log.push('CRASH >> suite afterEach');
throw new Error('crash!');
});
t.afterAll(() => log.push('suite afterAll'));
});
t.afterEach(() => log.push('root afterEach'));
t.afterAll(() => log.push('root afterAll'));
await t.run();
expect(log).toEqual([
'root beforeAll',
'suite beforeAll',
'root beforeEach',
'suite beforeEach',
'uno',
'CRASH >> suite afterEach',
'root afterEach',
'suite afterAll',
'root afterAll'
]);
});
});
describe('globalSetup & globalTeardwon', () => {
it('should run globalSetup and globalTeardown in proper order', async() => {
const t = new Runner({timeout: 10000});
const tracer = new TestTracer(t);
tracer.traceAllHooks();
tracer.addTest('', 'test1');
await t.run();
expect(tracer.trace()).toEqual([
'globalSetup',
'beforeAll',
'beforeEach',
'test1',
'afterEach',
'afterAll',
'globalTeardown',
]);
});
it('should run globalSetup and globalTeardown in proper order if parallel', async() => {
const t = new Runner({timeout: 10000, parallel: 2});
const tracer = new TestTracer(t);
tracer.traceAllHooks('', async (hookName) => {
// slowdown globalsetup to see the rest hooks awaiting this one
if (hookName === 'globalSetup')
await new Promise(x => setTimeout(x, 50));
});
tracer.addTest('', 'test1');
tracer.addTest('', 'test2');
await t.run();
expect(tracer.trace()).toEqual([
'<_global_> globalSetup',
'<worker 1> beforeAll',
'<worker 2> beforeAll',
'<worker 1> beforeEach',
'<worker 2> beforeEach',
'<worker 1> test1',
'<worker 2> test2',
'<worker 1> afterEach',
'<worker 2> afterEach',
'<worker 1> afterAll',
'<worker 2> afterAll',
'<_global_> globalTeardown',
]);
});
it('should support globalSetup/globalTeardown in nested suites', async() => {
const t = new Runner({timeout: 10000, parallel: 2});
const tracer = new TestTracer(t);
tracer.traceAllHooks('');
t.describe('suite', () => {
tracer.traceAllHooks(' ');
tracer.addTest(' ', 'test1');
tracer.addTest(' ', 'test2');
tracer.addTest(' ', 'test3');
});
await t.run();
expect(tracer.trace()).toEqual([
'<_global_> globalSetup',
'<worker 1> beforeAll',
'<worker 2> beforeAll',
' <_global_> globalSetup',
' <worker 1> beforeAll',
' <worker 2> beforeAll',
'<worker 1> beforeEach',
'<worker 2> beforeEach',
' <worker 1> beforeEach',
' <worker 2> beforeEach',
' <worker 1> test1',
' <worker 2> test2',
' <worker 1> afterEach',
' <worker 2> afterEach',
'<worker 1> afterEach',
'<worker 2> afterEach',
' <worker 2> afterAll',
'<worker 1> beforeEach',
'<worker 2> afterAll',
' <worker 1> beforeEach',
' <worker 1> test3',
' <worker 1> afterEach',
'<worker 1> afterEach',
' <worker 1> afterAll',
' <_global_> globalTeardown',
'<worker 1> afterAll',
'<_global_> globalTeardown',
]);
});
it('should report as crashed when global hook crashes', async() => {
const t = new Runner({timeout: 10000});
t.globalSetup(() => { throw new Error('crash!'); });
t.it('uno', () => { });
const result = await t.run();
expect(result.result).toBe('crashed');
});
it('should terminate and unwind hooks if globalSetup fails', async() => {
const t = new Runner({timeout: 10000});
const tracer = new TestTracer(t);
tracer.traceAllHooks();
t.describe('suite', () => {
tracer.traceAllHooks(' ', (hookName) => {
if (hookName === 'globalSetup') {
tracer.log(' !! CRASH !!');
throw new Error('crash');
}
});
tracer.addTest(' ', 'test1');
});
await t.run();
expect(tracer.trace()).toEqual([
'globalSetup',
'beforeAll',
' globalSetup',
' !! CRASH !!',
' afterAll',
' globalTeardown',
'afterAll',
'globalTeardown',
]);
});
it('should not run globalSetup / globalTeardown if all tests are skipped', async() => {
const t = new Runner({timeout: 10000});
const tracer = new TestTracer(t);
tracer.traceAllHooks();
t.describe('suite', () => {
tracer.addSkippedTest(' ', 'test1');
});
await t.run();
expect(tracer.trace()).toEqual([
]);
});
it('should properly run globalTeardown if some tests are not run', async() => {
const t = new Runner({timeout: 10000});
const tracer = new TestTracer(t);
tracer.traceAllHooks();
t.describe('suite', () => {
tracer.addSkippedTest(' ', 'test1');
tracer.addFailingTest(' ', 'test2');
tracer.addTest(' ', 'test3');
});
await t.run();
expect(tracer.trace()).toEqual([
'globalSetup',
'beforeAll',
'beforeEach',
' test3',
'afterEach',
'afterAll',
'globalTeardown',
]);
});
});
describe('TestRunner.run', () => {
it('should run a test', async() => {
const t = new Runner();
let ran = false;
t.it('uno', () => ran = true);
await t.run();
expect(ran).toBe(true);
});
it('should handle repeat', async() => {
const t = new Runner();
let suite = 0;
let test = 0;
let beforeAll = 0;
let beforeEach = 0;
t.describe.repeat(2)('suite', () => {
suite++;
t.beforeAll(() => beforeAll++);
t.beforeEach(() => beforeEach++);
t.it.repeat(3)('uno', () => test++);
});
await t.run();
expect(suite).toBe(1);
expect(beforeAll).toBe(1);
expect(beforeEach).toBe(6);
expect(test).toBe(6);
});
it('should repeat without breaking test order', async() => {
const t = new Runner();
const log = [];
t.describe.repeat(2)('suite', () => {
t.it('uno', () => log.push(1));
t.it.repeat(2)('dos', () => log.push(2));
});
t.it('tres', () => log.push(3));
await t.run();
expect(log.join()).toBe('1,2,2,1,2,2,3');
});
it('should run tests if some fail', async() => {
const t = new Runner();
const log = [];
t.it('uno', () => log.push(1));
t.it('dos', () => { throw new Error('bad'); });
t.it('tres', () => log.push(3));
await t.run();
expect(log.join()).toBe('1,3');
});
it('should run tests if some timeout', async() => {
const t = new Runner({timeout: 1});
const log = [];
t.it('uno', () => log.push(1));
t.it('dos', async() => new Promise(() => {}));
t.it('tres', () => log.push(3));
await t.run();
expect(log.join()).toBe('1,3');
});
it('should break on first failure if configured so', async() => {
const log = [];
const t = new Runner({breakOnFailure: true});
t.it('test#1', () => log.push('test#1'));
t.it('test#2', () => log.push('test#2'));
t.it('test#3', () => { throw new Error('crash'); });
t.it('test#4', () => log.push('test#4'));
await t.run();
expect(log).toEqual([
'test#1',
'test#2',
]);
});
it('should pass a state and a test as a test parameters', async() => {
const log = [];
const t = new Runner();
t.beforeEach(state => state.FOO = 42);
t.it('uno', (state, testRun) => {
log.push('state.FOO=' + state.FOO);
log.push('test=' + testRun.test().name());
});
await t.run();
expect(log.join()).toBe('state.FOO=42,test=uno');
});
it('should run async test', async() => {
const t = new Runner();
let ran = false;
t.it('uno', async() => {
await new Promise(x => setTimeout(x, 10));
ran = true;
});
await t.run();
expect(ran).toBe(true);
});
it('should run async tests in order of their declaration', async() => {
const log = [];
const t = new Runner();
t.it('uno', async() => {
await new Promise(x => setTimeout(x, 30));
log.push(1);
});
t.it('dos', async() => {
await new Promise(x => setTimeout(x, 20));
log.push(2);
});
t.it('tres', async() => {
await new Promise(x => setTimeout(x, 10));
log.push(3);
});
await t.run();
expect(log.join()).toBe('1,2,3');
});
it('should run multiple tests', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.it('dos', () => log.push(2));
await t.run();
expect(log.join()).toBe('1,2');
});
it('should NOT run a skipped test', async() => {
const t = new Runner();
let ran = false;
t.xit('uno', () => ran = true);
await t.run();
expect(ran).toBe(false);
});
it('should run ONLY non-skipped tests', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.xit('dos', () => log.push(2));
t.it('tres', () => log.push(3));
await t.run();
expect(log.join()).toBe('1,3');
});
it('should run ONLY focused tests', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.xit('dos', () => log.push(2));
t.fit('tres', () => log.push(3));
await t.run();
expect(log.join()).toBe('3');
});
it('should run tests in order of their declaration', async() => {
const log = [];
const t = new Runner();
t.it('uno', () => log.push(1));
t.describe('suite1', () => {
t.it('dos', () => log.push(2));
t.it('tres', () => log.push(3));
});
t.it('cuatro', () => log.push(4));
await t.run();
expect(log.join()).toBe('1,2,3,4');
});
it('should respect total timeout', async() => {
const t = new Runner({timeout: 10000, totalTimeout: 1});
t.it('uno', async () => { await new Promise(() => {}); });
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
expect(result.message).toContain('Total timeout');
});
});
describe('TestRunner.run result', () => {
it('should return OK if all tests pass', async() => {
const t = new Runner();
t.it('uno', () => {});
const result = await t.run();
expect(result.result).toBe('ok');
});
it('should return FAIL if at least one test fails', async() => {
const t = new Runner();
t.it('uno', () => { throw new Error('woof'); });
const result = await t.run();
expect(result.result).toBe('failed');
});
it('should return FAIL if at least one test times out', async() => {
const t = new Runner({timeout: 1});
t.it('uno', async() => new Promise(() => {}));
const result = await t.run();
expect(result.result).toBe('failed');
});
it('should return TERMINATED if it was terminated', async() => {
const t = new Runner({timeout: 1000000});
t.it('uno', async() => new Promise(() => {}));
const [result] = await Promise.all([
t.run(),
t.terminate(),
]);
expect(result.result).toBe('terminated');
});
it('should return CRASHED if it crashed', async() => {
const t = new Runner({timeout: 1});
t.it('uno', async() => new Promise(() => {}));
t.afterAll(() => { throw new Error('woof');});
const result = await t.run();
expect(result.result).toBe('crashed');
});
});
describe('TestRunner parallel', () => {
it('should run tests in parallel', async() => {
const log = [];
const t = new Runner({parallel: 2});
t.it('uno', async state => {
log.push(`Worker #${state.parallelIndex} Starting: UNO`);
await Promise.resolve();
log.push(`Worker #${state.parallelIndex} Ending: UNO`);
});
t.it('dos', async state => {
log.push(`Worker #${state.parallelIndex} Starting: DOS`);
await Promise.resolve();
log.push(`Worker #${state.parallelIndex} Ending: DOS`);
});
await t.run();
expect(log).toEqual([
'Worker #0 Starting: UNO',
'Worker #1 Starting: DOS',
'Worker #0 Ending: UNO',
'Worker #1 Ending: DOS',
]);
});
});
describe('TestRunner.hasFocusedTestsOrSuitesOrFiles', () => {
it('should work', () => {
const t = new Runner();
t.it('uno', () => {});
expect(t._filter.hasFocusedTestsOrSuitesOrFiles()).toBe(false);
});
it('should work #2', () => {
const t = new Runner();
t.fit('uno', () => {});
expect(t._filter.hasFocusedTestsOrSuitesOrFiles()).toBe(true);
});
it('should work #3', () => {
const t = new Runner();
t.describe('suite #1', () => {
t.fdescribe('suite #2', () => {
t.describe('suite #3', () => {
t.it('uno', () => {});
});
});
});
expect(t._filter.hasFocusedTestsOrSuitesOrFiles()).toBe(true);
});
});
describe('TestRunner result', () => {
it('should work for both throwing and timeouting tests', async() => {
const t = new Runner({timeout: 1});
t.it('uno', () => { throw new Error('boo');});
t.it('dos', () => new Promise(() => {}));
const result = await t.run();
expect(result.runs[0].result()).toBe('failed');
expect(result.runs[1].result()).toBe('timedout');
});
it('should report crashed tests', async() => {
const t = new Runner();
t.beforeEach(() => { throw new Error('woof');});
t.it('uno', () => {});
const result = await t.run();
expect(result.runs[0].result()).toBe('crashed');
});
it('skipped should work for both throwing and timeouting tests', async() => {
const t = new Runner({timeout: 1});
t.xit('uno', () => { throw new Error('boo');});
const result = await t.run();
expect(result.runs[0].result()).toBe('skipped');
});
it('should return OK', async() => {
const t = new Runner();
t.it('uno', () => {});
const result = await t.run();
expect(result.runs[0].result()).toBe('ok');
});
it('should return TIMEDOUT', async() => {
const t = new Runner({timeout: 1});
t.it('uno', async() => new Promise(() => {}));
const result = await t.run();
expect(result.runs[0].result()).toBe('timedout');
});
it('should return SKIPPED', async() => {
const t = new Runner();
t.xit('uno', () => {});
const result = await t.run();
expect(result.runs[0].result()).toBe('skipped');
});
it('should return FAILED', async() => {
const t = new Runner();
t.it('uno', async() => Promise.reject('woof'));
const result = await t.run();
expect(result.runs[0].result()).toBe('failed');
});
it('should return TERMINATED', async() => {
const t = new Runner();
t.it('uno', async() => t.terminate());
const result = await t.run();
expect(result.runs[0].result()).toBe('terminated');
});
it('should return CRASHED', async() => {
const t = new Runner();
t.it('uno', () => {});
t.afterEach(() => {throw new Error('foo');});
const result = await t.run();
expect(result.runs[0].result()).toBe('crashed');
});
});
describe('TestRunner delegate', () => {
it('should call delegate methods in proper order', async() => {
const log = [];
const t = new Runner({
onStarted: () => log.push('E:started'),
onTestRunStarted: () => log.push('E:teststarted'),
onTestRunFinished: () => log.push('E:testfinished'),
onFinished: () => log.push('E:finished'),
});
t.beforeAll(() => log.push('beforeAll'));
t.beforeEach(() => log.push('beforeEach'));
t.it('test#1', () => log.push('test#1'));
t.afterEach(() => log.push('afterEach'));
t.afterAll(() => log.push('afterAll'));
await t.run();
expect(log).toEqual([
'E:started',
'beforeAll',
'E:teststarted',
'beforeEach',
'test#1',
'afterEach',
'E:testfinished',
'afterAll',
'E:finished',
]);
});
it('should call onFinished with result', async() => {
let onFinished;
const finishedPromise = new Promise(f => onFinished = f);
const [result] = await Promise.all([
finishedPromise,
new TestRunner().run([], { onFinished }),
]);
expect(result.result).toBe('ok');
});
it('should crash when onStarted throws', async() => {
const t = new Runner({
onStarted: () => { throw 42; },
});
const result = await t.run();
expect(result.ok()).toBe(false);
expect(result.message).toBe('INTERNAL ERROR: 42');
});
it('should crash when onFinished throws', async() => {
const t = new Runner({
onFinished: () => { throw new Error('42'); },
});
const result = await t.run();
expect(result.ok()).toBe(false);
expect(result.message).toBe('INTERNAL ERROR');
expect(result.result).toBe('crashed');
});
});
};
class TestTracer {
constructor(testRunner) {
this._testRunner = testRunner;
this._trace = [];
}
addSkippedTest(prefix, testName, callback) {
this._testRunner.it.skip(testName, async(...args) => {
if (callback)
await callback(...args);
this._trace.push(prefix + this._workerPrefix(args[0]) + testName);
});
}
addFailingTest(prefix, testName, callback) {
this._testRunner.it.fail(testName, async(...args) => {
if (callback)
await callback(...args);
this._trace.push(prefix + this._workerPrefix(args[0]) + testName);
});
}
addTest(prefix, testName, callback) {
this._testRunner.it(testName, async(...args) => {
if (callback)
await callback(...args);
this._trace.push(prefix + this._workerPrefix(args[0]) + testName);
});
}
traceHooks(hookNames, prefix = '', callback) {
for (const hookName of hookNames) {
this._testRunner[hookName].call(this._testRunner, async (state) => {
this._trace.push(prefix + this._workerPrefix(state) + hookName);
if (callback)
await callback(hookName);
});
}
}
_workerPrefix(state) {
if (this._testRunner.parallel() === 1)
return '';
return state && (typeof state.parallelIndex !== 'undefined') ? `<worker ${state.parallelIndex + 1}> ` : `<_global_> `;
}
traceAllHooks(prefix = '', callback) {
this.traceHooks(['globalSetup', 'globalTeardown', 'beforeAll', 'afterAll', 'beforeEach', 'afterEach'], prefix, callback);
}
log(text) {
this._trace.push(text);
}
trace() {
return this._trace;
}
}