├── test ├── config │ └── config.json ├── cucumber-reports │ └── screenshot │ │ └── cucumber_report_bootstrap_snapshot.png ├── features │ ├── unhappy.feature │ ├── happy.feature │ └── step_definitions │ │ └── test.js └── assert │ └── assertReport.js ├── .gitignore ├── .travis.yml ├── .jshintrc ├── lib ├── requireHandler.js └── processHandler.js ├── LICENSE-MIT ├── CHANGELOG.md ├── package.json ├── Gruntfile.js ├── tasks └── cucumber.js └── README.md /test/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "scenario": { 3 | "totalScenarios": 9 4 | } 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | .DS_Store 5 | .idea/ 6 | test/report/ 7 | test/report/screenshot/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | install: npm install 5 | before_script: npm test 6 | 7 | -------------------------------------------------------------------------------- /test/cucumber-reports/screenshot/cucumber_report_bootstrap_snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mavdi/grunt-cucumberjs/HEAD/test/cucumber-reports/screenshot/cucumber_report_bootstrap_snapshot.png -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 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 | -------------------------------------------------------------------------------- /lib/requireHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function HandleUsingRequire(grunt, options, commands, callback) { 4 | 5 | var Cucumber = require('cucumber'), 6 | argv = ['node', 'cucumber-js']; 7 | 8 | argv.push.apply(argv, commands); 9 | 10 | function runtimeCallback() { 11 | var succeeded = arguments[0]; 12 | if (succeeded) { 13 | callback(); 14 | } else { 15 | callback(new Error("Cucumber tests failed!")); 16 | } 17 | } 18 | 19 | Cucumber.Cli(argv).run(runtimeCallback); 20 | }; -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Mehdi Avdi 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 | -------------------------------------------------------------------------------- /test/features/unhappy.feature: -------------------------------------------------------------------------------- 1 | Feature: Unhappy HTML reporting 2 | 3 | In order to review cucumber reports 4 | Fred, a cucumber user 5 | Wants to have cucumber reports in HTML 6 | 7 | 8 | @attachScreenshot 9 | Scenario: Fred wants to see a Screenshot attached to the HTML report 10 | Given Fred runs a failing cucumber scenario 11 | When he choose "html" output as one of the formatter 12 | And a failing scenario captures a screenshot 13 | Then the output should contain test results with screenshot in HTML format 14 | 15 | @pendingStep 16 | Scenario: Fred wants to see if steps are pending in the HTML report 17 | Given Fred attaches the "test data to be printed" to the Given step of passing cucumber scenario 18 | When he left one of the step as a pending 19 | Then the output should contain the pending test in the HTML pie chart 20 | 21 | @undefinedStep 22 | Scenario: Fred wants to see if steps are undefined on the HTML report 23 | Given Fred attaches the "test data to be printed" to the Given step of passing cucumber scenario 24 | When he left this step as a undefined 25 | Then the output should contain the undefined steps in the HTML pie chart 26 | 27 | @skippedStep 28 | Scenario: Fred wants to see if steps are skipped on the HTML report 29 | Given Fred attaches the "test data to be printed" to the Given step of passing cucumber scenario 30 | When he left this step as a pending 31 | Then the output should contain the skipped steps in the HTML pie chart -------------------------------------------------------------------------------- /test/assert/assertReport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fs = require('fs'); 3 | var chai = require('chai'); 4 | var expect = chai.expect; 5 | var config = require('../config/config.json'); 6 | 7 | module.exports = { 8 | 9 | assert: function(htmlFile) { 10 | var jsonFile = htmlFile + '.json'; 11 | 12 | function assertJsonContents() { 13 | var jsonOutput = require('../../' + jsonFile); 14 | var jsonOutputStringify = JSON.stringify(jsonOutput); 15 | 16 | 17 | //verify number of scenarios 18 | var numberOfScenarios = 0; 19 | jsonOutput.forEach(function(feature) { 20 | numberOfScenarios += feature.elements.length; 21 | }); 22 | 23 | expect(numberOfScenarios).to.be.equal(config.scenario.totalScenarios, 'Scenarios are missing in the report'); 24 | 25 | //verify screenshot is attached to the report 26 | expect(jsonOutputStringify).to.contain('mime_type":"image/png"', 'screenshot was not attached to report'); 27 | 28 | //verify test data is attached to the report 29 | expect(jsonOutputStringify).to.contain('mime_type":"text/plain"', 'test data was not attached to report'); 30 | 31 | //verify data-table is attached to the report 32 | expect(jsonOutputStringify).to.contain('rows', 'data-table rows was not attached to report'); 33 | expect(jsonOutputStringify).to.contain('cells', 'data-table rows was not attached to report'); 34 | } 35 | 36 | return assertJsonContents(); 37 | } 38 | }; -------------------------------------------------------------------------------- /test/features/happy.feature: -------------------------------------------------------------------------------- 1 | Feature: Happy HTML reporting with cucumber-parallel executions 2 | 3 | In order to review cucumber reports 4 | Fred, a cucumber user 5 | Wants to have cucumber reports in HTML 6 | 7 | Background: 8 | When this feature runs with background 9 | 10 | @testPassing 11 | Scenario: Fred wants to see passing scenarios in the HTML report 12 | Given Fred runs a passing cucumber scenario 13 | When he choose "html" output as one of the formatter 14 | Then the output should contain test results in HTML format 15 | 16 | @testScenarioOutline 17 | Scenario Outline: Fred wants to run scenario outline and print on HTML report 18 | Given Fred runs a passing cucumber scenario on behalf of "" 19 | When he choose "html" output as one of the formatter 20 | Then the output should contain test results in HTML format 21 | 22 | Examples: 23 | | name | 24 | | John | 25 | | Bob | 26 | 27 | @testAttachDebugData 28 | Scenario: Fred wants to print test data in the HTML reports for debugging purpose 29 | Given Fred attaches the "test data to be printed" to the Given step of passing cucumber scenario 30 | When he choose "html" output as one of the formatter 31 | Then the output should contain test data attached to the Given step in HTML format 32 | 33 | 34 | @testDataTable 35 | Scenario: Fred wants to use data table and print on HTML report 36 | Given Fred runs a passing scenario for the following data set 37 | | id | name | 38 | | 1 | data-A | 39 | | 2 | data-B | 40 | When he choose "html" output as one of the formatter 41 | Then the output should contain data table attached in HTML format 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.0.1 (2017-05-05) 2 | 3 | * Add `no-strict` option. Please visit ReadME for more info 4 | 5 | ### 0.10.2 (2016-06-23) 6 | 7 | * Show Project Name and Project Version on Foundation theme report 8 | * Fixed the report generation time-stamp on Bootstrap & Foundation themes 9 | * e.g. Generated 2 days ago 10 | * Fixed custom user defined HTML template 11 | * Added a tests to validate number of scenarios reported on JSON 12 | 13 | 14 | ### 0.10.1 (2016-06-22) 15 | 16 | * Fixed `chai` dependencies for /test 17 | 18 | ### 0.10.0 (2016-06-21) 19 | 20 | #### New Features 21 | 22 | * Report Suite As a Scenarios 23 | * `options.reportSuiteAsScenarios` 24 | 25 | Type: `Boolean` 26 | Default: `'false'` 27 | Available: `['true', 'false']` 28 | 29 | Reports total number of failed/passed Scenarios in headers if set to `true`. 30 | Reports total number of failed/passed Features in headers if set to `false` or `undefined`. 31 | 32 | e.g. 33 | 34 | ``` 35 | cucumberjs: { 36 | options: { 37 | formats: ['html', 'pretty'], 38 | output: 'test/report/cucumber_report.html', 39 | theme: 'bootstrap', 40 | debug: true, 41 | reportSuiteAsScenarios: true, 42 | executeParallel: grunt.option('executeParallel') || false, 43 | require: grunt.option('require', 'test/step_definitions/'), 44 | }, 45 | src: ['test/features'] 46 | } 47 | ``` 48 | 49 | * Support for Cucumber@^1.0.0 50 | 51 | * Print Scenarios Data Table on the HTML report 52 | 53 | * Deprecated Console logs on HTML report. Instead, use `scenario.attach('text')` 54 | 55 | * Deprecated `options.saveJson` as JSON will be saved by default to the file specified in `options.output` 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-cucumberjs", 3 | "description": "Generates documentation from Cucumber features", 4 | "version": "2.0.2", 5 | "homepage": "https://github.com/mavdi/grunt-cucumberjs", 6 | "author": { 7 | "name": "Mehdi Avdi", 8 | "email": "mehdi.avdi@gmail.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/mavdi/grunt-cucumberjs.git" 13 | }, 14 | "contributors": [ 15 | { 16 | "name": "Andrew Keig", 17 | "email": "andrew.keig@gmail.com", 18 | "url": "https://github.com/AndrewKeig" 19 | }, 20 | { 21 | "name": "Jozz Hart", 22 | "email": "me@jozzhart.com", 23 | "url": "http://jozzhart.com" 24 | }, 25 | { 26 | "name": "Kushang Gajjar", 27 | "email": "g.kushang@gmail.com", 28 | "url": "https://github.com/gkushang" 29 | }, 30 | { 31 | "name": "Sowmya Yellamilli", 32 | "url": "https://github.com/yellamillis" 33 | }, 34 | { 35 | "name": "Casey Goodhew", 36 | "email": "goodhewc@gmail.com", 37 | "url": "https://github.com/caseygoodhew" 38 | }, 39 | { 40 | "name": "John J. Barton", 41 | "email": "johnjbarton@johnjbarton.com", 42 | "url": "https://github.com/johnjbarton", 43 | } 44 | ], 45 | "dependencies": { 46 | "chai": "^4.1.2", 47 | "cucumber-html-reporter": "^4.0.3", 48 | "cucumber-parallel": "^2.0.3", 49 | "node-fs": "^0.1.7", 50 | "ramda": "^0.24.1" 51 | }, 52 | "devDependencies": { 53 | "cucumber": "^2.3.1", 54 | "grunt": "^1.0.1", 55 | "grunt-cli": "^1.2.0", 56 | "grunt-contrib-clean": "^1.0.0", 57 | "grunt-contrib-jshint": "^1.1.0", 58 | "grunt-jsbeautifier": "^0.2.13" 59 | }, 60 | "bugs": { 61 | "url": "https://github.com/mavdi/grunt-cucumberjs/issues" 62 | }, 63 | "license": "MIT", 64 | "licenses": [ 65 | { 66 | "type": "MIT", 67 | "url": "https://github.com/mavdi/grunt-cucumberjs/blob/master/LICENSE-MIT" 68 | } 69 | ], 70 | "main": "Gruntfile.js", 71 | "engines": { 72 | "node": ">= 0.8.0" 73 | }, 74 | "scripts": { 75 | "test": "npm run clean && grunt cucumberjs", 76 | "clean": "rm -rf test/report/*.html test/report/cucumber_report.json test/report/*.json test/report/screenshot" 77 | }, 78 | "keywords": [ 79 | "gruntplugin", 80 | "cucumber", 81 | "cucumberjs", 82 | "htmlreport", 83 | "parellel" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /lib/processHandler.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var fs = require('fs'); 4 | var spawn = require('child_process').spawn; 5 | var reporter = require('cucumber-html-reporter'); 6 | 7 | module.exports = function HandleUsingProcess(grunt, options, commands, callback) { 8 | 9 | function getBinPath(winBinPath, unixBinPath) { 10 | return process.platform === 'win32' ? winBinPath : unixBinPath; 11 | } 12 | 13 | var WIN32_BIN_PATH, 14 | UNIX_BIN_PATH; 15 | 16 | if (options.executeParallel) { 17 | UNIX_BIN_PATH = 'node_modules/grunt-cucumberjs/node_modules/cucumber-parallel/bin/cucumber-parallel'; 18 | WIN32_BIN_PATH = 'node_modules\\grunt-cucumberjs\\node_modules\\cucumber-parallel\\bin\\cucumber-parallel'; 19 | if (!grunt.file.exists(getBinPath(WIN32_BIN_PATH, UNIX_BIN_PATH))) { 20 | UNIX_BIN_PATH = 'node_modules/cucumber-parallel/bin/cucumber-parallel'; 21 | WIN32_BIN_PATH = 'node_modules\\cucumber-parallel\\bin\\cucumber-parallel'; 22 | } 23 | } else { 24 | WIN32_BIN_PATH = 'node_modules\\.bin\\cucumber-js.cmd'; 25 | UNIX_BIN_PATH = 'node_modules/cucumber/bin/cucumber.js'; 26 | } 27 | 28 | var buffer = []; 29 | var cucumber; 30 | var binPath = getBinPath(WIN32_BIN_PATH, UNIX_BIN_PATH); 31 | 32 | if (!grunt.file.exists(binPath)) { 33 | if (options.executeParallel) { 34 | grunt.log.error('cucumber parallel binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows'); 35 | } else { 36 | grunt.log.error('cucumberjs binary not found at path ' + binPath + '\n NOTE: You cannot install grunt-cucumberjs without bin links on windows'); 37 | } 38 | return callback(false); 39 | } 40 | 41 | cucumber = spawn(binPath, commands); 42 | 43 | cucumber.stdout.on('data', function(data) { 44 | if (options.isHtml) { 45 | if (options.debug) { 46 | process.stdout.write(data); 47 | } 48 | buffer.push(data); 49 | } else { 50 | grunt.log.write(data); 51 | } 52 | }); 53 | 54 | cucumber.stderr.on('data', function(data) { 55 | if (options.debug) { 56 | process.stdout.write(data); 57 | } 58 | var stderr = new Buffer(data); 59 | grunt.log.error(stderr.toString()); 60 | }); 61 | 62 | cucumber.on('close', function(code) { 63 | 64 | if (options.isHtml) { 65 | reporter.generate(options); 66 | } 67 | 68 | return callback(code); 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /test/features/step_definitions/test.js: -------------------------------------------------------------------------------- 1 | var {defineSupportCode} = require('cucumber'); 2 | 3 | defineSupportCode(function ({Before, Given, Then}) { 4 | 5 | Before(function(scenario, callback) { 6 | console.log('console logs should not break the report'); 7 | callback(); 8 | }); 9 | 10 | Then(/^this feature runs with background$/, function(callback) { 11 | callback(); 12 | }); 13 | 14 | Then(/^Fred runs a passing cucumber scenario$/, function(callback) { 15 | callback(); 16 | }); 17 | 18 | Given(/^Fred runs a passing cucumber scenario on behalf of "([^"]*)"/, function(name, callback) { 19 | callback(); 20 | }); 21 | 22 | Then(/^he choose "([^"]*)" output as one of the formatter$/, function(arg1, callback) { 23 | callback(); 24 | }); 25 | 26 | Then(/^the output should contain test results in HTML format$/, function(callback) { 27 | callback(); 28 | }); 29 | 30 | Then(/^Fred runs a failing cucumber scenario$/, function(callback) { 31 | callback(); 32 | }); 33 | 34 | Then(/^a failing scenario captures a screenshot$/, function(callback) { 35 | this.attach('', 'image/png'); 36 | callback(); 37 | }); 38 | 39 | Then(/^the output should contain test results with screenshot in HTML format$/, function(callback) { 40 | callback(); 41 | }); 42 | 43 | Then(/^he left one of the step as a pending$/, function(callback) { 44 | callback(); 45 | }); 46 | 47 | Then(/^the output should contain the pending test in the HTML pie chart$/, function(callback) { 48 | callback(null, 'pending'); 49 | }); 50 | 51 | Then(/^the output should contain the skipped steps in the HTML pie chart$/, function(callback) { 52 | callback(); 53 | }); 54 | 55 | Then(/^Fred attaches the "([^"]*)" to the Given step of passing cucumber scenario$/, function(testData, callback) { 56 | this.attach(testData); 57 | callback(); 58 | }); 59 | 60 | Then(/^the output should contain test data attached to the Given step in HTML format$/, function(callback) { 61 | callback(); 62 | }); 63 | 64 | Given(/^Fred runs a passing scenario for the following data set$/, function(table, callback) { 65 | // Write code here that turns the phrase above into concrete actions 66 | callback(); 67 | }); 68 | 69 | Then(/^the output should contain data table attached in HTML format$/, function(callback) { 70 | // Write code here that turns the phrase above into concrete actions 71 | callback(); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-cucumberjs 3 | * https://github.com/mavdi/cucumberjs 4 | * 5 | * Copyright (c) 2013 Mehdi Avdi 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | var assertReport = require('./test/assert/assertReport'); 11 | 12 | module.exports = function(grunt) { 13 | var options = { 14 | theme: 'bootstrap', 15 | output: 'test/report/features_report.html', 16 | debug: true, 17 | reportSuiteAsScenarios: true, 18 | launchReport: true, 19 | metadata: { 20 | "App Version": "0.10.8", 21 | "Test Environment": "QA", 22 | "Browser": "Chrome 54.0.2840.98", 23 | "Platform": "Windows 10", 24 | "Parallel": "Scenarios", 25 | "Executed": "Remote" 26 | } 27 | }; 28 | 29 | function verifyReport() { 30 | assertReport.assert(options.output); 31 | } 32 | 33 | function setParallelMode() { 34 | options.executeParallel = true; 35 | options.parallel = 'scenarios'; 36 | return options; 37 | } 38 | 39 | function setSingleFormatter() { 40 | options.format = 'html'; 41 | return options; 42 | } 43 | 44 | function setMultiFormatter() { 45 | options.formats = ['html', 'pretty']; 46 | return options; 47 | } 48 | 49 | // Project configuration. 50 | grunt.initConfig({ 51 | jshint: { 52 | all: [ 53 | 'Gruntfile.js', 54 | 'package.json', 55 | 'tasks/*.js', 56 | 'features/**/*.js' 57 | ], 58 | options: { 59 | jshintrc: '.jshintrc' 60 | } 61 | }, 62 | 63 | // Before generating any new files, remove any previously-created files. 64 | clean: { 65 | tests: ['test/report'] 66 | }, 67 | 68 | // Configuration to be run (and then tested). 69 | cucumberjs: { 70 | options: options, 71 | src: ['test/features'] 72 | }, 73 | 74 | jsbeautifier: { 75 | src: ['<%= jshint.all %>'] 76 | } 77 | }); 78 | 79 | // Actually load this plugin's task(s). 80 | grunt.loadTasks('tasks'); 81 | 82 | // These plugins provide necessary tasks. 83 | grunt.loadNpmTasks('grunt-contrib-jshint'); 84 | grunt.loadNpmTasks('grunt-contrib-clean'); 85 | grunt.loadNpmTasks('grunt-jsbeautifier'); 86 | 87 | grunt.registerTask('assertReport', verifyReport); 88 | grunt.registerTask('setSingleFormatter', setSingleFormatter); 89 | grunt.registerTask('setMultiFormatter', setMultiFormatter); 90 | grunt.registerTask('setParallelMode', setParallelMode); 91 | 92 | grunt.registerTask('testSingleFormatter', ['clean', 'setSingleFormatter', 'cucumberjs', 'assertReport']); 93 | grunt.registerTask('testMultiFormatter', ['clean', 'setMultiFormatter', 'cucumberjs', 'assertReport']); 94 | grunt.registerTask('testParallelMode', ['clean', 'setParallelMode', 'cucumberjs', 'assertReport']); 95 | 96 | 97 | // By default, lint and run all tests. 98 | grunt.registerTask('default', ['jshint', 'jsbeautifier', 'testSingleFormatter', 'testMultiFormatter', 'testParallelMode']); 99 | }; 100 | -------------------------------------------------------------------------------- /tasks/cucumber.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-cucumberjs 3 | * https://github.com/mavdi/grunt-cucumberjs 4 | * 5 | * Copyright (c) 2013 Mehdi Avdi 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 10 | var fs = require('fs'); 11 | var nodeFs = require('node-fs'); 12 | var path = require('path'); 13 | var R = require('ramda'); 14 | 15 | module.exports = function(grunt) { 16 | grunt.registerMultiTask('cucumberjs', 'Run cucumber.js features', function() { 17 | var done = this.async(); 18 | 19 | var options = this.options({ 20 | output: 'features_report.html', 21 | format: 'html', 22 | theme: 'foundation', 23 | tags: '', 24 | require: '', 25 | debug: false, 26 | 'debugger': false, 27 | failFast: false, 28 | reportSuiteAsScenarios: false, 29 | noStrict: false 30 | }); 31 | 32 | var handler = options.debugger ? require('../lib/requireHandler') : require('../lib/processHandler'); 33 | 34 | var applyLegacyFormatters = function() { 35 | if (options.format === 'html') { 36 | options.isHtml = true; 37 | commands.push('-f', 'json:' + options.output + '.json'); 38 | } else { 39 | commands.push('-f', options.format); 40 | } 41 | }; 42 | 43 | // resolve options set via cli 44 | for (var key in options) { 45 | if (grunt.option(key)) { 46 | if (key === 'tags') { 47 | if (options.tags === '') { 48 | options[key] = grunt.option(key); 49 | } 50 | } else { 51 | options[key] = grunt.option(key); 52 | } 53 | } 54 | } 55 | 56 | var commands = []; 57 | 58 | if (options.executeParallel && options.workers) { 59 | commands.push('-w', options.workers); 60 | } 61 | 62 | if (options.steps) { 63 | commands.push('-r', options.steps); 64 | } 65 | 66 | if (options.tags) { 67 | if (options.tags instanceof Array) { 68 | options.tags.forEach(function(element) { 69 | commands.push('-t', element); 70 | }); 71 | } else { 72 | commands.push('-t', options.tags); 73 | } 74 | } 75 | 76 | if (options.formats) { 77 | options.formats.forEach(function(format) { 78 | if (format === 'html') { 79 | options.isHtml = true; 80 | commands.push('-f', 'json:' + options.output + '.json'); 81 | } else { 82 | commands.push('-f', format); 83 | } 84 | }); 85 | } else if (options.format) { 86 | applyLegacyFormatters(); 87 | } 88 | 89 | if (options.failFast || grunt.option('fail-fast') || grunt.cli.options['fail-fast'] === true) { 90 | commands.push('--fail-fast'); 91 | } 92 | 93 | if (options.noStrict || grunt.option('no-strict') || grunt.cli.options['no-strict'] === true) { 94 | commands.push('--no-strict'); 95 | } 96 | 97 | if (options.dryRun || grunt.option('dry-run') || grunt.cli.options['dry-run'] === true) { 98 | commands.push('--dry-run'); 99 | } 100 | 101 | if (grunt.cli.options.parallel) { 102 | commands.push('--parallel', grunt.cli.options.parallel); 103 | } else if (options.parallel) { 104 | commands.push('--parallel', options.parallel); 105 | } 106 | 107 | if (grunt.cli.options.compiler) { 108 | commands.push('--compiler', grunt.cli.options.compiler); 109 | } else if (options.compiler) { 110 | commands.push('--compiler', options.compiler); 111 | } 112 | 113 | if (options.require) { 114 | if (options.require instanceof Array) { 115 | options.require.forEach(function(element) { 116 | commands.push('--require', element); 117 | }); 118 | } else { 119 | commands.push('--require', options.require); 120 | } 121 | } 122 | 123 | if (grunt.option('require')) { 124 | commands.push('--require', grunt.option('require')); 125 | } 126 | 127 | var isScenarioEmpty = true; 128 | 129 | if (grunt.option('rerun')) { 130 | 131 | var rerunFile = grunt.option('rerun'); 132 | 133 | if (!grunt.file.exists(rerunFile)) { 134 | grunt.log.warn('Rerun file "' + rerunFile + '" not found.'); 135 | return done(); 136 | } 137 | 138 | var scenarios = fs.readFileSync(rerunFile, 'utf-8').split('\n'); 139 | 140 | if (R.isEmpty(scenarios)) { 141 | grunt.log.warn('Rerun file "' + rerunFile + '" is empty. Exiting from task: ' + scenario); 142 | return done(); 143 | } 144 | 145 | scenarios.forEach(function registerScenarios(scenario) { 146 | if (isScenarioEmpty && R.isEmpty(scenario)) { 147 | grunt.log.warn('Rerun file "' + rerunFile + '" is empty. Exiting from task: ' + scenario); 148 | return done(); 149 | } 150 | 151 | if (!R.isEmpty(scenario)) { 152 | commands.push(scenario); 153 | isScenarioEmpty = false; 154 | } 155 | 156 | }); 157 | 158 | commands.push('--rerun', grunt.option('rerun')); 159 | 160 | } else if (grunt.option('features')) { 161 | commands.push(grunt.option('features')); 162 | } else { 163 | 164 | this.files.forEach(function(f) { 165 | f.src.forEach(function(filepath) { 166 | if (!grunt.file.exists(filepath)) { 167 | grunt.log.warn('Source file "' + filepath + '" not found.'); 168 | return; 169 | } 170 | commands.push(filepath); 171 | }); 172 | }); 173 | 174 | } 175 | 176 | var createReportDirectoryIfNotExists = function() { 177 | if (!fs.existsSync(options.output)) { 178 | nodeFs.mkdirSync(path.dirname(options.output), parseInt('0777', 8), true); 179 | } 180 | }; 181 | 182 | createReportDirectoryIfNotExists(); 183 | 184 | handler(grunt, options, commands, function handlerCallback(err) { 185 | if (err) { 186 | grunt.log.error('failed tests, please see the output'); 187 | return done(false); 188 | } 189 | return done(); 190 | }); 191 | 192 | }); 193 | }; 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Grunt Cucumberjs 2 | ================ 3 | ***Grunt task to run Cucumber.js. Outputs results in various HTML themes. Runs Cucumber.js in Parallel*** 4 | 5 | [![v][npm-shield]][npm-link] [![v][license-shield]][license-link] 6 | 7 | 8 | ## Getting Started 9 | 10 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 11 | 12 | ```shell 13 | npm install grunt-cucumberjs --save-dev 14 | ``` 15 | 16 | ***Note:*** 17 | 1. Install grunt-cucumberjs@1.0.2 for cucumber version < Cucumber@2 18 | 19 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 20 | 21 | ```js 22 | grunt.loadNpmTasks('grunt-cucumberjs'); 23 | ``` 24 | 25 | ## Sample HTML Reports 26 | 27 | 1. [Bootstrap Theme Reports with Pie Chart][3] 28 | 2. [Foundation Theme Reports][4] 29 | 3. [Simple Theme Reports][5] 30 | 31 | 32 | ## Snapshot of Bootstrap Report 33 | ![Alt text](/test/cucumber-reports/screenshot/cucumber_report_bootstrap_snapshot.png "Snapshot - Bootstrap Report") 34 | 35 | ## The "cucumberjs" task 36 | 37 | ### Overview 38 | In your project's Gruntfile, add a section named `cucumberjs` to the data object passed into `grunt.initConfig()`. 39 | 40 | ```js 41 | grunt.initConfig({ 42 | cucumberjs: { 43 | options: { 44 | format: 'html', 45 | output: 'my_report.html', 46 | theme: 'bootstrap' 47 | }, 48 | my_features: ['features/feature1.feature', 'features/feature2.feature'], 49 | other_features: { 50 | options: { 51 | output: 'other_report.html' 52 | }, 53 | src: ['other_features/feature1.feature', 'other_features/feature2.feature'] 54 | } 55 | } 56 | }); 57 | ``` 58 | 59 | If all your feature files are located in the default location of ```features/``` then just leave the feature configuation as an empty array. See following: 60 | 61 | ``` 62 | cucumberjs: { 63 | options: { 64 | format: 'html', 65 | output: './public/report.html', 66 | theme: 'foundation' 67 | }, 68 | features : [] 69 | } 70 | ``` 71 | 72 | ## Usage 73 | ```bash 74 | #runs all features specified in task 75 | $ grunt cucumberjs 76 | 77 | #you can override options via the cli 78 | $ grunt cucumberjs --require=test/functional/step_definitions/ --features=features/myFeature.feature --format=pretty 79 | ``` 80 | 81 | ## Options 82 | 83 | #### options.steps 84 | Type: `String` 85 | Default: `''` 86 | 87 | Passes the value as ```--steps``` parameter to cucumber. 88 | 89 | #### options.require 90 | Type: `String` 91 | Default: `''` 92 | 93 | Passes the value as ```--require``` parameter to cucumber. If an array, each item is passed as a separate ```--require``` parameter. 94 | Use if step_definitions and hooks are NOT in default location of ```features/step_definitions``` 95 | 96 | #### options.tags 97 | Type: `String|Array` 98 | Default: `''` 99 | 100 | Passes the value as ```--tags``` parameter to cucumber. If an array, each item is passed as a separate ```--tags``` parameter. 101 | We've seen issues in cucumberjs using the old style of negative tag expression such as ```'~@ignore'```. Suggest that you use the expanded syntax ```'not @ignore'``` instead. 102 | 103 | [Cucumber Tag Expressions](https://docs.cucumber.io/tag-expressions/) 104 | 105 | #### options.theme 106 | Type: `String` 107 | Default: `'foundation'` 108 | Available: `['foundation', 'bootstrap', 'simple']` 109 | 110 | Specifies which theme to use for the html report 111 | 112 | #### options.templateDir 113 | Type: `String` 114 | Default: `'features/templates'` 115 | 116 | Location of your custom templates. Simply name the template the same as the one you are trying to override and 117 | grunt-cucumberjs will use it over the default template 118 | 119 | #### options.output 120 | Type: `String` 121 | Default: `'features_report.html'` 122 | 123 | 124 | #### options.format 125 | Type: `String` 126 | Default: `'html'` 127 | Available: `['pretty', 'progress', 'summary', 'html']` 128 | 129 | #### options.formats 130 | Supports multiple formatter. 131 | Type: `Array` 132 | Available: `['pretty', 'progress', 'summary', 'html']` 133 | 134 | e.g. `formats: ['html', 'pretty']` 135 | 136 | Enable `debug: true` to print pretty console. 137 | 138 | Note: `html` formatter will provide Json as well as `html` report. Multiple formatter is supported for cucumber v@0.8.0 or higher. 139 | 140 | #### options.executeParallel 141 | Type: `Boolean` 142 | Default: `'undefined'` 143 | Available: `['true', 'false']` 144 | 145 | A flag to enable Parallel execution. 146 | 147 | ``` 148 | • You can run Cucumber Features and/or Scenarios Parallel 149 | • `--parallel scenarios` runs scenarios parallel 150 | • By default or `--parallel features` runs features in parallel 151 | ``` 152 | 153 | For more information visit [cucumber-parallel][8] module 154 | 155 | 156 | #### options.failFast 157 | Type: `Boolean` 158 | Default: `'false'` 159 | Available: `['true', 'false']` 160 | 161 | ends the suite after the first failure 162 | 163 | it can also be activated without setting `options.failFast` and passing `--fail-fast` as a grunt task option 164 | 165 | #### options.noStrict 166 | Type: `Boolean` 167 | Default: `'false'` 168 | Available: `['true', 'false']` 169 | 170 | will cause cucumber to succeed even if there are undefined or pending steps. 171 | 172 | it can also be activated without setting `options.noStrict` and passing `--no-strict` as a grunt task option 173 | 174 | #### options.dryRun 175 | Type: `Boolean` 176 | Default: `'false'` 177 | Available: `['true', 'false']` 178 | 179 | dry-run the suite and provides snippets for pending steps 180 | 181 | it can also be activated without setting `options.dryRun` and passing `--dry-run` as a grunt task option 182 | 183 | #### options.debug 184 | Type: `Boolean` 185 | Default: `'false'` 186 | Available: `['true', 'false']` 187 | 188 | A flag to turn console log on or off 189 | 190 | #### options.debugger 191 | Type: `Boolean` 192 | Default: `'false'` 193 | Available: `['true', 'false']` 194 | 195 | A flag to enabling debugging from IDE like WebStorm. Limitation of this flag is it only does not support the HTML output, yet ;) 196 | 197 | #### options.rerun 198 | Type: `String` 199 | Default: `undefined` 200 | 201 | Rerun the failed scenarios recorded in the `@rerun.txt` file. 202 | 203 | To Re-run failed scenarios: 204 | 205 | * Set the cucumber-js task format to `rerun:@rerun.txt` 206 | ``` 207 | options: { 208 | format: 'rerun:@rerun.txt', 209 | ..... 210 | .... 211 | } 212 | ``` 213 | It will record all the failed scenarios to `@rerun.txt`. 214 | 215 | Take a look at `options.formats` to generate html report 216 | 217 | * Run failed scenarios by passing `--rerun=path/to/@rerun.txt` grunt option 218 | 219 | N.B.: If `@rerun.txt` file does not exists or if file is empty, the grunt task will return success. 220 | 221 | 222 | #### options.compiler 223 | Type: `String` 224 | 225 | Sets the Cucumber Compiler options. It can also be set by passing through command line `--compiler` 226 | 227 | 228 | #### options.reportSuiteAsScenarios 229 | Type: `Boolean` 230 | Default: `'false'` 231 | Available: `['true', 'false']` 232 | 233 | Reports total number of failed/passed Scenarios in headers if set to `true`. 234 | Reports total number of failed/passed Features in headers if set to `false` or `undefined`. 235 | 236 | 237 | #### options.launchReport 238 | Type: `Boolean` 239 | 240 | Automatically launch HTML report at the end of test suite 241 | 242 | `true`: Launch HTML report in the default browser 243 | 244 | `false`: Do not launch HTML report at the end of test suite 245 | 246 | 247 | #### `metadata` 248 | Type: `JSON` (optional) 249 | Default: `undefined` 250 | 251 | Print more data to your report, such as _browser info, platform, app info, environments_ etc. Data can be passed as JSON `key-value` pair. Reporter will parse the JSON and will show the _Key-Value_ under `Metadata` section on HTML report. Checkout the below preview HTML Report with Metadata. 252 | 253 | Pass the _Key-Value_ pair as per your need, as shown in below example, 254 | 255 | ```json 256 | 257 | metadata: { 258 | "App Version":"0.3.2", 259 | "Test Environment": "STAGING", 260 | "Browser": "Chrome 54.0.2840.98", 261 | "Platform": "Windows 10", 262 | "Parallel": "Scenarios", 263 | "Executed": "Remote" 264 | } 265 | 266 | ``` 267 | 268 | * [HTML Report Preview with Metadata][3] 269 | 270 | 271 | ### Pie Charts 272 | 273 | Sample pie chart is available at [Bootstrap Theme Report with Pie Chart][3] 274 | 275 | Two pie charts are displayed on report 276 | 277 | 1. Features: number of passed/failed features 278 | 2. Scenarios: number of passed/failed/pending scenarios. 279 | 280 | Please note that Pie Charts are available only for Bootstrap Theme. 281 | 282 | 283 | ## Changelog 284 | 285 | [changelog][changelog] 286 | 287 | ### Tips 288 | 289 | Take a look at [cucumber-html-reporter][9] for more information on Attaching ScreenShots, Plain Texts, Pretty JSON to the HTML report 290 | 291 | 292 | [1]: https://code.google.com/p/selenium/wiki/WebDriverJs "WebDriverJS" 293 | [2]: https://github.com/cucumber/cucumber-js "cucumber-js" 294 | [3]: http://htmlpreview.github.io/?https://github.com/gkushang/cucumber-html-reporter/blob/develop/samples/html_reports/cucumber_report_bootstrap.html "Bootstrap Theme Reports" 295 | [4]: http://htmlpreview.github.io/?https://github.com/gkushang/grunt-cucumberjs/blob/cucumber-reports/test/cucumber-reports/cucumber-report-foundation.html "Foundation Theme Reports" 296 | [5]: http://htmlpreview.github.io/?https://github.com/gkushang/grunt-cucumberjs/blob/cucumber-reports/test/cucumber-reports/cucumber-report-simple.html "Simple Theme Reports" 297 | [8]: https://www.npmjs.com/package/cucumber-parallel 298 | [9]: https://www.npmjs.com/package/cucumber-html-reporter 299 | 300 | 301 | 302 | [changelog]: https://github.com/mavdi/grunt-cucumberjs/blob/master/CHANGELOG.md 303 | 304 | [npm-shield]: https://img.shields.io/npm/v/grunt-cucumberjs.svg 305 | [npm-link]: https://www.npmjs.com/package/grunt-cucumberjs 306 | 307 | [license-shield]: https://img.shields.io/github/license/mashape/apistatus.svg 308 | [license-link]: https://github.com/mavdi/grunt-cucumberjs/ 309 | 310 | 311 | 312 | --------------------------------------------------------------------------------