Open-source project
tape-testing/tape avatar
tape-testing/tape

tape: a TAP-producing test harness for Node and browsers

tap-producing test harness for node and browsers

5,797 stars304 forksJavaScriptMIT

At a glance

What is it?
tape is a minimal JavaScript test harness that emits TAP, so any reporter or CI system that reads TAP can consume its output. It suits projects that want a small core and plain node execution rather than a bundled runner.
Who is it for?
Adopt tape if you want tests that run as plain node scripts and emit TAP that any reporter can read, and if you are comfortable adding separate modules for pretty output, uncaught-exception handling and describe blocks. Do not adopt it if you need a runner that bundles assertion styles, mocking, coverage and a watch mode in one package.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What tape solves, and who it is for

tape is a test harness that produces TAP, the Test Anything Protocol. The README describes it as a "tap-producing test harness for node and browsers". That single sentence explains most of its design. Tests are ordinary JavaScript files that call test(), and the harness prints TAP to stdout. Anything that reads TAP can then consume the result: a CI job, a terminal reporter, or another program.

The audience is narrow on purpose. If you already have a way to run node files and a way to read TAP, tape adds almost nothing between your test code and your build. You do not need a configuration file to start, and the README's first example is a single require call plus a test() invocation. Projects that want assertions, mocking, coverage and a watch mode from one dependency will find tape incomplete, and the README says so directly: "tape maintains a fairly minimal core. Additional features are usually added by using another module alongside tape."

That sentence is the whole trade-off. tape gives you a stable output format and a small API. Everything else, from colored output to uncaught-exception handling, is a separate package you choose.

How TAP output and t.plan work together

The mechanism is assertion counting. In the README example, a test declares t.plan(2) and then makes exactly two assertions, one synchronously and one inside a setTimeout callback. The harness knows how many assertions to expect, so it can decide when the test is finished without you calling an end function. The README also shows an async form: a test callback declared async, with assertions after an await.

The output is TAP version 13. The README's sample run prints a comment line with the test name, then ok 1 and not ok 2 lines, then a YAML diagnostic block for the failing assertion showing operator, expected and actual, then a plan line 1..2, then summary comments for tests, pass and fail counts. That structure is why piping into a reporter works: the reporter parses lines, not a proprietary event stream.

The cost of plan-based counting is that a mismatch between t.plan and the number of assertions is itself a failure. The repository's example directory contains files named too_many_fail.js, not_enough_fail.js and no_callback.js, which suggests these are treated as distinct failure modes worth demonstrating. If you write asynchronous tests, the plan number is the contract, and getting it wrong produces a failure rather than a hang in the cases the harness can detect.

Running a first tape test and using the tape binary

The README does not include an install command, but the package is published on npm as tape and package.json declares "bin": "./bin/tape", so the usual npm install puts an executable named tape on your path.

Create a test file that requires tape and declares a plan. This is the README's timing example, which asserts a type and then a duration.

js
var test = require('tape');

test('timing test', function (t) {
    t.plan(2);

    t.equal(typeof Date.now, 'function');
    var start = Date.now();

    setTimeout(function () {
        t.equal(Date.now() - start, 100);
    }, 100);
});

Run it with node directly, or through the tape binary. The README states you can run tests by usual node means, and that you can also use the tape binary to get globbing, which matters on Windows.

sh
tape tests/**/*.js

You should see TAP version 13, numbered ok and not ok lines, a plan line, and summary comments for tests, pass and fail. If the timing assertion drifts, the diagnostic block prints expected 100 and the actual value, which is exactly what the README sample shows.

Two flags matter early. --strict makes tape error when no files are found, and -r or --require loads a module before any tests run, so tape -r babel-register tests/**/*.js allows JIT compilation. The README notes that all modules loaded with -r run before any tests regardless of where they appear in the argument list, and that local modules need a ./ or ../ prefix because the flag uses node's require resolution.

sh
tape -r babel-register tests/**/*.js
tape --strict 'tests/**/*.js'

The minimal core is a real limitation, not a slogan

The most consequential limitation is stated in the README itself: "By default, uncaught exceptions in your tests will not be intercepted, and will cause tape to crash." A thrown error outside an assertion does not become a TAP failure. It kills the process. The README points to tape-catch to report exceptions as TAP errors, which means correct failure reporting in the presence of throw statements depends on adding a module the core does not ship.

