npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@substrate-system/tapout

v0.0.30

Published

Run tests in a browser from the command line.

Downloads

909

Readme

tapout

tests types module semantic versioning Common Changelog license

The easiest way to run tests in a browser from the command line. Just pipe some JS into this command. A spiritual successor to tape-run.

This uses playwright under the hood.

Usage: tapout [options]
            
Options:
  -t, --timeout <ms>    Timeout in milliseconds (default: 10000)
  -b, --browser <name>  Browser to use: chromium, firefox, webkit, edge (default: chromium)
  -r, --reporter <name> Output format: tap, html (default: tap)
  --outdir <path>       Output directory for HTML reports (default: current directory)
  --outfile <name>      Output filename for HTML reports (default: index.html)
  -h, --help           Show this help message

Examples:
  cat test.js | tapout --timeout 5000
  cat test.js | tapout --browser firefox
  cat test.js | tapout -b webkit -t 3000
  cat test.js | tapout --browser edge
  cat test.js | tapout --reporter html
  cat test.js | tapout --reporter html --outdir ./reports
  cat test.js | tapout --reporter html --outfile my-test-results.html

Featuring

  • Cross-browser testing: Run tests in Chrome, Firefox, Safari (WebKit), or Edge
  • Vite support: Automatic support for import.meta.env variables
  • Smart timeout handling: Use custom timeouts or auto-timeout
  • Beautiful HTML reports: Generate HTML reports perfect for CI
  • TAP compatible: Standard TAP output works with any TAP formatter
  • Zero configuration: Just pipe JavaScript into this command
  • CI/CD friendly: Proper exit codes and error detection

Install

npm i -D @substrate-system/tapout

Use

Pipe some Javascript to this command.

cat ./test/index.js | npx tapout

Use shell redirection

npx esbuild --bundle ./test/index.ts | npx tapout | npx tap-spec

window.testsFinished

The test browser will automatically close within few seconds of no activity.

To explicitly end the tests, set a property on window.

import { test } from '@substrate-system/tapzero'

test('example test', (t) => {
  t.ok(true)
})

test('all done', () => {
  // This will cause the tests to exit immediately.
  // @ts-expect-error tests
  window.testsFinished = true
})

Vite environment variables

Vite environment variables, like import.meta.env.DEV are defined, so your tests wont break if you use them in your application code.

  • import.meta.env.DEV - true (tests run in development mode)
  • import.meta.env.PROD - false
  • import.meta.env.MODE - "test"
  • import.meta.env.BASE_URL - "/"
  • import.meta.env.SSR - false

Example

// Your Vite app code can use these environment variables
if (import.meta.env.DEV) {
  console.log('Running in development mode')
}

const apiUrl = import.meta.env.DEV ?
  'http://localhost:3000/api' :
  'https://production.api.com'

CI

After npm install, you will need to do an npx playwright install.

For example, in Github CI,

# ...

    - name: npm install, build
      run: |
        npm install
        npm run build --if-present
        npm run lint
        npx playwright install --with-deps
      env:
        CI: true

# ...

Generate HTML reports

By default writes to stdout.

cat ./test/index.js | npx tapout --reporter html > index.html
open index.html  # View the generated report

HTML Summary

  • --reporter html with no other options -> output HTML to stdout
  • --reporter html --outfile filename.html -> save to filename.html in current directory
  • --reporter html --outdir ./reports -> save to ./reports/index.html
  • --reporter html --outdir ./reports --outfile custom.html -> save to ./reports/custom.html

-b, --browser

Pass in the name of a browser to use. Default is Chrome.

Possibilities are chromium, firefox, webkit, or edge.

-t, --timeout

Pass in a different timeout value. The default is 10 seconds.

The timeout respects the auto-finish behavior:

  • With default timeout (no -t flag): Auto-finish triggers after a short delay (1-2 seconds) when no test activity is detected
  • With custom timeout (using -t): Auto-finish uses 80% of the specified timeout, giving tests more time to complete naturally
cat test.js | npx tapout --timeout 5000

Error Detection: tapout automatically detects and reports test failures from:

  • Unhandled promise rejections
  • Uncaught exceptions
  • Console error messages with common error patterns
  • TAP "not ok" results

Tests will exit with code 1 if any errors are detected, making it perfect for CI/CD pipelines.

-r, --reporter

Choose the output format. Default is TAP.

[!NOTE]
For HTML output, you will want to redirect stdout to a file. cat test.js | npx tapout --reporter html > test-output.html

Available reporters:

  • tap - TAP output (default) - Standard Test Anything Protocol format
  • html - Generate an HTML report file with beautiful, responsive design
# Generate HTML report
cat test.js | npx tapout --reporter html

# Generate HTML and output to stdout
cat test.js | npx tapout --reporter html > my-report.html

# Use TAP output (default)
cat test.js | npx tapout

# Customize output location
cat test.js | npx tapout --reporter html --outdir ./reports
cat test.js | npx tapout --reporter html --outfile my-test-results.html
cat test.js | npx tapout --reporter html --outdir ./reports --outfile custom-report.html

The HTML reporter generates an index.html file by default with:

  • Test summary with pass/fail counts and percentages
  • Individual test results with status indicators
  • Browser and timing information

