├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .nycrc ├── ACKNOWLEDGEMENT.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── browser-do.js ├── build-reporter.js ├── index.js ├── jasmine-tap-reporter.js ├── lib ├── browser-run.js ├── browsers │ ├── _chromium-args.js │ ├── chrome-headless.js │ ├── chrome.js │ ├── chromium-headless.js │ ├── chromium.js │ ├── edge-headless.js │ ├── edge.js │ ├── electron-runner.js │ ├── electron.js │ ├── firefox-headless.js │ ├── firefox.js │ └── safari.js ├── get-bin.js ├── get-browser.js ├── get-darwin-bin.js ├── get-exe.js ├── launch.js └── tap-parse.js ├── package.json ├── reporter.js └── test ├── _jasmine-bad.html ├── _jasmine-good.html ├── _mock-jasmine-bad.html ├── _mock-jasmine-good.html ├── _mock.js ├── browser-do.spec.js ├── e2e.spec.js ├── get-browser.spec.js ├── samples ├── _jasmine-bad.js ├── _jasmine-fdescribe.js ├── _jasmine-fit.js ├── _jasmine-good.js ├── _jasmine-xdescribe.js ├── _jasmine-xit.js ├── _mocha-bad.js ├── _mocha-good.js ├── _zora-bad.js └── _zora-good.js └── tap-parse.spec.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "ecmaVersion": 2018, 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 2 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "unix" 19 | ], 20 | "semi": [ 21 | "error", 22 | "always" 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | name: Nodejs ${{ matrix.node_version }} on ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | node_version: ['18', '20'] 19 | os: [ubuntu-latest, windows-latest, macOS-latest] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Use Node.js ${{ matrix.node_version }} 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node_version }} 27 | - run: npm i 28 | - run: xvfb-run -a npm test 29 | if: runner.os == 'Linux' 30 | - run: npm test 31 | if: runner.os != 'Linux' 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | .nyc_output 4 | coverage 5 | /__play.js 6 | /tmp 7 | /dist 8 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "index.js", 4 | "lib/**/*.js" 5 | ], 6 | "reporter": [ 7 | "lcov", 8 | "text-summary" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /ACKNOWLEDGEMENT.md: -------------------------------------------------------------------------------- 1 | This software borrowed code from [karma-chrome-launcher](https://github.com/karma-runner/karma-chrome-launcher), [karma-firefox-launcher](https://github.com/karma-runner/karma-firefox-launcher), [karma-safari-launcher](https://github.com/karma-runner/karma-safari-launcher), [karma-ie-launcher](https://github.com/karma-runner/karma-ie-launcher), [jslegers/karma-edge-launcher](https://github.com/jslegers/karma-edge-launcher), [karma-electron](https://github.com/twolfson/karma-electron), [browser-run](https://github.com/juliangruber/browser-run), [tape-run](https://github.com/juliangruber/tape-run), [browser-launcher](https://github.com/substack/browser-launcher), [jasmine-reporters](https://github.com/larrymyers/jasmine-reporters). 2 | 3 | Below are required notices from them. 4 | 5 | ==== 6 | 7 | The MIT License 8 | 9 | Copyright (C) 2011-2013 Google, Inc. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of 12 | this software and associated documentation files (the "Software"), to deal in 13 | the Software without restriction, including without limitation the rights to 14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 15 | of the Software, and to permit persons to whom the Software is furnished to do 16 | so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 24 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | 28 | ==== 29 | 30 | This is free and unencumbered software released into the public domain. 31 | 32 | Anyone is free to copy, modify, publish, use, compile, sell, or 33 | distribute this software, either in source code form or as a compiled 34 | binary, for any purpose, commercial or non-commercial, and by any 35 | means. 36 | 37 | In jurisdictions that recognize copyright laws, the author or authors 38 | of this software dedicate any and all copyright interest in the 39 | software to the public domain. We make this dedication for the benefit 40 | of the public at large and to the detriment of our heirs and 41 | successors. We intend this dedication to be an overt act of 42 | relinquishment in perpetuity of all present and future rights to this 43 | software under copyright law. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 46 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 47 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 48 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 49 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 50 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 51 | OTHER DEALINGS IN THE SOFTWARE. 52 | 53 | For more information, please refer to `` 54 | 55 | ==== 56 | 57 | Copyright (c) 2013 Julian Gruber `` 58 | 59 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 62 | 63 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 64 | 65 | ==== 66 | 67 | This software is released under the MIT license: 68 | 69 | Permission is hereby granted, free of charge, to any person obtaining a copy of 70 | this software and associated documentation files (the "Software"), to deal in 71 | the Software without restriction, including without limitation the rights to 72 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 73 | the Software, and to permit persons to whom the Software is furnished to do so, 74 | subject to the following conditions: 75 | 76 | The above copyright notice and this permission notice shall be included in all 77 | copies or substantial portions of the Software. 78 | 79 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 80 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 81 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 82 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 83 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 84 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 85 | 86 | ==== 87 | 88 | The MIT License 89 | 90 | Copyright (c) 2010 Larry Myers 91 | 92 | Permission is hereby granted, free of charge, to any person obtaining a copy 93 | of this software and associated documentation files (the "Software"), to deal 94 | in the Software without restriction, including without limitation the rights 95 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 96 | copies of the Software, and to permit persons to whom the Software is 97 | furnished to do so, subject to the following conditions: 98 | 99 | The above copyright notice and this permission notice shall be included in 100 | all copies or substantial portions of the Software. 101 | 102 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 103 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 104 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 105 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 106 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 107 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 108 | THE SOFTWARE. 109 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [5.0.0](https://github.com/3cp/browser-do/compare/v4.1.0...v5.0.0) (2024-05-11) 2 | 3 | 4 | ### chore 5 | 6 | * upgrade deps ([fd1adef](https://github.com/3cp/browser-do/commit/fd1adef16da60f03eafa30b32363347c9cfc8407)) 7 | 8 | 9 | ### BREAKING CHANGES 10 | 11 | * drop support of Nodejs < v18 12 | 13 | 14 | 15 | # [4.1.0](https://github.com/3cp/browser-do/compare/v4.0.0...v4.1.0) (2022-01-23) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * support both jasmine-core v3 and v4 ([9ea5ee2](https://github.com/3cp/browser-do/commit/9ea5ee21644dac1b73b0d2f621fa468bdedd57a4)), closes [#8](https://github.com/3cp/browser-do/issues/8) 21 | 22 | 23 | 24 | # [4.0.0](https://github.com/3cp/browser-do/compare/v3.0.2...v4.0.0) (2021-11-03) 25 | 26 | 27 | ### chore 28 | 29 | * upgrade deps, es6ify doesn't work with latest socket.io anymore ([de56192](https://github.com/3cp/browser-do/commit/de561924cccea3c6b9a0997cc1efc3f474fc0e6a)) 30 | 31 | 32 | ### BREAKING CHANGES 33 | 34 | * drop IE support 35 | 36 | 37 | 38 | ## [3.0.2](https://github.com/3cp/browser-do/compare/v3.0.1...v3.0.2) (2020-10-02) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * fix wrong jasmine report on fit/fdescribe ([3a1f72e](https://github.com/3cp/browser-do/commit/3a1f72e3c2de80efb5c393aa6d05c0cda4a11a53)), closes [#3](https://github.com/3cp/browser-do/issues/3) 44 | 45 | 46 | 47 | ## [3.0.1](https://github.com/3cp/browser-do/compare/v3.0.0...v3.0.1) (2020-10-01) 48 | 49 | 50 | ### Bug Fixes 51 | 52 | * has to use yarn to build to support IE11 ([59ff7a2](https://github.com/3cp/browser-do/commit/59ff7a28e4d21aa840e23712155a96a23780b84e)) 53 | 54 | 55 | 56 | # [3.0.0](https://github.com/3cp/browser-do/compare/v2.0.2...v3.0.0) (2020-08-18) 57 | 58 | 59 | ### Features 60 | 61 | * drop nodejs v8 support ([d3680c3](https://github.com/3cp/browser-do/commit/d3680c340e11ec473aa4930c7302dd4f07825349)) 62 | 63 | 64 | ### BREAKING CHANGES 65 | 66 | * drop nodejs v8 support 67 | 68 | 69 | 70 | ## [2.0.2](https://github.com/3cp/browser-do/compare/v2.0.1...v2.0.2) (2020-05-15) 71 | 72 | 73 | ### Performance Improvements 74 | 75 | * remove through and duplexer ([4b11b1e](https://github.com/3cp/browser-do/commit/4b11b1e4a7cca2fcedabd3649048318b15695a59)) 76 | 77 | 78 | 79 | ## [2.0.1](https://github.com/3cp/browser-do/compare/v2.0.0...v2.0.1) (2020-02-19) 80 | 81 | 82 | 83 | # [2.0.0](https://github.com/3cp/browser-do/compare/v1.0.0...v2.0.0) (2020-02-18) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * bypass IE11 SCRIPT5045 ([a86710c](https://github.com/3cp/browser-do/commit/a86710c7aa5b9e0178328c46b934ca4e350c188d)) 89 | * fix macOS false browser detection ([961a8c6](https://github.com/3cp/browser-do/commit/961a8c60e16fda9771574cc40f8f46ea34b1d2eb)) 90 | * fix reporter build ([2a8fc38](https://github.com/3cp/browser-do/commit/2a8fc38f8d5dc3b872c210729831810db096a1ce)) 91 | * make reporter runnable on IE11 again ([0929c9d](https://github.com/3cp/browser-do/commit/0929c9dda6c88416a69479c95d8908a49670f317)) 92 | 93 | 94 | ### Features 95 | 96 | * migrate to latest chromium based edge ([b1a54c1](https://github.com/3cp/browser-do/commit/b1a54c195249469c94fa48c7a73b2679781680d5)) 97 | * support browser edge-headless ([63474b9](https://github.com/3cp/browser-do/commit/63474b979bfa36ad55aafca979099e1200d9136f)) 98 | 99 | 100 | ### BREAKING CHANGES 101 | 102 | * doesn't work with old MS Edge anymore. 103 | 104 | 105 | 106 | # [1.0.0](https://github.com/3cp/browser-do/compare/v0.5.0...v1.0.0) (2020-01-21) 107 | 108 | 109 | 110 | # [0.5.0](https://github.com/3cp/browser-do/compare/v0.4.0...v0.5.0) (2020-01-19) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * use large socket.io timeout to cater huge test file ([49fe1ff](https://github.com/3cp/browser-do/commit/49fe1ffef5b2ed12df5a5b082ed0fd180604753c)) 116 | 117 | 118 | 119 | # [0.4.0](https://github.com/3cp/browser-do/compare/v0.3.4...v0.4.0) (2020-01-14) 120 | 121 | 122 | ### Features 123 | 124 | * print out console debug/info/warn/error ([390df91](https://github.com/3cp/browser-do/commit/390df919f261d1766ebbd82f8cafe015bb577d52)) 125 | 126 | 127 | 128 | ## [0.3.4](https://github.com/3cp/browser-do/compare/v0.3.3...v0.3.4) (2020-01-05) 129 | 130 | 131 | 132 | ## [0.3.3](https://github.com/3cp/browser-do/compare/v0.3.2...v0.3.3) (2019-11-20) 133 | 134 | 135 | 136 | ## [0.3.2](https://github.com/3cp/browser-do/compare/v0.3.1...v0.3.2) (2019-06-27) 137 | 138 | 139 | ### Bug Fixes 140 | 141 | * don't check leak in default mocha setup, mocha thinks window.__coverage__ is a leak ([53695e3](https://github.com/3cp/browser-do/commit/53695e3)) 142 | 143 | 144 | 145 | ## [0.3.1](https://github.com/3cp/browser-do/compare/v0.3.0...v0.3.1) (2019-06-27) 146 | 147 | 148 | 149 | # [0.3.0](https://github.com/3cp/browser-do/compare/v0.2.0...v0.3.0) (2019-06-27) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * avoid multiple tap complete callback ([e32b354](https://github.com/3cp/browser-do/commit/e32b354)) 155 | 156 | 157 | ### Features 158 | 159 | * support window.__coverage__ report ([c4c2aa0](https://github.com/3cp/browser-do/commit/c4c2aa0)) 160 | 161 | 162 | 163 | # [0.2.0](https://github.com/3cp/browser-do/compare/v0.1.0...v0.2.0) (2019-06-19) 164 | 165 | 166 | ### Features 167 | 168 | * replace xhr-write-stream with socket.io ([9be4d62](https://github.com/3cp/browser-do/commit/9be4d62)) 169 | 170 | 171 | 172 | # 0.1.0 (2019-06-18) 173 | 174 | 175 | ### Bug Fixes 176 | 177 | * add gitattributes for windows linebreak issue ([f99d952](https://github.com/3cp/browser-do/commit/f99d952)) 178 | * add polyfill for mocha+ie ([d8ad455](https://github.com/3cp/browser-do/commit/d8ad455)) 179 | * fix edge script. ([244fb40](https://github.com/3cp/browser-do/commit/244fb40)) 180 | * fix unnecessary and broken browser path check ([db4d6e2](https://github.com/3cp/browser-do/commit/db4d6e2)) 181 | * get back enstore ([cf32452](https://github.com/3cp/browser-do/commit/cf32452)) 182 | 183 | 184 | ### Features 185 | 186 | * first try of browser-run + tape-run ([ac20c9f](https://github.com/3cp/browser-do/commit/ac20c9f)) 187 | * in keep-open mode, forward test result to ctrl-c exit ([9a56224](https://github.com/3cp/browser-do/commit/9a56224)) 188 | * internal tap parser ([9ed7206](https://github.com/3cp/browser-do/commit/9ed7206)) 189 | * support jasmine out of the box ([16e69db](https://github.com/3cp/browser-do/commit/16e69db)) 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Chunpeng Huo. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # browser-do ![CI](https://github.com/3cp/browser-do/workflows/CI/badge.svg) 2 | 3 | Run JavaScript in a browser, forward browser console log to stdout, great for running unit tests in browser. 4 | 5 | ```bash 6 | npm i -D browser-do 7 | ``` 8 | 9 | > Requires minimum Node.js v10.13+. 10 | 11 | browser-do is an alternative implementation of [browser-run](https://github.com/juliangruber/browser-run) and [tape-run](https://github.com/juliangruber/tape-run), with better Windows support, supports running [mocha](https://mochajs.org), [jasmine](https://jasmine.github.io), [tape](https://github.com/substack/tape), [zora](https://github.com/lorenzofox3/zora) unit tests out of the box. 12 | 13 | browser-do offers: 14 | 15 | 1. Browser detection borrowed from various karma browser launchers. Simpler and more reliable on Windows than [browser-launcher](https://github.com/substack/browser-launcher). 16 | 2. [TAP output](http://testanything.org) support. 17 | 3. Kept browser-run options `-p, --port`, `-b, --browser browser-name`, `-s, --static`, and `-m, --mock`. 18 | 4. Removed `--input html` as browser-do auto detects input format. 19 | 5. Removed `-n, --node` and `--basedir` as browser-do doesn't want to support Node.js code. (In original browser-run, Node.js code only works with electron anyway) 20 | 6. Added options `-t, --tap` to handle generic TAP output. 21 | 7. Added `--jasmine` and `--mocha` to conveniently support jasmine/mocha (setup global vars, switch to TAP reporter). 22 | 8. Added `-k, --keep-open` (inherited from tap-run) to keep browser running after TAP finished. 23 | 24 | browser-do is simple and flexible. Just pipe your code to browser-do with a browser of your choice (default to a headless electron). 25 | 26 | ```bash 27 | esbuild test/all-my-tape-tests.js --bundle | browser-do --tap 28 | esbuild test/all-my-zora-tests.js --bundle | browser-do --tap 29 | esbuild test/all-my-jasmine-tests.js --bundle | browser-do --jasmine 30 | esbuild test/all-my-mocha-tests.js --bundle | browser-do --mocha --browser chrome-headless 31 | ``` 32 | 33 | You don't need to use esbuild with browser-do. You can prepare a JavaScript bundle with any bundler, then just pipe it to browser-do. 34 | 35 | ```bash 36 | cat dist/my-test-bundle.js | browser-do --tap # or jasmine/mocha 37 | # or avoid "cat" on windows 38 | browser-do --tap < dist/my-test-bundle.js 39 | # Or in PowerShell 40 | Get-Content dist/my-test-bundle.js | browser-do --tap # or jasmine/mocha 41 | ``` 42 | 43 | One more tip, because browser-do normalises jasmine/mocha into TAP output, plus the default TAP output from tape/zora, you can further pipe the output to any TAP [pretty reporters](https://github.com/substack/tape#pretty-reporters) 44 | ```bash 45 | esbuild test/all-my-jasmine-tests.js --bundle | browser-do --jasmine | tap-dot 46 | ``` 47 | 48 | ## Supported Browsers 49 | 50 | electron is the always available default. 51 | 52 | | | macOS | Linux | Windows | 53 | |--------------------|-------|-------|---------| 54 | | electron (default) | Yes | Yes | Yes | 55 | | chrome | Yes | Yes | Yes | 56 | | chrome-headless | Yes | Yes | Yes | 57 | | chromium | Yes | Yes | Yes | 58 | | chromium-headless | Yes | Yes | Yes | 59 | | firefox | Yes | Yes | Yes | 60 | | firefox-headless | Yes | Yes | Yes | 61 | | edge | Yes | | Yes | 62 | | edge-headless | Yes | | Yes | 63 | | safari | Yes | | | 64 | 65 | > browser-do v4+ dropped support of Microsoft IE. To work with IE, please use browser-do v3. 66 | > browser-do v2+ only supports **Chromium based Microsoft Edge**. To work with old Microsoft Edge, please use browser-do v1. 67 | 68 | ## Usage 69 | 70 | ``` 71 | Usage: browser-do [options] 72 | 73 | Options: 74 | -V, --version output the version number 75 | -b, --browser Browser to use, see available browsers below (default: "electron") 76 | -p, --port Starts listening on that port and waits for you to open a browser 77 | -s, --static Serve static assets from this directory 78 | -m, --mock Path to code to handle requests for mocking a dynamic back-end 79 | -t, --tap Treat output as TAP test result, automatically exit when TAP finishes 80 | --jasmine Support jasmine test, uses jasmine TAP reporter, implicitly turns on option "tap", automatically exit when TAP finishes 81 | --mocha Support mocha test, assumes BDD setup, uses TAP reporter, implicitly turns on option "tap", automatically exit when TAP finishes 82 | -k, --keep-open Only for -t, --tap, --jasmine and --mocha, leave the browser open for debugging after running tests 83 | -h, --help output usage information 84 | 85 | Available browsers if installed (for -b, --browser ): 86 | electron (embedded, default choice), chrome, chrome-headless, chromium, chromium-headless, firefox, firefox-headless, edge, edge-headless, safari 87 | 88 | There is some tolerance on browser name, for example: 89 | -b ChromeHeadless 90 | -b chromeHeadless 91 | -b chrome_headless 92 | -b "chrome headless" 93 | all work just like -b chrome-headless 94 | ``` 95 | 96 | ### Custom html file 97 | 98 | Your can provide a custom html file for browser-do to use. Keep in mind it always needs to have `` above other script tags so browser-do is able to properly forward your `console.log`s etc to the terminal. 99 | 100 | > Different from browser-run, you don't need `--input html`, browser-do detects the input automatically. 101 | 102 | > You would need to combine custom html file with `--static some-dir` or `--mock mock-code.js` in order to have some way to load your JavaScript code. 103 | 104 | ### Dynamic back-end mock 105 | 106 | By using `--mock mock.js` (or `{ mock: 'mock.js'}` in code) you can provide a custom server-side implementation and handle all requests that are sent to paths beginning with `/mock` 107 | 108 | mock.js needs to export a function that accepts `req` and `res` arguments for handling requests. 109 | 110 | Example: 111 | 112 | ```js 113 | module.exports = function(req, res){ 114 | if (req.url === '/mock/echo') { 115 | req.pipe(res); 116 | } 117 | }; 118 | ``` 119 | 120 | ### Run browser-do in code 121 | 122 | API: `run(opts)`, all the opts have same meaning as the command line options. 123 | 124 | `port`, `browser`, `static`, `mock`, `tap`, `jasmine`, `mocha`, and `keepOpen`. 125 | 126 | ```js 127 | var run = require('browser-do'); 128 | 129 | var browser = run(); 130 | browser.pipe(process.stdout); 131 | browser.end('console.log(location); window.close()'); 132 | ``` 133 | 134 | > Note `window.close()` will quit the default electron browser, but it would not work with some other browsers. Because many browsers reject `window.close()` for a window not opened by JavaScript. (In browser perspective, it opened a URL, although browser-do programmatically did that.) 135 | 136 | ### Close browser window by code 137 | 138 | When using browser-do in code with a browser not electron, you have to close the window manually (only if you didn't use `tap`, `jasmine` or `mocha` option). 139 | 140 | ```js 141 | var run = require('browser-do'); 142 | 143 | var browser = run({browser: 'chrome'}); 144 | browser.pipe(process.stdout); 145 | browser.end('console.log(location);'); 146 | 147 | setTimeout(function() { browser.stop(); }, 5000); 148 | ``` 149 | 150 | ### Get TAP result by code 151 | 152 | Follow example takes unit test JS code from stdin, capture final result (either pass or fail). 153 | 154 | ```js 155 | var run = require('browser-do'); 156 | 157 | var browser = run({tap: true}); // or jasmine: true, or mocha: true 158 | process.stdin.pipe(browser).pipe(process.stdout); 159 | 160 | browser.on('exit', code => { 161 | // the code is 0 for passed tests, 1 for failed tests 162 | }); 163 | ``` 164 | 165 | > Note browser-do only generates a simple pass/fail result from the whole TAP output. browser-do retains original TAP output, so if you need detailed TAP output parsing, further pipe the stream to [tap-parser](https://github.com/tapjs/tap-parser). 166 | 167 | ### Unit tests support 168 | 169 | browser-do conveniently supports running [mocha](https://mochajs.org), [jasmine](https://jasmine.github.io), [tape](https://github.com/substack/tape) unit tests out of the box. 170 | 171 | #### Tape 172 | 173 | Tape is easy to support, as it doesn't pollute global variables. All browser-do needs to do is to parse [TAP output](http://testanything.org) and automatically exit when tests finish. 174 | 175 | ```bash 176 | esbuild some-tap-test.js --bundle | browser-do -t # or --tap 177 | ``` 178 | 179 | #### Zora 180 | 181 | Zora is the same story as Tape. 182 | ```bash 183 | esbuild some-zora-test.js --bundle | browser-do -t # or --tap 184 | ``` 185 | 186 | #### Jasmine 187 | 188 | browser-do helps jasmine test by setup global vars before running your code. 189 | 190 | ```bash 191 | esbuild some-jasmine-test.js --bundle | browser-do --jasmine 192 | ``` 193 | 194 | You don't need to load jasmine before running your code, browser-do does that automatically, as long as you did `npm i -D jasmine-core`. 195 | 196 | FYI, here is the special `index.html` browser-do provided for jasmine. Showing here only to help you to understand how browser-do does the magic. 197 | 198 | ```html 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | ``` 216 | 217 | #### Mocha 218 | 219 | browser-do helps mocha test by setup global vars before running your code. 220 | 221 | ```bash 222 | esbuild some-mocha-test.js --bundle | browser-do --mocha 223 | ``` 224 | 225 | You don't need to load mocha before running your code, browser-do does that automatically, as long as you did `npm i -D mocha`. 226 | 227 | FYI, here is the special `index.html` browser-do provided for mocha. Showing here only to help you to understand how browser-do does the magic. 228 | 229 | Note we use default BDD style in mocha. 230 | 231 | ```html 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 |
241 | 242 | 245 | 246 | 249 | 250 | 251 | ``` 252 | 253 | > The default mocha setup uses "tap" reporter so browser-do can understand tests result. 254 | 255 | > **Only for mocha, when `-k, --keep-open` option is on, it switches setup to use "html" reporter.** Because mocha doesn't support multiple reporters, and we want to show user a nice result in browser window. As a result, browser-do cannot detect the final test result in `keepOpen` mode. 256 | 257 | If you want to use different setup of mocha, just pipe a custom html file to browser-do 258 | 259 | ```bash 260 | cat my-mocha-index.html | browser-do --mocha --static . 261 | # or avoid "cat" on windows 262 | browser-do --mocha --static . < my-mocha-index.html 263 | ``` 264 | 265 | In your special html file: 266 | 267 | 1. you need `` above any script tags. 268 | 2. you need retain most of the above html file, just modify the mocha setup part. 269 | 2. you need something like `` to replace ``, you need to make sure you generated that bundle file before using browser-do. 270 | 3. The `--static .` option is to let browser-do to access all the local files including the `dist/my-prepared-bundle.js`. 271 | 272 | ## CI setup 273 | 274 | ### GitHub Actions 275 | 276 | When running unit tests with browser-do, you need xvfb on Linux. 277 | 278 | ```yml 279 | - run: xvfb-run -a npm test 280 | if: runner.os == 'Linux' 281 | - run: npm test 282 | if: runner.os != 'Linux' 283 | ``` 284 | 285 | You may also need to enlarge xvfb default screen size (640x480x8) for your tests. 286 | 287 | ```sh 288 | xvfb-run -a -s '-screen 0 1024x768x24' npm test 289 | ``` 290 | 291 | ### Travis 292 | 293 | To use browser-do on travis, add this to your `.travis.yml`: 294 | 295 | ```yml 296 | addons: 297 | apt: 298 | packages: 299 | - xvfb 300 | before_install: 301 | - export DISPLAY=':99.0' 302 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 303 | ``` 304 | 305 | If you run travis with multiple OS including windows: 306 | 307 | ```yml 308 | os: 309 | - linux 310 | - windows 311 | - osx 312 | addons: 313 | apt: 314 | packages: 315 | - xvfb 316 | before_install: 317 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=':99.0' ; fi 318 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & fi 319 | ``` 320 | 321 | # Code coverage report 322 | 323 | browser-do supports code coverage in a humble way. To understand how browser-do supports it, first we need to understand how [nyc/Istanbul](https://istanbul.js.org) works internally. 324 | 325 | To generate code coverage report, when in Node.js environment, nyc/Istanbul needs to 326 | 1. instrument source code. It injects special code into your source code in order to gather code coverage information. 327 | 2. when running unit tests, those instrumented source code writes code coverage information into an intermediate JavaScript object `global.__coverage__`. This becomes `window.__coverage__` in browser. 328 | 3. nyc automatically writes `global.__coverage__` object to a local json file in `.nyc_output/` folder. 329 | 4. nyc then uses various reporters to turn the json file into human readable report (text summary, or html file). 330 | 331 | When running browser-do, your unit tests is running in browser. All browser-do does is: 332 | 1. if your source code is instrumented, it will generate `window.__coverage__` object. browser-do does nothing here. 333 | 2. when tape/jasmine/mocha tests finished, browser-do check whether there is `window.__coverage__`. If so, it will write the object to `.nyc_output/out.json` (the default nyc output file). 334 | 335 | You then can follow it up with another command to turn that json file into readable report! 336 | 337 | ```bash 338 | npx nyc report --reporter=lcov --reporter=text 339 | ``` 340 | 341 | Here browser-do does the bare minimum job: only writes `window.__coverage__` into `.nyc_output/out.json` when `window.__coverage__` exits. 342 | 343 | The task of instrumenting is not handled by browser-do, nor should (as browser-do is the consumer of a piece of JavaScript, not the creator). 344 | 345 | ### babel-plugin-istanbul 346 | 347 | For projects using babel, you can easily turn on [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) in test environment. That plugin will instrument your code. 348 | 349 | > I have not found out what's the native way to instrument TypeScript file. But if you use [babel to compile ts files](https://babeljs.io/docs/en/babel-preset-typescript), you can continue to use babel-plugin-istanbul. 350 | 351 | For example on babel-plugin-istanbul + browser-do + nyc, try to create a SPA app with dumberjs skeleton: 352 | 353 | ```bash 354 | npx makes dumberjs 355 | ``` 356 | 357 | Choose Aurelia or Vue (but not .vue single file component), babel, jasmine/mocha/tape, (not jest. jest runs in Node.js, not browser, is irrelevant here). 358 | Then follow the README file on code coverage. Note the React non-jest setup has some trouble in code coverage right now. 359 | 360 | ## License 361 | 362 | browser-do is licensed under the [MIT license](https://github.com/3cp/browser-do/blob/master/LICENSE). 363 | 364 | ## Acknowledgement 365 | 366 | browser-do borrowed code from many projects, details in [ACKNOWLEDGEMENT](https://github.com/3cp/browser-do/blob/master/ACKNOWLEDGEMENT.md). 367 | 368 | -------------------------------------------------------------------------------- /bin/browser-do.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | 4 | const { program } = require('commander'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const browserDo = require('../index'); 8 | 9 | program 10 | .version(require('../package.json').version) 11 | .option('-b, --browser ', 'Browser to use, see available browsers below', 'electron') 12 | .option('-p, --port ', 'Starts listening on that port and waits for you to open a browser') 13 | .option('-s, --static ', 'Serve static assets from this directory') 14 | .option('-m, --mock ', 'Path to code to handle requests for mocking a dynamic back-end') 15 | .option('-t, --tap', 'Treat output as TAP test result, automatically exit when TAP finishes') 16 | .option('--jasmine', 'Support jasmine test, uses jasmine TAP reporter, implicitly turns on option "tap", automatically exit when TAP finishes') 17 | .option('--mocha', 'Support mocha test, assumes BDD setup, uses TAP reporter, implicitly turns on option "tap", automatically exit when TAP finishes') 18 | .option('-k, --keep-open', 'Only for -t, --tap, --jasmine and --mocha, leave the browser open for debugging after running tests') 19 | .addHelpText('after', ` 20 | Available browsers if installed (for -b, --browser ): 21 | electron (embedded, default choice), chrome, chrome-headless, chromium, chromium-headless, firefox, firefox-headless, edge, edge-headless, safari 22 | 23 | There is some tolerance on browser name, for example: 24 | -b ChromeHeadless 25 | -b chromeHeadless 26 | -b chrome_headless 27 | -b "chrome headless" 28 | all work just like -b chrome-headless 29 | `) 30 | .parse(process.argv); 31 | 32 | function onCoverage(result) { 33 | if (!result) return; 34 | 35 | try { 36 | fs.mkdirSync('.nyc_output'); 37 | } catch (e) { 38 | if (e.code !== 'EEXIST') throw e; 39 | } 40 | 41 | fs.writeFileSync(path.join('.nyc_output', 'out.json'), result); 42 | console.log('# code coverage is written to .nyc_output/out.json\n' + 43 | '# you can use "npx nyc report --reporter=lcov --reporter=text"\n' + 44 | '# then view coverage/lcov-report/index.html in a browser\n'); 45 | } 46 | 47 | const options = program.opts(); 48 | options.onCoverage = onCoverage; 49 | 50 | const run = browserDo(options); 51 | process.stdin 52 | .pipe(run) 53 | .pipe(process.stdout); 54 | 55 | run.on('exit', code => process.exit(code)); 56 | process.on('exit', () => run.stop()); 57 | 58 | process.on('SIGINT', () => { 59 | // manually call process.exit() so it will trigger 'exit' event. 60 | process.exit(run.failed ? 1 : 0); 61 | }); 62 | -------------------------------------------------------------------------------- /build-reporter.js: -------------------------------------------------------------------------------- 1 | const { build } = require("esbuild"); 2 | const { polyfillNode } = require("esbuild-plugin-polyfill-node"); 3 | 4 | build({ 5 | entryPoints: ["reporter.js"], 6 | bundle: true, 7 | outfile: "dist/reporter.js", 8 | plugins: [ 9 | polyfillNode(), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const run = require('./lib/browser-run'); 3 | const {Transform} = require('stream'); 4 | const tapParse = require('./lib/tap-parse'); 5 | 6 | module.exports = function(opts = {}) { 7 | const tap = opts.tap || opts.tape || opts.jasmine || opts.mocha; 8 | 9 | const chunks = []; 10 | const dpl = new Transform({ 11 | transform(chunk, enc, cb) { 12 | chunks.push(chunk); 13 | cb(); 14 | }, 15 | flush(cb) { 16 | const self = this; 17 | let failed = false; 18 | let browserDo; 19 | self.stop = () => browserDo && browserDo.stop(); 20 | const holdOutput = new Transform({ 21 | transform(chunk, enc, _cb) { 22 | _cb(null, chunk); 23 | self.push(chunk); 24 | }, 25 | flush(_cb) { 26 | if (!tap) cb(); 27 | _cb(); 28 | } 29 | }); 30 | 31 | const data = Buffer.concat(chunks).toString(); 32 | browserDo = run(opts, data, holdOutput); 33 | 34 | if (tap) { 35 | tapParse(holdOutput, (err, passed) => { 36 | failed = !passed; 37 | 38 | if (err) { 39 | console.error(err.message); 40 | } 41 | 42 | if (opts.onCoverage) { 43 | browserDo.askCoverage(); 44 | } 45 | 46 | if (!opts.keepOpen) { 47 | setTimeout(() => { 48 | if (opts.onCoverage) { 49 | browserDo.checkCoverage(opts.onCoverage); 50 | } 51 | self.stop(); 52 | cb(); 53 | self.emit('exit', failed ? 1 : 0); 54 | }, 1000); 55 | } 56 | }); 57 | } 58 | } 59 | }); 60 | 61 | return dpl; 62 | }; 63 | -------------------------------------------------------------------------------- /jasmine-tap-reporter.js: -------------------------------------------------------------------------------- 1 | // https://github.com/larrymyers/jasmine-reporters/blob/master/src/tap_reporter.js 2 | (function(global) { 3 | function trim(str) { return str.replace(/^\s+/, "").replace(/\s+$/, ""); } 4 | function elapsed(start, end) { return (end - start)/1000; } 5 | function isFailed(obj) { return obj.status === "failed"; } 6 | function isSkipped(obj) { return obj.status === "pending"; } 7 | function isExcluded(obj) { return obj.status === "excluded" || obj.status === "disabled"; } 8 | function extend(dupe, obj) { // performs a shallow copy of all props of `obj` onto `dupe` 9 | for (var prop in obj) { 10 | if (obj.hasOwnProperty(prop)) { 11 | dupe[prop] = obj[prop]; 12 | } 13 | } 14 | return dupe; 15 | } 16 | function log(str) { 17 | var con = global.console || console; 18 | if (con && con.log) { 19 | con.log(str); 20 | } 21 | } 22 | 23 | /** 24 | * TAP (http://en.wikipedia.org/wiki/Test_Anything_Protocol) reporter. 25 | * outputs spec results to the console. 26 | */ 27 | var TapReporter = function() { 28 | var self = this; 29 | self.started = false; 30 | self.finished = false; 31 | 32 | var startTime, 33 | endTime, 34 | currentSuite = null, 35 | totalSpecsExecuted = 0, 36 | totalSpecsSkipped = 0, 37 | totalSpecsFailed = 0, 38 | totalSpecsDefined, 39 | // when use use fit, jasmine never calls suiteStarted / suiteDone, so make a fake one to use 40 | fakeFocusedSuite = { 41 | id: "focused", 42 | description: "focused specs", 43 | fullName: "focused specs" 44 | }; 45 | 46 | var __suites = {}, __specs = {}; 47 | function getSuite(suite) { 48 | __suites[suite.id] = extend(__suites[suite.id] || {}, suite); 49 | return __suites[suite.id]; 50 | } 51 | function getSpec(spec, suite) { 52 | __specs[spec.id] = extend(__specs[spec.id] || {}, spec); 53 | var ret = __specs[spec.id]; 54 | if (suite && !ret._suite) { 55 | ret._suite = suite; 56 | } 57 | return ret; 58 | } 59 | 60 | self.jasmineStarted = function(summary) { 61 | log("TAP version 13"); 62 | self.started = true; 63 | totalSpecsDefined = summary && summary.totalSpecsDefined || NaN; 64 | startTime = new Date(); 65 | }; 66 | self.suiteStarted = function(suite) { 67 | suite = getSuite(suite); 68 | currentSuite = suite; 69 | }; 70 | self.specStarted = function() { 71 | if (!currentSuite) { 72 | // focused spec (fit) -- suiteStarted was never called 73 | self.suiteStarted(fakeFocusedSuite); 74 | } 75 | totalSpecsExecuted++; 76 | }; 77 | self.specDone = function(spec) { 78 | spec = getSpec(spec, currentSuite); 79 | var resultStr = "ok " + totalSpecsExecuted + " - " + spec._suite.description + " : " + spec.description; 80 | if (isFailed(spec)) { 81 | totalSpecsFailed++; 82 | resultStr = "not " + resultStr; 83 | for (var i = 0, failure; i < spec.failedExpectations.length; i++) { 84 | failure = spec.failedExpectations[i]; 85 | resultStr += "\n # Failure: " + trim(failure.message); 86 | if (failure.stack && failure.stack !== failure.message) { 87 | resultStr += "\n # === STACK TRACE ==="; 88 | resultStr += "\n # " + failure.stack.replace(/\n/mg, "\n # "); 89 | resultStr += "\n # === END STACK TRACE ==="; 90 | } 91 | } 92 | } 93 | if (isSkipped(spec)) { 94 | totalSpecsExecuted--; 95 | totalSpecsSkipped++; 96 | resultStr = "# Skipped (xit or xdescribe): " + spec._suite.description + " : " + spec.description; 97 | } 98 | if (isExcluded(spec)) { 99 | totalSpecsExecuted--; 100 | totalSpecsSkipped++; 101 | resultStr = "# Excluded (by fit or fdescribe on other spec): " + spec._suite.description + " : " + spec.description; 102 | } 103 | log(resultStr); 104 | }; 105 | self.suiteDone = function(suite) { 106 | suite = getSuite(suite); 107 | if (suite._parent === undefined) { 108 | // disabled suite (xdescribe) -- suiteStarted was never called 109 | self.suiteStarted(suite); 110 | } 111 | currentSuite = suite._parent; 112 | }; 113 | self.jasmineDone = function() { 114 | if (currentSuite) { 115 | // focused spec (fit) -- suiteDone was never called 116 | self.suiteDone(fakeFocusedSuite); 117 | } 118 | endTime = new Date(); 119 | var dur = elapsed(startTime, endTime), 120 | totalSpecs = totalSpecsDefined || totalSpecsExecuted; 121 | 122 | if (totalSpecsExecuted === 0) { 123 | log("1..0 # All tests disabled"); 124 | } else { 125 | log("1.." + totalSpecsExecuted); 126 | } 127 | var diagStr = "#"; 128 | diagStr = "# " + totalSpecs + " spec" + (totalSpecs < 2 ? "" : "s"); 129 | diagStr += ", " + totalSpecsFailed + " failure" + (totalSpecsFailed < 2 ? "" : "s"); 130 | diagStr += ", " + totalSpecsSkipped + " skipped"; 131 | diagStr += " in " + dur + "s."; 132 | log(diagStr); 133 | 134 | self.finished = true; 135 | }; 136 | }; 137 | 138 | global.jasmine.getEnv().addReporter(new TapReporter()); 139 | })(this); -------------------------------------------------------------------------------- /lib/browser-run.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const http = require('http'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const socketIo = require('socket.io'); 6 | const launch = require('./launch'); 7 | const serveStatic = require('serve-static'); 8 | const finalhandler = require('finalhandler'); 9 | const destroyable = require('server-destroy'); 10 | const kebabCase = require('lodash.kebabcase'); 11 | const getBrowser = require('./get-browser'); 12 | const c = require('ansi-colors'); 13 | 14 | const reporterPath = path.join(__dirname, '..', 'dist', 'reporter.js'); 15 | const jasmineTapReporterPath = path.join(__dirname, '..', 'jasmine-tap-reporter.js'); 16 | 17 | function once(func) { 18 | let ran = false; 19 | return function() { 20 | if (ran) return; 21 | ran = true; 22 | return func.apply(null, arguments); 23 | }; 24 | } 25 | 26 | try { 27 | fs.statSync(reporterPath); 28 | } catch (_) { 29 | console.error('Reporter script missing.'); 30 | } 31 | 32 | module.exports = function (opts, data, output) { 33 | if (!opts) opts = {}; 34 | if ('number' == typeof opts) opts = { port: opts }; 35 | if (!opts.browser) opts.browser = 'electron'; 36 | return runner(opts, data, output); 37 | }; 38 | 39 | function runner (opts, data, output) { 40 | const browserName = kebabCase(opts.browser); 41 | const browser = getBrowser(browserName); 42 | 43 | if (!browser) { 44 | console.error('No browser found for ' + opts.browser); 45 | process.exit(1); 46 | } 47 | 48 | const isHtmlData = data.match(/^\s*'); 88 | res.write(''); 89 | 90 | if (opts.jasmine) { 91 | res.write(''); 92 | } 93 | 94 | if (opts.mocha) { 95 | res.write(''); 96 | } 97 | 98 | res.write(''); 99 | 100 | if (opts.jasmine) { 101 | res.write(''); 102 | res.write(''); 103 | // jasmine-core v4 removed boot.js 104 | // both jasmine-core v3 and v4 provide boot0.js and boot1.js 105 | res.write(''); 106 | res.write(''); 107 | res.write(''); 108 | } 109 | 110 | if (opts.mocha) { 111 | res.write('
'); 112 | res.write(''); 113 | res.write(``); 116 | } 117 | 118 | res.write(''); 119 | 120 | if (opts.mocha) { 121 | res.write(''); 122 | } 123 | 124 | res.end(''); 125 | return; 126 | } 127 | } 128 | 129 | if (req.url == '/reporter.js') { 130 | res.setHeader('content-type', 'application/javascript'); 131 | fs.createReadStream(reporterPath).pipe(res); 132 | return; 133 | } 134 | 135 | if (req.url == '/jasmine-tap-reporter.js') { 136 | res.setHeader('content-type', 'application/javascript'); 137 | fs.createReadStream(jasmineTapReporterPath).pipe(res); 138 | return; 139 | } 140 | 141 | const m = req.url.match(/^\/jasmine-core\/(.+)/); 142 | 143 | if (m) { 144 | const fn = m[1]; 145 | if (path.extname(fn) === '.js') { 146 | res.setHeader('content-type', 'application/javascript'); 147 | } else if (path.extname(fn) === '.css') { 148 | res.setHeader('content-type', 'text/css'); 149 | } 150 | 151 | fs.createReadStream(path.join(jasminePath, fn)).pipe(res); 152 | return; 153 | } 154 | 155 | const m2 = req.url.match(/^\/mocha\/(.+)/); 156 | 157 | if (m2) { 158 | const fn = m2[1]; 159 | if (path.extname(fn) === '.js') { 160 | res.setHeader('content-type', 'application/javascript'); 161 | } else if (path.extname(fn) === '.css') { 162 | res.setHeader('content-type', 'text/css'); 163 | } 164 | 165 | fs.createReadStream(path.join(mochaPath, fn)).pipe(res); 166 | return; 167 | } 168 | 169 | if (opts.static) { 170 | serveStatic(opts.static)(req, res, finalhandler(req, res)); 171 | return; 172 | } 173 | 174 | if (mockHandler && '/mock' === req.url.slice(0,5)){ 175 | return mockHandler(req, res); 176 | } 177 | 178 | res.end('not supported'); 179 | }); 180 | 181 | const io = socketIo(server, { 182 | serverClient: false, 183 | // Use large timeout to cater huge test file. 184 | pingTimeout: 50000, 185 | pingInterval: 500000, 186 | upgradeTimeout: 50000, 187 | cookie: false 188 | }); 189 | 190 | let coverage; 191 | io.on('connection', socket => { 192 | socket.on('log', msg => output.write(msg + '\n')); 193 | socket.on('debug', msg => console.info(c.bgWhite(msg))); 194 | socket.on('info', msg => console.info(c.bgCyan(msg))); 195 | socket.on('warn', msg => console.warn(c.bgYellow(msg))); 196 | socket.on('error', msg => console.error(c.bgRed(msg))); 197 | socket.on('coverage', msg => coverage = msg); 198 | }); 199 | 200 | destroyable(server); 201 | 202 | let browserProc; 203 | 204 | if (opts.port) { 205 | server.listen(opts.port); 206 | console.log('Web server is up at http://localhost:' + opts.port); 207 | } else { 208 | server.listen(function () { 209 | var address = server.address(); 210 | if (!address) return; // already closed 211 | var port = address.port; 212 | 213 | try { 214 | browserProc = launch('http://localhost:' + port, browser); 215 | } catch (err) { 216 | stop(); 217 | console.error(err); 218 | process.exit(1); 219 | } 220 | 221 | browserProc.on('exit', stop); 222 | }); 223 | } 224 | 225 | const stop = once(function() { 226 | try { 227 | output.end(); 228 | server.destroy(); 229 | } catch (err) { 230 | // ignore 231 | console.error(err); 232 | } 233 | 234 | if (browserProc && !browserProc.killed) browserProc.kill(); 235 | }); 236 | 237 | const askCoverage = once(function() { 238 | try { 239 | io.emit('ask-coverage'); 240 | } catch(e) { 241 | // ignore 242 | } 243 | }); 244 | 245 | function checkCoverage(cb) { 246 | if (cb && coverage) { 247 | cb(coverage); 248 | } 249 | } 250 | 251 | return {stop, askCoverage, checkCoverage}; 252 | } 253 | -------------------------------------------------------------------------------- /lib/browsers/_chromium-args.js: -------------------------------------------------------------------------------- 1 | exports.args = [ 2 | // https://github.com/GoogleChrome/chrome-launcher/blob/master/docs/chrome-flags-for-tools.md#--enable-automation 3 | '--enable-automation', 4 | '--no-default-browser-check', 5 | '--no-first-run', 6 | '--disable-default-apps', 7 | '--disable-popup-blocking', 8 | '--disable-translate', 9 | '--disable-background-timer-throttling', 10 | // on macOS, disable-background-timer-throttling is not enough 11 | // and we need disable-renderer-backgrounding too 12 | // see https://github.com/karma-runner/karma-chrome-launcher/issues/123 13 | '--disable-renderer-backgrounding', 14 | '--disable-device-discovery-notifications', 15 | '--disable-background-networking', 16 | '--enable-features=NetworkService,NetworkServiceInProcess', 17 | '--disable-backgrounding-occluded-windows', 18 | '--disable-breakpad', 19 | '--disable-client-side-phishing-detection', 20 | '--disable-component-extensions-with-background-pages', 21 | '--disable-dev-shm-usage', 22 | '--disable-extensions', 23 | '--disable-features=TranslateUI', 24 | '--disable-hang-monitor', 25 | '--disable-ipc-flooding-protection', 26 | '--disable-prompt-on-repost', 27 | '--disable-sync', 28 | '--force-color-profile=srgb', 29 | '--metrics-recording-only', 30 | '--password-store=basic', 31 | '--use-mock-keychain', 32 | '--no-sandbox' 33 | ]; 34 | 35 | exports.headlessArgs = [ 36 | '--headless', 37 | '--disable-gpu', 38 | '--hide-scrollbars', 39 | '--mute-audio', 40 | '--remote-debugging-port=9222' 41 | ]; 42 | -------------------------------------------------------------------------------- /lib/browsers/chrome-headless.js: -------------------------------------------------------------------------------- 1 | const chrome = require('./chrome'); 2 | const headlessArgs = require('./_chromium-args').headlessArgs; 3 | 4 | module.exports = function() { 5 | const info = chrome(); 6 | 7 | return { 8 | ...info, 9 | args: [ 10 | ...info.args, 11 | ...headlessArgs 12 | ] 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/browsers/chrome.js: -------------------------------------------------------------------------------- 1 | const getBin = require('../get-bin'); 2 | const getDarwinBin = require('../get-darwin-bin'); 3 | const getExe = require('../get-exe'); 4 | const args = require('./_chromium-args').args; 5 | const tmp = require('tmp'); 6 | tmp.setGracefulCleanup(); 7 | 8 | module.exports = function() { 9 | const tmpobj = tmp.dirSync({unsafeCleanup: true}); 10 | 11 | return { 12 | path: { 13 | linux: getBin(['google-chrome', 'google-chrome-stable']), 14 | darwin: getDarwinBin('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'), 15 | win32: getExe('\\Google\\Chrome\\Application\\chrome.exe') 16 | }, 17 | args: [ 18 | '--user-data-dir=' + tmpobj.name, 19 | ...args 20 | ] 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/browsers/chromium-headless.js: -------------------------------------------------------------------------------- 1 | const chromium = require('./chromium'); 2 | const headlessArgs = require('./_chromium-args').headlessArgs; 3 | 4 | module.exports = function() { 5 | const info = chromium(); 6 | 7 | return { 8 | ...info, 9 | args: [ 10 | ...info.args, 11 | ...headlessArgs 12 | ] 13 | }; 14 | }; -------------------------------------------------------------------------------- /lib/browsers/chromium.js: -------------------------------------------------------------------------------- 1 | const getBin = require('../get-bin'); 2 | const getDarwinBin = require('../get-darwin-bin'); 3 | const getExe = require('../get-exe'); 4 | const args = require('./_chromium-args').args; 5 | const tmp = require('tmp'); 6 | tmp.setGracefulCleanup(); 7 | 8 | module.exports = function() { 9 | const tmpobj = tmp.dirSync({unsafeCleanup: true}); 10 | 11 | return { 12 | path: { 13 | linux: getBin(['chromium-browser', 'chromium']), 14 | darwin: getDarwinBin('/Applications/Chromium.app/Contents/MacOS/Chromium'), 15 | win32: getExe('\\Chromium\\Application\\chrome.exe') 16 | }, 17 | args: [ 18 | '--user-data-dir=' + tmpobj.name, 19 | ...args 20 | ] 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/browsers/edge-headless.js: -------------------------------------------------------------------------------- 1 | const edge = require('./edge'); 2 | const headlessArgs = require('./_chromium-args').headlessArgs; 3 | 4 | module.exports = function() { 5 | const info = edge(); 6 | 7 | return { 8 | ...info, 9 | args: [ 10 | ...info.args, 11 | ...headlessArgs 12 | ] 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/browsers/edge.js: -------------------------------------------------------------------------------- 1 | const getDarwinBin = require('../get-darwin-bin'); 2 | const getExe = require('../get-exe'); 3 | const args = require('./_chromium-args').args; 4 | const tmp = require('tmp'); 5 | tmp.setGracefulCleanup(); 6 | 7 | module.exports = function() { 8 | const tmpobj = tmp.dirSync({unsafeCleanup: true}); 9 | 10 | return { 11 | path: { 12 | darwin: getDarwinBin('/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'), 13 | win32: getExe('Microsoft\\Edge\\Application\\msedge.exe') 14 | }, 15 | args: [ 16 | '--user-data-dir=' + tmpobj.name, 17 | ...args 18 | ] 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/browsers/electron-runner.js: -------------------------------------------------------------------------------- 1 | // When we run into an uncaught exception, fail hard 2 | // DEV: Without this line, Karma can hang indefinitely 3 | process.on('uncaughtException', function handleUncaughtException (err) { 4 | throw err; 5 | }); 6 | 7 | // Load in our dependencies 8 | var app = require('electron').app; 9 | var BrowserWindow = require('electron').BrowserWindow; 10 | var [userDataDir, url] = process.argv.slice(-2); 11 | 12 | // When all windows are closed, exit out 13 | app.on('window-all-closed', function handleWindowsClosed () { 14 | app.quit(); 15 | }); 16 | 17 | // Update `userData` before Electron loads 18 | // DEV: This prevents cookies/localStorage from contaminating across apps 19 | app.setPath('userData', userDataDir); 20 | 21 | // When Electron is done loading, launch our applicaiton 22 | app.on('ready', function() { 23 | // Create our browser window 24 | var browserWindow = new BrowserWindow({ 25 | show: false, 26 | webPreferences: { 27 | webSecurity: false, 28 | allowRunningInsecureContent: true 29 | } 30 | }); 31 | browserWindow.loadURL(url); 32 | }); 33 | -------------------------------------------------------------------------------- /lib/browsers/electron.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const path = require('path'); 3 | const tmp = require('tmp'); 4 | tmp.setGracefulCleanup(); 5 | 6 | module.exports = function() { 7 | const tmpobj = tmp.dirSync({unsafeCleanup: true}); 8 | 9 | return { 10 | path: { 11 | linux: electron, 12 | darwin: electron, 13 | win32: electron 14 | }, 15 | args: [ 16 | path.join(__dirname, 'electron-runner.js'), 17 | tmpobj.name 18 | ] 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /lib/browsers/firefox-headless.js: -------------------------------------------------------------------------------- 1 | const firefox = require('./firefox'); 2 | 3 | module.exports = function() { 4 | const info = firefox(); 5 | 6 | return { 7 | ...info, 8 | args: [ 9 | ...info.args, 10 | '-headless' 11 | ] 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /lib/browsers/firefox.js: -------------------------------------------------------------------------------- 1 | const getBin = require('../get-bin'); 2 | const getDarwinBin = require('../get-darwin-bin'); 3 | const getExe = require('../get-exe'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const tmp = require('tmp'); 7 | tmp.setGracefulCleanup(); 8 | 9 | const prefs = ` 10 | user_pref("browser.shell.checkDefaultBrowser", false); 11 | user_pref("browser.bookmarks.restore_default_bookmarks", false); 12 | user_pref("dom.disable_open_during_load", false); 13 | user_pref("dom.max_script_run_time", 0); 14 | user_pref("dom.min_background_timeout_value", 10); 15 | user_pref("extensions.autoDisableScopes", 0); 16 | user_pref("browser.tabs.remote.autostart", false); 17 | user_pref("browser.tabs.remote.autostart.2", false); 18 | user_pref("extensions.enabledScopes", 15); 19 | `; 20 | 21 | const paths = { 22 | linux: getBin(['firefox']), 23 | darwin: getDarwinBin('/Applications/Firefox.app/Contents/MacOS/firefox-bin'), 24 | win32: getExe('\\Mozilla Firefox\\firefox.exe') 25 | }; 26 | 27 | module.exports = function() { 28 | const tmpobj = tmp.dirSync({unsafeCleanup: true}); 29 | 30 | fs.writeFileSync(path.join(tmpobj.name, 'user.js'), prefs); 31 | 32 | return { 33 | path: paths, 34 | args: [ 35 | '-profile', 36 | tmpobj.name, 37 | '-no-remote' 38 | ] 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/browsers/safari.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | 3 | module.exports = { 4 | path: { 5 | darwin: 'open' 6 | }, 7 | args: [ 8 | '-F', // Opens the application "fresh," that is, without restoring windows. 9 | '-W', // Causes open to wait until the applications it opens (or that were already open) have exited. 10 | '-n', // Open a new instance of the application(s) even if one is already running. 11 | // '-g', // Do not bring the application to the foreground. 12 | // '-a', // Specifies the application to use for opening the file. 13 | // getDarwinBin('/Applications/Safari.app/Contents/MacOS/Safari') 14 | '-b', // Specifies the bundle identifier for the application to use when opening the file. 15 | 'com.apple.Safari' 16 | ], 17 | onExit() { 18 | // kill the last Safari process, this is the best guess. 19 | try { 20 | execSync('pkill -nx Safari'); 21 | } catch (e) { 22 | // ignore 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/get-bin.js: -------------------------------------------------------------------------------- 1 | const which = require('which'); 2 | 3 | module.exports = function(commands) { 4 | if (process.platform !== 'linux') { 5 | return null; 6 | } 7 | for (let i = 0; i < commands.length; i++) { 8 | try { 9 | if (which.sync(commands[i])) { 10 | return commands[i]; 11 | } 12 | } catch (e) { 13 | // ignore 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /lib/get-browser.js: -------------------------------------------------------------------------------- 1 | function load(name) { 2 | return require('./browsers/' + name); 3 | } 4 | 5 | module.exports = function(browsername, _load = load) { 6 | let browser; 7 | try { 8 | browser = _load(browsername); 9 | } catch (e) { 10 | return; 11 | } 12 | 13 | if (typeof browser === 'function') { 14 | browser = browser(); 15 | } 16 | 17 | if (!browser) return; 18 | 19 | const bpath = browser.path[process.platform]; 20 | if (bpath) { 21 | return {...browser, path: bpath}; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /lib/get-darwin-bin.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | module.exports = function(defaultPath) { 5 | if (process.platform !== 'darwin') { 6 | return null; 7 | } 8 | 9 | try { 10 | var homePath = path.join(process.env.HOME, defaultPath); 11 | fs.accessSync(homePath); 12 | return homePath; 13 | } catch (e) { 14 | try { 15 | fs.accessSync(defaultPath); 16 | return defaultPath; 17 | } catch (e) { 18 | return null; 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /lib/get-exe.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env['PROGRAMFILES(X86)']]; 4 | 5 | module.exports = function(suffix) { 6 | // Only run these checks on win32 7 | if (process.platform !== 'win32') { 8 | return null; 9 | } 10 | 11 | for (let i = 0; i < prefixes.length; i++) { 12 | try { 13 | const exePath = path.join(prefixes[i], suffix); 14 | fs.accessSync(exePath); 15 | return exePath; 16 | } catch (e) { 17 | // ignore 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /lib/launch.js: -------------------------------------------------------------------------------- 1 | const {spawn} = require('child_process'); 2 | 3 | module.exports = function(url, browser) { 4 | const args = browser.args || []; 5 | args.push(url); 6 | 7 | const proc = spawn(browser.path, args, { 8 | env: { 9 | ...process.env, 10 | 'ELECTRON_DISABLE_SECURITY_WARNINGS': 'true' 11 | } 12 | }); 13 | 14 | const oldKill = proc.kill; 15 | proc.kill = function() { 16 | if (browser.onExit) { 17 | browser.onExit(proc.pid); 18 | } 19 | oldKill.call(proc); 20 | }; 21 | 22 | return proc; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/tap-parse.js: -------------------------------------------------------------------------------- 1 | // Different from https://github.com/tapjs/tap-parser 2 | // This implementation doesn't support subtests, because 3 | // subtests is not in TAP spec v12 or v13, it's in v14 draft 4 | // which only node-tap uses. 5 | // But node-tap does not run in browser, so that's irrelevant. 6 | 7 | const readline = require('readline'); 8 | 9 | module.exports = function(readableStream, cb) { 10 | const rl = readline.createInterface({ 11 | input: readableStream, 12 | crlfDelay: Infinity // '\r\n' as single line break. 13 | }); 14 | 15 | let done = false; 16 | 17 | function panic(err) { 18 | done = true; 19 | cb(err); 20 | } 21 | 22 | function complete(passed) { 23 | done = true; 24 | cb(null, passed); 25 | } 26 | 27 | let plan = null; 28 | let tests = []; 29 | 30 | function captureVersion(line) { 31 | const m = line.match(/^TAP version (\d+)$/); 32 | if (m) { 33 | const version = parseInt(m[1], 10); 34 | if (version < 12 || version > 13) { 35 | throw new Error('TAP version ' + version + ' is not supported'); 36 | } 37 | return true; 38 | } 39 | } 40 | 41 | function capturePlan(line) { 42 | const m = line.match(/^1\.\.(\d+)$/); 43 | if (m) { 44 | plan = parseInt(m[1], 10); 45 | return true; 46 | } 47 | } 48 | 49 | function captureTest(line) { 50 | const m = line.match(/^((?:not )?ok)\b/); 51 | if (m) { 52 | const pass = m[1] === 'ok'; 53 | const todo = (/# TODO /i).test(line); 54 | const skip = !todo && (/# SKIP\S*\s+/i).test(line); 55 | tests.push({pass, todo, skip}); 56 | return true; 57 | } 58 | } 59 | 60 | function captureBailout(line) { 61 | const m = line.match(/^Bail out!/); 62 | if (m) { 63 | throw new Error(line); 64 | } 65 | } 66 | 67 | rl.on('line', line => { 68 | if (done) return; 69 | if (line.match(/^\s*$/)) return; 70 | 71 | try { 72 | captureVersion(line) || 73 | capturePlan(line) || 74 | captureTest(line) || 75 | captureBailout(line); 76 | 77 | if (plan && tests.length === plan) { 78 | complete(tests.every(t => t.pass || t.todo || t.skip)); 79 | } 80 | } catch (err) { 81 | panic(err); 82 | } 83 | }); 84 | 85 | return rl; 86 | }; 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-do", 3 | "version": "5.0.0", 4 | "description": "Run JavaScript in a browser, forward browser console log to stdout, great for running unit tests in browser.", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=18.0.0" 8 | }, 9 | "files": [ 10 | "index.js", 11 | "jasmine-tap-reporter.js", 12 | "lib", 13 | "dist", 14 | "bin" 15 | ], 16 | "bin": "./bin/browser-do.js", 17 | "scripts": { 18 | "lint": "eslint index.js lib test", 19 | "preversion": "npm test", 20 | "version": "standard-changelog && git add CHANGELOG.md", 21 | "postversion": "git push && git push --tags && npm publish", 22 | "pretest": "npm run lint && npm run build", 23 | "test": "pta 'test/*.spec.js' --timeout 300000 --reporter tap", 24 | "build": "node build-reporter.js", 25 | "prepare": "npm run build" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/3cp/browser-do" 30 | }, 31 | "keywords": [ 32 | "browser", 33 | "test" 34 | ], 35 | "author": "Chunpeng Huo", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/3cp/browser-do/issues" 39 | }, 40 | "homepage": "https://github.com/3cp/browser-do#readme", 41 | "devDependencies": { 42 | "cat": "^0.2.0", 43 | "chai": "^5.1.1", 44 | "concat-stream": "^2.0.0", 45 | "esbuild": "^0.21.1", 46 | "esbuild-plugin-polyfill-node": "^0.3.0", 47 | "eslint": "^8.57.0", 48 | "jasmine-core": "^5.1.2", 49 | "mocha": "^10.4.0", 50 | "parcel": "^2.12.0", 51 | "pta": "^1.2.0", 52 | "socket.io-client": "^4.7.5", 53 | "source-map-support": "^0.5.21", 54 | "standard-changelog": "^6.0.0", 55 | "zora": "^5.2.0" 56 | }, 57 | "dependencies": { 58 | "ansi-colors": "^4.1.3", 59 | "commander": "^12.0.0", 60 | "electron": "^30.0.3", 61 | "finalhandler": "^1.2.0", 62 | "lodash.kebabcase": "^4.1.1", 63 | "serve-static": "^1.15.0", 64 | "server-destroy": "^1.0.1", 65 | "socket.io": "^4.7.5", 66 | "tmp": "^0.2.3", 67 | "which": "^4.0.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /reporter.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | // copied from browser-run 3 | // changed xhr-write-stream to socket.io 4 | 5 | require('source-map-support').install(); 6 | 7 | // delay window.close 8 | var close = window.close; 9 | // ['c' + 'lose'] is to bypass IE11 SCRIPT5045: 10 | // Assignment to read-only properties is not allowed in strict mode 11 | window['c' + 'lose'] = function () { 12 | setTimeout(function () { 13 | close.call(window); 14 | }, 1000); 15 | }; 16 | 17 | window.onerror = function (msg, file, line, column, err) { 18 | if (err && msg.indexOf(err.stack) > -1) { 19 | err.stack = err.stack + '\n at ' + file + ':' + line + ':' + column; 20 | } 21 | console.error(err && err.stack || err); 22 | }; 23 | 24 | var io = require('socket.io-client')(); 25 | 26 | var console = window.console || {}; 27 | 28 | function patch(method) { 29 | var old = console[method]; 30 | console[method] = function() { 31 | const message = Array.prototype.slice.call(arguments, 0).join(' '); 32 | if (message) io.emit(method, message); 33 | if (old) old.apply(console, arguments); 34 | }; 35 | } 36 | 37 | var methods = ['log', 'error', 'warn', 'dir', 'debug', 'info', 'trace']; 38 | var i; 39 | for (i = 0; i < methods.length; i++) { 40 | patch(methods[i]); 41 | } 42 | 43 | io.on('ask-coverage', function() { 44 | if (window.__coverage__) { 45 | io.emit('coverage', JSON.stringify(window.__coverage__)); 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /test/_jasmine-bad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/_jasmine-good.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/_mock-jasmine-bad.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/_mock-jasmine-good.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/_mock.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = function(req, res){ 5 | if (req.url === '/mock/_jasmine-good.js') { 6 | fs.createReadStream( 7 | path.join(__dirname, 'samples', '_jasmine-good.js') 8 | ).pipe(res); 9 | } else if (req.url === '/mock/_jasmine-bad.js') { 10 | fs.createReadStream( 11 | path.join(__dirname, 'samples', '_jasmine-bad.js') 12 | ).pipe(res); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /test/browser-do.spec.js: -------------------------------------------------------------------------------- 1 | const {test} = require('zora'); 2 | const concat = require('concat-stream'); 3 | const run = require('../index'); 4 | 5 | test('browser-do rans javascript code', async t => { 6 | return new Promise(resolve => { 7 | const browser = run(); 8 | browser.pipe(concat(data => { 9 | t.equal(data.toString(), 'hello\n'); 10 | resolve(); 11 | })); 12 | browser.on('error', err => { 13 | t.fail(err.message); 14 | resolve(); 15 | }); 16 | browser.end('console.log("hello");window.close();'); 17 | }); 18 | }); 19 | 20 | test('browser-do rans javascript, close by api', async t => { 21 | return new Promise(resolve => { 22 | const browser = run(); 23 | browser.pipe(concat(data => { 24 | t.equal(data.toString(), 'hello\n'); 25 | resolve(); 26 | })); 27 | browser.on('error', err => { 28 | t.fail(err.message); 29 | resolve(); 30 | }); 31 | browser.end('console.log("hello");'); 32 | setTimeout(() => browser.stop(), 4000); 33 | }); 34 | }); 35 | 36 | test('browser-do detects tap output, auto close', async t => { 37 | function onCoverage() { 38 | t.fail('should not trigger onCoverage when there is no window.__coverage__'); 39 | } 40 | 41 | return new Promise(resolve => { 42 | const browser = run({tap: true, onCoverage}); 43 | browser.pipe(concat(data => { 44 | t.equal(data.toString(), '1..2\nok\nok\n'); 45 | resolve(); 46 | })); 47 | browser.on('error', err => { 48 | t.fail(err.message); 49 | resolve(); 50 | }); 51 | browser.end('console.log("1..2");console.log("ok");console.log("ok");'); 52 | }); 53 | }); 54 | 55 | test('browser-do detects tap output, auto close, write coverage report', async t => { 56 | function onCoverage(result) { 57 | t.equal(result, '{"a":1}'); 58 | } 59 | 60 | return new Promise(resolve => { 61 | const browser = run({tap: true, onCoverage}); 62 | browser.pipe(concat(data => { 63 | t.equal(data.toString(), '1..2\nok\nok\n'); 64 | resolve(); 65 | })); 66 | browser.on('error', err => { 67 | t.fail(err.message); 68 | resolve(); 69 | }); 70 | browser.end('window.__coverage__ = {a:1};console.log("1..2");console.log("ok");console.log("ok");'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/e2e.spec.js: -------------------------------------------------------------------------------- 1 | const {test} = require('zora'); 2 | const {exec, execSync} = require('child_process'); 3 | const {version} = require('../package.json'); 4 | const getBrowser = require('../lib/get-browser'); 5 | 6 | const nodeMajorVersion = parseInt(process.version.split('.')[0].substring(1), 10); 7 | 8 | test('browser-do prints out version number', t => { 9 | t.equal(execSync('node bin/browser-do.js --version').toString().trim(), version); 10 | }); 11 | 12 | test('In browsers', async t => { 13 | const browsers = [ 14 | 'electron', 15 | 'chrome-headless', 16 | 'chromium-headless', 17 | 'edge-headless', 18 | 'firefox-headless', 19 | 'safari' 20 | ]; 21 | 22 | for (const browser of browsers) { 23 | const hasTheBrowser = getBrowser(browser); 24 | if (hasTheBrowser) { 25 | const browserArg = ' -b ' + browser; 26 | 27 | if (browser === 'electron') { 28 | await t.test('browser-do by default uses electron to detect passed zora tests', async t => { 29 | return new Promise(resolve => { 30 | exec('npx esbuild --bundle test/samples/_zora-good.js | node bin/browser-do.js --tap', error => { 31 | t.notOk(error); 32 | resolve(); 33 | }); 34 | }); 35 | }); 36 | 37 | await t.test('browser-do by default uses electron to detect failed zora tests', async t => { 38 | return new Promise(resolve => { 39 | exec('npx esbuild --bundle test/samples/_zora-bad.js | node bin/browser-do.js --tap', error => { 40 | t.ok(error); 41 | resolve(); 42 | }); 43 | }); 44 | }); 45 | } 46 | 47 | await t.test(`browser-do:${browser} detects passed zora tests`, async t => { 48 | return new Promise(resolve => { 49 | exec('npx esbuild --bundle test/samples/_zora-good.js | node bin/browser-do.js --tap' + browserArg, error => { 50 | t.notOk(error); 51 | resolve(); 52 | }); 53 | }); 54 | }); 55 | 56 | await t.test(`browser-do:${browser} detects failed zora tests`, async t => { 57 | return new Promise(resolve => { 58 | exec('npx esbuild --bundle test/samples/_zora-bad.js | node bin/browser-do.js --tap' + browserArg, error => { 59 | t.ok(error); 60 | resolve(); 61 | }); 62 | }); 63 | }); 64 | 65 | await t.test(`browser-do:${browser} detects passed jasmine tests`, async t => { 66 | return new Promise(resolve => { 67 | exec('npx esbuild --bundle test/samples/_jasmine-good.js | node bin/browser-do.js --jasmine' + browserArg, error => { 68 | t.notOk(error); 69 | resolve(); 70 | }); 71 | }); 72 | }); 73 | 74 | await t.test(`browser-do:${browser} detects failed jasmine tests`, async t => { 75 | return new Promise(resolve => { 76 | exec('npx esbuild --bundle test/samples/_jasmine-bad.js | node bin/browser-do.js --jasmine' + browserArg, error => { 77 | t.ok(error); 78 | resolve(); 79 | }); 80 | }); 81 | }); 82 | 83 | await t.test(`browser-do:${browser} supports jasmine fit tests`, async t => { 84 | return new Promise(resolve => { 85 | exec('npx esbuild --bundle test/samples/_jasmine-fit.js | node bin/browser-do.js --jasmine' + browserArg, error => { 86 | t.notOk(error); 87 | resolve(); 88 | }); 89 | }); 90 | }); 91 | 92 | await t.test(`browser-do:${browser} supports jasmine fdescribe tests`, async t => { 93 | return new Promise(resolve => { 94 | exec('npx esbuild --bundle test/samples/_jasmine-fdescribe.js | node bin/browser-do.js --jasmine' + browserArg, error => { 95 | t.notOk(error); 96 | resolve(); 97 | }); 98 | }); 99 | }); 100 | 101 | await t.test(`browser-do:${browser} supports jasmine xit tests`, async t => { 102 | return new Promise(resolve => { 103 | exec('npx esbuild --bundle test/samples/_jasmine-xit.js | node bin/browser-do.js --jasmine' + browserArg, error => { 104 | t.notOk(error); 105 | resolve(); 106 | }); 107 | }); 108 | }); 109 | 110 | await t.test(`browser-do:${browser} supports jasmine xdescribe tests`, async t => { 111 | return new Promise(resolve => { 112 | exec('npx esbuild --bundle test/samples/_jasmine-xdescribe.js | node bin/browser-do.js --jasmine' + browserArg, error => { 113 | t.notOk(error); 114 | resolve(); 115 | }); 116 | }); 117 | }); 118 | 119 | await t.test(`browser-do:${browser} detects passed mocha tests`, async t => { 120 | return new Promise(resolve => { 121 | exec('npx esbuild --bundle test/samples/_mocha-good.js | node bin/browser-do.js --mocha' + browserArg, error => { 122 | t.notOk(error); 123 | resolve(); 124 | }); 125 | }); 126 | }); 127 | 128 | await t.test(`browser-do:${browser} detects failed mocha tests`, async t => { 129 | return new Promise(resolve => { 130 | exec('npx esbuild --bundle test/samples/_mocha-bad.js | node bin/browser-do.js --mocha' + browserArg, error => { 131 | t.ok(error); 132 | resolve(); 133 | }); 134 | }); 135 | }); 136 | 137 | await t.test(`browser-do:${browser} supports static assets and html input`, async t => { 138 | return new Promise(resolve => { 139 | exec('npx cat test/_jasmine-good.html | node bin/browser-do.js --jasmine --static test/samples' + browserArg, error => { 140 | t.notOk(error); 141 | resolve(); 142 | }); 143 | }); 144 | }); 145 | 146 | await t.test(`browser-do:${browser} supports static assets and html input, with failed tests`, async t => { 147 | return new Promise(resolve => { 148 | exec('npx cat test/_jasmine-bad.html | node bin/browser-do.js --jasmine --static test/samples' + browserArg, error => { 149 | t.ok(error); 150 | resolve(); 151 | }); 152 | }); 153 | }); 154 | 155 | await t.test(`browser-do:${browser} supports mock and html input`, async t => { 156 | return new Promise(resolve => { 157 | exec('npx cat test/_mock-jasmine-good.html | node bin/browser-do.js --jasmine --mock test/_mock.js' + browserArg, error => { 158 | t.notOk(error); 159 | resolve(); 160 | }); 161 | }); 162 | }); 163 | 164 | if (browser === 'firefox-headless' && process.platform === 'win32' && nodeMajorVersion < 16) { 165 | await t.test(`browser-do:${browser} supports mock and html input, with failed tests. Bypassed on Windows Nodejs v${nodeMajorVersion} because of intermittent failure. Assume Nodejs v16 fixed some bug.`, t => { 166 | t.ok(true, `browser-do:${browser} supports mock and html input, with failed tests. Bypassed on Windows Nodejs v${nodeMajorVersion} because of intermittent failure. Assume Nodejs v16 fixed some bug.`); 167 | }); 168 | } else { 169 | await t.test(`browser-do:${browser} supports mock and html input, with failed tests`, async t => { 170 | return new Promise(resolve => { 171 | exec('npx cat test/_mock-jasmine-bad.html | node bin/browser-do.js --jasmine --mock test/_mock.js' + browserArg, error => { 172 | t.ok(error); 173 | resolve(); 174 | }); 175 | }); 176 | }); 177 | } 178 | } else { 179 | await t.test(`bypass ${browser} because it is not present`, t => { 180 | t.ok(true, `bypass ${browser} because it is not present`); 181 | }); 182 | } 183 | } 184 | }); -------------------------------------------------------------------------------- /test/get-browser.spec.js: -------------------------------------------------------------------------------- 1 | const {test} = require('zora'); 2 | const getBrowser = require('../lib/get-browser'); 3 | 4 | function load(name) { 5 | if (name === 'safari') { 6 | return { 7 | path: { 8 | darwin: '/mac/safari' 9 | } 10 | }; 11 | } 12 | 13 | if (name === 'chrome-headless') { 14 | return () => ({ 15 | path: { 16 | linux: '/linux/chrome', 17 | darwin: '/mac/chrome', 18 | win32: '/win/chrome.exe' 19 | }, 20 | args: ['--enable-automation', '--headless', '--disable-gpu'] 21 | }); 22 | } 23 | 24 | throw new Error('not available'); 25 | } 26 | 27 | function _getBrowser(name) { 28 | return getBrowser(name, load); 29 | } 30 | 31 | test('getBrowser returns nothing for missing browser', t => { 32 | t.notOk(_getBrowser('na')); 33 | }); 34 | 35 | if (process.platform === 'linux') { 36 | test('getBrowser returns nothing missing browser on linux', t => { 37 | t.notOk(_getBrowser('safari')); 38 | }); 39 | 40 | test('getBrowser gets browser on linux', t => { 41 | t.deepEqual(_getBrowser('chrome-headless'), { 42 | args: ['--enable-automation', '--headless', '--disable-gpu'], 43 | path: '/linux/chrome' 44 | }); 45 | }); 46 | } 47 | 48 | if (process.platform === 'darwin') { 49 | test('getBrowser gets browser on macOS', t => { 50 | t.deepEqual(_getBrowser('safari'), { 51 | path: '/mac/safari' 52 | }); 53 | 54 | t.deepEqual(_getBrowser('chrome-headless'), { 55 | args: ['--enable-automation', '--headless', '--disable-gpu'], 56 | path: '/mac/chrome' 57 | }); 58 | }); 59 | } 60 | 61 | if (process.platform === 'win32') { 62 | test('getBrowser returns nothing missing browser on win32', t => { 63 | t.notOk(_getBrowser('safari')); 64 | }); 65 | 66 | test('getBrowser gets browser on win32', t => { 67 | t.deepEqual(_getBrowser('chrome-headless'), { 68 | args: ['--enable-automation', '--headless', '--disable-gpu'], 69 | path: '/win/chrome.exe' 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /test/samples/_jasmine-bad.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect */ 2 | describe('scope1', function() { 3 | it('test1', function() { 4 | expect(1).toBe(1); 5 | }); 6 | }); 7 | 8 | describe('scope2', function() { 9 | it('test2', function(done) { 10 | setTimeout(function() { 11 | expect(2).toBe(1); 12 | done(); 13 | }, 200); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/samples/_jasmine-fdescribe.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect, fdescribe */ 2 | fdescribe('scope1', function() { 3 | it('test1', function() { 4 | expect(1).toBe(1); 5 | }); 6 | }); 7 | 8 | describe('scope2', function() { 9 | it('test2', function(done) { 10 | setTimeout(function() { 11 | expect(2).toBe(1); 12 | done(); 13 | }, 200); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/samples/_jasmine-fit.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect, fit */ 2 | describe('scope1', function() { 3 | fit('test1', function() { 4 | expect(1).toBe(1); 5 | }); 6 | }); 7 | 8 | describe('scope2', function() { 9 | it('test2', function(done) { 10 | setTimeout(function() { 11 | expect(2).toBe(1); 12 | done(); 13 | }, 200); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/samples/_jasmine-good.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect */ 2 | describe('scope1', function() { 3 | it('test1', function() { 4 | expect(1).toBe(1); 5 | }); 6 | }); 7 | 8 | describe('scope2', function() { 9 | it('test2', function(done) { 10 | setTimeout(function() { 11 | expect(2).toBe(2); 12 | done(); 13 | }, 200); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/samples/_jasmine-xdescribe.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect, xdescribe */ 2 | describe('scope1', function() { 3 | it('test1', function() { 4 | expect(1).toBe(1); 5 | }); 6 | }); 7 | 8 | xdescribe('scope2', function() { 9 | it('test2', function(done) { 10 | setTimeout(function() { 11 | expect(2).toBe(1); 12 | done(); 13 | }, 200); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/samples/_jasmine-xit.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, expect, xit */ 2 | describe('scope1', function() { 3 | it('test1', function() { 4 | expect(1).toBe(1); 5 | }); 6 | }); 7 | 8 | describe('scope2', function() { 9 | xit('test2', function(done) { 10 | setTimeout(function() { 11 | expect(2).toBe(1); 12 | done(); 13 | }, 200); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/samples/_mocha-bad.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | const expect = require('chai').expect; 3 | 4 | describe('scope1', function() { 5 | it('test1', function() { 6 | expect(1).to.equal(1); 7 | }); 8 | }); 9 | 10 | describe('scope2', function() { 11 | it('test2', function(done) { 12 | setTimeout(function() { 13 | expect(2).to.equal(1); 14 | done(); 15 | }, 200); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/samples/_mocha-good.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | const expect = require('chai').expect; 3 | 4 | describe('scope1', function() { 5 | it('test1', function() { 6 | expect(1).to.equal(1); 7 | }); 8 | }); 9 | 10 | describe('scope2', function() { 11 | it('test2', function(done) { 12 | setTimeout(function() { 13 | expect(2).to.equal(2); 14 | done(); 15 | }, 200); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/samples/_zora-bad.js: -------------------------------------------------------------------------------- 1 | const {test} = require('zora'); 2 | 3 | test('test1', function(t) { 4 | t.equal(1, 1); 5 | }); 6 | 7 | test('test2', async function(t) { 8 | return new Promise(resolve => { 9 | setTimeout(function() { 10 | t.equal(2, 1); 11 | resolve(); 12 | }, 200); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/samples/_zora-good.js: -------------------------------------------------------------------------------- 1 | const {test} = require('zora'); 2 | 3 | test('test1', function(t) { 4 | t.equal(1, 1); 5 | }); 6 | 7 | test('test2', async function(t) { 8 | return new Promise(resolve => { 9 | setTimeout(function() { 10 | t.equal(2, 2); 11 | resolve(); 12 | }, 200); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/tap-parse.spec.js: -------------------------------------------------------------------------------- 1 | const {test} = require('zora'); 2 | const {PassThrough} = require('stream'); 3 | const tapParse = require('../lib/tap-parse'); 4 | 5 | test('tapParse passes all ok', async t => { 6 | const inp = new PassThrough(); 7 | 8 | return new Promise(resolve => { 9 | tapParse(inp, (err, passed) => { 10 | t.equal(err, null); 11 | t.ok(passed); 12 | resolve(); 13 | }); 14 | 15 | [ 16 | 'TAP version 13', 17 | '# wait', 18 | 'ok 1 (unnamed assert)', 19 | 'ok 2 should be equal', 20 | '1..2', 21 | '# tests 2', 22 | '# pass 2' 23 | ].forEach(l => inp.write(l + '\n')); 24 | }); 25 | }); 26 | 27 | test('tapParse passes all ok with plan at top', async t => { 28 | const inp = new PassThrough(); 29 | 30 | return new Promise(resolve => { 31 | tapParse(inp, (err, passed) => { 32 | t.equal(err, null); 33 | t.ok(passed); 34 | resolve(); 35 | }); 36 | 37 | [ 38 | '1..2', 39 | 'ok', 40 | 'ok', 41 | '# tests 2', 42 | '# pass 2' 43 | ].forEach(l => inp.write(l + '\r\n')); 44 | }); 45 | }); 46 | 47 | test('tapParse fails the fail', async t => { 48 | const inp = new PassThrough(); 49 | 50 | return new Promise(resolve => { 51 | tapParse(inp, (err, passed) => { 52 | t.equal(err, null); 53 | t.notOk(passed); 54 | resolve(); 55 | }); 56 | 57 | [ 58 | 'TAP version 13', 59 | '# wait', 60 | 'ok 1 (unnamed assert)', 61 | 'not ok 2 should be equal', 62 | ' ---', 63 | ' operator: equal', 64 | ' expected: 5', 65 | ' actual: 4', 66 | ' ...', 67 | '', 68 | '1..2', 69 | '# tests 2', 70 | '# pass 1', 71 | '# fail 1' 72 | ].forEach(l => inp.write(l + '\n')); 73 | }); 74 | }); 75 | 76 | test('tapParse fails the fail with plan on top', async t => { 77 | const inp = new PassThrough(); 78 | 79 | return new Promise(resolve => { 80 | tapParse(inp, (err, passed) => { 81 | t.equal(err, null); 82 | t.notOk(passed); 83 | resolve(); 84 | }); 85 | 86 | [ 87 | 'TAP version 13', 88 | '1..2', 89 | '# wait', 90 | 'ok 1 (unnamed assert)', 91 | 'not ok 2 should be equal', 92 | ' ---', 93 | ' operator: equal', 94 | ' expected: 5', 95 | ' actual: 4', 96 | ' ...', 97 | '', 98 | '# tests 2', 99 | '# pass 1', 100 | '# fail 1' 101 | ].forEach(l => inp.write(l + '\n')); 102 | }); 103 | }); 104 | 105 | test('tapParse never finish with no plan', async t => { 106 | const inp = new PassThrough(); 107 | 108 | return new Promise(resolve => { 109 | const rl = tapParse(inp, () => { 110 | t.fail('should not reach here'); 111 | resolve(); 112 | }); 113 | 114 | [ 115 | 'TAP version 13', 116 | 'ok 1 (unnamed assert)', 117 | 'not ok 2 should be equal', 118 | ' ---', 119 | ' operator: equal', 120 | ' expected: 5', 121 | ' actual: 4', 122 | ' ...', 123 | '', 124 | '# tests 2', 125 | '# pass 1', 126 | '# fail 1' 127 | ].forEach(l => inp.write(l + '\n')); 128 | 129 | setTimeout(() => { 130 | inp.end(); 131 | rl.close(); 132 | t.ok(true, 'never calls cb'); 133 | resolve(); 134 | }); 135 | }); 136 | }); 137 | 138 | test('tapParse ignore fails on todo', async t => { 139 | const inp = new PassThrough(); 140 | 141 | return new Promise(resolve => { 142 | tapParse(inp, (err, passed) => { 143 | t.equal(err, null); 144 | t.ok(passed); 145 | resolve(); 146 | }); 147 | 148 | [ 149 | 'TAP version 13', 150 | 'ok 1 (unnamed assert)', 151 | 'not ok 2 should be equal # TODO to fix', 152 | ' ---', 153 | ' operator: equal', 154 | ' expected: 5', 155 | ' actual: 4', 156 | ' ...', 157 | '', 158 | '1..2', 159 | '# tests 2', 160 | '# pass 1', 161 | '# fail 1' 162 | ].forEach(l => inp.write(l + '\n')); 163 | }); 164 | }); 165 | 166 | test('tapParse ignore fails on skip', async t => { 167 | const inp = new PassThrough(); 168 | 169 | return new Promise(resolve => { 170 | tapParse(inp, (err, passed) => { 171 | t.equal(err, null); 172 | t.ok(passed); 173 | resolve(); 174 | }); 175 | 176 | [ 177 | 'TAP version 13', 178 | '1..2', 179 | 'ok 1 (unnamed assert)', 180 | 'not ok 2 should be equal # skipped due to bla', 181 | ' ---', 182 | ' operator: equal', 183 | ' expected: 5', 184 | ' actual: 4', 185 | ' ...', 186 | '', 187 | '# tests 2', 188 | '# pass 1', 189 | '# fail 1' 190 | ].forEach(l => inp.write(l + '\n')); 191 | }); 192 | }); 193 | 194 | test('tapParse throws error on Bail out!', async t => { 195 | const inp = new PassThrough(); 196 | 197 | return new Promise(resolve => { 198 | tapParse(inp, (err, passed) => { 199 | t.equal(err.message, 'Bail out! lorem'); 200 | t.equal(passed, undefined); 201 | resolve(); 202 | }); 203 | 204 | [ 205 | '1..10', 206 | 'ok 1', 207 | 'ok 2', 208 | 'Bail out! lorem', 209 | 'bla bla' 210 | ].forEach(l => inp.write(l + '\n')); 211 | }); 212 | }); 213 | 214 | test('tapParse throws error on unsupported TAP version!', async t => { 215 | const inp = new PassThrough(); 216 | 217 | return new Promise(resolve => { 218 | tapParse(inp, (err, passed) => { 219 | t.equal(err.message, 'TAP version 14 is not supported'); 220 | t.equal(passed, undefined); 221 | resolve(); 222 | }); 223 | 224 | [ 225 | 'TAP version 14', 226 | '1..2', 227 | 'ok 1', 228 | 'ok 2', 229 | ].forEach(l => inp.write(l + '\n')); 230 | }); 231 | }); 232 | --------------------------------------------------------------------------------