A second limitation is that only tests are a first-class concept. There are no describe blocks in the core. The README lists tape-describe as an external package for that syntax, along with tape-repeater for repetition and mixed-tape for concurrency. This is a deliberate split, but it means a team migrating from a framework with nested suites has to rebuild that structure from separate dependencies, each with its own maintenance status.

The third is that the default reporter is unfiltered TAP. The README's own joke is that the output is "good for machines and humans that are robots", and it lists a long set of reporters such as tap-spec, tap-dot, faucet and tap-min to pipe into. Nothing is wrong with that, but it is another choice and another dependency in the pipeline. If your team expects readable output without configuration, tape is the wrong starting point.

tape compared with a batteries-included runner like Jest

The natural alternative is a runner that bundles everything. Jest is the common example: it ships its own assertion library, its own mocking, its own coverage and its own watch mode, and it does not emit TAP as its primary output. The difference is where the integration work sits. With Jest you configure one tool and get a complete experience. With tape you configure a test file, then decide separately how to make output readable, how to catch uncaught exceptions, and how to structure nested tests.

The other difference is the interface tape exposes to the outside world. TAP is a text protocol, so a tape run can be piped into tap-spec for terminal output, tap-xunit for a JUnit report, or tap-json for structured data, all without changing the test code. The README lists these and many more. That decoupling is the reason to pick tape over a monolithic runner: the harness does not own your reporting, your CI format or your editor integration.

The cost is that none of those integrations are guaranteed to exist or to be maintained. The reporter list in the README is a list, not a support commitment. If you need a specific CI report format, verify the reporter exists and works before you commit to the harness.

Maintenance, versioning and the MIT licence

The repository is not archived. The last push was on 2026-09-18, which is four days before this article's reference point, so the project is receiving changes. The package.json declares version 5.10.2, and the repository contains a CHANGELOG.md, so version history is tracked in the repository rather than only in release notes.

The dependency list is worth reading before adoption. tape depends on a set of small, focused packages including deep-equal, glob, minimist, resolve, object-inspect and several @ljharb packages such as @ljharb/now, @ljharb/resumer and @ljharb/through. That is a long list for a minimal harness, and it means your install tree grows with transitive dependencies. It also means upgrades can be driven by changes in those packages rather than by tape itself.

The licence is MIT, declared in package.json and present as a LICENSE file at the repository root. MIT permits use, modification and redistribution with the licence and copyright notice retained. This is a description of the licence text, not legal advice; if your organisation has policies about attribution or dependency review, run the MIT terms past whoever handles that.

Upgrade cost is mostly the API surface. The package declares an exports map with entries for the root, lib/default_stream, lib/results, lib/test and package.json, and it ships index.d.ts plus a types directory, so TypeScript consumers have type definitions available. The repository also carries a tsconfig.json and an eslint.config.mjs, which indicates the project type-checks and lints its own code.

Editorial conclusion

Adopt tape if you want tests that run as plain node scripts and emit TAP that any reporter can read, and if you are comfortable adding separate modules for pretty output, uncaught-exception handling and describe blocks. Do not adopt it if you need a runner that bundles assertion styles, mocking, coverage and a watch mode in one package. Before committing, check that your CI can consume TAP or that you have chosen a reporter, and decide how you will handle uncaught exceptions, since the README states tape crashes on them by default unless tape-catch is loaded.

Frequently asked questions

How do I install tape?

Install it from npm as a development dependency. The package declares a bin entry pointing at ./bin/tape, so the install also provides the tape executable used for globbing test files. The README does not give an install command, but the package is published on npm and the binary is part of the package metadata.

Does tape work in browsers as well as Node?

The package description calls it a tap-producing test harness for node and browsers, and package.json includes a browser field mapping fs to false. The README's usage section focuses on requiring tape in test files and running them with node or the tape binary.

What happens if a test throws an uncaught exception?

By default, uncaught exceptions in your tests are not intercepted and will cause tape to crash. The README suggests tape-catch if you want exceptions reported as TAP errors instead.

How can I get readable output instead of raw TAP?

Pipe the TAP output into a reporter module. The README lists options including tap-spec, tap-dot, faucet, tap-min and tap-json, and gives the pattern node test/index.js | tap-spec.

Can I load a module before my tests run?

Yes, use the -r or --require flag, which behaves like node's require and uses the same module resolution. For local modules, prepend the path with ./ or ../, and note that all required modules load before any tests regardless of argument order.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. tape-testing/tape on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tape-testing-tape.svg)](https://hysenlabs.com/projects/tape-testing-tape)