├── .babelrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── gulpfile.js
├── package.json
├── public
└── jasmine_favicon.png
├── spec
├── fixtures
│ ├── dummy_spec.js
│ ├── gulpfile.js
│ └── mutable_spec.js
├── index_spec.js
├── lib
│ ├── server_spec.js
│ └── spec_runner_spec.js
├── spec_helper.js
├── support
│ ├── deferred.js
│ ├── jasmine_webdriver.js
│ ├── selenium.js
│ ├── unhandled_rejection_helper.js
│ └── webdriver_helper.js
└── webpack
│ └── jasmine-plugin_spec.js
├── src
├── index.js
├── lib
│ ├── boot.js
│ ├── drivers
│ │ ├── chrome.js
│ │ ├── phantomjs.js
│ │ ├── phantomjs1.js
│ │ └── slimerjs.js
│ ├── headless.js
│ ├── helper.js
│ ├── reporters
│ │ ├── add_profile_reporter.js
│ │ └── add_sourcemapped_stacktrace_reporter.js
│ ├── runners
│ │ ├── chrome_evaluate.js
│ │ ├── chrome_runner
│ │ ├── phantom_runner.js
│ │ └── slimer_runner.js
│ ├── server.js
│ ├── spec_runner.js
│ └── stylesheets
│ │ └── sourcemapped_stacktrace_reporter.css
└── webpack
│ └── jasmine-plugin.js
├── tasks
├── build.js
├── default.js
├── lint.js
├── publish.js
└── spec.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "ignore": ["node_modules"],
3 | "presets": [["es2015", {"loose": true}], "stage-0"],
4 | "plugins": [
5 | "add-module-exports",
6 | "transform-runtime"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "browser": true,
5 | "phantomjs": true,
6 | "node": true,
7 | "jasmine": true
8 | },
9 |
10 | "ecmaFeatures": {
11 | "modules": true
12 | },
13 |
14 | "parser": "babel-eslint",
15 |
16 | "plugins": ["jasmine"],
17 |
18 | "rules": {
19 | "camelcase": 0,
20 | "curly": 0,
21 | "eol-last": 0,
22 | "jasmine/no-spec-dupes": 0,
23 | "jasmine/no-suite-dupes": 0,
24 | "jasmine/no-disabled-tests": 1,
25 | "jasmine/no-focused-tests": 2,
26 | "no-undef": 0,
27 | "no-path-concat": 0,
28 | "no-process-exit": 0,
29 | "no-shadow": 0,
30 | "no-underscore-dangle": 0,
31 | "no-unused-expressions": 0,
32 | "quotes": [1, "single"],
33 | "strict": 0
34 | }
35 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | dist/*
3 | .idea/*
4 | yarn-error.log
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "6"
5 | env:
6 | - CXX=g++-4.8
7 | addons:
8 | apt:
9 | sources:
10 | - ubuntu-toolchain-r-test
11 | packages:
12 | - g++-4.8
13 | before_install:
14 | # phantomjs and slimerjs need access to TMPDIR
15 | - export TMPDIR=/tmp
16 | - export DISPLAY=:99.0
17 | - sh -e /etc/init.d/xvfb start
18 | before_script:
19 | - selenium-standalone install
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Copyright 2015 Pivotal Software, Inc. All Rights Reserved.
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
13 | all 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
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gulp-jasmine-browser
2 |
3 | Run jasmine tests in a browser or headless browser using gulp.
4 |
5 | [](https://travis-ci.org/jasmine/gulp-jasmine-browser)
6 |
7 | ## Discontinued
8 | The `gulp-jasmine-browser` package is discontinued. There will be no further releases. We recommend migrating to the
9 | [jasmine-browser-runner](https://github.com/jasmine/jasmine-browser) package.
10 |
11 | ## Installing
12 | `gulp-jasmine-browser` is available as an
13 | [npm package](https://www.npmjs.com/package/gulp-jasmine-browser).
14 |
15 | ## Usage
16 |
17 | Gulp Jasmine Browser currently works with any synchronous method of loading files. The beginning examples all assume basic script loading.
18 | If you are using CommonJS to load files, you may want to skip to [Usage with Webpack](#usage-with-webpack)
19 |
20 | ### Create a Jasmine server to run specs in a browser
21 |
22 | In `gulpfile.js`
23 |
24 | ```js
25 | var gulp = require('gulp');
26 | var jasmineBrowser = require('gulp-jasmine-browser');
27 |
28 | gulp.task('jasmine', function() {
29 | return gulp.src(['src/**/*.js', 'spec/**/*_spec.js'])
30 | .pipe(jasmineBrowser.specRunner())
31 | .pipe(jasmineBrowser.server({port: 8888}));
32 | });
33 | ```
34 | In `gulp.src` include all files you need for testing other than jasmine itself.
35 | This should include your spec files, and may also include your production JavaScript and
36 | CSS files.
37 |
38 | The jasmine server will run on the `port` given to `server`, or will default to port 8888.
39 |
40 | ### Watching for file changes
41 |
42 | To have the server automatically refresh when files change, you will want something like [gulp-watch](https://github.com/floatdrop/gulp-watch).
43 |
44 | ```js
45 | var gulp = require('gulp');
46 | var jasmineBrowser = require('gulp-jasmine-browser');
47 | var watch = require('gulp-watch');
48 |
49 | gulp.task('jasmine', function() {
50 | var filesForTest = ['src/**/*.js', 'spec/**/*_spec.js'];
51 | return gulp.src(filesForTest)
52 | .pipe(watch(filesForTest))
53 | .pipe(jasmineBrowser.specRunner())
54 | .pipe(jasmineBrowser.server({port: 8888}));
55 | });
56 | ```
57 |
58 | If you are using Webpack or Browserify, you may want to use their watching mechanisms instead of this example.
59 |
60 | ### Run jasmine tests headlessly
61 |
62 | In `gulpfile.js`
63 |
64 | For Headless Chrome
65 | ```js
66 | var gulp = require('gulp');
67 | var jasmineBrowser = require('gulp-jasmine-browser');
68 |
69 | gulp.task('jasmine-chrome', function() {
70 | return gulp.src(['src/**/*.js', 'spec/**/*_spec.js'])
71 | .pipe(jasmineBrowser.specRunner({console: true}))
72 | .pipe(jasmineBrowser.headless({driver: 'chrome'}));
73 | });
74 | ```
75 |
76 | To use this driver, [puppeteer](https://www.npmjs.com/package/puppeteer) must be installed in your project.
77 |
78 | For PhantomJs
79 | ```js
80 | var gulp = require('gulp');
81 | var jasmineBrowser = require('gulp-jasmine-browser');
82 |
83 | gulp.task('jasmine-phantom', function() {
84 | return gulp.src(['src/**/*.js', 'spec/**/*_spec.js'])
85 | .pipe(jasmineBrowser.specRunner({console: true}))
86 | .pipe(jasmineBrowser.headless({driver: 'phantomjs'}));
87 | });
88 | ```
89 |
90 | To use this driver, the PhantomJS npm [package](https://www.npmjs.com/package/phantomjs) must be installed in your project.
91 |
92 | GulpJasmineBrowser assumes that if the package is not installed `phantomjs` is already installed and in your path.
93 | It is only tested with PhantomJS 2.
94 |
95 | For SlimerJs
96 | ```js
97 | var gulp = require('gulp');
98 | var jasmineBrowser = require('gulp-jasmine-browser');
99 |
100 | gulp.task('jasmine-slimer', function() {
101 | return gulp.src(['src/**/*.js', 'spec/**/*_spec.js'])
102 | .pipe(jasmineBrowser.specRunner({console: true}))
103 | .pipe(jasmineBrowser.headless({driver: 'slimerjs'}));
104 | });
105 | ```
106 |
107 | To use this driver, the SlimerJS npm [package](https://www.npmjs.com/package/slimerjs) must be installed in your project.
108 |
109 | Note the `{console: true}` passed into specRunner.
110 |
111 |
112 |
113 | ### Usage with Webpack
114 |
115 | If you would like to compile your front end assets with Webpack, for example to use
116 | commonjs style require statements, you can pipe the compiled assets into
117 | GulpJasmineBrowser.
118 |
119 | In `gulpfile.js`
120 |
121 | ```js
122 | var gulp = require('gulp');
123 | var jasmineBrowser = require('gulp-jasmine-browser');
124 | var webpack = require('webpack-stream');
125 |
126 | gulp.task('jasmine', function() {
127 | return gulp.src(['spec/**/*_spec.js'])
128 | .pipe(webpack({watch: true, output: {filename: 'spec.js'}}))
129 | .pipe(jasmineBrowser.specRunner())
130 | .pipe(jasmineBrowser.server());
131 | });
132 | ```
133 |
134 | When using webpack, it is helpful to delay the jasmine server when the webpack bundle becomes invalid (to prevent serving
135 | javascript that is out of date). Adding the plugin to your webpack configuration, and adding the whenReady function to
136 | the server configuration enables this behavior.
137 |
138 | ```js
139 | var gulp = require('gulp');
140 | var jasmineBrowser = require('gulp-jasmine-browser');
141 | var webpack = require('webpack-stream');
142 |
143 | gulp.task('jasmine', function() {
144 | var JasminePlugin = require('gulp-jasmine-browser/webpack/jasmine-plugin');
145 | var plugin = new JasminePlugin();
146 | return gulp.src(['spec/**/*_spec.js'])
147 | .pipe(webpack({watch: true, output: {filename: 'spec.js'}, plugins: [plugin]}))
148 | .pipe(jasmineBrowser.specRunner())
149 | .pipe(jasmineBrowser.server({whenReady: plugin.whenReady}));
150 | });
151 | ```
152 | ### Options
153 | #### for specRunner
154 | ##### console
155 | Generates a console reporter for the spec runner that should be used with a headless browser.
156 |
157 | ##### profile
158 | Prints out timing information for your slowest specs after Jasmine is done.
159 | If used in the browser, this will print into the developer console. In headless mode, this will print to the terminal.
160 |
161 | #### for server and headless server
162 |
163 | ##### catch
164 | If true, the headless server catches exceptions raised while running tests
165 |
166 | ##### driver
167 | Sets the driver used by the headless server
168 |
169 | ##### findOpenPort
170 | To force the headless port to use a specific port you can pass an option to the headless configuration so it does not search for an open port.
171 | ```js
172 | gulp.task('jasmine', function() {
173 | var port = 8080;
174 | return gulp.src(['spec/**/*_spec.js'])
175 | .pipe(jasmineBrowser.specRunner())
176 | .pipe(jasmineBrowser.headless({port: 8080, findOpenPort: false}));
177 | });
178 | ```
179 |
180 | ##### onCoverage
181 | Called with the `__coverage__` from the browser, can be used with code coverage like [istanbul](http://gotwarlost.github.io/istanbul/)
182 |
183 | ##### port
184 | Sets the port for the server
185 |
186 | ##### random
187 | If true, the headless server runs the tests in random order
188 |
189 | ##### reporter
190 | Provide a [custom reporter](http://jasmine.github.io/2.1/custom_reporter.html) for the output, defaults to the jasmine
191 | terminal reporter.
192 |
193 | ##### seed
194 | Sets the randomization seed if randomization is turned on
195 |
196 | ##### sourcemappedStacktrace
197 | **EXPERIMENTAL** asynchronously loads the sourcemapped stacktraces for better stacktraces in chrome and firefox.
198 |
199 | ##### spec
200 | Only runs specs that match the given string
201 |
202 | ##### throwFailures
203 | If true, the headless server fails tests on the first failed expectation
204 |
205 | ## Development
206 | ### Getting Started
207 | The application requires the following external dependencies:
208 | * [Node](https://nodejs.org/)
209 | * [Gulp](http://gulpjs.com/)
210 | * [PhantomJS](http://phantomjs.org/) - if you want to run tests with Phantom, see your options under 'Usage.'
211 |
212 | The rest of the dependencies are handled through:
213 | ```bash
214 | npm install
215 | ```
216 |
217 | Run tests with:
218 | ```bash
219 | npm test
220 | ```
221 |
222 | Note: `npm test` need a webdriver server up and running. An easy way of accomplish that is by using `webdriver-manager`:
223 |
224 | ```bash
225 | npm install --global webdriver-manager
226 | webdriver-manager update
227 | webdriver-manager start
228 | ```
229 |
230 | (c) Copyright 2016 Pivotal Software, Inc. All Rights Reserved.
231 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | require('babel-core/register');
2 | require('babel-polyfill');
3 | process.env.NODE_ENV = process.env.NODE_ENV || 'development';
4 | require('./tasks/build');
5 | require('./tasks/lint');
6 | require('./tasks/publish');
7 | require('./tasks/spec');
8 | require('./tasks/default');
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gulp-jasmine-browser",
3 | "version": "4.1.0",
4 | "license": "MIT",
5 | "description": "",
6 | "keywords": [
7 | "gulp",
8 | "gulpplugin",
9 | "jasmine",
10 | "test",
11 | "testing",
12 | "spec"
13 | ],
14 | "homepage": "https://github.com/jasmine/gulp-jasmine-browser",
15 | "bugs": "https://github.com/jasmine/gulp-jasmine-browser/issues",
16 | "main": "index.js",
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/jasmine/gulp-jasmine-browser"
20 | },
21 | "devDependencies": {
22 | "babel-core": "^6.4.0",
23 | "babel-eslint": "^6.1.2",
24 | "babel-plugin-add-module-exports": "^0.2.1",
25 | "babel-plugin-transform-object-assign": "^6.3.13",
26 | "babel-plugin-transform-runtime": "^6.4.0",
27 | "babel-preset-es2015": "^6.24.1",
28 | "babel-preset-stage-0": "^6.3.13",
29 | "cheerio": "^0.20.0",
30 | "debug": "^2.2.0",
31 | "del": "^2.2.0",
32 | "eslint-plugin-jasmine": "^1.8.1",
33 | "gulp": "^4.0.0",
34 | "gulp-babel": "^6.1.1",
35 | "gulp-eslint": "^3.0.1",
36 | "gulp-if": "^2.0.0",
37 | "gulp-jasmine": "^4.0.0",
38 | "gulp-plumber": "^1.0.0",
39 | "jasmine-async-suite": "^0.0.7",
40 | "merge-stream": "^1.0.0",
41 | "methods": "^1.1.1",
42 | "npm": "^5.0.4",
43 | "phantomjs": "^2.1.7",
44 | "phantomjs-prebuilt": "^2.1.16",
45 | "puppeteer": "^1.3.0",
46 | "require-dir": "^0.3.2",
47 | "selenium-standalone": "^6.5.0",
48 | "slimerjs": "^0.906.2",
49 | "supertest": "^3.0.0",
50 | "wait-on": "^2.0.2",
51 | "webdriverio": "^4.12.0",
52 | "webpack-stream": "^3.2.0"
53 | },
54 | "dependencies": {
55 | "babel-polyfill": "^6.3.14",
56 | "babel-runtime": "^6.3.19",
57 | "express": "^4.13.3",
58 | "flat-map": "^1.0.0",
59 | "jasmine-json-stream-reporter": "^0.3.1",
60 | "jasmine-profile-reporter": "^0.0.2",
61 | "jasmine-terminal-reporter": "^1.0.2",
62 | "lodash.once": "^4.0.0",
63 | "mime": "^1.3.4",
64 | "multipipe": "^2.0.1",
65 | "portastic": "^1.0.1",
66 | "qs": "^6.5.1",
67 | "serve-favicon": "^2.3.0",
68 | "sourcemapped-stacktrace": "^1.0.1",
69 | "split2": "^2.1.0",
70 | "thenify": "^3.1.1",
71 | "through2": "^2.0.0",
72 | "through2-reduce": "^1.1.1",
73 | "vinyl": "^1.2.0",
74 | "ws": "^3.3.1"
75 | },
76 | "peerDependencies": {
77 | "fancy-log": "^1.3.2",
78 | "gulp": "^4.0.0",
79 | "jasmine-core": "^3.1.0",
80 | "plugin-error": "^1.0.1"
81 | },
82 | "scripts": {
83 | "test": "gulp"
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/public/jasmine_favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasmine/gulp-jasmine-browser/606f0b50dd39b3527f5da08685a53884d3e3f9ad/public/jasmine_favicon.png
--------------------------------------------------------------------------------
/spec/fixtures/dummy_spec.js:
--------------------------------------------------------------------------------
1 | describe('dummy', function() {
2 | it('makes a basic passing assertion', function() {
3 | console.log('A message from the page.');
4 | expect(true).toBe(true);
5 | });
6 |
7 | it('makes a basic failing assertion', function() {
8 | expect(true).toBe(false);
9 | });
10 | });
--------------------------------------------------------------------------------
/spec/fixtures/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const webpack = require('webpack-stream');
3 | const jasmineBrowser = require('../../dist/index');
4 |
5 | gulp.task('phantomjs', function() {
6 | return gulp.src('dummy_spec.js')
7 | .pipe(jasmineBrowser.specRunner({console: true}))
8 | .pipe(jasmineBrowser.headless({driver: 'phantomjs', showColors: false}));
9 | });
10 |
11 | gulp.task('slimerjs', function() {
12 | return gulp.src('dummy_spec.js')
13 | .pipe(jasmineBrowser.specRunner({console: true}))
14 | .pipe(jasmineBrowser.headless({driver: 'slimerjs', showColors: false}));
15 | });
16 |
17 | gulp.task('chrome', () => {
18 | return gulp.src('dummy_spec.js')
19 | .pipe(jasmineBrowser.specRunner({console: true}))
20 | .pipe(jasmineBrowser.headless({driver: 'chrome', showColors: false}));
21 | });
22 |
23 | gulp.task('server', function() {
24 | return gulp.src('dummy_spec.js')
25 | .pipe(jasmineBrowser.specRunner({console: false}))
26 | .pipe(jasmineBrowser.server());
27 | });
28 |
29 | gulp.task('webpack-server', function() {
30 | return gulp.src('mutable_spec.js')
31 | .pipe(webpack({watch: true, output: {filename: 'spec.js'}}))
32 | .pipe(jasmineBrowser.specRunner({console: false}))
33 | .pipe(jasmineBrowser.server());
34 | });
35 |
--------------------------------------------------------------------------------
/spec/fixtures/mutable_spec.js:
--------------------------------------------------------------------------------
1 | it('makes a basic failing assertion', function() { expect(true).toBe(false); });
--------------------------------------------------------------------------------
/spec/index_spec.js:
--------------------------------------------------------------------------------
1 | import {describeWithWebdriver, describeWithoutTravisCI, visit} from './spec_helper';
2 | import fs from 'fs';
3 | import path from 'path';
4 | import childProcess from 'child_process';
5 |
6 | describe('gulp-jasmine-browser', function() {
7 | const gulpTimeout = 15000;
8 |
9 | let processes;
10 |
11 | beforeEach(function() {
12 | processes = [];
13 | });
14 |
15 | function gulp(task) {
16 | let resolveCompleted;
17 | const completed = new Promise(resolve => resolveCompleted = resolve);
18 |
19 | const gulpPath = path.resolve('node_modules', '.bin', 'gulp');
20 | const gulpFile = path.resolve(__dirname, 'fixtures', 'gulpfile.js');
21 | const process = childProcess.exec([gulpPath, '--gulpfile', gulpFile, task].join(' '),
22 | {timeout: gulpTimeout}, (error, stdout, stderr) => resolveCompleted({error, stdout, stderr}));
23 |
24 | const closed = new Promise(resolve => process.on('close', resolve));
25 |
26 | processes.push({process, closed});
27 |
28 | return {
29 | completed,
30 | process
31 | };
32 | }
33 |
34 | afterEach.async(async function() {
35 | (await Promise.all(processes)).filter(p => p.process).map(p => (p.process.kill(), p.closed));
36 | });
37 |
38 | describeWithoutTravisCI('when running a headless browser', () => {
39 | it.async('can run tests via PhantomJS', async function() {
40 | const {error, stdout, stderr} = await gulp('phantomjs').completed;
41 | expect(error).toBeTruthy();
42 | expect(stderr).toContain('Error: 1 failure');
43 | expect(stdout).toContain('.F');
44 | expect(stdout).toContain('A message from the page');
45 | });
46 |
47 | it.async('can run tests via SlimerJS', async function() {
48 | const {error, stdout, stderr} = await gulp('slimerjs').completed;
49 | expect(error).toBeTruthy();
50 | expect(stderr).toContain('Error: 1 failure');
51 | expect(stdout).toContain('.F');
52 | expect(stdout).toContain('A message from the page');
53 | });
54 |
55 | it.async('can run tests via headless chrome', async () => {
56 | const {error, stdout, stderr} = await gulp('chrome').completed;
57 | expect(error).toBeTruthy();
58 | expect(stderr).toContain('Error: 1 failure');
59 | expect(stdout).toContain('.F');
60 | expect(stdout).toContain('A message from the page');
61 | });
62 | });
63 |
64 | describeWithoutTravisCI('when running in a browser', function() {
65 | describeWithWebdriver('when running with webdriver', () => {
66 | let page;
67 | it.async('allows running tests in a browser', async function() {
68 | gulp('server');
69 | page = (await visit('http://localhost:8888')).page;
70 | await page.waitForExist('.jasmine-bar.jasmine-failed');
71 | const text = await page.getText('.jasmine-bar.jasmine-failed');
72 | expect(text).toMatch('2 specs, 1 failure');
73 | });
74 |
75 | it.async('allows re-running tests in a browser', async function() {
76 | gulp('server');
77 | page = (await visit('http://localhost:8888')).page;
78 | await page.url('http://localhost:8888');
79 | await page.refresh();
80 | await page.waitForExist('.jasmine-bar.jasmine-failed');
81 | const text = await page.getText('.jasmine-bar.jasmine-failed');
82 | expect(text).toMatch('2 specs, 1 failure');
83 | });
84 |
85 | describe('when the file is mutated', function() {
86 | const oldSpec = 'it(\'makes a basic failing assertion\', function() { expect(true).toBe(false); });';
87 | const newSpec = 'it(\'makes a basic passing assertion\', function() { expect(true).toBe(true); });';
88 | let pathToMutableSpec;
89 |
90 | beforeEach(function() {
91 | pathToMutableSpec = path.resolve(__dirname, 'fixtures', 'mutable_spec.js');
92 | });
93 |
94 | afterEach(function(done) {
95 | fs.writeFile(pathToMutableSpec, oldSpec, done);
96 | });
97 |
98 | it.async('supports webpack with watch: true', async function() {
99 | const {process: gulpProcess} = gulp('webpack-server');
100 | page = (await visit('http://localhost:8888')).page;
101 | await page.waitForExist('.jasmine-bar.jasmine-failed');
102 | let text = await page.getText('.jasmine-bar.jasmine-failed');
103 | expect(text).toMatch('1 spec, 1 failure');
104 | function waitForWebpack() {
105 | return new Promise(resolve => {
106 | gulpProcess.stdout.on('data', chunk => chunk.match(/webpack is watching for changes/i) && resolve());
107 | });
108 | }
109 | fs.writeFileSync(pathToMutableSpec, newSpec);
110 |
111 | await waitForWebpack();
112 | text = await page.refresh()
113 | .waitForExist('.jasmine-bar.jasmine-passed')
114 | .getText('.jasmine-bar.jasmine-passed');
115 | expect(text).toMatch('1 spec, 0 failures');
116 | });
117 | });
118 | });
119 | });
120 | });
121 |
--------------------------------------------------------------------------------
/spec/lib/server_spec.js:
--------------------------------------------------------------------------------
1 | import {Deferred, withUnhandledRejection} from '../spec_helper';
2 | import request from 'supertest';
3 | import {createServer} from '../../dist/lib/server';
4 |
5 | describe('Server', function() {
6 | let app, files;
7 | beforeEach(function() {
8 | files = {
9 | 'specRunner.html': 'The Spec Runner'
10 | };
11 | });
12 |
13 | describe('when the server is not passed options', function() {
14 | beforeEach(function() {
15 | app = createServer(files);
16 | });
17 |
18 | describe('GET /', function() {
19 | it.async('renders the spec runner', async function() {
20 | const res = await request(app).get('/').expect(200);
21 | expect(res.text).toContain('The Spec Runner');
22 | });
23 | });
24 |
25 | describe('GET *', function() {
26 | describe('with a file that exists', function() {
27 | beforeEach(function() {
28 | files['foo.js'] = 'Foo Content';
29 | });
30 |
31 | it.async('renders the file', async function() {
32 | const res = await request(app).get('/foo.js').expect(200);
33 | expect(res.text).toContain('Foo Content');
34 | });
35 | });
36 |
37 | describe('with a file that does not exist', function() {
38 | it.async('returns 404', async function() {
39 | const res = await request(app).get('/bar.js');
40 | expect(res.statusCode).toBe(404);
41 | });
42 | });
43 | });
44 | });
45 |
46 | describe('when the server is passed whenReady', function() {
47 | let whenReady;
48 | beforeEach(function() {
49 | whenReady = new Deferred();
50 | app = createServer(files, {whenReady: () => whenReady});
51 | });
52 |
53 | describe('GET /', function() {
54 | describe('whenReady is resolved', function() {
55 | it.async('renders the valid version of spec runner', async function() {
56 | setTimeout(function() {
57 | files['specRunner.html'] = 'The New Version';
58 | whenReady.resolve();
59 | }, 100);
60 |
61 | const res = await request(app).get('/').expect(200);
62 | expect(res.text).toContain('The New Version');
63 | });
64 |
65 | describe('when there is an error', () => {
66 | withUnhandledRejection();
67 |
68 | it.async('does not render intermediate invalid states', async function() {
69 | setTimeout(function() {
70 | files['specRunner.html'] = 'The Bad Version';
71 | whenReady.reject(new Error('some error'));
72 | whenReady = new Deferred();
73 | }, 100);
74 |
75 | setTimeout(function() {
76 | files['specRunner.html'] = 'The Good Version';
77 | whenReady.resolve();
78 | }, 200);
79 |
80 | const res = await request(app).get('/').expect(200);
81 | expect(res.text).toContain('The Good Version');
82 | });
83 | });
84 |
85 | });
86 | });
87 | });
88 | });
--------------------------------------------------------------------------------
/spec/lib/spec_runner_spec.js:
--------------------------------------------------------------------------------
1 | import '../spec_helper';
2 | import jasmineCore from 'jasmine-core';
3 | import fs from 'fs';
4 | import path from 'path';
5 | import $ from 'cheerio';
6 | import SpecRunner from '../../dist/lib/spec_runner';
7 |
8 | describe('SpecRunner', () => {
9 | let cssFiles, jsFiles, subject;
10 |
11 | function loadJasmineFiles(...types) {
12 | return types.map(type => {
13 | return jasmineCore.files[type].map(fileName => fs.readFileSync(path.resolve(jasmineCore.files.path, fileName), 'utf8'));
14 | });
15 | }
16 |
17 | beforeEach(() => {
18 | const result = loadJasmineFiles('jsFiles', 'cssFiles');
19 | jsFiles = result[0];
20 | cssFiles = result[1];
21 | });
22 |
23 | describe('when the console true option is true', () => {
24 | let jsonStreamReporter, bootJs;
25 |
26 | beforeEach(() => {
27 | jsonStreamReporter = fs.readFileSync(require.resolve('jasmine-json-stream-reporter/browser.js'), 'utf8');
28 | bootJs = fs.readFileSync(path.resolve(__dirname, '..', '..', 'dist', 'lib', 'boot.js'), 'utf8');
29 | subject = new SpecRunner({console: true});
30 | });
31 |
32 | it('includes all of the Jasmine library files', () => {
33 | const html = subject.contents.toString();
34 | const $tags = $.load(html)('script,style,link');
35 |
36 | expect($tags.length).toBe(6);
37 |
38 | expect($tags.eq(0).is('style')).toBe(true);
39 | expect($tags.eq(0).html()).toBe(cssFiles[0]);
40 |
41 | expect($tags.eq(1).is('script')).toBe(true);
42 | expect($tags.eq(1).html()).toBe(jsFiles[0]);
43 |
44 | expect($tags.eq(2).is('script')).toBe(true);
45 | expect($tags.eq(2).html()).toBe(jsFiles[1]);
46 |
47 | expect($tags.eq(3).is('script')).toBe(true);
48 | expect($tags.eq(3).html()).toBe(jsFiles[2]);
49 |
50 | expect($tags.eq(4).is('script')).toBe(true);
51 | expect($tags.eq(4).html()).toBe(jsonStreamReporter);
52 |
53 | expect($tags.eq(5).is('script')).toBe(true);
54 | expect($tags.eq(5).html()).toBe(bootJs);
55 | });
56 |
57 | it('allows adding additional css and js files', () => {
58 | subject.addFile('foo.js');
59 | subject.addFile('bar.css');
60 | subject.addFile('foo.js');
61 | subject.addFile('bar.css');
62 | subject.addFile('bar.css');
63 |
64 | const html = subject.contents;
65 | const $tags = $.load(html)('script,style,link');
66 |
67 | expect($tags.length).toBe(8);
68 |
69 | expect($tags.eq(6).attr('src')).toBe('/foo.js');
70 | expect($tags.eq(6).is('script')).toBe(true);
71 |
72 | expect($tags.eq(7).attr('href')).toBe('/bar.css');
73 | expect($tags.eq(7).attr('type')).toBe('text/css');
74 | expect($tags.eq(7).attr('rel')).toBe('stylesheet');
75 | expect($tags.eq(7).is('link')).toBe(true);
76 | });
77 |
78 | describe('when profile is true', () => {
79 | let profileReporterJs;
80 |
81 | beforeEach(() => {
82 | profileReporterJs = fs.readFileSync(require.resolve('jasmine-profile-reporter/browser.js'), 'utf8');
83 | subject = new SpecRunner({profile: true});
84 | });
85 |
86 | it('includes all of the Jasmine library files', () => {
87 | const html = subject.contents.toString();
88 | const $tags = $.load(html)('script,style,link');
89 |
90 | expect($tags.length).toBe(7);
91 |
92 | expect($tags.eq(4).is('script')).toBe(true);
93 | expect($tags.eq(4).html()).toBe(profileReporterJs);
94 | });
95 | });
96 | });
97 |
98 | describe('when the console true option is not true', () => {
99 | let bootFiles;
100 |
101 | beforeEach(() => {
102 | bootFiles = loadJasmineFiles('bootFiles')[0];
103 | });
104 |
105 | describe('when the sourcemapped stacktrace is not true', () => {
106 | beforeEach(() => {
107 | subject = new SpecRunner();
108 | });
109 |
110 | it('includes all of the Jasmine library files', () => {
111 | const html = subject.contents.toString();
112 | const $tags = $.load(html)('script,style,link');
113 |
114 | expect($tags.length).toBe(5);
115 |
116 | expect($tags.eq(0).is('style')).toBe(true);
117 | expect($tags.eq(0).html()).toBe(cssFiles[0]);
118 |
119 | expect($tags.eq(1).is('script')).toBe(true);
120 | expect($tags.eq(1).html()).toBe(jsFiles[0]);
121 |
122 | expect($tags.eq(2).is('script')).toBe(true);
123 | expect($tags.eq(2).html()).toBe(jsFiles[1]);
124 |
125 | expect($tags.eq(3).is('script')).toBe(true);
126 | expect($tags.eq(3).html()).toBe(jsFiles[2]);
127 |
128 | expect($tags.eq(4).is('script')).toBe(true);
129 | expect($tags.eq(4).html()).toBe(bootFiles[0]);
130 | });
131 | });
132 |
133 | describe('when the sourcemapped stacktrace is true', () => {
134 | let sourcemappedStacktraceJs, sourceMappedStacktraceReporterCss, sourceMappedStacktraceReporterJs;
135 |
136 | beforeEach(() => {
137 | sourcemappedStacktraceJs = fs.readFileSync(require.resolve('sourcemapped-stacktrace/dist/sourcemapped-stacktrace.js'), 'utf8');
138 | sourceMappedStacktraceReporterCss = fs.readFileSync(path.resolve(__dirname, '..', '..', 'dist', 'lib', 'stylesheets', 'sourcemapped_stacktrace_reporter.css'), 'utf8');
139 | sourceMappedStacktraceReporterJs = fs.readFileSync(path.resolve(__dirname, '..', '..', 'dist', 'lib', 'reporters', 'add_sourcemapped_stacktrace_reporter.js'), 'utf8');
140 | subject = new SpecRunner({sourcemappedStacktrace: true});
141 | });
142 |
143 | it('includes all of the Jasmine library files', () => {
144 | const html = subject.contents.toString();
145 | const $tags = $.load(html)('script,style,link');
146 |
147 | expect($tags.length).toBe(8);
148 |
149 | expect($tags.eq(0).is('style')).toBe(true);
150 | expect($tags.eq(0).html()).toBe(cssFiles[0]);
151 |
152 | expect($tags.eq(1).is('style')).toBe(true);
153 | expect($tags.eq(1).html()).toBe(sourceMappedStacktraceReporterCss);
154 |
155 | expect($tags.eq(2).is('script')).toBe(true);
156 | expect($tags.eq(2).html()).toBe(jsFiles[0]);
157 |
158 | expect($tags.eq(3).is('script')).toBe(true);
159 | expect($tags.eq(3).html()).toBe(jsFiles[1]);
160 |
161 | expect($tags.eq(4).is('script')).toBe(true);
162 | expect($tags.eq(4).html()).toBe(jsFiles[2]);
163 |
164 | expect($tags.eq(5).is('script')).toBe(true);
165 | expect($tags.eq(5).html()).toBe(bootFiles[0]);
166 |
167 | expect($tags.eq(6).is('script')).toBe(true);
168 | expect($tags.eq(6).html()).toBe(sourcemappedStacktraceJs);
169 |
170 | expect($tags.eq(7).is('script')).toBe(true);
171 | expect($tags.eq(7).html()).toBe(sourceMappedStacktraceReporterJs);
172 | });
173 | });
174 |
175 | describe('when profile is true', () => {
176 | let addProfileReporterJs, profileReporterJs;
177 |
178 | beforeEach(() => {
179 | addProfileReporterJs = fs.readFileSync(path.resolve(__dirname, '..', '..', 'dist', 'lib', 'reporters', 'add_profile_reporter.js'), 'utf8');
180 | profileReporterJs = fs.readFileSync(require.resolve('jasmine-profile-reporter/browser.js'), 'utf8');
181 | subject = new SpecRunner({profile: true});
182 | });
183 |
184 | it('includes all of the Jasmine library files', () => {
185 | const html = subject.contents.toString();
186 | const $tags = $.load(html)('script,style,link');
187 |
188 | expect($tags.length).toBe(7);
189 |
190 | expect($tags.eq(0).is('style')).toBe(true);
191 | expect($tags.eq(0).html()).toBe(cssFiles[0]);
192 |
193 | expect($tags.eq(1).is('script')).toBe(true);
194 | expect($tags.eq(1).html()).toBe(jsFiles[0]);
195 |
196 | expect($tags.eq(2).is('script')).toBe(true);
197 | expect($tags.eq(2).html()).toBe(jsFiles[1]);
198 |
199 | expect($tags.eq(3).is('script')).toBe(true);
200 | expect($tags.eq(3).html()).toBe(jsFiles[2]);
201 |
202 | expect($tags.eq(4).is('script')).toBe(true);
203 | expect($tags.eq(4).html()).toBe(profileReporterJs);
204 |
205 | expect($tags.eq(5).is('script')).toBe(true);
206 | expect($tags.eq(5).html()).toBe(bootFiles[0]);
207 |
208 | expect($tags.eq(6).is('script')).toBe(true);
209 | expect($tags.eq(6).html()).toBe(addProfileReporterJs);
210 | });
211 | });
212 | });
213 | });
--------------------------------------------------------------------------------
/spec/spec_helper.js:
--------------------------------------------------------------------------------
1 | import Deferred from './support/deferred';
2 | import {visit, describeWithWebdriver} from './support/webdriver_helper';
3 | import JasmineAsync from 'jasmine-async-suite';
4 | import {withUnhandledRejection} from './support/unhandled_rejection_helper';
5 |
6 | const {DEFAULT_TIMEOUT_INTERVAL} = jasmine;
7 |
8 | JasmineAsync.install();
9 |
10 | function describeWithoutTravisCI(text, callback) {
11 | if (process.env.TRAVIS !== 'true') callback();
12 | }
13 |
14 | function timeout(duration = 0) {
15 | return new Promise(resolve => setTimeout(resolve, duration));
16 | }
17 |
18 | beforeEach(() => {
19 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
20 | });
21 |
22 | afterAll(() => {
23 | JasmineAsync.uninstall();
24 | jasmine.DEFAULT_TIMEOUT_INTERVAL = DEFAULT_TIMEOUT_INTERVAL;
25 | delete require.cache[require.resolve(__filename)];
26 | });
27 |
28 | export {describeWithoutTravisCI, describeWithWebdriver, Deferred, timeout, visit, withUnhandledRejection};
--------------------------------------------------------------------------------
/spec/support/deferred.js:
--------------------------------------------------------------------------------
1 | export default function Deferred() {
2 | let resolver, rejector;
3 | const promise = new Promise(function(res, rej) {
4 | resolver = res;
5 | rejector = rej;
6 | });
7 |
8 | const wrapper = Object.assign(promise, {
9 | resolve(...args) {
10 | resolver(...args);
11 | return wrapper;
12 | },
13 | reject(...args) {
14 | rejector(...args);
15 | return wrapper;
16 | },
17 | promise() {
18 | return promise;
19 | }
20 | });
21 | return wrapper;
22 | }
--------------------------------------------------------------------------------
/spec/support/jasmine_webdriver.js:
--------------------------------------------------------------------------------
1 | import * as selenium from './selenium';
2 | import thenify from 'thenify';
3 | import * as webdriverio from 'webdriverio';
4 | import waitOnCallback from 'wait-on';
5 |
6 | const waitOn = thenify(waitOnCallback);
7 | const privates = new WeakMap();
8 |
9 | export default class JasmineWebdriver {
10 | constructor({browser = 'firefox', timeout = 500} = {}) {
11 | privates.set(this, {processes: [], desiredCapabilities: {browserName: browser}, timeout});
12 | }
13 |
14 | driver() {
15 | const {desiredCapabilities, processes, timeout} = privates.get(this);
16 | return new Promise(async (resolve, reject) => {
17 | const port = 4444;
18 | await selenium.install();
19 | const process = await selenium.start({spawnOptions: {stdio: ['ignore', 'ignore', 'ignore']}});
20 | processes.push({process, closed: new Promise(res => process.once('close', res))});
21 | await waitOn({resources: [`tcp:${port}`], timeout: 30000}).catch(() => reject(`error in waiting for selenium server on port ${port}`));
22 | const driver = webdriverio.remote({desiredCapabilities, waitforTimeout: timeout}).init();
23 | await driver;
24 | processes.push({driver});
25 | resolve({driver});
26 | });
27 | }
28 |
29 | async end() {
30 | const {processes} = privates.get(this);
31 | const webdriverProcesses = processes.filter(p => p.driver).map(p => p.driver.end());
32 | await Promise.all(webdriverProcesses);
33 | const otherProcesses = processes.filter(p => p.process).map(p => (p.process.kill(), p.closed));
34 | await Promise.all(otherProcesses);
35 | return Promise.all([webdriverProcesses, ...otherProcesses]);
36 | }
37 | }
--------------------------------------------------------------------------------
/spec/support/selenium.js:
--------------------------------------------------------------------------------
1 | import seleniumStandalone from 'selenium-standalone';
2 | import thenify from 'thenify';
3 |
4 | export const install = thenify(seleniumStandalone.install);
5 | export const start = thenify(seleniumStandalone.start);
6 |
--------------------------------------------------------------------------------
/spec/support/unhandled_rejection_helper.js:
--------------------------------------------------------------------------------
1 | export function withUnhandledRejection() {
2 | let unhandledRejection;
3 | beforeEach(() => {
4 | unhandledRejection = jasmine.createSpy('unhandledRejection');
5 | if (!process.listeners('unhandledRejection').length) process.on('unhandledRejection', unhandledRejection);
6 | });
7 |
8 | afterEach(() => {
9 | process.removeListener(unhandledRejection, unhandledRejection);
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/spec/support/webdriver_helper.js:
--------------------------------------------------------------------------------
1 | import JasmineWebdriver from './jasmine_webdriver';
2 |
3 | let webdriver;
4 |
5 | export function visit(url) {
6 | return webdriver.driver().then(({driver}) => {
7 | return driver.url(url).then(() => ({page: driver}));
8 | });
9 | }
10 |
11 | export function describeWithWebdriver(name, callback, options = {}) {
12 | describe(name, function() {
13 | beforeEach(() => {
14 | webdriver = webdriver || new JasmineWebdriver({timeout: 5000, ...options});
15 | });
16 |
17 | afterEach.async(async function() {
18 | await webdriver.end();
19 | });
20 |
21 | callback();
22 | });
23 | }
--------------------------------------------------------------------------------
/spec/webpack/jasmine-plugin_spec.js:
--------------------------------------------------------------------------------
1 | import '../spec_helper';
2 | import JasminePlugin from '../../dist/webpack/jasmine-plugin';
3 | import {EventEmitter} from 'events';
4 |
5 | describe('JasminePlugin', function() {
6 | let subject, compiler;
7 | beforeEach(function() {
8 | subject = new JasminePlugin();
9 | compiler = new EventEmitter();
10 | compiler.plugin = compiler.on;
11 | subject.apply(compiler);
12 | });
13 |
14 | describe('#whenReady', function() {
15 | let doneSpy, failSpy;
16 | beforeEach(function() {
17 | doneSpy = jasmine.createSpy('done');
18 | failSpy = jasmine.createSpy('fail');
19 | });
20 |
21 | it('does not resolve or reject the promise', function(done) {
22 | subject.whenReady().then(doneSpy, failSpy);
23 | expect(doneSpy).not.toHaveBeenCalled();
24 | expect(failSpy).not.toHaveBeenCalled();
25 | done();
26 | });
27 |
28 | describe('when the done event is emitted', function() {
29 | beforeEach(function() {
30 | compiler.emit('done');
31 | });
32 |
33 | it.async('resolves the promise', async function() {
34 | await subject.whenReady().then(doneSpy, failSpy);
35 | expect(doneSpy).toHaveBeenCalled();
36 | });
37 |
38 | describe('and then the invalid event is emitted', function() {
39 | it('resets the promise', function(done) {
40 | compiler.emit('invalid');
41 | subject.whenReady().then(doneSpy, failSpy);
42 | setTimeout(function() {
43 | expect(doneSpy).not.toHaveBeenCalled();
44 | expect(failSpy).not.toHaveBeenCalled();
45 | done();
46 | }, 1);
47 | });
48 | });
49 | });
50 |
51 | describe('when the invalid event is emitted', function() {
52 | it('rejects the promise', async function(done) {
53 | try {
54 | const whenReady = subject.whenReady();
55 | whenReady.then(doneSpy, failSpy);
56 | compiler.emit('invalid');
57 | await whenReady;
58 | } finally {
59 | expect(failSpy).toHaveBeenCalled();
60 | done();
61 | }
62 | });
63 | });
64 | });
65 | });
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import {obj as through} from 'through2';
2 | import {headless, server, slimerjs, phantomjs, chrome} from './lib/headless';
3 | import SpecRunner from './lib/spec_runner';
4 |
5 | function specRunner({profile, sourcemappedStacktrace} = {}) {
6 | const specRunner = new SpecRunner({profile, sourcemappedStacktrace, path: '/specRunner.html'});
7 | const consoleRunner = new SpecRunner({console: true, path: '/consoleRunner.html'});
8 | return through(function(file, encoding, next) {
9 | this.push(file);
10 | this.push(specRunner.addFile(file.relative));
11 | this.push(consoleRunner.addFile(file.relative));
12 | next();
13 | });
14 | }
15 |
16 | export {headless, server, slimerjs, phantomjs, chrome, specRunner};
--------------------------------------------------------------------------------
/src/lib/boot.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | function injectQueryParams(env, QueryString, SpecFilter) {
3 | //TODO: test query params
4 |
5 | var queryString = new QueryString({
6 | getWindowLocation: function() { return window.location; }
7 | });
8 |
9 | var stoppingOnSpecFailure = queryString.getParam('failFast');
10 | env.stopOnSpecFailure(typeof stoppingOnSpecFailure === 'undefined' ? true : stoppingOnSpecFailure);
11 |
12 | var throwingExpectationFailures = queryString.getParam('throwFailures');
13 | env.throwOnExpectationFailure(throwingExpectationFailures);
14 |
15 | var random = queryString.getParam('random');
16 | env.randomizeTests(random);
17 |
18 | var seed = queryString.getParam('seed');
19 | if (seed) {
20 | env.seed(seed);
21 | }
22 |
23 | var specFilter = new SpecFilter({
24 | filterString: function() { return queryString.getParam('spec'); }
25 | });
26 |
27 | env.specFilter = function(spec) {
28 | return specFilter.matches(spec.getFullName());
29 | };
30 | }
31 |
32 | function extend(destination, source) {
33 | for (var property in source) destination[property] = source[property];
34 | return destination;
35 | }
36 |
37 | window.jasmine = window.jasmine || jasmineRequire.core(jasmineRequire);
38 |
39 | var env = jasmine.getEnv();
40 | injectQueryParams(env, jasmineRequire.QueryString(), jasmineRequire.HtmlSpecFilter());
41 |
42 | var jasmineInterface = jasmineRequire.interface(jasmine, env);
43 |
44 | extend(window, jasmineInterface);
45 |
46 | if (window.JasmineJsonStreamReporter) {
47 | var jsonStreamReporter = new JasmineJsonStreamReporter({
48 | print: function(message) {
49 | callPhantom({message: message});
50 | },
51 | onComplete: function() {
52 | if (window.__coverage__) jsonStreamReporter.coverage(__coverage__);
53 | callPhantom({exit: true});
54 | }
55 | });
56 | jasmine.snapshot = function(snapshot) {
57 | jsonStreamReporter.snapshot(snapshot);
58 | };
59 | env.addReporter(jsonStreamReporter);
60 | }
61 |
62 | var currentWindowOnload = window.onload;
63 | window.onload = function() {
64 | if (currentWindowOnload) {
65 | currentWindowOnload();
66 | }
67 | if (window.callPhantom) env.execute();
68 | };
69 | })();
70 |
--------------------------------------------------------------------------------
/src/lib/drivers/chrome.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | export default function chrome() {
3 | return {
4 | get command() {
5 | return path.resolve(__dirname, '../runners/chrome_runner');
6 | },
7 | output: 'stderr'
8 | };
9 | }
--------------------------------------------------------------------------------
/src/lib/drivers/phantomjs.js:
--------------------------------------------------------------------------------
1 | export default function phantomJs() {
2 | return {
3 | get command() {
4 | try {
5 | return require('phantomjs-prebuilt').path;
6 | } catch(e) {
7 | try {
8 | return require('phantomjs').path;
9 | } catch(e) {
10 | return 'phantomjs';
11 | }
12 | }
13 | },
14 | runner: 'phantom_runner.js',
15 | output: 'stderr'
16 | };
17 | }
--------------------------------------------------------------------------------
/src/lib/drivers/phantomjs1.js:
--------------------------------------------------------------------------------
1 | export default function phantomJs1() {
2 | return {
3 | get command() {
4 | try {
5 | return require('phantomjs').path;
6 | } catch(e) {
7 | return 'phantomjs';
8 | }
9 | },
10 | runner: 'phantom_runner.js',
11 | output: 'stderr'
12 | };
13 | }
--------------------------------------------------------------------------------
/src/lib/drivers/slimerjs.js:
--------------------------------------------------------------------------------
1 | export default function slimerJs() {
2 | return {
3 | get command() {
4 | try {
5 | return require('slimerjs').path;
6 | } catch(e) {
7 | return 'slimerjs';
8 | }
9 | },
10 | runner: 'slimer_runner.js',
11 | output: 'stdout'
12 | };
13 | }
--------------------------------------------------------------------------------
/src/lib/headless.js:
--------------------------------------------------------------------------------
1 | import {listen} from './server';
2 | import {resolve} from 'path';
3 | import {stringify} from 'qs';
4 | import {spawn} from 'child_process';
5 | import {obj as through} from 'through2';
6 | import {obj as reduce} from 'through2-reduce';
7 | import ProfileReporter from 'jasmine-profile-reporter';
8 | import TerminalReporter from 'jasmine-terminal-reporter';
9 | import toReporter from 'jasmine-json-stream-reporter/to-reporter';
10 | import split from 'split2';
11 | import flatMap from 'flat-map';
12 | import once from 'lodash.once';
13 | import ChromeDriver from './drivers/chrome';
14 | import PhantomJsDriver from './drivers/phantomjs';
15 | import PhantomJs1Driver from './drivers/phantomjs1';
16 | import SlimerJsDriver from './drivers/slimerjs';
17 | import portastic from 'portastic';
18 | import {compact, parse} from './helper';
19 | import pipe from 'multipipe';
20 |
21 | const DEFAULT_JASMINE_PORT = 8888;
22 |
23 | const drivers = {
24 | chrome: ChromeDriver,
25 | phantomjs: PhantomJsDriver,
26 | phantomjs1: PhantomJs1Driver,
27 | slimerjs: SlimerJsDriver,
28 | _default: PhantomJsDriver
29 | };
30 |
31 | function onError(message) {
32 | try {
33 | const {PluginError} = require('plugin-error');
34 | return new PluginError('gulp-jasmine-browser', {message, showProperties: false});
35 | } catch(e) {
36 | return new Error(message);
37 | }
38 | }
39 |
40 | function findPort() {
41 | return portastic.find({min: 8000, max: DEFAULT_JASMINE_PORT, retrieve: 1}).then(([port]) => port);
42 | }
43 |
44 | function startServer(files, options) {
45 | const {port} = options;
46 | if (!port) return findPort().then(port => listen(port, files, options));
47 | return listen(port, files, options);
48 | }
49 |
50 | function defaultReporters(options, profile) {
51 | return compact([new TerminalReporter(options), profile && new ProfileReporter(options)]);
52 | }
53 |
54 | function findOrStartServer(options) {
55 | function helper(port, streamPort, files) {
56 | let serverPromise, streamPortPromise;
57 | if (!port) serverPromise = startServer(files, options);
58 | else serverPromise = portastic.test(port).then(isOpen => {
59 | if (!isOpen) return {server: {close: () => {}}, port};
60 | return startServer(files, options);
61 | });
62 | if (!streamPort) streamPortPromise = findPort();
63 | else streamPortPromise = Promise.resolve(streamPort);
64 | return Promise.all([serverPromise, streamPortPromise]);
65 | }
66 |
67 | return through((files, enc, next) => helper(options.port, options.streamPort, files)
68 | .then(i => next(null, i)).catch(next));
69 | }
70 |
71 | function createServer(options) {
72 | const {driver = 'chrome', file, random, throwFailures, spec, seed, reporter, profile, onCoverage, onSnapshot,
73 | onConsoleMessage = (...args) => console.log(...args), withSandbox, ...opts} = options;
74 | const query = stringify({catch: options.catch, file, random, throwFailures, spec, seed});
75 | const {command, runner, output} = drivers[driver in drivers ? driver : '_default']();
76 |
77 | return pipe(
78 | reduce((memo, file) => (memo[file.relative] = file.contents, memo), {}),
79 | findOrStartServer(options),
80 | flatMap(([{server, port}, streamPort], next) => {
81 | const stdio = ['pipe', output === 'stdout' ? 'pipe' : 1, output === 'stderr' ? 'pipe' : 2];
82 | const env = {...process.env};
83 | env.STREAM_PORT = streamPort;
84 | withSandbox && (env.WITH_SANDBOX = true);
85 | const phantomProcess = spawn(command, compact([runner, port, query]), {cwd: resolve(__dirname, './runners'), env, stdio});
86 | phantomProcess.on('close', () => server.close());
87 | ['SIGINT', 'SIGTERM'].forEach(e => process.once(e, () => {
88 | phantomProcess && phantomProcess.kill();
89 | process.exit();
90 | }));
91 | next(null, phantomProcess[output].pipe(split(parse)));
92 | }),
93 | toReporter(reporter || defaultReporters(opts, profile), {onError, onConsoleMessage, onCoverage, onSnapshot}),
94 | );
95 | }
96 |
97 | function createServerWatch(options) {
98 | const files = {};
99 | const createServerOnce = once(() => startServer(files, options));
100 | return through((file, enc, next) => {
101 | files[file.relative] = file.contents;
102 | createServerOnce();
103 | next(null, file);
104 | });
105 | }
106 |
107 | function headless(options = {}) {
108 | return createServer(options);
109 | }
110 |
111 | function server(options = {}) {
112 | return createServerWatch({port: DEFAULT_JASMINE_PORT, ...options});
113 | }
114 |
115 | const [slimerjs, phantomjs, chrome] = ['slimerjs', 'phantomjs', 'chrome'].map(driver => {
116 | return function(options = {}) {
117 | return headless({...options, driver});
118 | };
119 | });
120 |
121 | export {headless, server, slimerjs, phantomjs, chrome};
122 |
--------------------------------------------------------------------------------
/src/lib/helper.js:
--------------------------------------------------------------------------------
1 | function compact(array) {
2 | return array.filter(Boolean);
3 | }
4 |
5 | function parse(obj) {
6 | try {
7 | return JSON.parse(obj);
8 | } catch(e) {
9 | }
10 | }
11 |
12 | export {compact, parse};
--------------------------------------------------------------------------------
/src/lib/reporters/add_profile_reporter.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | if (window.JasmineProfileReporter) {
3 | var profileReporter = new JasmineProfileReporter({
4 | print: function(message) { console.log(message); }
5 | });
6 | jasmine.getEnv().addReporter(profileReporter);
7 | }
8 | })();
9 |
--------------------------------------------------------------------------------
/src/lib/reporters/add_sourcemapped_stacktrace_reporter.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | jasmine.getEnv().addReporter({
3 | jasmineDone: function() {
4 | Array.prototype.slice.call(document.querySelectorAll('.jasmine-stack-trace'), 0).forEach(function(node) {
5 | sourceMappedStackTrace.mapStackTrace(node.textContent, function(stack) {
6 | stack = stack.filter(function(line) { return line.match(/\.js:\d+:\d+\)$/); });
7 | node.textContent = node.previousSibling.textContent + '\n' + stack.join('\n');
8 | node.style.display = 'block';
9 | });
10 | });
11 | }
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/lib/runners/chrome_evaluate.js:
--------------------------------------------------------------------------------
1 | module.exports = streamPort => {
2 | let resolve;
3 | const promise = new Promise(res => resolve = res);
4 | const socket = new WebSocket(`ws://localhost:${streamPort}`);
5 | window.callPhantom = result => {
6 | if (result.message) socket.send(result.message);
7 | if (result.exit) (socket.close(), resolve())
8 | };
9 | socket.onopen = () => jasmine.getEnv().execute();
10 | return promise;
11 | };
--------------------------------------------------------------------------------
/src/lib/runners/chrome_runner:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 | require('babel-polyfill');
4 |
5 | (async function run(...args) {
6 | const {Server} = require('ws');
7 | const puppeteer = require('puppeteer');
8 | const run = require('./chrome_evaluate');
9 |
10 | const [,,port = 8888, query] = args;
11 | const {STREAM_PORT, WITH_SANDBOX = 'false'} = process.env;
12 | let url = `http://localhost:${port}/consoleRunner`;
13 | if (query) url += `/?${query}`;
14 |
15 | let server;
16 |
17 | try {
18 | server = new Server({port: STREAM_PORT});
19 | server.on('connection', socket => socket.on('message', console.error));
20 |
21 | let browser;
22 | try {
23 | const args = [];
24 | if (!JSON.parse(WITH_SANDBOX)) args.push('--no-sandbox');
25 | browser = await puppeteer.launch({args});
26 | const page = await browser.newPage();
27 | await page.on('pageerror', ({message}) => {
28 | console.error(JSON.stringify({id: ':consoleMessage', message}));
29 | });
30 | await page.on('error', ({message}) => {
31 | console.error(JSON.stringify({id: ':consoleMessage', message}));
32 | });
33 | await page.on('console', msg => {
34 | const type = typeof msg.type ==='string' ? msg.type : msg.type();
35 | const text = typeof msg.text ==='string' ? msg.text : msg.text();
36 | console.error(JSON.stringify({id: ':consoleMessage', message: `${type}: ${text}`}));
37 | });
38 |
39 | await page.goto(url);
40 | await page.evaluate(run, STREAM_PORT);
41 | } finally {
42 | browser && browser.close();
43 | }
44 | } finally {
45 | server.close();
46 | }
47 | })(...process.argv);
48 |
--------------------------------------------------------------------------------
/src/lib/runners/phantom_runner.js:
--------------------------------------------------------------------------------
1 | var system = require('system');
2 | var webPage = require('webpage');
3 | var args = system.args;
4 |
5 | var port = args[1] || 8888;
6 | var query = args[2];
7 |
8 | var page = webPage.create();
9 | page.onCallback = function(result) {
10 | if (result.message) system.stderr.writeLine(result.message);
11 | if (result.exit) phantom.exit();
12 | };
13 | page.onConsoleMessage = function() {
14 | page.onCallback({message: JSON.stringify({id: ':consoleMessage', message: Array.prototype.slice.call(arguments, 0).join('')})});
15 | };
16 |
17 | var url = 'http://localhost:' + port + '/consoleRunner';
18 | if (query) url += '/?' + query;
19 | page.open(url);
--------------------------------------------------------------------------------
/src/lib/runners/slimer_runner.js:
--------------------------------------------------------------------------------
1 | var system = require('system');
2 | var webPage = require('webpage');
3 | var args = system.args;
4 |
5 | var port = args[1] || 8888;
6 | var query = args[2];
7 |
8 | var page = webPage.create();
9 | page.onCallback = function(result) {
10 | if (result.message) system.stdout.writeLine(result.message);
11 | if (result.exit) slimer.exit(0);
12 | };
13 | page.onConsoleMessage = function() {
14 | page.onCallback({message: JSON.stringify({id: ':consoleMessage', message: Array.prototype.slice.call(arguments, 0).join('')})});
15 | };
16 |
17 | var url = 'http://localhost:' + port + '/consoleRunner';
18 | if (query) url += '/?' + query;
19 | page.open(url);
--------------------------------------------------------------------------------
/src/lib/server.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import mime from 'mime';
3 | import path from 'path';
4 | import favicon from 'serve-favicon';
5 | import {PassThrough} from 'stream';
6 |
7 | function log(message) {
8 | try {
9 | const log = require('fancy-log');
10 | log(message);
11 | } catch(e) {
12 | console.log(message);
13 | }
14 | }
15 |
16 | function renderFile(res, files, pathname, whenReady) {
17 | whenReady()
18 | .then(function() {
19 | pathname = decodeURIComponent(pathname);
20 | if (pathname && files[pathname]) {
21 | res.status(200).type(mime.lookup(pathname));
22 | const stream = new PassThrough();
23 | stream.end(files[pathname]);
24 | stream.pipe(res);
25 | return;
26 | }
27 | res.status(404).send('File not Found');
28 | }, function() {
29 | renderFile(res, files, pathname, whenReady);
30 | });
31 | }
32 |
33 | function createServer(files, options = {}) {
34 | const app = express();
35 |
36 | app.use(favicon(path.resolve(__dirname, '..', 'public', 'jasmine_favicon.png')));
37 |
38 | app.get('/', function(req, res) {
39 | const {whenReady = () => Promise.resolve()} = options;
40 | renderFile(res, files, 'specRunner.html', whenReady);
41 | });
42 |
43 | app.get('/consoleRunner', function(req, res) {
44 | const {whenReady = () => Promise.resolve()} = options;
45 | renderFile(res, files, 'consoleRunner.html', whenReady);
46 | });
47 |
48 | app.get('*', function(req, res) {
49 | const {whenReady = () => Promise.resolve()} = options;
50 | const filePath = req.path.replace(/^\//, '');
51 | const pathname = path.normalize(filePath);
52 | renderFile(res, files, pathname, whenReady);
53 | });
54 |
55 | return app;
56 | }
57 |
58 | function listen(port, files, options = {}) {
59 | return new Promise(resolve => {
60 | const server = createServer(files, options).listen(port, function() {
61 | log(`Jasmine server listening on port ${port}`);
62 | resolve({server, port});
63 | });
64 | });
65 | }
66 |
67 | export {createServer, listen};
--------------------------------------------------------------------------------
/src/lib/spec_runner.js:
--------------------------------------------------------------------------------
1 | import File from 'vinyl';
2 | import {files as jasmineCoreFiles} from 'jasmine-core';
3 | import {readFileSync} from 'fs';
4 | import {resolve, extname} from 'path';
5 |
6 | function resolveJasmineFiles(directoryProp, fileNamesProp) {
7 | const directory = jasmineCoreFiles[directoryProp];
8 | const fileNames = jasmineCoreFiles[fileNamesProp];
9 | return fileNames.map(fileName => resolve(directory, fileName));
10 | }
11 |
12 | const inlineTagExtensions = {'.css': 'style', '.js': 'script'};
13 |
14 | const htmlForExtension = {
15 | '.js': filePath => ``,
16 | '.css': filePath => ``,
17 | '_default': () => ''
18 | };
19 |
20 | const privates = new WeakMap();
21 |
22 | export default class SpecRunner extends File {
23 | constructor(options = {}) {
24 | const {path, profile, console, sourcemappedStacktrace} = options;
25 | super({path, base: '/'});
26 |
27 | this.contents = new Buffer('');
28 | const useSourcemappedStacktrace = !console && sourcemappedStacktrace;
29 | privates.set(this, {files: new Set()});
30 | [
31 | ...resolveJasmineFiles('path', 'cssFiles'),
32 | useSourcemappedStacktrace && 'stylesheets/sourcemapped_stacktrace_reporter.css',
33 | ...resolveJasmineFiles('path', 'jsFiles'),
34 | profile && require.resolve('jasmine-profile-reporter/browser.js'),
35 | ...(console ? [require.resolve('jasmine-json-stream-reporter/browser.js'), 'boot.js'] : resolveJasmineFiles('bootDir', 'bootFiles')),
36 | profile && !console && 'reporters/add_profile_reporter.js',
37 | useSourcemappedStacktrace && require.resolve('sourcemapped-stacktrace/dist/sourcemapped-stacktrace.js'),
38 | useSourcemappedStacktrace && 'reporters/add_sourcemapped_stacktrace_reporter.js'
39 | ].filter(Boolean).forEach(fileName => this.inlineFile(fileName));
40 | }
41 |
42 | inlineFile(filePath) {
43 | const fileContents = readFileSync(resolve(__dirname, filePath), {encoding: 'utf8'});
44 | const fileExtension = inlineTagExtensions[extname(filePath)];
45 | this.contents = Buffer.concat([
46 | this.contents,
47 | new Buffer(`<${fileExtension}>${fileContents}${fileExtension}>`)
48 | ]);
49 | return this;
50 | }
51 |
52 | addFile(filePath) {
53 | const {files} = privates.get(this);
54 | if (files.has(filePath)) return this;
55 | files.add(filePath);
56 | const fileExtension = extname(filePath);
57 | this.contents = Buffer.concat([
58 | this.contents,
59 | new Buffer(htmlForExtension[fileExtension in htmlForExtension ? fileExtension : '_default'](filePath))
60 | ]);
61 | return this;
62 | }
63 | }
--------------------------------------------------------------------------------
/src/lib/stylesheets/sourcemapped_stacktrace_reporter.css:
--------------------------------------------------------------------------------
1 | .jasmine-stack-trace {
2 | display: none;
3 | }
--------------------------------------------------------------------------------
/src/webpack/jasmine-plugin.js:
--------------------------------------------------------------------------------
1 | const privates = new WeakMap();
2 |
3 | export default class JasminePlugin {
4 | constructor() {
5 | let resolve = function() {};
6 | let reject = function() {};
7 | const promise = new Promise(function(res, rej) {
8 | resolve = res;
9 | reject = rej;
10 | });
11 |
12 | privates.set(this, {promise, resolve, reject});
13 |
14 | this.whenReady = () => privates.get(this).promise;
15 | }
16 | apply(compiler) {
17 | compiler.plugin('invalid', () => {
18 | let {resolve, reject, promise} = privates.get(this);
19 | reject();
20 | promise = new Promise(function(res, rej) {
21 | resolve = res;
22 | reject = rej;
23 | });
24 | privates.set(this, {promise, resolve, reject});
25 | return promise;
26 | });
27 | compiler.plugin('done', () => privates.get(this).resolve());
28 | }
29 | }
--------------------------------------------------------------------------------
/tasks/build.js:
--------------------------------------------------------------------------------
1 | import babel from 'gulp-babel';
2 | import del from 'del';
3 | import gulp from 'gulp';
4 | import mergeStream from 'merge-stream';
5 |
6 | const TRANSPILE_SRC = ['src/lib/drivers/**/*.js', 'src/webpack/**/*.js', 'src/lib/headless.js', 'src/lib/server.js', 'src/lib/spec_runner.js', 'src/lib/helper.js', 'src/lib/runners/chrome_runner', 'src/index.js'];
7 | const RAW_SRC = ['src/lib/boot.js', 'src/lib/runners/phantom_runner.js', 'src/lib/runners/slimer_runner.js', 'src/lib/runners/chrome_evaluate.js', 'src/lib/reporters/**/*.js'];
8 | const CSS_SRC = ['src/lib/stylesheets/**/*.css'];
9 | const NON_JS_SRC = ['LICENSE.md', 'README.md', 'package.json', 'public/jasmine_favicon.png'];
10 |
11 | gulp.task('clean', done => del('dist', done));
12 |
13 | gulp.task('babel', () => {
14 | return mergeStream(
15 | gulp.src(TRANSPILE_SRC, {base: 'src'}).pipe(babel()),
16 | gulp.src(RAW_SRC, {base: 'src'}),
17 | gulp.src(CSS_SRC, {base: 'src'}),
18 | gulp.src(NON_JS_SRC, {base: '.'})
19 | ).pipe(gulp.dest('dist'));
20 | });
21 |
22 | gulp.task('build', gulp.series('clean', 'babel'));
23 |
24 | gulp.task('build-watch', () => {
25 | return gulp.watch([...TRANSPILE_SRC, ...RAW_SRC], ['babel']);
26 | });
27 |
28 | gulp.task('watch', gulp.series('build', 'build-watch'));
29 |
--------------------------------------------------------------------------------
/tasks/default.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 |
3 | gulp.task('default', gulp.series('lint', 'spec'));
4 |
--------------------------------------------------------------------------------
/tasks/lint.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import eslint from 'gulp-eslint';
3 | import gulpIf from 'gulp-if';
4 |
5 | gulp.task('lint', () => {
6 | const {FIX: fix = true} = process.env;
7 | return gulp.src(['gulpfile.js', 'tasks/**/*.js', 'src/**/*.js', 'spec/**/*.js'], {base: '.'})
8 | .pipe(eslint({fix}))
9 | .pipe(eslint.format('stylish'))
10 | .pipe(gulpIf(file => file.eslint && typeof file.eslint.output === 'string', gulp.dest('.')))
11 | .pipe(eslint.failOnError());
12 | });
13 |
--------------------------------------------------------------------------------
/tasks/publish.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import npm from 'npm';
3 |
4 | gulp.task('publish', gulp.series('build', done => {
5 | npm.load({}, error => {
6 | if (error) {
7 | console.error(error);
8 | done();
9 | }
10 | npm.commands.publish(['dist'], error => {
11 | if (error) console.error(error);
12 | done();
13 | });
14 | });
15 | }));
--------------------------------------------------------------------------------
/tasks/spec.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import jasmine from 'gulp-jasmine'
3 | import plumber from 'gulp-plumber';
4 |
5 | gulp.task('spec-run', () => {
6 | return gulp.src(['spec/**/*_spec.js', '!spec/fixtures/**/*.js'])
7 | .pipe(plumber())
8 | .pipe(jasmine({includeStackTrace: true}));
9 | });
10 |
11 | gulp.task('spec', gulp.series('build', 'spec-run'));
12 |
--------------------------------------------------------------------------------