This project is still experimental
A test runner for the modern web.
The goal of this project is simple:
- Open a browser
- Import your test files (as es modules)
- Report back the results
The test runner is highly configurable, you can bring your own browser launcher, web server, test framework and/or test reporter.
There is a good default implementation which doesn't require any configuration.
See wtr-example for an example of how WTR can be used.
npm i --save-dev web-test-runnerCurrently, the wtr command runs your test with the default configuration:
- es-dev-server for serving your tests
- puppeteer for launching the browser
- mocha for running the tests in the browser
These are all configurable, see below for more info.
Do a single test run:
wtr test/**/*.test.jsRun in watch mode, reloading on file changes:
wtr test/**/*.test.js --watchDebug your tests in the browser:
wtr test/**/*.test.js --debugRun each test in a separate tab, executing them in parallel and preventing global scope pollution:
wtr test/**/*.test.js --test-isolationThe default web-test-runner setup uses mocha:
describe('my test', () => {
it('foo is bar', () => {
if ('foo' !== 'bar') {
throw new Error('foo does not equal bar');
}
});
});You can use any assertion library as long as it works in the browser. For example this es module version of chai:
import { expect } from '@bundled-es-modules/chai';
test('foo is bar', () => {
expect(foo).to.equal('bar');
});To scaffold an HTML test fixture you can use the @open-wc/testing-helpers library.
import { fixture, html } from '@open-wc/testing-helpers';
import { expect } from '@bundled-es-modules/chai';
import '../my-element.js';
describe('my-element', () => {
it('should render properly', async () => {
const element = await fixture(html`<my-element></my-element>`);
expect(element.localName).to.equal('my-element');
});
});We look for a web-test-runner.config.js file in the currently working directory. It should export an object with the following options:
export interface TestRunnerConfig {
files: string | string[];
testRunnerImport: string;
browsers: BrowserLauncher | BrowserLauncher[];
server: Server;
address: string;
port: number;
watch?: boolean;
debug?: boolean;
testIsolation?: boolean;
}A test runner runs the tests in the browser. For example mocha. When the browser is launched, the testRunnerImport file is imported from the browser as an es module.
This module is responsible for importing your tests and reporting back the results. If you're using a test framework, this file acts as a bridge between the test framework and web test runner.
Each test runner receives a set of tests, this can be one or more files depending on the user's input.
Example implementation:
import {
finished,
log,
getConfig,
captureConsoleOutput,
logUncaughtErrors,
} from 'web-test-runner/runtime.js';
// optional helpers
captureConsoleOutput();
logUncaughtErrors();
(async () => {
const { testFiles, debug, testIsolation, watch } = await getConfig();
let importTestFailed = false;
// import all test files
await Promise.all(
testFiles.map((file) => {
const importPath = new URL(file, document.baseURI).href;
return import(importPath).catch((error) => {
importTestFailed = true;
console.error(
`\x1b[31m[web-test-runner] Error loading test file: ${file}\n${error.stack}\x1b[0m`
);
});
})
);
/** run your tests, you need to implement this yourself */
const succeeded = runTests();
// report back when you're done, passing a boolean to indicate if tests succeeded
finished(!importTestFailed && succeeded);
})();The browser launcher is what boots up the browser. It should open the browser with the test paramater id in the URL.
import puppeteer from 'puppeteer';
import { BrowserLauncher } from '../core/BrowserLauncher.js';
import { TestRunnerConfig } from '../core/TestRunnerConfig.js';
import { TEST_SET_ID_PARAM } from '../core/constants.js';
export function createBrowserLauncher(): BrowserLauncher {
let config: TestRunnerConfig;
let serverAddress: string;
let browser: puppeteer.Browser;
return {
async start(_config) {
config = _config;
browser = await puppeteer.launch({ devtools: config.debug });
serverAddress = `${config.address}:${config.port}/`;
},
async stop() {
await browser.close();
},
async runTests(testSets) {
for (const [id] of testSets) {
browser.newPage().then((page) => {
page.goto(`${serverAddress}?${TEST_SET_ID_PARAM}=${id}`);
});
}
},
};
}The server is responsible for serving the test files and responding to requests from the browser.
See the types and reference implementation