Guide

From install to two real runtimes.

Write one standards-only ES module, load it unchanged in Node.js and Chrome, then add independent native coverage for both hosts.

1. Install

Use Node.js 22.12 or newer and native ES modules. Install the package in your project:

npm install vanilla-test
Check the terminal.

A successful install exits with status 0. Add "type": "module" to package.json if the project does not already use ESM.

2. Write the shared suite

Start with one application module:

// src/add.js
export function add(left, right) {
    return left + right;
}

Put its assertions in one host-neutral suite. Export a function and return the immutable result from report().

// test/shared-suite.js
import VanillaTest from 'vanilla-test';
import { add } from '../src/add.js';

export default async function run() {
    const test = new VanillaTest();

    test.expects('addition preserves the total');
    try {
        test.compare(add(1, 2), 3);
        test.pass();
    } catch (error) {
        console.error(error);
        test.fail();
    }
    test.done();

    return test.report();
}
Check the console or DevTools.

expects(), pass(), fail(), and report() write progress and results. The returned object—not parsed console text—is the programmatic result.

Each expects() opens one test. Call pass() or fail(), then done(). Call report() only after every active test is done.

3. Run it in Node.js

A small adapter maps result.ok to the process exit status expected by shells and CI.

// test/node.js
import run from './shared-suite.js';

const result = await run();
process.exitCode = result.ok ? 0 : 1;
node ./test/node.js
Check the terminal.

The full report prints there. Exit status 0 means the suite passed; 1 means at least one test failed.

4. Run it in Chrome

Map package specifiers to browser-loadable files, import the same suite, and render the result. Serve this page over HTTP rather than opening it as a file: URL.

<!-- test/browser.html -->
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Browser tests</title>
    <script type="importmap">
    {
        "imports": {
            "vanilla-test": "/node_modules/vanilla-test/index.js",
            "ansi-colors-es6": "/node_modules/ansi-colors-es6/index.js",
            "strong-type": "/node_modules/strong-type/index.js"
        }
    }
    </script>
</head>
<body>
    <p data-status>Running…</p>
    <script type="module">
        import run from './shared-suite.js';

        const result = await run();
        document.querySelector('[data-status]').textContent =
            result.ok ? 'Passed' : 'Failed';
    </script>
</body>
</html>
npx serve .
Check the terminal, then check the console or DevTools.

The terminal shows the local URL. Open the page there, then open DevTools to see vanilla-test progress, assertion errors, and the final report.

5. Add CLI coverage

Create vanilla-test.config.json at the project root. The entry is the shared module; each runtime gets its own include scope.

{
    "entry": "./test/shared-suite.js",
    "reportsDirectory": "./coverage",
    "thresholds": {
        "statements": 100,
        "branches": 100,
        "functions": 100,
        "lines": 100
    },
    "timeoutMs": 30000,
    "node": {
        "include": ["src/**/*.js"]
    },
    "chrome": {
        "include": ["src/**/*.js"],
        "imports": {},
        "headless": true,
        "executablePath": null
    }
}

"executablePath": null enables portable Chrome discovery: vanilla-test checks CHROME_PATH, standard Chrome Stable locations, then executable names on PATH. Use a string only for a known custom installation.

npx vanilla-test coverage all
Check the terminal.

Node and Chrome run independently and write separate reports below coverage/. Use the terminal exit status in CI.

Next steps

Learn

Understand every API contract

Review lifecycle guards, return values, immutable snapshots, completion events, and error behavior.

Open the API reference →
Experiment

Edit and run immediately

Try a suite safely in the browser and inspect its mirrored output.

Open the playground →