├── .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 
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*);
49 |
50 | let jasminePath;
51 | let mochaPath;
52 | if (opts.jasmine) {
53 | try {
54 | jasminePath = path.dirname(require.resolve('jasmine-core/lib/jasmine-core/jasmine.js'));
55 | } catch (e) {
56 | console.error('To use --jasmine, you need "npm i -D jasmine-core"');
57 | process.exit(1);
58 | }
59 | }
60 | if (opts.mocha) {
61 | try {
62 | mochaPath = path.dirname(require.resolve('mocha/mocha.js'));
63 | } catch (e) {
64 | console.error('To use --mocha, you need "npm i -D mocha"');
65 | process.exit(1);
66 | }
67 | }
68 |
69 | var mockHandler = opts.mock && require(path.resolve('./', opts.mock));
70 |
71 | var server = http.createServer(function (req, res) {
72 | if (isHtmlData) {
73 | if (req.url == '/') {
74 | res.end(data);
75 | return;
76 | }
77 | } else {
78 | // JavaScript data
79 | if (/^\/bundle\.js/.test(req.url)) {
80 | res.setHeader('content-type', 'application/javascript');
81 | res.setHeader('cache-control', 'no-cache');
82 | res.end(data);
83 | return;
84 | }
85 |
86 | if (req.url == '/') {
87 | res.write('');
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 |
--------------------------------------------------------------------------------