├── .gitignore
├── test
├── fixtures
│ ├── includes
│ │ ├── inc.js
│ │ └── testIncludes.js
│ ├── basicSite.html
│ ├── testPassEngine.js
│ ├── basicSiteWithImages.html
│ ├── testScreenshots.js
│ ├── testLogColors.js
│ ├── testParallel3.js
│ ├── testPass.js
│ ├── testParallel.js
│ ├── testParallel2.js
│ ├── testParallel4.js
│ ├── testParallel5.js
│ ├── testPass2.js
│ ├── testTimeout.js
│ ├── testFail.js
│ ├── testFailFast.js
│ ├── testArgs.js
│ └── testArgsTest.js
├── expected
│ ├── testIncludes-results.xml
│ ├── testPass-results.xml
│ └── testFail-results.xml
└── casper_test.js
├── .jshintrc
├── expected
├── testPass.xml
└── testPass2.xml
├── .travis.yml
├── LICENSE-MIT
├── package.json
├── tasks
├── casper.js
└── lib
│ └── casper.js
├── Gruntfile.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | tmp
3 | node_modules
--------------------------------------------------------------------------------
/test/fixtures/includes/inc.js:
--------------------------------------------------------------------------------
1 |
2 | exports = {
3 | includeFunction : function(test) {
4 | test.assertTrue(true, 'testFunction-assertion');
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/basicSite.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Test Title
4 |
5 |
6 | Header
7 | Paragraph
8 |
9 |
--------------------------------------------------------------------------------
/test/fixtures/testPassEngine.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Casperjs Browser Support', 1, function suite(test) {
3 | test.assertTruthy("Loaded Via Specified Engine! Good enough for me.");
4 | test.done();
5 | });
--------------------------------------------------------------------------------
/test/fixtures/basicSiteWithImages.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Test Title
4 |
5 |
6 | Header
7 | Paragraph
8 |
9 |
10 |
--------------------------------------------------------------------------------
/test/fixtures/testScreenshots.js:
--------------------------------------------------------------------------------
1 | var casper = require("casper").create();
2 |
3 | casper.start('test/fixtures/basicSiteWithImages.html');
4 |
5 | casper.then(function() {
6 | this.capture('tmp/test.png');
7 | });
8 |
9 | casper.run();
10 |
11 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": false,
3 | "eqeqeq": true,
4 | "immed": true,
5 | "latedef": true,
6 | "newcap": true,
7 | "noarg": true,
8 | "sub": true,
9 | "undef": true,
10 | "boss": true,
11 | "eqnull": true,
12 | "node": true
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixtures/includes/testIncludes.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Site Testing With Includes', 1, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 | exports.includeFunction(test);
6 | });
7 |
8 | casper.run(function() {
9 | test.done();
10 | });
11 |
12 | });
--------------------------------------------------------------------------------
/test/fixtures/testLogColors.js:
--------------------------------------------------------------------------------
1 | var casper = require('casper').create({
2 | verbose: true,
3 | logLevel: 'debug'
4 | });
5 |
6 | casper.log('this is a debug message', 'debug');
7 | casper.log('and an informative one', 'info');
8 | casper.log('and a warning', 'warning');
9 | casper.log('and an error', 'error');
10 |
11 | casper.exit();
--------------------------------------------------------------------------------
/expected/testPass.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/expected/testPass2.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/expected/testIncludes-results.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/testParallel3.js:
--------------------------------------------------------------------------------
1 | casper.test.begin('Basic Parallel Site Testing Pass Tests 3', 3, function suite(test) {
2 |
3 | casper.start('test/fixtures/basicSite.html', function() {
4 | test.assertTitle('Test Title');
5 | test.assertExists('h1', 'Header Exists');
6 | test.assertExists('p', 'P Tag Exists');
7 | });
8 |
9 | casper.run(function() {
10 | test.done();
11 | });
12 |
13 | });
--------------------------------------------------------------------------------
/test/fixtures/testPass.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Site Testing Pass Tests', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 |
6 | test.assertTitle('Test Title');
7 | test.assertExists('h1', 'Header Exists');
8 | test.assertExists('p', 'P Tag Exists');
9 |
10 | });
11 |
12 | casper.run(function() {
13 | test.done();
14 | });
15 |
16 | });
--------------------------------------------------------------------------------
/test/fixtures/testParallel.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Parallel Site Testing Pass Tests', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 | test.assertTitle('Test Title');
6 | test.assertExists('h1', 'Header Exists');
7 | test.assertExists('p', 'P Tag Exists');
8 | });
9 |
10 | casper.run(function() {
11 | test.done();
12 | });
13 |
14 | });
--------------------------------------------------------------------------------
/test/fixtures/testParallel2.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Parallel Site Testing Pass Tests 2', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 | test.assertTitle('Test Title');
6 | test.assertExists('h1', 'Header Exists');
7 | test.assertExists('p', 'P Tag Exists');
8 | });
9 |
10 | casper.run(function() {
11 | test.done();
12 | });
13 |
14 | });
--------------------------------------------------------------------------------
/test/fixtures/testParallel4.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Parallel Site Testing Pass Tests 4', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 | test.assertTitle('Test Title');
6 | test.assertExists('h1', 'Header Exists');
7 | test.assertExists('p', 'P Tag Exists');
8 | });
9 |
10 | casper.run(function() {
11 | test.done();
12 | });
13 |
14 | });
--------------------------------------------------------------------------------
/test/fixtures/testParallel5.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Parallel Site Testing Pass Tests 5', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 | test.assertTitle('Test Title');
6 | test.assertExists('h1', 'Header Exists');
7 | test.assertExists('p', 'P Tag Exists');
8 | });
9 |
10 | casper.run(function() {
11 | test.done();
12 | });
13 |
14 | });
--------------------------------------------------------------------------------
/test/fixtures/testPass2.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Site Testing Pass Tests', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 |
6 | test.assertTitle('Test Title');
7 | test.assertExists('h1', 'Header Exists');
8 | test.assertExists('p', 'P Tag Exists');
9 |
10 | });
11 |
12 | casper.run(function() {
13 | test.done();
14 | });
15 |
16 | });
--------------------------------------------------------------------------------
/test/fixtures/testTimeout.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Site Testing Pass Tests', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 |
6 | test.assertTitle('Test Title');
7 | test.assertExists('h1', 'Header Exists');
8 | test.assertExists('p', 'P Tag Exists');
9 |
10 | });
11 |
12 | casper.run(function() {
13 | setTimeout(function() {
14 | test.done();
15 | }, 3000);
16 | });
17 |
18 | });
--------------------------------------------------------------------------------
/test/fixtures/testFail.js:
--------------------------------------------------------------------------------
1 | casper.test.begin('Basic Site Testing Fail Tests', 4, function suite(test) {
2 |
3 | casper.start('test/fixtures/basicSite.html', function() {
4 | test.assertTitle('Test Title');
5 | test.assertExists('h1', 'Header Exists');
6 | test.assertExists('p', 'P Tag Exists');
7 | //Fail Case
8 | test.assertExists('span', 'Should Fail - Span Tag Does Not Exist');
9 | });
10 |
11 | casper.run(function() {
12 | test.done();
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/test/fixtures/testFailFast.js:
--------------------------------------------------------------------------------
1 |
2 | casper.test.begin('Basic Site Testing Fail Fast Tests', 3, function suite(test) {
3 |
4 | casper.start('test/fixtures/basicSite.html', function() {
5 | test.assertTitle('Test Title');
6 | test.assertExists('h1', 'Header Exists');
7 | test.assertExists('p', 'P Tag Exists');
8 | //Fail
9 | test.assertExists('span', 'Span Tag Does Not Exist - Should Fail');
10 | });
11 |
12 | casper.run(function() {
13 | test.done();
14 | });
15 |
16 | });
--------------------------------------------------------------------------------
/test/expected/testPass-results.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/testArgs.js:
--------------------------------------------------------------------------------
1 | var casper = require("casper").create();
2 |
3 | casper.start('test/fixtures/basicSite.html');
4 |
5 | var isBazAnArg = casper.cli.args.indexOf('baz') == 0;
6 | var isFooAnOption = typeof casper.cli.options['foo'] !== 'undefined';
7 | var isFooOptionBar = casper.cli.options.foo == 'bar';
8 |
9 | casper.then(function() {
10 | casper.echo('expected baz to be an argument : ' + isBazAnArg);
11 | casper.echo('expected foo to be an option : ' + isFooAnOption);
12 | casper.echo('expected foo option to be bar : ' + isFooOptionBar);
13 | });
14 |
15 | casper.run(function() {
16 | casper.exit();
17 | });
18 |
--------------------------------------------------------------------------------
/test/fixtures/testArgsTest.js:
--------------------------------------------------------------------------------
1 | var casper = require("casper").create();
2 |
3 | casper.start('test/fixtures/basicSite.html');
4 |
5 | casper.then(function() {
6 |
7 | var isFooAnArg = casper.cli.args.indexOf('foo') !== -1,
8 | isBarAnArg = casper.cli.args.indexOf('bar') !== -1,
9 | isBazAnArg = casper.cli.args.indexOf('baz') !== -1;
10 |
11 | casper.echo('expecting foo to be an argument : ' + isFooAnArg);
12 | casper.echo('expecting bar to be an argument : ' + isBarAnArg);
13 | casper.echo('expecting baz to be an argument : ' + isBazAnArg);
14 |
15 | });
16 |
17 | casper.run(function() {
18 | casper.exit();
19 | });
20 |
--------------------------------------------------------------------------------
/test/expected/testFail-results.xml:
--------------------------------------------------------------------------------
1 | Should Fail - Span Tag Does Not Exist
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.8"
4 | - "0.10"
5 | env:
6 | - SLIMERJSLAUNCHER=$(which firefox) DISPLAY=:99.0 PATH=$TRAVIS_BUILD_DIR/slimerjs:$PATH
7 | before_install:
8 | - npm install -g npm
9 | - "echo 'Installing Casper'"
10 | - git clone git://github.com/n1k0/casperjs.git ~/casperjs
11 | - cd ~/casperjs
12 | - git checkout tags/1.1-beta3
13 | - export PATH=$PATH:`pwd`/bin
14 | - cd -
15 | before_script:
16 | - "export DISPLAY=:99.0"
17 | - phantomjs --version
18 | - casperjs --version
19 | - "sh -e /etc/init.d/xvfb start"
20 | - "echo 'Installing Slimer'"
21 | - "wget http://download.slimerjs.org/v0.9/0.9.0/slimerjs-0.9.0.zip"
22 | - "unzip slimerjs-0.9.0.zip"
23 | - "mv slimerjs-0.9.0 ./slimerjs"
24 | - slimerjs --version
25 | addons:
26 | firefox: "25.0"
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | wCopyright (c) 2012 Chris Miller
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grunt-casper",
3 | "description": "Run Casper CLI Scripts With Grunt",
4 | "version": "0.4.2",
5 | "author": {
6 | "name": "Chris Miller",
7 | "email": "cmille142@gmail.com",
8 | "url": "http://chris-miller.me"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:iamchrismiller/grunt-casper.git"
13 | },
14 | "bugs": "https://github.com/iamchrismiller/grunt-casper/issues/",
15 | "licenses": [
16 | {
17 | "type": "MIT",
18 | "url": "https://github.com/iamchrismiller/grunt-casper/blob/master/LICENSE-MIT"
19 | }
20 | ],
21 | "scripts": {
22 | "test": "grunt test --verbose"
23 | },
24 | "keywords": [
25 | "gruntplugin",
26 | "gruntjs",
27 | "casperjs",
28 | "grunt-casperjs",
29 | "grunt-casper"
30 | ],
31 | "devDependencies": {
32 | "grunt-contrib-jshint": "~0.10.0",
33 | "grunt-contrib-clean": "~0.5.0",
34 | "grunt-contrib-nodeunit": "~0.4.0",
35 | "grunt": "~0.4.2",
36 | "grunt-cli": "~0.1.13"
37 | },
38 | "dependencies": {
39 | "casperjs": "~1.1.0-beta3",
40 | "duration": "~0.2.0",
41 | "lodash": "~2.4.1",
42 | "phantomjs": "~1.9.7-1",
43 | "slimerjs": "^0.9.1-2"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/casper_test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //npm
4 | var grunt = require('grunt');
5 |
6 | //node
7 | var path = require('path'),
8 | fs = require('fs');
9 |
10 | var actualDir = path.join('tmp','casper'),
11 | expectedDir = path.join('test','expected');
12 |
13 | // Is there a way to specify the classname?
14 | function stripRelative(source) {
15 | return source.replace(/classname="[^"]+"/g,'classname="STRIPPED"')
16 | .replace(/time="[^"]+"/g,'time="123"')
17 | .replace(/duration="[^"]+"/g,'duration="123"')
18 | .replace(/timestamp="[^"]+"/g,'timestamp="123"')
19 | .replace(/package="[^"]+"/g,'package="STRIPPED"');
20 | }
21 |
22 | exports.casper = {
23 |
24 | tests : function(test) {
25 | var files = [
26 | 'testPass-results.xml',
27 | 'testFail-results.xml',
28 | 'testIncludes-results.xml'
29 | ];
30 |
31 | test.expect(files.length);
32 |
33 | files.forEach(function(file){
34 | var actual = grunt.file.read(path.join(actualDir, file));
35 | var expected = grunt.file.read(path.join(expectedDir, file));
36 |
37 | test.equal(stripRelative(actual), stripRelative(expected), 'should Pass all casper tests');
38 | });
39 |
40 | test.done();
41 | },
42 |
43 | screenshot : function(test) {
44 | test.expect(1);
45 | test.ok(fs.existsSync('tmp/test.png','Screenshot should exist'));
46 | test.done();
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/tasks/casper.js:
--------------------------------------------------------------------------------
1 | /*global require */
2 | "use strict";
3 |
4 | //npm
5 | var Duration = require("duration");
6 |
7 |
8 | module.exports = function (grunt) {
9 |
10 | //Retrieve Casper Lib
11 | var casperLib = require('./lib/casper').init(grunt);
12 |
13 |
14 | grunt.registerMultiTask('casper', 'execute casperjs tasks', function () {
15 | var args = Array.prototype.slice.call(arguments),
16 | options = this.options(),
17 | done = this.async(),
18 | taskName = this.nameArgs,
19 | startTime = new Date();
20 |
21 | //Once Current Task is complete
22 | //Log Duration and Finish
23 | function taskComplete(error) {
24 |
25 | var msg = "Casper Task '" + taskName + "' took ~" + new Duration(startTime).milliseconds + "ms to run";
26 | grunt.log.success(msg);
27 | if (error) {
28 | return done(false);
29 | }
30 | done();
31 | }
32 |
33 | grunt.verbose.writeflags(args, 'Arguments');
34 |
35 | grunt.util.async.forEachSeries(this.files, function(file, iteratorCb) {
36 |
37 | if (file.src.length) {
38 | //Allow Files in each task to be run concurrently
39 | if (options.parallel) {
40 | //Don't Pass this through to spawn
41 | delete options.parallel;
42 | //https://github.com/gruntjs/grunt-contrib-sass/issues/16
43 | //Set Default Concurrency at 5 (Supposed Memory Leak > 10)
44 | var concurrency = 5;
45 | if (options.concurrency) {
46 | if (options.concurrency > 10 ) {
47 | grunt.verbose.writeln('Concurrency Too High. Max 10, updating to 10.');
48 | concurrency = 10;
49 | } else if (options.concurrency < 1) {
50 | grunt.verbose.writeln('Concurrency Too Low. Min 1, updating to default 5.');
51 | } else {
52 | concurrency = options.concurrency;
53 | }
54 | //Don't Pass this through to spawn
55 | delete options.concurrency;
56 | }
57 | //Run Tests In Parallel
58 | if (file.src) {
59 | grunt.util.async.forEachLimit(file.src, concurrency, function(srcFile, next) {
60 | //Spawn Child Process
61 | casperLib.execute(srcFile, file.dest !== 'src' ? file.dest : null, options, args, next);
62 | }, function(err) {
63 | if (err) grunt.log.write('error:', err);
64 | //Call Done and Log Duration
65 | iteratorCb(err);
66 | });
67 | }
68 | } else {
69 |
70 | if (file.src) {
71 | casperLib.execute(file.src, file.dest, options, args, function(err) {
72 | //Call Done and Log Duration
73 | iteratorCb(err);
74 | });
75 | }
76 | }
77 | } else {
78 | grunt.fail.warn('Unable to compile; no valid source files were found.');
79 | }
80 |
81 | }, function(err) {
82 | taskComplete(err);
83 | });
84 |
85 | });
86 | };
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*global module, process*/
2 |
3 | //For Tests Override Casper Dir
4 | process.env.CASPERJS_EXECUTABLE = require('path').resolve(
5 | __dirname,
6 | 'node_modules/casperjs/bin/casperjs',
7 | (/^win/.test(process.platform) ? ".exe" : "")
8 | );
9 |
10 |
11 | module.exports = function (grunt) {
12 | "use strict";
13 | grunt.initConfig({
14 |
15 | opts : {
16 | port : 'fff'
17 | },
18 |
19 | jshint: {
20 | options : {
21 | jshintrc : '.jshintrc'
22 | },
23 | all : ['tasks/**/*.js', 'test/*.js', 'Gruntfile.js']
24 | },
25 | casper: {
26 | options : {
27 | test : true
28 | },
29 |
30 | screenshots : {
31 | options : {
32 | test : false,
33 | 'load-images' : 'no'
34 | },
35 | src : ['test/fixtures/testScreenshots.js']
36 | },
37 |
38 | passEngine : {
39 | src : ['test/fixtures/testPassEngine.js']
40 | },
41 |
42 | pass : {
43 | options : {
44 | "log-level" : "debug",
45 | "test" : true
46 | },
47 | files : {
48 | 'tmp/casper/testPass-results.xml' : ['test/fixtures/testPass.js']
49 | }
50 | },
51 |
52 | passMultiple : {
53 | src : ['test/fixtures/testPass*.js']
54 | },
55 |
56 | parallel : {
57 | options : {
58 | parallel : true,
59 | concurrency: 5,
60 | concise : true
61 | },
62 | files : {
63 | src : [
64 | 'test/fixtures/testParallel*.js'
65 | ]
66 | }
67 | },
68 |
69 | multipleFiles : {
70 | options : {
71 | parallel : true,
72 | concurrency: 5,
73 | concise : true
74 | },
75 | files : {
76 | 'tmp/casper/multipleFiles-results.xml' : ['test/fixtures/testPass.js'],
77 | 'tmp/casper/multipleFiles-results2.xml' : ['test/fixtures/testTimeout.js']
78 | }
79 | },
80 |
81 | args: {
82 | options: {
83 | test: false
84 | },
85 | files: {
86 | 'tmp/casper/testArgs-results.xml': ['test/fixtures/testArgs.js']
87 | }
88 | },
89 |
90 | argsTest: {
91 | options: {
92 | test: false,
93 | args : [
94 | "foo",
95 | "bar",
96 | "baz"
97 | ]
98 | },
99 | files: {
100 | 'tmp/casper/testArgs-results.xml': ['test/fixtures/testArgsTest.js']
101 | }
102 | },
103 |
104 | fail : {
105 | files : {
106 | 'tmp/casper/testFail-results.xml' : ['test/fixtures/testFail.js']
107 | }
108 | },
109 |
110 | failFast : {
111 | options : {
112 | 'fail-fast' : true
113 | },
114 | src : ['test/fixtures/testPass.js', 'test/fixtures/testFail.js', 'test/fixtures/testPass2.js'],
115 | dest : 'tmp/casper/testFailFast-results.xml'
116 | },
117 |
118 | multiple : {
119 | src : ['test/fixtures/testPass.js','test/fixtures/testPass2.js'],
120 | dest : 'tmp/multi/testResults.xml'
121 | },
122 |
123 | includes : {
124 | options : {
125 | includes : 'test/fixtures/includes/inc.js'
126 | },
127 | files : {
128 | 'tmp/casper/testIncludes-results.xml' : ['test/fixtures/includes/testIncludes.js']
129 | }
130 | }
131 | },
132 |
133 | clean : {
134 | tmp : ['tmp']
135 | },
136 |
137 | nodeunit: {
138 | tasks: ['test/*_test.js']
139 | }
140 | });
141 |
142 | grunt.loadTasks('tasks');
143 |
144 | grunt.loadNpmTasks('grunt-contrib-nodeunit');
145 | grunt.loadNpmTasks('grunt-contrib-jshint');
146 | grunt.loadNpmTasks('grunt-contrib-clean');
147 |
148 | /* can't pass arguments to alias tasks but we can use grunt.task.run */
149 | grunt.registerTask('casperargs', function() {
150 | var args = ['casper','args'].concat(Array.prototype.slice.call(arguments));
151 | grunt.log.writeln(args);
152 | grunt.task.run(args.join(':'));
153 | });
154 |
155 | grunt.registerTask('spawnFailure', function(){
156 | var options = {
157 | grunt: true,
158 | args: ['casper:fail']
159 | };
160 | var done = this.async();
161 | grunt.util.spawn(options, function(){done(true);});
162 | });
163 |
164 | grunt.registerTask('runtests', [
165 | 'clean',
166 | 'casper:argsTest',
167 | 'casperargs:baz:--foo=bar',
168 | 'casper:pass',
169 | 'casper:passEngine',
170 | 'casper:multiple',
171 | 'casper:includes',
172 | 'casper:screenshots',
173 | 'casper:parallel',
174 | 'spawnFailure'
175 | ]);
176 |
177 | grunt.registerTask('test', ['jshint', 'runtests', 'nodeunit']);
178 |
179 | //Should Run Locally To Test Fail Cases - Fails Travis/Grunt
180 | grunt.registerTask('testFail', ['casper:fail', 'casper:failFast']);
181 | grunt.registerTask('default', ['test']);
182 | };
--------------------------------------------------------------------------------
/tasks/lib/casper.js:
--------------------------------------------------------------------------------
1 | /*global exports, require, process*/
2 |
3 | //node
4 | var path = require('path'),
5 | fs = require('fs');
6 |
7 | //npm
8 | var _ = require('lodash');
9 |
10 | //npm install wrapper
11 | var phantomjs = require('phantomjs');
12 | var slimerjs = require('slimerjs');
13 |
14 | /**
15 | * Initializer For Grunt
16 | * @param grunt
17 | */
18 | exports.init = function (grunt) {
19 | 'use strict';
20 |
21 | var casper = {
22 |
23 | testableOptions : {
24 | pre : true,
25 | post : true,
26 | includes : true,
27 | verbose : true,
28 | 'log-level' : true,
29 | 'fail-fast' : true,
30 | 'concise' : true,
31 | 'xunit' : true,
32 | 'no-colors' : true
33 | },
34 |
35 | supportedEngines : [
36 | 'phantomjs',
37 | 'slimerjs'
38 | ],
39 |
40 |
41 | modulePaths : [
42 | path.resolve(__dirname, '../../node_modules'), //local
43 | path.resolve(__dirname, '../../..'), //sibling
44 | '/usr/local/lib/node_modules' //global
45 | ],
46 |
47 | /**
48 | * Spawn Casperjs Child Process
49 | * @param cwd
50 | * @param args
51 | * @param next
52 | */
53 | spawn : function (cwd, args, next) {
54 | grunt.verbose.write('Spawning casperjs with args: ', args, '\n');
55 | //No CasperBin Found Yet
56 | var casperBin = null;
57 |
58 | //Set PhantomJS Path only if the file exists, otherwise fall back to ENV
59 | if (fs.existsSync(phantomjs.path)) {
60 | grunt.verbose.write('Found PhantomJS Executable', phantomjs.path, '\n');
61 | process.env["PHANTOMJS_EXECUTABLE"] = phantomjs.path;
62 | }
63 |
64 | if (fs.existsSync(slimerjs.path)) {
65 | grunt.verbose.write('Found SlimerJS Executable', slimerjs.path, '\n');
66 | process.env["SLIMERJS_EXECUTABLE"] = slimerjs.path;
67 | }
68 |
69 |
70 | //Is environment variable `CASPERJS_EXECUTABLE` set?
71 | if (process.env["CASPERJS_EXECUTABLE"] && fs.existsSync(process.env["CASPERJS_EXECUTABLE"])) {
72 | casperBin = process.env["CASPERJS_EXECUTABLE"];
73 | } else {
74 | //Windows Check
75 | var isWindows = /^win/.test(process.platform),
76 | //NPM Module Path
77 | moduleBinPath = "/casperjs/bin/casperjs";
78 |
79 | //Loop through local/global node_modules dirs
80 | casper.modulePaths.every(function (path) {
81 | var moduleBin = path + moduleBinPath + (isWindows ? ".exe" : "");
82 |
83 | if (fs.existsSync(moduleBin)) {
84 | casperBin = moduleBin;
85 | //essentially a break
86 | return false;
87 | }
88 | return true;
89 | });
90 | }
91 |
92 | //Did we find casper in the module Paths?
93 | if (casperBin === null) {
94 | grunt.log.error("CasperJS Binary Not Found, try `npm install`");
95 | return next(true);
96 | }
97 |
98 | grunt.verbose.write('Found CasperJS Executable', casperBin);
99 |
100 | //Spawn Casper Process
101 | grunt.util.spawn({
102 | cmd : casperBin,
103 | args : args,
104 | opts : {
105 | cwd : cwd,
106 | //see CasperJs output live
107 | stdio : 'inherit'
108 | }
109 | }, function (errorObj, result, code) {
110 |
111 | if (code > 0) {
112 | grunt.log.error(result.stdout);
113 | return next(true);
114 | }
115 |
116 | if (result.stdout) grunt.log.write(result.stdout + '\n\n');
117 | if (result.stderr) grunt.log.write(result.stderr + '\n\n');
118 | next();
119 | });
120 | }
121 | };
122 |
123 |
124 | return {
125 |
126 | execute : function (src, dest, options, args, next) {
127 | grunt.verbose.write('Preparing casperjs spawn\n');
128 | var spawnOpts = [];
129 | var cwd = options.cwd || process.cwd();
130 |
131 | //add verbose flag for printing logs to screen
132 | if (options['log-level'] && !options.verbose) spawnOpts.push('--verbose');
133 |
134 | _.forEach(options, function (value, option) {
135 | if (!options.test && casper.testableOptions[option]) {
136 | grunt.log.warn('Option ' + option + ' only available in test mode');
137 | return;
138 | }
139 |
140 | if (option) {
141 | switch (option) {
142 | case 'test':
143 | //Test requires specific order logic
144 | break;
145 | case 'xunit_out':
146 | if (typeof options.xunit_out === 'function') {
147 | //src passed as array reference
148 | options.xunit = options.xunit_out(src);
149 | } else {
150 | options.xunit = options.xunit_out;
151 | }
152 | break;
153 | case 'args' :
154 | if (args && args.length) spawnOpts.push(args);
155 | value.forEach(function (arg) {
156 | spawnOpts.push(arg);
157 | });
158 | break;
159 | //add engine support outside of phantomJS
160 | case 'engine' :
161 | if (casper.supportedEngines.indexOf(options['engine']) !== -1) {
162 | spawnOpts.push('--engine=' + options['engine']);
163 | } else {
164 | grunt.log.warn('Engine ' + options['engine'] + ' not available. [' + casper.supportedEngines.join(',') + ']');
165 | }
166 | break;
167 | default:
168 | var currentOption = '--' + option + '=' + value;
169 | grunt.verbose.write('Adding Option ' + currentOption + '\n');
170 | spawnOpts.push(currentOption);
171 | }
172 | }
173 | });
174 |
175 | if (dest) {
176 | if (typeof dest === 'function') {
177 | dest = dest(src);
178 | }
179 | spawnOpts.push('--xunit=' + dest);
180 | }
181 |
182 | if (typeof src === 'object') {
183 | src.reverse().filter(function (file) {
184 | if (!grunt.file.exists(file)) {
185 | grunt.log.warn('Source file "' + file + '" not found.');
186 | return false;
187 | }
188 | return true;
189 | }).map(function (file) {
190 | //Make path absolute for SlimerJS
191 | if (!grunt.file.isPathAbsolute(file)) {
192 | file = path.join(cwd, file);
193 | }
194 | spawnOpts.unshift(file);
195 | });
196 | } else {
197 | spawnOpts.unshift(src);
198 | }
199 |
200 | if (options.test) {
201 | spawnOpts.unshift('test');
202 | }
203 |
204 | //Spawn Child Process
205 | casper.spawn(cwd, spawnOpts, next);
206 | }
207 | };
208 | };
209 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grunt-casper [](http://travis-ci.org/iamchrismiller/grunt-casper) [](https://david-dm.org/iamchrismiller/grunt-casper.png) [](http://gruntjs.com/) [](https://bitdeli.com/free "Bitdeli Badge")
2 |
3 | > Run CasperJS Scripts/Functional Tests
4 |
5 | If You need Casper 1.0 Support - Please Check out this [tag](https://github.com/iamchrismiller/grunt-casper/tree/1.0)
6 | *@note : You no longer need PhantomJS/CasperJS binaries installed. They are now managed by npm*
7 |
8 | ## Installation
9 |
10 | This task makes use of PhantomJS to drive the casperJS scripts in a headless manner.
11 |
12 | You will need to install [phantomjs](http://phantomjs.org/), with a fairly simple package [install](http://phantomjs.org/download.html)
13 | After [phantomjs](http://phantomjs.org/) is installed, you will need to install [casperjs](http://casperjs.org/installation.html)
14 |
15 | Now install the grunt task
16 |
17 | ```shell
18 | npm install grunt-casper --save
19 | ```
20 |
21 | ## Getting Started
22 |
23 | CasperJS is a navigation scripting & testing utility for PhantomJS. It eases the process of defining a full navigation scenario and provides useful high-level functions, methods & syntaxic sugar for doing common tasks in a headless browser.
24 |
25 | If you haven't used [casperjs](http://casperjs.org/) before, be sure to check out the [Get Started](http://casperjs.org/quickstart.html) guide, as it explains how to create your first test case.
26 |
27 |
28 | ## casper task
29 | _Run this task with the `grunt casper` command._
30 |
31 | _This task is a [multi task](https://github.com/gruntjs/grunt/wiki/Configuring-tasks) so any targets, files and options should be specified according to the [multi task][] documentation._
32 |
33 | ### Options
34 |
35 | #### Grunt 'dest'
36 | Type: `String` || `Function`
37 |
38 | The 'dest' option in Grunt's configuration is passed as the --save option to casper, allowing you to access
39 | your destination programmatically. If passed as a function, the return value will be used.
40 |
41 | #### test
42 | Type: `Boolean`
43 | Default: false
44 |
45 | Run the casperjs script(s) in test mode. Thus allowing you to split up your tests (casperjs test tests/)
46 |
47 | #### includes
48 | Type: `String`
49 | Default: undefined
50 |
51 | Comma separated list of scripts to "include" before executing tests.
52 |
53 | #### pre
54 | Type: `String`
55 | Default: undefined
56 |
57 | Scripts to be executed before the test suite
58 |
59 | #### post
60 | Type: `String`
61 | Default: undefined
62 |
63 | Scripts to be executed after the test suite
64 |
65 | #### verbose
66 | Type: `Boolean`
67 | Default: false
68 |
69 | Output log messages directly to the console
70 |
71 | #### log-level
72 | Type: `String`
73 | Default: `error`
74 | Options: `debug` `info` `warning` `error`
75 |
76 | Sets the casperjs logging level
77 |
78 | #### fail-fast
79 | Type: `Boolean`
80 | Default: false
81 |
82 | Terminate as soon as a first failure is encountered.
83 |
84 | #### concise
85 | Type: `Boolean`
86 | Default: false
87 |
88 | Create a more concise output of the test suite.
89 |
90 |
91 | #### engine
92 | Type: `String`
93 | Default: phantomjs
94 |
95 | Specify Browser Engine (phantomjs|slimerjs)
96 |
97 | #### concurrency
98 | Type: `Number`
99 | Default: How many test files to run concurrently (1-10)
100 |
101 | #### parallel
102 | Type: `Boolean`
103 | Default: Run tests in Parallel instead of Series
104 |
105 | ### Usage Examples
106 |
107 | Basic usage
108 | ```js
109 | casper : {
110 | yourTask : {
111 | options : {
112 | test : true
113 | },
114 | files : {
115 | 'xunit/casper-results.xml' : ['test/functionalTests.js']
116 | }
117 | }
118 | }
119 | ```
120 |
121 | Basic Parallel usage
122 | ```js
123 | casper : {
124 | yourTask : {
125 | options : {
126 | test : true,
127 | parallel : true,
128 | concurrency : 5
129 | },
130 | files : {
131 | 'xunit/casper-results.xml' : ['test/functionalTests.js'],
132 | 'xunit/casper-results-2.xml' : ['test/functionalTests2.js'],
133 | }
134 | }
135 | }
136 | ```
137 |
138 | Global options and custom destination
139 |
140 | ```js
141 | casper : {
142 | options : {
143 | test : true,
144 | includes : 'path/to/inc.js',
145 | post : 'path/to/post.js',
146 | pre : 'path/to/pre.js',
147 | 'log-level' : 'warning',
148 | 'fail-fast' : true,
149 | concise : true,
150 | engine : 'slimerjs'
151 | },
152 | yourTask : {
153 | src: ['path/to/tests/*_test.js'],
154 | dest : function(input) {
155 | return input.replace(/\.js$/,'.xml');
156 | }
157 | }
158 | }
159 | ```
160 |
161 | ### Options and Arguments
162 | CasperJS supports options and arguments on the [command line](http://docs.casperjs.org/en/latest/cli.html).
163 |
164 | `casperjs script.js baz --foo=bar`
165 |
166 | Grunt tasks can accept additional arguments and grunt-casper will pass these through to CasperJS, for instance
167 |
168 | `grunt casper:yourTask:baz:--foo=bar`
169 |
170 | will pass `baz` as an argument and `foo` as an option with a value of `bar`. These are then available in your CasperJS script
171 |
172 | ```js
173 | casper.cli.args.indexOf('baz'); // 0
174 | casper.cli.options.foo; //bar
175 | ```
176 |
177 | Arguments can also be specified in the Task Options Object
178 |
179 | ```js
180 | casper : {
181 | options : {
182 | args : ['foo', 'bar']
183 | }
184 | }
185 | ```
186 |
187 | Arguments and options will be ignored in `test` mode as CasperJS does not support them.
188 |
189 | ## PhantomJS / CasperJS Binaries
190 |
191 | You may also override the location of the PhantomJS and CasperJS binaries like so:
192 |
193 | process.env.PHANTOMJS_EXECUTABLE = '/path/to/phantomjs';
194 | process.env.CASPERJS_EXECUTABLE = '/path/to/casperjs';
195 |
196 | The CasperJS Binary, by default, is loaded from the local ./node_modules directory and has a fallback to look in the
197 | global node_modules directory (/usr/local/lib/node_modules)
198 |
199 |
200 |
201 | ## Release History
202 |
203 | * 2015-02-11 v0.4.2 Reverse Source List before unshift
204 | * 2014-08-19 v0.4.1 Expose Slimerjs binary export
205 | * 2014-08-19 v0.4.0 Refactored Fail cases
206 | * 2014-07-16 v0.3.10 Added local binary module path
207 | * 2014-06-09 v0.3.9 Refactored exports and binary module loading
208 | * 2014-05-12 v0.3.8 Removed test arguments constraint
209 | * 2014-04-24 v0.3.7 Merge pull request #39 add no-colors option
210 | * 2014-04-21 v0.3.6 Fixed issue with testableOptions
211 | * 2014-03-20 v0.3.5 Fixed issue with engine indexOf conditional
212 | * 2014-03-12 v0.3.4 Merge pull request #36 check PhantomJS path
213 | * 2014-03-06 v0.3.3 Cleaned up Cross Platform Binary Location
214 | * 2014-03-05 v0.3.2 Fixed CasperJS Binary for windows platform
215 | * 2014-03-03 v0.3.1 Export CasperJS binary to node_module/.bin
216 | * 2014-02-23 v0.3.0 CasperJS npm managed binary
217 | * 2014-02-23 v0.2.7 PhantomJS install via wrapper
218 | * 2014-02-22 v0.2.6 Parallel exit logic
219 | * 2014-02-22 v0.2.5 Changed deprecated 1.1 direct flag to verbose
220 | * 2014-02-22 v0.2.4 Fixed test option position in array
221 | * 2014-02-17 v0.2.3 Added engine support (phantomjs, slimerjs)
222 | * 2014-02-11 v0.2.2 Added args option for casper args, added concise option support
223 | * 2014-01-24 v0.2.1 Refactored exit logic
224 | * 2014-01-14 v0.2.0 Refactored non-parallel Runs, fixing --fail-fast parameter
225 | * 2013-11-22 Refactored task dependencies, added parallel option and task duration
226 | * 2013-10-08 v0.1.4 Merged pull request - cwd spawn option
227 | * 2013-09-05 v0.1.3 Fixed logging from grunt.verbose -> grunt.log
228 | * 2013-08-10 v0.1.2 Added xunit support
229 | * 2013-02-01 v0.1.1 Update Task To Run With grunt0.4.0rc7
230 | * 2013-01-01 v0.1.0 Initial Release
231 |
232 | ---
233 |
234 | Task submitted by [Chris Miller](http://chris-miller.me)
235 |
--------------------------------------------------------------------------------