├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── History.md ├── Makefile ├── README.md ├── bin └── run.js ├── example ├── api.js └── coverify.sh ├── index.js ├── package-lock.json ├── package.json ├── tea.yaml └── test ├── api.js ├── cli.js └── fixtures ├── error.js ├── fail.js └── one.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node: ['14', '16', '18'] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: ${{ matrix.node }} 16 | - run: npm ci 17 | - run: xvfb-run npm test 18 | timeout-minutes: 5 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 14 5 | addons: 6 | apt: 7 | packages: 8 | - xvfb 9 | install: 10 | - export DISPLAY=':99.0' 11 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 12 | - npm install 13 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.3.0 / 2014-08-13 3 | ================== 4 | 5 | * bump browser-run for security reasons 6 | * add history 7 | 8 | 0.2.0 / 2014-07-13 9 | ================== 10 | 11 | * ignore node_modules 12 | * update browser-run 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @node_modules/.bin/tape test/*.js 4 | 5 | .PHONY: test 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # tape-run 3 | 4 | A [tape](https://github.com/substack/tape) test runner that runs your tests in 5 | a (headless) browser and returns 0/1 as exit code, so you can use it as your 6 | `npm test` script. 7 | 8 | [![CI](https://github.com/juliangruber/tape-run/actions/workflows/ci.yml/badge.svg)](https://github.com/juliangruber/tape-run/actions/workflows/ci.yml) 9 | [![downloads](https://img.shields.io/npm/dm/tape-run.svg)](https://www.npmjs.org/package/tape-run) 10 | 11 | ## Usage 12 | 13 | First write a test utilizing [tape](https://github.com/substack/tape) and save 14 | it to `test/test.js`: 15 | 16 | ```js 17 | var test = require('tape'); 18 | 19 | test('a test', function (t) { 20 | t.ok(true); 21 | t.end(); 22 | }); 23 | ``` 24 | 25 | Then run this command using tape-run and 26 | [browserify](https://github.com/substack/node-browserify) and watch the magic happen 27 | as the TAP results stream in from a browser (default: electron): 28 | 29 | ```bash 30 | $ browserify test/*.js | tape-run 31 | TAP version 13 32 | # one 33 | ok 1 true 34 | 35 | 1..1 36 | # tests 1 37 | # pass 1 38 | 39 | # ok 40 | 41 | $ echo $? 42 | 0 43 | ``` 44 | 45 | ## rollup 46 | 47 | In simple cases you can run `rollup` and `tape-run` right from command line: 48 | ```bash 49 | $ rollup test/test.js -f iife | tape-run 50 | ``` 51 | If you want to use a configuration file, here's an example for `rollup -c | tape-run`: 52 | ```js 53 | import resolve from 'rollup-plugin-node-resolve'; 54 | import commonjs from 'rollup-plugin-commonjs'; 55 | import builtins from 'rollup-plugin-node-builtins'; 56 | import istanbul from 'rollup-plugin-istanbul'; 57 | 58 | export default { 59 | input: 'test/test.js', 60 | output: { format: 'iife', sourcemap: 'inline' }, 61 | plugins: [ 62 | resolve(), 63 | commonjs(), 64 | builtins(), 65 | istanbul({ exclude: ['dist'] }) 66 | ] 67 | } 68 | ``` 69 | 70 | ## With webpack 71 | 72 | To use with [webpack](https://webpack.github.io/), set up a `webpack.test.config.js` to bundle your tape tests. Then, include [webpack-tape-run](https://github.com/syarul/webpack-tape-run) plugin in it. As a result, `$ webpack --config webpack.test.config.js` builds your tests with webpack, runs them in a headless browser, and outputs tap into console with correct exit code. Neat! 73 | 74 | ## API 75 | 76 | You can use tape-run from JavaScript too: 77 | 78 | ```js 79 | var run = require('tape-run'); 80 | var browserify = require('browserify'); 81 | 82 | browserify(__dirname + '/test/test.js') 83 | .bundle() 84 | .pipe(run()) 85 | .on('results', console.log) 86 | .pipe(process.stdout); 87 | ``` 88 | 89 | And run it: 90 | 91 | ```bash 92 | $ node example/api.js 93 | TAP version 13 94 | # one 95 | ok 1 true 96 | 97 | 1..1 98 | # tests 1 99 | # pass 1 100 | 101 | # ok 102 | { ok: true, 103 | asserts: [ { ok: true, number: 1, name: 'true' } ], 104 | pass: [ { ok: true, number: 1, name: 'true' } ], 105 | fail: [], 106 | errors: [], 107 | plan: { start: 1, end: 1 } } 108 | ``` 109 | 110 | ### run([opts]) 111 | 112 | `opts` can be: 113 | 114 | * `wait (Number) [Default: 1000]`: Make `tap-finished` wait longer for results. 115 | Increase this value if tests finish without all tests being run. 116 | * `port (Number)`: If you specify a port it will wait for you to open a browser 117 | on `http://localhost:` and tests will be run there. 118 | * `static (String)`: Serve static files from this directory. 119 | * `browser (String)`: Browser to use. Defaults to `electron`. Available if installed: 120 | * `chrome` 121 | * `firefox` 122 | * `ie` 123 | * `phantom` 124 | * `safari` 125 | * `keepOpen (Boolean)`: Leave the browser open for debugging after running tests. 126 | * `node (Boolean)` Enable nodejs integration for electron. 127 | * `sandbox (Boolean) [Default: true]`: Enable electron sandbox. 128 | * `basedir` (String): Set this if you need to require node modules in `node` mode. 129 | 130 | The **CLI** takes the same arguments, plus `--render` (see blow): 131 | 132 | ```bash 133 | $ tape-run --help 134 | Pipe a browserify stream into this. 135 | browserify [opts] [files] | tape-run [opts] 136 | 137 | Options: 138 | --wait Timeout for tap-finished 139 | --port Wait to be opened by a browser on that port 140 | --static Serve static files from this directory 141 | --browser Browser to use. Always available: electron. Available if installed: chrome, firefox, ie, phantom, safari [default: "electron"] 142 | --render Command to pipe tap output to for custom rendering 143 | --keep-open Leave the browser open for debugging after running tests 144 | --node Enable nodejs integration for electron 145 | --sandbox Enable electron sandbox [default: true] 146 | --basedir Set this if you need to require node modules in node mode 147 | --help Print usage instructions 148 | ``` 149 | 150 | ...or any of the [other options you can pass to browser-run](https://github.com/juliangruber/browser-run#runopts). 151 | 152 | ## Custom Rendering 153 | 154 | In order to apply custom transformations to tap output without sacrificing the proper exit code, pass `--render` with a command like [tap-spec](https://npmjs.org/package/tap-spec): 155 | 156 | ```bash 157 | $ browserify test.js | tape-run --render="tap-spec" 158 | 159 | one 160 | 161 | ✔ true 162 | 163 | ``` 164 | 165 | ## Headless testing 166 | 167 | In environments without a screen, you can use `Xvfb` to simulate one. We recommend using the default electron browser, 168 | which however requires you to add additional parts to your headless configurations. 169 | 170 | ### GitHub Actions 171 | 172 | This is a full example to run `npm test`. Refer to the last 2 lines in the YAML config: 173 | 174 | ```yml 175 | on: 176 | - pull_request 177 | - push 178 | 179 | jobs: 180 | test: 181 | runs-on: ubuntu-latest 182 | steps: 183 | - uses: actions/checkout@v4 184 | - run: npm install 185 | - run: xvfb-run npm test 186 | timeout-minutes: 5 # If the tests fails, the browser will hang open indefinitely 187 | ``` 188 | 189 | ### Travis 190 | 191 | Add this to your travis.yml: 192 | 193 | ```yml 194 | addons: 195 | apt: 196 | packages: 197 | - xvfb 198 | install: 199 | - export DISPLAY=':99.0' 200 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 201 | - npm install 202 | ``` 203 | 204 | [Full example](https://github.com/rhysd/Shiba/blob/055a11a0a2b4f727577fe61371a88d8db9277de5/.travis.yml). 205 | 206 | ### Any gnu/linux box 207 | 208 | ```bash 209 | $ sudo apt-get install xvfb # or equivalent 210 | $ export DISPLAY=':99.0' 211 | $ Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 212 | $ browser-run ... 213 | ``` 214 | 215 | ### Docker 216 | 217 | There is also an example [Docker image](https://hub.docker.com/r/kipparker/docker-tape-run). [Source](https://github.com/fraserxu/docker-tape-run) 218 | 219 | ## Installation 220 | 221 | With [npm](http://npmjs.org) do 222 | 223 | ```bash 224 | $ npm install tape-run -g # for cli 225 | $ npm install tape-run # for api 226 | ``` 227 | 228 | ## Sponsors 229 | 230 | This module is proudly supported by my [Sponsors](https://github.com/juliangruber/sponsors)! 231 | 232 | Do you want to support modules like this to improve their quality, stability and weigh in on new features? Then please consider donating to my [Patreon](https://www.patreon.com/juliangruber). Not sure how much of my modules you're using? Try [feross/thanks](https://github.com/feross/thanks)! 233 | 234 | ## Security contact information 235 | 236 | To report a security vulnerability, please use the 237 | [Tidelift security contact](https://tidelift.com/security). 238 | Tidelift will coordinate the fix and disclosure. 239 | 240 | ## License 241 | 242 | (MIT) 243 | 244 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 245 | 246 | Permission is hereby granted, free of charge, to any person obtaining a copy of 247 | this software and associated documentation files (the "Software"), to deal in 248 | the Software without restriction, including without limitation the rights to 249 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 250 | of the Software, and to permit persons to whom the Software is furnished to do 251 | so, subject to the following conditions: 252 | 253 | The above copyright notice and this permission notice shall be included in all 254 | copies or substantial portions of the Software. 255 | 256 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 257 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 258 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 259 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 260 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 261 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 262 | SOFTWARE. 263 | -------------------------------------------------------------------------------- /bin/run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var run = require('..'); 4 | var yargs = require('yargs/yargs'); 5 | var { hideBin } = require('yargs/helpers'); 6 | var spawn = require('child_process').spawn; 7 | 8 | var argv = yargs(hideBin(process.argv)) 9 | .usage('Pipe a browserify stream into this.\nbrowserify [opts] [files] | $0 [opts]') 10 | 11 | .option('wait', { 12 | alias: 'w', 13 | type: 'number', 14 | description: 'Timeout for tap-finished' 15 | }) 16 | .option('port', { 17 | alias: 'p', 18 | type: 'number', 19 | description: 'Wait to be opened by a browser on that port' 20 | }) 21 | .option('static', { 22 | alias: 's', 23 | type: 'string', 24 | description: 'Serve static files from this directory' 25 | }) 26 | .option('browser', { 27 | alias: 'b', 28 | type: 'string', 29 | default: 'electron', 30 | description: 'Browser to use. ' + 31 | 'Always available: electron. ' + 32 | 'Available if installed: chrome, firefox, ie, phantom, safari' 33 | }) 34 | .option('render', { 35 | alias: 'r', 36 | type: 'string', 37 | description: 'Command to pipe tap output to for custom rendering' 38 | }) 39 | .option('keep-open', { 40 | alias: ['k', 'keepOpen'], 41 | type: 'boolean', 42 | description: 'Leave the browser open for debugging after running tests' 43 | }) 44 | .option('node', { 45 | alias: ['n', 'node-integration', 'nodeIntegration'], 46 | type: 'boolean', 47 | description: 'Enable nodejs integration for electron' 48 | }) 49 | .option('sandbox', { 50 | type: 'boolean', 51 | default: true, 52 | description: 'Enable electron sandbox' 53 | }) 54 | .option('basedir', { 55 | description: 'Set this if you need to require node modules in node mode' 56 | }) 57 | .parse(); 58 | 59 | var runner = run(argv); 60 | 61 | process.stdin 62 | .pipe(runner) 63 | .on('results', function (results) { 64 | process.exit(Number(!results.ok)); 65 | }); 66 | 67 | if (argv.render) { 68 | var ps = spawn(argv.render); 69 | runner.pipe(ps.stdin); 70 | ps.stdout.pipe(process.stdout, { end: false }); 71 | ps.stderr.pipe(process.stderr, { end: false }); 72 | } else { 73 | runner.pipe(process.stdout); 74 | } 75 | -------------------------------------------------------------------------------- /example/api.js: -------------------------------------------------------------------------------- 1 | var run = require('..'); 2 | var browserify = require('browserify'); 3 | var resolve = require('path').resolve; 4 | 5 | var script = process.argv[2] || __dirname + '/../test/fixtures/one.js'; 6 | script = resolve(script); 7 | 8 | browserify(script) 9 | .bundle() 10 | .pipe(run()) 11 | .on('results', console.log) 12 | .pipe(process.stdout); 13 | -------------------------------------------------------------------------------- /example/coverify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | BIN="../node_modules/.bin" 3 | 4 | $BIN/browserify -t coverify ../test/fixtures/one.js | ../bin/run.js | $BIN/coverify 5 | 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var browserRun = require('browser-run') 2 | var finished = require('@juliangruber/tap-finished'); 3 | var through = require('through'); 4 | var throughout = require('throughout'); 5 | 6 | module.exports = run; 7 | 8 | function run (opts) { 9 | if (!opts) opts = {}; 10 | 11 | var input = through(); 12 | var browser = browserRun(opts); 13 | var dpl = throughout(input, browser); 14 | 15 | browser 16 | .pipe(finished(opts, function (results) { 17 | if(!opts.keepOpen) browser.stop(); 18 | dpl.emit('results', results); 19 | })); 20 | 21 | return dpl; 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tape-run", 3 | "description": "Headless tape test runner", 4 | "version": "11.0.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/juliangruber/tape-run.git" 8 | }, 9 | "homepage": "https://github.com/juliangruber/tape-run", 10 | "main": "index.js", 11 | "scripts": { 12 | "test": "make test" 13 | }, 14 | "bin": { 15 | "tape-run": "./bin/run.js" 16 | }, 17 | "dependencies": { 18 | "@juliangruber/tap-finished": "0.0.2", 19 | "browser-run": "^12.0.0", 20 | "through": "^2.3.8", 21 | "throughout": "0.0.0", 22 | "yargs": "^17.4.1" 23 | }, 24 | "devDependencies": { 25 | "browserify": "^14.0.0", 26 | "coverify": "^1.4.1", 27 | "tape": "^4.6.0" 28 | }, 29 | "keywords": [ 30 | "tape", 31 | "test", 32 | "tap", 33 | "runner", 34 | "phantomjs", 35 | "headless" 36 | ], 37 | "author": { 38 | "name": "Julian Gruber", 39 | "email": "mail@juliangruber.com", 40 | "url": "http://juliangruber.com" 41 | }, 42 | "license": "MIT", 43 | "engines": { 44 | "node": ">=14" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xE7DEE1B8Bb97C3065850Cf582D6DED57C6009587' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /test/api.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var run = require('..'); 3 | var browserify = require('browserify'); 4 | 5 | test('api: one', function (t) { 6 | t.plan(1); 7 | 8 | browserify(__dirname + '/fixtures/one.js') 9 | .bundle() 10 | .pipe(run()) 11 | .on('results', function (results) { 12 | t.equal(results.ok, true); 13 | }) 14 | }); 15 | 16 | test('api: fail', function (t) { 17 | t.plan(1); 18 | 19 | browserify(__dirname + '/fixtures/fail.js') 20 | .bundle() 21 | .pipe(run()) 22 | .on('results', function (results) { 23 | t.equal(results.ok, false); 24 | }); 25 | }); 26 | 27 | test('api: error', function (t) { 28 | t.plan(1); 29 | 30 | browserify(__dirname + '/fixtures/error.js') 31 | .bundle() 32 | .pipe(run()) 33 | .on('results', function (results) { 34 | t.equal(results.ok, false); 35 | }); 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /test/cli.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var spawn = require('child_process').spawn; 3 | var browserify = require('browserify'); 4 | 5 | test('cli: one', function (t) { 6 | t.plan(1); 7 | 8 | var run = spawn('node', [ __dirname + '/../bin/run.js' ]); 9 | run.stderr.pipe(process.stderr, { end: false }); 10 | 11 | run.on('exit', function (code) { 12 | t.equals(code, 0); 13 | }); 14 | 15 | browserify(__dirname + '/fixtures/one.js') 16 | .bundle() 17 | .pipe(run.stdin); 18 | }); 19 | 20 | test('cli: fail', function (t) { 21 | t.plan(1); 22 | 23 | var run = spawn('node', [ __dirname + '/../bin/run.js' ]); 24 | run.stderr.pipe(process.stderr, { end: false }); 25 | 26 | run.on('exit', function (code) { 27 | t.equals(code, 1); 28 | }); 29 | 30 | browserify(__dirname + '/fixtures/fail.js') 31 | .bundle() 32 | .pipe(run.stdin); 33 | }); 34 | 35 | test('cli: error', function (t) { 36 | t.plan(1); 37 | 38 | var run = spawn('node', [ __dirname + '/../bin/run.js' ]); 39 | run.stderr.pipe(process.stderr, { end: false }); 40 | 41 | run.on('exit', function (code) { 42 | t.equals(code, 1); 43 | }); 44 | 45 | browserify(__dirname + '/fixtures/error.js') 46 | .bundle() 47 | .pipe(run.stdin); 48 | }); 49 | -------------------------------------------------------------------------------- /test/fixtures/error.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | 3 | test('...', function (t) { 4 | t.ok(true); 5 | throw new Error('hmm') 6 | t.end() 7 | }) 8 | -------------------------------------------------------------------------------- /test/fixtures/fail.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | test('fail', function (t) { 4 | t.plan(1); 5 | t.ok(false); 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/one.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | 3 | test('one', function (t) { 4 | t.plan(1); 5 | t.ok(true, 'true'); 6 | }); 7 | --------------------------------------------------------------------------------