├── .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 [![Build Status](https://secure.travis-ci.org/iamchrismiller/grunt-casper.svg?branch=master)](http://travis-ci.org/iamchrismiller/grunt-casper) [![Dependency Status](https://david-dm.org/iamchrismiller/grunt-casper.svg)](https://david-dm.org/iamchrismiller/grunt-casper.png) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/iamchrismiller/grunt-casper/trend.png)](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 | --------------------------------------------------------------------------------