Compare commits
7 Commits
master
...
headless-c
Author | SHA1 | Date | |
---|---|---|---|
|
9a510a8f18 | ||
|
ae839dbd3f | ||
|
9aec3c2cd8 | ||
|
7dcf2425de | ||
|
0a156892cc | ||
|
1b1a899f0e | ||
|
c949b89055 |
35
README.md
35
README.md
@ -1,6 +1,6 @@
|
||||
# grunt-contrib-jasmine v1.1.0 [![Build Status: Linux](https://travis-ci.org/gruntjs/grunt-contrib-jasmine.svg?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-jasmine) [![Build Status: Windows](https://ci.appveyor.com/api/projects/status/5985958by5rhnh31/branch/master?svg=true)](https://ci.appveyor.com/project/gruntjs/grunt-contrib-jasmine/branch/master)
|
||||
|
||||
> Run jasmine specs headlessly through PhantomJS
|
||||
> Run jasmine specs headlessly
|
||||
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ grunt.loadNpmTasks('grunt-contrib-jasmine');
|
||||
## Jasmine task
|
||||
_Run this task with the `grunt jasmine` command._
|
||||
|
||||
Automatically builds and maintains your spec runner and runs your tests headlessly through PhantomJS.
|
||||
Automatically builds and maintains your spec runner and runs your tests headlessly.
|
||||
|
||||
#### Run specs locally or on a remote server
|
||||
|
||||
@ -98,7 +98,7 @@ Automatically deleted upon normal runs.
|
||||
Type: `String`
|
||||
Default: `_SpecRunner.html`
|
||||
|
||||
The auto-generated specfile that phantomjs will use to run your tests.
|
||||
The auto-generated specfile that the browser will use to run your tests.
|
||||
Automatically deleted upon normal runs. Use the `:build` flag to generate a SpecRunner manually e.g.
|
||||
`grunt jasmine:myTask:build`
|
||||
|
||||
@ -130,7 +130,7 @@ Specify a custom JUnit template instead of using the default `junitTemplate`.
|
||||
Type: `String`
|
||||
Default: `''`
|
||||
|
||||
The host you want PhantomJS to connect against to run your tests.
|
||||
The host you want the browser to connect against to run your tests.
|
||||
|
||||
e.g. if using an ad hoc server from within grunt
|
||||
|
||||
@ -175,6 +175,33 @@ Default: `false`
|
||||
|
||||
Display a list of all failed tests and their failure messages
|
||||
|
||||
#### options.browser
|
||||
Type: `String`
|
||||
Default: `phantomjs`
|
||||
|
||||
Which headless browser to use. Supported options are:
|
||||
|
||||
* `phantomjs` runs your tests through PhantomJS
|
||||
* `chrome` runs your tests through Chrome. See the headless option
|
||||
|
||||
#### options.headless
|
||||
Type: `Boolean`
|
||||
Default: `false`
|
||||
|
||||
Whether Chrome should be started headlessly or not. Requires Chrome 59
|
||||
|
||||
#### options.chromePoll
|
||||
Type: `Number`
|
||||
Default: `100`
|
||||
|
||||
Frequency (in milliseconds) in which the plugin polls Chrome for updates on the specs.
|
||||
|
||||
#### options.reportSlowerThan
|
||||
Type: `Number`
|
||||
Default: `-1`
|
||||
|
||||
If a spec is takes longer than this option it will be printed out after the run ends. Unit is milliseconds.
|
||||
|
||||
### Flags
|
||||
|
||||
Name: `build`
|
||||
|
@ -21,6 +21,7 @@
|
||||
"jasmine-core": "~2.4.0",
|
||||
"lodash": "~2.4.1",
|
||||
"rimraf": "^2.1.4",
|
||||
"simple-headless-chrome": "^4.3.1",
|
||||
"sprintf-js": "~1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
62
simplechrome.js
Normal file
62
simplechrome.js
Normal file
@ -0,0 +1,62 @@
|
||||
var Chrome = require('simple-headless-chrome');
|
||||
var grunt = require('grunt');
|
||||
|
||||
var browser = new Chrome({
|
||||
headless: true,
|
||||
});
|
||||
|
||||
function consume (tab) {
|
||||
return tab.evaluate(function () {
|
||||
return ChromeQueue.splice(0, ChromeQueue.length);
|
||||
});
|
||||
}
|
||||
|
||||
var duration;
|
||||
var finishedResult = {
|
||||
failed: 0
|
||||
};
|
||||
var fails = [];
|
||||
// All of this would be much more straightforward with async/await
|
||||
browser.init()
|
||||
.then(browser => browser.newTab({ privateTab: false }))
|
||||
.then(tab => {
|
||||
return tab.goTo("http://localhost:8088/_SpecRunner-affected.html")
|
||||
// Return a promise that resolves when we get the FINISHED event from our ChromeReporter
|
||||
.then(() => new Promise(resolve => {
|
||||
// Periodically evaluate
|
||||
var interval = setInterval(function () {
|
||||
consume(tab)
|
||||
.then(result => {
|
||||
var events = result.result.value;
|
||||
events.forEach(e => {
|
||||
if (e.type === 'JASMINE_STARTED') {
|
||||
duration = e.when;
|
||||
}
|
||||
if (e.type === 'FINISHED') {
|
||||
duration = e.when - duration;
|
||||
}
|
||||
if (e.type === 'SPEC_FINISHED') {
|
||||
if (e.payload.status === 'passed') {
|
||||
process.stdout.write('.')
|
||||
} else if (e.payload.status === 'pending') {
|
||||
process.stdout.write('*');
|
||||
} else if (e.payload.status === 'failed') {
|
||||
process.stdout.write('x');
|
||||
}
|
||||
if (e.payload.failedExpectations.length > 0) {
|
||||
finishedResult.failed++;
|
||||
fails.concat(e.payload.failedExpectations);
|
||||
}
|
||||
}
|
||||
})
|
||||
if (events.length > 0 && events[events.length - 1].type === 'FINISHED') {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
}, 100);
|
||||
}))
|
||||
.then(() => { grunt.log.writeln('\nFinished testing (' + (duration / 1000) + ' seconds)')})
|
||||
.then(() => browser.close())
|
||||
}
|
||||
)
|
119
tasks/jasmine.js
119
tasks/jasmine.js
@ -1,3 +1,4 @@
|
||||
/* global Promise:true */
|
||||
/*
|
||||
* grunt-contrib-jasmine
|
||||
* http://gruntjs.com/
|
||||
@ -19,6 +20,8 @@ module.exports = function(grunt) {
|
||||
var phantomjs = require('grunt-lib-phantomjs').init(grunt),
|
||||
chalk = require('chalk'),
|
||||
_ = require('lodash');
|
||||
|
||||
var Chrome = require('simple-headless-chrome');
|
||||
|
||||
// local lib
|
||||
var jasmine = require('./lib/jasmine').init(grunt, phantomjs);
|
||||
@ -71,6 +74,10 @@ module.exports = function(grunt) {
|
||||
|
||||
// Merge task-specific options with these defaults.
|
||||
var options = this.options({
|
||||
browser: 'phantomjs',
|
||||
headless: false,
|
||||
chromePoll: 100,
|
||||
reportSlowerThan: -1,
|
||||
version: '2.2.0',
|
||||
timeout: 10000,
|
||||
styles: [],
|
||||
@ -108,7 +115,14 @@ module.exports = function(grunt) {
|
||||
}
|
||||
|
||||
var done = this.async();
|
||||
phantomRunner(options, function(err, status) {
|
||||
|
||||
var runner = phantomRunner;
|
||||
|
||||
if (options.browser === 'chrome') {
|
||||
runner = chromeRunner;
|
||||
}
|
||||
|
||||
runner(options, function(err, status) {
|
||||
var success = !err && status.failed === 0;
|
||||
|
||||
if (err) {
|
||||
@ -127,7 +141,7 @@ module.exports = function(grunt) {
|
||||
|
||||
});
|
||||
|
||||
function phantomRunner(options, cb) {
|
||||
function __fileOrHost (options) {
|
||||
var file = options.outfile;
|
||||
|
||||
if (options.host) {
|
||||
@ -135,8 +149,91 @@ module.exports = function(grunt) {
|
||||
options.host += '/';
|
||||
}
|
||||
file = options.host + options.outfile;
|
||||
} else {
|
||||
file = path.resolve(file);
|
||||
file = 'file://' + file;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
function chromeRunner(options, cb) {
|
||||
var file = __fileOrHost(options);
|
||||
var intervalId;
|
||||
var lastEvent;
|
||||
var exitReason = null;
|
||||
|
||||
var message = 'Testing Jasmine specs via Chrome';
|
||||
|
||||
if (options.headless) {
|
||||
message += ' (headless)';
|
||||
}
|
||||
|
||||
grunt.verbose.subhead(message).or.writeln(message);
|
||||
grunt.log.writeln('');
|
||||
|
||||
var browser = new Chrome({
|
||||
headless: options.headless,
|
||||
});
|
||||
|
||||
var emitChromeEvent = function (e) {
|
||||
phantomjs.emit(e.type, e.payload);
|
||||
};
|
||||
|
||||
function consume (tab) {
|
||||
return tab.evaluate('function () { return ChromeQueue.splice(0, ChromeQueue.length); }');
|
||||
}
|
||||
|
||||
function somethingWrong (what) {
|
||||
cb(what.message, status);
|
||||
}
|
||||
|
||||
// All of this would be much more straightforward with async/await
|
||||
browser.init()
|
||||
.then(function (browser) {
|
||||
return browser.newTab()
|
||||
.then(function (tab) {
|
||||
lastEvent = new Date();
|
||||
return tab.goTo(file)
|
||||
// Return a promise that resolves when we get the FINISHED event from our ChromeReporter
|
||||
.then(function () {
|
||||
return new Promise(function (resolve) {
|
||||
// Periodically evaluate
|
||||
intervalId = setInterval(function () {
|
||||
var date = new Date();
|
||||
consume(tab)
|
||||
.then(function (result) {
|
||||
var events = result.result.value;
|
||||
if (!events || events.length === 0) {
|
||||
if (date - lastEvent > options.timeout) {
|
||||
clearInterval(intervalId);
|
||||
exitReason = 'Chrome has timed out';
|
||||
resolve();
|
||||
}
|
||||
} else {
|
||||
lastEvent = date;
|
||||
events.forEach(emitChromeEvent);
|
||||
// Exit condition
|
||||
if (events.length > 0 && events[events.length - 1].type === 'jasmine.jasmineDone') {
|
||||
clearInterval(intervalId);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, options.chromePoll);
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return browser.close();
|
||||
})
|
||||
.then(function () { cb(exitReason, status); });
|
||||
});
|
||||
}, somethingWrong);
|
||||
}
|
||||
|
||||
function phantomRunner(options, cb) {
|
||||
var file = __fileOrHost(options);
|
||||
|
||||
grunt.verbose.subhead('Testing Jasmine specs via PhantomJS').or.writeln('Testing Jasmine specs via PhantomJS');
|
||||
grunt.log.writeln('');
|
||||
|
||||
@ -173,11 +270,10 @@ module.exports = function(grunt) {
|
||||
tabstop = 2,
|
||||
thisRun = {},
|
||||
suites = {},
|
||||
slowSpecs = [],
|
||||
currentSuite;
|
||||
|
||||
status = {
|
||||
failed: 0
|
||||
};
|
||||
status.failed = 0;
|
||||
|
||||
function indent(times) {
|
||||
return new Array(+times * tabstop).join(' ');
|
||||
@ -272,6 +368,11 @@ module.exports = function(grunt) {
|
||||
failureMessages: []
|
||||
};
|
||||
|
||||
if (options.reportSlowerThan !== -1 &&
|
||||
specMetaData.duration > options.reportSlowerThan) {
|
||||
slowSpecs.push(specMetaData);
|
||||
}
|
||||
|
||||
suites[currentSuite].tests++;
|
||||
|
||||
var color = 'yellow',
|
||||
@ -371,6 +472,14 @@ module.exports = function(grunt) {
|
||||
logSummary(thisRun.summary);
|
||||
}
|
||||
|
||||
if (options.reportSlowerThan !== -1 && slowSpecs.length > 0) {
|
||||
grunt.log.writeln(chalk.yellow('Some specs were slower than ' + options.reportSlowerThan + ' ms:'));
|
||||
grunt.log.writeln();
|
||||
_.forEach(slowSpecs, function (slowSpec) {
|
||||
grunt.log.writeln(chalk.yellow(slowSpec.fullName) + chalk.red(' (' + slowSpec.duration + ' ms)'));
|
||||
})
|
||||
}
|
||||
|
||||
if (options.junit && options.junit.path) {
|
||||
writeJunitXml(suites);
|
||||
}
|
||||
|
56
tasks/jasmine/reporters/ChromeReporter.js
Normal file
56
tasks/jasmine/reporters/ChromeReporter.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* global window:true, jasmine:true */
|
||||
|
||||
(function () {
|
||||
window.ChromeQueue = [];
|
||||
|
||||
function pushToQueue (msg) {
|
||||
window.ChromeQueue.push(msg);
|
||||
}
|
||||
|
||||
var ChromeReporter = {
|
||||
lastSpecStartTime: null,
|
||||
jasmineStarted: function (suiteInfo) {
|
||||
console.log('Jasmine started: ', new Date().toString());
|
||||
pushToQueue({
|
||||
type: 'jasmine.jasmineStarted',
|
||||
when: new Date().getTime(),
|
||||
payload: suiteInfo
|
||||
});
|
||||
},
|
||||
suiteStarted: function (result) {
|
||||
pushToQueue({
|
||||
type: 'jasmine.suiteStarted',
|
||||
payload: result
|
||||
});
|
||||
},
|
||||
specStarted: function (result) {
|
||||
this.lastSpecStartTime = new Date();
|
||||
pushToQueue({
|
||||
type: 'jasmine.specStarted',
|
||||
payload: result
|
||||
});
|
||||
},
|
||||
specDone: function (result) {
|
||||
result.duration = new Date() - this.lastSpecStartTime;
|
||||
pushToQueue({
|
||||
type: 'jasmine.specDone',
|
||||
payload: result
|
||||
});
|
||||
},
|
||||
suiteDone: function (result) {
|
||||
pushToQueue({
|
||||
type: 'jasmine.suiteDone',
|
||||
payload: result
|
||||
});
|
||||
},
|
||||
jasmineDone: function () {
|
||||
pushToQueue({
|
||||
type: 'jasmine.jasmineDone'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var env = jasmine.getEnv();
|
||||
|
||||
env.addReporter(ChromeReporter);
|
||||
})();
|
@ -54,13 +54,19 @@ exports.init = function(grunt, phantomjs) {
|
||||
}
|
||||
}
|
||||
|
||||
exports.copyTempFile(path.join(__dirname, '/../jasmine/reporters/PhantomReporter.js'), path.join(tempDir, 'reporter.js'));
|
||||
var reporter = 'PhantomReporter.js';
|
||||
if (options.browser === 'chrome') {
|
||||
reporter = 'ChromeReporter.js';
|
||||
}
|
||||
|
||||
exports.copyTempFile(path.join(__dirname, '/../jasmine/reporters/' + reporter), path.join(tempDir, 'reporter.js'));
|
||||
|
||||
[].concat(jasmineRequire.files.cssFiles, jasmineRequire.files.jsFiles).forEach(function(name) {
|
||||
var srcPath = path.join(jasmineRequire.files.path, name);
|
||||
exports.copyTempFile(srcPath, path.join(tempDir, name));
|
||||
});
|
||||
|
||||
// IMHO This is broken, it will copy all boot files but later only inject boot.js, I don't see the point
|
||||
jasmineRequire.files.bootFiles.forEach(function(name) {
|
||||
var srcPath = path.join(jasmineRequire.files.bootDir, name);
|
||||
exports.copyTempFile(srcPath, path.join(tempDir, name));
|
||||
|
Loading…
Reference in New Issue
Block a user