Examples

Complete files you can run.

Start with the live browser suite, then copy full examples for assertions, async work, type checks, host adapters, coverage, and npm scripts.

Running now

Native browser suite

The import map above resolves local ES modules and basic.js executes directly—without a bundle or transform.

No build step

Current run

Running example tests…

Waiting for the suite result.

  • 1 + 2 equals 3
  • The result is a number
  • Invalid boolean input throws TypeError

Basic assertions

This is the complete module running at the top of this page. Its small run() helper guarantees that each test is decided and closed.

// basic.js
import VanillaTest from 'vanilla-test';

const test = new VanillaTest();

function run(description, assertion) {
    test.expects(description);

    try {
        assertion();
        test.pass();
    } catch (error) {
        console.error(error);
        test.fail();
    }

    test.done();
}

run('1 + 2 equals 3', () => test.compare(1 + 2, 3));
run('the result is a number', () => test.is.number(1 + 2));
run('invalid boolean input throws TypeError', () => {
    try {
        test.is.boolean([]);
    } catch (error) {
        test.is.typeError(error);
        return;
    }

    throw new Error('Expected a TypeError');
});

const result = test.report();
const status = document.querySelector('[data-status]');
const details = document.querySelector('[data-details]');

status.textContent = result.ok ? 'All example tests passed' : 'Example tests failed';
status.dataset.ok = String(result.ok);
details.textContent = `${result.total} tests · ${result.passed.length} passed · ${result.failureCount} failed`;
Check the console or DevTools.

The page summary comes from the frozen result object. DevTools contains the detailed runtime output.

Async assertion

Await application work inside the active test. The lifecycle remains explicit before and after the promise settles.

// async.js
import VanillaTest from 'vanilla-test';

async function loadUser() {
    return Promise.resolve({ id: 7, name: 'Ada' });
}

const test = new VanillaTest();

test.expects('loadUser resolves a valid user');
try {
    const user = await loadUser();
    test.is.object(user);
    test.is.number(user.id);
    test.compare(user.name, 'Ada');
    test.pass();
} catch (error) {
    console.error(error);
    test.fail();
}
test.done();

const result = test.report();
console.log({ ok: result.ok, total: result.total });
Check the console or DevTools.

The final report is created only after the promise resolves and the active test calls done().

Intentional failure

This complete Node module shows the failure path and intentionally exits with status 1.

// intentional-failure.js
import VanillaTest from 'vanilla-test';

const test = new VanillaTest();

test.expects('the API returns HTTP 200');
try {
    const response = { status: 503 };
    test.compare(response.status, 200);
    test.pass();
} catch (error) {
    console.error('Expected failure:', error);
    test.fail();
}
test.done();

const result = test.report();
console.log({ ok: result.ok, failures: result.failureCount });
process.exitCode = result.ok ? 0 : 1;
Check the console or DevTools.

You should see the comparison error, one failed test, and { ok: false, failures: 1 }. In Node.js this console is the terminal.

node ./intentional-failure.js
Check the terminal.

An exit status of 1 is expected for this deliberate failure.

Type checks

The is helpers throw on mismatch while strict mode is enabled. Catch the error and fail the active test.

// types.js
import VanillaTest from 'vanilla-test';

const test = new VanillaTest();
const payload = {
    tags: ['docs', 'esm'],
    publish: () => true
};

test.expects('payload has the required shapes');
try {
    test.is.object(payload);
    test.is.array(payload.tags);
    test.is.function(payload.publish);
    test.is.string(payload.tags[0]);
    test.pass();
} catch (error) {
    console.error(error);
    test.fail();
}
test.done();

const result = test.report();
process.exitCode = result.ok ? 0 : 1;
Check the console or DevTools.

The example passes. Change payload.tags to a string to inspect a strict type mismatch and failed result.

node ./types.js
Check the terminal.

The terminal shows the expectation, decision, and final report.

One suite in Node and Chrome

These three complete files form a minimal cross-runtime project. The shared suite avoids host-specific globals.

shared-suite.js

import VanillaTest from 'vanilla-test';

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

    test.expects('Web-standard values behave consistently');
    try {
        const value = await Promise.resolve('ready');
        test.is.string(value);
        test.compare(value, 'ready');
        test.pass();
    } catch (error) {
        console.error(error);
        test.fail();
    }
    test.done();

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

Both adapters below display this suite's native console output.

node-runner.js

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

const result = await run();
console.log(`${result.passed.length}/${result.total} passed`);
process.exitCode = result.ok ? 0 : 1;
Check the console or DevTools.

In Node.js, console output appears in the terminal along with the summary.

node ./node-runner.js
Check the terminal.

The Node adapter prints the report and sets a CI-friendly exit status.

browser-runner.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>vanilla-test browser example</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>
    <h1>Browser test</h1>
    <p data-run-status aria-live="polite">Running…</p>
    <script type="module">
        import run from './shared-suite.js';

        const result = await run();
        const status = document.querySelector('[data-run-status]');
        status.textContent = result.ok
            ? `${result.total} tests passed`
            : `${result.failureCount} tests failed`;
    </script>
</body>
</html>
Check the console or DevTools.

Open DevTools after loading the served page to see the expectation and full report.

npx serve .
Check the terminal.

Open the local HTTP URL printed by the server. Do not open the HTML as a file: URL.

CLI and npm commands

Run both collectors, one collector, or supply a machine-specific Chrome path without changing committed configuration.

npx vanilla-test coverage all
npx vanilla-test coverage node
npx vanilla-test coverage chrome
npx vanilla-test coverage chrome --chrome-path "/opt/google/chrome/google-chrome"
Check the terminal.

The suite report and collector errors appear there. Use the exit status for automation and open the configured report directory for coverage details.

The repository's npm scripts provide shorter commands for common work:

npm test
npm run test:core
npm run test:tooling
npm run coverage
npm run coverage:node
npm run coverage:chrome
npm start
Check the terminal.

npm start prints the documentation URL. Test and coverage scripts print results and exit nonzero when a required gate fails.

Append CLI options to an npm script after --:

npm run coverage -- --timeout-ms 60000
npm run coverage:chrome -- --headed
npm run coverage:chrome -- --chrome-path "C:\Program Files\Google\Chrome\Application\chrome.exe"
Check the terminal.

Confirm the suite result and final exit status, then inspect the configured report directory for the runtime's coverage files.