Output Control:

  • --outdir <path> - Specify where to save the HTML report (default: current directory)
  • --outfile <name> - Specify the filename for the HTML report (default: index.html)
  • If neither --outdir nor --outfile is specified, HTML output is sent to stdout

GitHub Pages Integration

The generated HTML file is self-contained and can be easily hosted on GitHub Pages or any static hosting service. Simply commit the HTML file to your repository.

# Example CI workflow
npm test 2>&1 | npx tapout --reporter html --outfile test-results.html
git add test-results.html
git commit -m "Update test results"
git push

Accessibility Testing with Axe

You can integrate axe-core to test accessibility.

1. Install axe-core

npm install --save-dev axe-core

2. Create an accessibility helper

// test/helpers/axe-helper.js
import axe from 'axe-core'

/**
 * Run axe accessibility scan and assert no violations
 *
 * @param t - Tapzero tester
 * @param {Partial<{ context, tags, rules }>} options Options object
 */
export async function assertNoViolations(
  t,
  options = {},
  message = 'should have no accessibility violations'
) {
  const {
    context = document,
    tags = ['wcag2a', 'wcag2aa'],
    rules = {}
  } = options

  const results = await axe.run(context, {
    runOnly: { type: 'tag', values: tags },
    rules
  })

  t.equal(results.violations.length, 0, message)

  // Log violations for debugging
  if (results.violations.length > 0) {
    console.error('\n=== Accessibility Violations ===')
    results.violations.forEach((violation, index) => {
      console.error(`\n${index + 1}. ${violation.help}`)
      console.error(`   Impact: ${violation.impact}`)
      console.error(`   WCAG: ${violation.tags.filter(tag => tag.startsWith('wcag')).join(', ')}`)
      console.error(`   Affected elements: ${violation.nodes.length}`)
      violation.nodes.forEach((node, nodeIndex) => {
        console.error(`     ${nodeIndex + 1}. ${node.html}`)
        console.error(`        ${node.failureSummary}`)
      })
    })
    console.error('\n================================\n')
  }

  return results
}

/**
 * Check WCAG compliance level
 */
export async function assertWCAGCompliance(t, level = 'AA', options = {}) {
  const tags = {
    'A': ['wcag2a'],
    'AA': ['wcag2a', 'wcag2aa'],
    'AAA': ['wcag2a', 'wcag2aa', 'wcag2aaa']
  }

  return assertNoViolations(t, {
    ...options,
    tags: tags[level] || tags['AA']
  }, `should meet WCAG ${level} compliance`)
}

3. Use in your tests

import { test } from '@substrate-system/tapzero'
import {
  assertNoViolations,
  assertWCAGCompliance
} from './helpers/axe-helper.js'

test('page has no accessibility violations', async (t) => {
  document.body.innerHTML = `
    <main>
      <h1>Welcome</h1>
      <button>Click me</button>
      <img src="test.jpg" alt="Test image" />
    </main>
  `

  await assertNoViolations(t)
})

test('form meets WCAG AA compliance', async (t) => {
  document.body.innerHTML = `
    <form>
      <label for="username">Username</label>
      <input id="username" type="text" />

      <label for="password">Password</label>
      <input id="password" type="password" />

      <button type="submit">Submit</button>
    </form>
  `

  await assertWCAGCompliance(t, 'AA')
})

test('can test specific elements', async (t) => {
  document.body.innerHTML = `
    <nav aria-label="Main navigation">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
  `

  const nav = document.querySelector('nav')
  await assertNoViolations(t, {
    context: nav
  }, 'navigation should be accessible')
})

test('cleanup', () => {
  // @ts-expect-error browser global
  window.testsFinished = true
})

4. Run the accessibility tests

# Bundle and run
npx esbuild test/a11y-test.js --bundle --format=esm | npx tapout | npx tap-spec

# Or add to package.json scripts
npm run test:a11y

Advanced Use

Test specific WCAG rules

import axe from 'axe-core'

test('check color contrast only', async (t) => {
  document.body.innerHTML = `<div style="color: #333; background: #fff;">
    Content
  </div>`

  const results = await axe.run(document, {
    runOnly: { type: 'rule', values: ['color-contrast'] }
  })

  t.equal(results.violations.length, 0, 'should pass color contrast')
})

Disable specific rules

await assertNoViolations(t, {
  rules: {
    'color-contrast': { enabled: false }  // Skip incomplete styles
  }
})

Component testing

import { MyComponent } from '../src/components/MyComponent.js'

test('MyComponent is accessible', async (t) => {
  const container = document.createElement('div')
  document.body.appendChild(container)

  // Render your component
  const component = new MyComponent()
  component.mount(container)

  // Test accessibility
  await assertNoViolations(t, { context: container })

  container.remove()
})

Example Tests

Write tests for the browser environment.

[!TIP] End the tests explicity with window.testsFinished = true. Else they time out naturally, which is ok too.

// test/index.ts
import { test } from '@substrate-system/tapzero'

test('example', t => {
    t.ok(document.body, 'should find a body tag')
})

test('all done', t => {
  // @ts-expect-error explicitly end
  window.testsFinished = true
})

Run the tests on the command line.

npx esbuild ./test/index.ts | npx tapout

HTML report

# HTML reporter examples  
npm run test:simple -- --reporter html     # Generate HTML report

Test

See the test/ directory.

npm test