diff --git a/README.md b/README.md index b42865eee..2df9f7f8c 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,15 @@ This is still a work in progress; some step definition mappings are missing to r You can run the following script which will execute cucumber.js recursively against all known passing features and "core.feature": $ ./run_all_features.js + +### Debug messages + +You can display debug messages by setting the DEBUG_LEVEL environment variable. It goes from `1` to `5`. `5` will diplay everything, `1` will only print out the critical things. + + $ DEBUG_LEVEL=5 ./run_all_features.js + $ DEBUG_LEVEL=5 ./cucumber.js features/cucumber-features/core.feature + +It even works with Aruba: + + $ rm -rf doc; DEBUG_LEVEL=5 ARUBA_REPORT_DIR=doc cucumber features/cucumber-features/core.feature -r features + $ open doc/features/cucumber-features/*.html # you'll see debug messages in Aruba-generated docs diff --git a/features/step_definitions/cucumber_js_mappings.rb b/features/step_definitions/cucumber_js_mappings.rb index 9b1a9aa76..126d65ef7 100644 --- a/features/step_definitions/cucumber_js_mappings.rb +++ b/features/step_definitions/cucumber_js_mappings.rb @@ -1,4 +1,3 @@ -# FIXME: Rename to CucumberJsMappings? module CucumberJsMappings STEP_DEFINITIONS_FILE = "features/step_definitions/cucumber_steps.js" FEATURE_FILE = "features/a_feature.feature" diff --git a/features/step_definitions/cucumber_steps.js b/features/step_definitions/cucumber_steps.js index 8f9cf0be2..7616d0e33 100644 --- a/features/step_definitions/cucumber_steps.js +++ b/features/step_definitions/cucumber_steps.js @@ -1,4 +1,178 @@ var cucumberSteps = function() { + var shouldPrepare = true; + var featureSource; + var stepDefinitions; + var touchedSteps; + var lastRunSucceeded; + var lastRunOutput; + Given(/^a scenario "([^"]*)" with:$/, function(scenarioName, steps, callback) { + prepare(); + featureSource += "Feature: A feature\n"; + featureSource += " Scenario: " + scenarioName + "\n"; + featureSource += steps.replace(/^/gm, ' '); + callback(); + }); + + Given(/^the step "([^"]*)" has a passing mapping$/, function(stepName, callback) { + prepare(); + stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\ + touchStep(\"" + stepName + "\");\ + callback();\ +});\n"; + callback(); + }); + + Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) { + prepare(); + stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\ + touchStep(\"" + stepName + "\");\ + throw('I was supposed to fail.');\ +});\n"; + callback(); + }); + + Given(/^the step "([^"]*)" has a pending mapping$/, function(stepName, callback) { + prepare(); + stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\ + touchStep(\"" + stepName + "\");\ + callback.pending('I am pending.');\ +});\n"; + callback(); + }); + + Given(/^the following feature:$/, function(feature, callback) { + prepare(); + featureSource = feature; + callback(); + }); + + When(/^Cucumber executes the scenario "([^"]*)"$/, function(scenarioName, callback) { + runFeature(callback); + }); + + When(/^Cucumber runs the feature$/, function(callback) { + runFeature(callback); + }); + + When(/^Cucumber runs the scenario with steps for a calculator$/, function(callback) { + RpnCalculator = require('../support/rpn_calculator'); + supportCode = function() { require('./calculator_steps')(RpnCalculator) }; + runFeatureWithSupportCodeSource(supportCode, callback); + }); + + Then(/^the scenario passes$/, function(callback) { + if (!lastRunSucceeded) + throw("Expected the scenario to pass but it failed"); + callback(); + }); + + Then(/^the scenario fails$/, function(callback) { + assertFailedScenario(); + callback(); + }); + + Then(/^the scenario is pending$/, function(callback) { + assertPendingScenario(); + callback(); + }); + + Then(/^the scenario is undefined$/, function(callback) { + assertUndefinedScenario(); + callback(); + }); + + Then(/^the step "([^"]*)" is skipped$/, function(stepName, callback) { + assertSkippedStep(stepName); + callback(); + }); + + Then(/^the feature passes$/, function(callback) { + assertPassingFeature(); + callback(); + }); + + function prepare() { + if (shouldPrepare) { + shouldPrepare = false; + touchedSteps = []; + featureSource = ""; + stepDefinitions = ""; + } + } + + function runFeature(callback) { + var supportCode; + var supportCodeSource = "supportCode = function() {\n" + stepDefinitions + "};\n"; + eval(supportCodeSource); + runFeatureWithSupportCodeSource(supportCode, callback); + } + + function runFeatureWithSupportCodeSource(supportCode, callback) { + var Cucumber = require('../../lib/cucumber'); + var cucumber = Cucumber(featureSource, supportCode); + var formatter = Cucumber.Listener.ProgressFormatter({logToConsole: false}); + cucumber.attachListener(formatter); + cucumber.start(function(succeeded) { + lastRunSucceeded = succeeded; + lastRunOutput = formatter.getLogs(); + Cucumber.Debug.notice(lastRunOutput, 'cucumber output', 5); + shouldPrepare = true; + callback(); + }); + } + + function touchStep(string) { + touchedSteps.push(string); + } + + function isStepTouched(pattern) { + return (touchedSteps.indexOf(pattern) >= 0); + } + + function assertPassingFeature() { + assertNoPartialOutput("failed", lastRunOutput); + assertSuccess(); + } + + function assertFailedScenario() { + assertPartialOutput("1 scenario (1 failed)", lastRunOutput); + assertFailure(); + } + + function assertPendingScenario() { + assertPartialOutput("1 scenario (1 pending)", lastRunOutput); + assertSuccess(); + } + + function assertUndefinedScenario() { + assertPartialOutput("1 scenario (1 undefined)", lastRunOutput); + assertSuccess(); + } + + function assertSkippedStep(stepName) { + if (isStepTouched(stepName)) + throw("Expected step \"" + stepName + "\" to have been skipped."); + } + + function assertSuccess() { + if (!lastRunSucceeded) + throw("Expected Cucumber to succeed but it failed."); + } + + function assertFailure() { + if (lastRunSucceeded) + throw("Expected Cucumber to fail but it succeeded."); + } + + function assertPartialOutput(expected, actual) { + if (actual.indexOf(expected) < 0) + throw("Expected:\n\"" + actual + "\"\nto match:\n\"" + expected + "\""); + } + + function assertNoPartialOutput(expected, actual) { + if (actual.indexOf(expected) >= 0) + throw("Expected:\n\"" + actual + "\"\nnot to match:\n\"" + expected + "\""); + } }; module.exports = cucumberSteps; diff --git a/lib/cucumber/debug.js b/lib/cucumber/debug.js index 7a2a30b35..f0e664cd8 100644 --- a/lib/cucumber/debug.js +++ b/lib/cucumber/debug.js @@ -1,7 +1,36 @@ var Debug = { - TODO: function(description) { + TODO: function TODO(description) { return function() { throw("IMPLEMENT ME: " + description); }; + }, + + warn: function warn(string, caption, level) { + if (Debug.isMessageLeveltoBeDisplayed(level)) + process.stdout.write(Debug.warningString(string, caption)); + }, + + notice: function notice(string, caption, level) { + if (Debug.isMessageLeveltoBeDisplayed(level)) + process.stdout.write(Debug.noticeString(string, caption)); + }, + + warningString: function warningString(string, caption) { + caption = caption || 'debug-warning'; + return "\033[30;43m" + caption + ":\033[0m \033[33m" + string + "\033[0m" + }, + + noticeString: function noticeString(string, caption) { + caption = caption || 'debug-notice'; + return "\033[30;46m" + caption + ":\033[0m \033[36m" + string + "\033[0m" + }, + + prefix: function prefix() { + return ; + }, + + isMessageLeveltoBeDisplayed: function isMessageLeveltoBeDisplayed(level) { + level = level || 3; // default level + return (level <= process.env['DEBUG_LEVEL']); } }; Debug.SimpleAstListener = require('./debug/simple_ast_listener'); -module.exports = Debug; \ No newline at end of file +module.exports = Debug; diff --git a/lib/cucumber/support_code/step_definition.js b/lib/cucumber/support_code/step_definition.js index 7284ca5ba..394de721c 100644 --- a/lib/cucumber/support_code/step_definition.js +++ b/lib/cucumber/support_code/step_definition.js @@ -19,6 +19,8 @@ var StepDefinition = function(regexp, code) { try { code.apply(undefined, parameters); } catch (err) { + if (err) + Cucumber.Debug.warn(err, 'exception inside feature', 3); var stepResult = (err instanceof Cucumber.Runtime.PendingStepException) ? Cucumber.Runtime.PendingStepResult() : Cucumber.Runtime.FailedStepResult();