An example project setup for running mocha tests in chrome headless.
You can checkout and run this project with:
npm install
npm test
This is a proof of concept for:
- running mocha tests
- with webpack, react, typescript
- in chrome headless (without karma or jsdom)
- concurrently
I have a few projects that are using Mocha, React, Typescript, and Webpack. I started this project to see how difficult it would be to run those mocha tests concurrently (each file in a separate process) in chrome headless. Moreoever, I want access to a real browser DOM in my tests and want to avoid Karma. In short, I want to have a test like this:
describe('A basic test', function () {
let element: HTMLDivElement;
before('setup', function () {
element = document.createElement('div');
document.body.appendChild(element);
});
after('teardown', function () {
ReactDOM.unmountComponentAtNode(element);
document.body.removeChild(element);
});
it('renders', function () {
ReactDOM.render(<Greeting name="world" />, element);
const intro = document.querySelector('.Greeting');
expect(intro).to.exist;
expect(intro!.textContent).to.contain('Hello, world');
});
});
At the time of writing (dec 2017):
- mocha is the most popular testing framework
- mocha test's run in the browser but with little built-in tooling and no concurrency
- karma solves the browser and concurrency issue, but requires configuration and regular troubleshooting (example)
- jest provides excellent tooling and active development but no browser support, yet
I found Karma to be confusing to set-up and difficult to debug / maintain, and wasn't ready to migrate my existing projects to Jest. I suspected manually setting up chrome headless w/ pupeteer would be a small amount of work for existing mocha and webpack projects and wanted to validate that hypothesis.
- Compile each test file into an independent test output file
- Launch chrome headless and one or more tabs
- Inject mocha and a test file into each tab
- Use a custom mocha-reporter in each tab to collect test results
- Pass completed test results from each file to reporter
- Print out a final report on number of passes and failures
A few noteworthy points about this setup.
- Test files are compiled from multiple test input files into multiple test output files, rather than a single test bundle. Webpack's
entry
configuration can run asynchronously, allowing us to find all test files dynamically:
// webpack.config.ts
entry: async (): Promise<any> => {
const { stdout } = await execa('find', ['src', '-type', 'f', '-name', '*.spec.*']);
const testfiles = stdout.split('\n');
return testfiles.reduce((entryMap: EntryMap, filename: string) => {
const source = path.resolve(__dirname, '..', filename);
entryMap[filename] = source;
return entryMap;
}, {});
},
- Test file's and dependencies are loaded into tabs on demand
// runner.ts
const page = await this.browser.newPage();
// ...
await page.addScriptTag({
path: path.resolve(__dirname, '../node_modules/mocha/mocha.js')
})
await page.evaluate(createMochaReporter);
for (const bundle of test.bundles) {
await page.addScriptTag({
path: bundle,
})
}
await page.evaluate(runMocha);
This could be extended to load test from memory (instead of output files), or to use different test frameworks via configuration.
- Mocha expects to run all tests to completion and report as it goes. Because we want to run each test file in isolation, in parallel, and not output test results from different suites at the same time, we use two custom reporters:
mocha-reporter.js
is injected into the browser, and collects, rather than prints out, test results as they runlocal-reporter.ts
prints out the collected results, styled similar to mocha'sspec
reporter.runner.ts
coordinates passing the collected results frommocha-reporter.js
tolocal-reporter.ts
.mocha-reporter.js
stores test resultswindow.__TEST_RESULT__
so they can be accessed when complete:
// runner.ts
await page.waitForFunction(() => (window as any).__TEST_RESULT__);
const resultHandle = await page.evaluateHandle(() => (window as any).__TEST_RESULT__);
const testResults = await resultHandle.jsonValue() as MochaRunResult;
// ...
reportTests(test.entry, testResults);
- A fantastic overview of Javascript Testing in 2017
- Jest with puppeteer and when will Jest target more than JSDOM
- Use chrome headless from node with Puppeteer
- An example project running mocha tests in chrome that i used for inspiration.
- Mocha doesn't have plans for supporting concurrent test runs