diff --git a/README.md b/README.md
index d5c9ff6df..d99f64b18 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Cucumber.js is still a work in progress. Here is its current status.
| [Failing steps](/~https://github.com/cucumber/cucumber-tck/blob/master/failing_steps.feature) | Done |
| [Hooks](/~https://github.com/cucumber/cucumber-tck/blob/master/hooks.feature) | Done |
| [I18n](/~https://github.com/cucumber/cucumber-tck/blob/master/i18n.feature) | To do |
-| [JSON formatter](/~https://github.com/cucumber/cucumber-tck/blob/master/json_formatter.feature) | To do |
+| [JSON formatter](/~https://github.com/cucumber/cucumber-tck/blob/master/json_formatter.feature) | WIP4 |
| [Pretty formatter](/~https://github.com/cucumber/cucumber-tck/blob/master/pretty_formatter.feature) | WIP2 |
| [Scenario outlines and examples](/~https://github.com/cucumber/cucumber-tck/blob/master/scenario_outlines_and_examples.feature) | To do |
| [Stats collector](/~https://github.com/cucumber/cucumber-tck/blob/master/stats_collector.feature) | To do |
@@ -38,6 +38,12 @@ Cucumber.js is still a work in progress. Here is its current status.
1. Not certified by [Cucumber TCK](/~https://github.com/cucumber/cucumber-tck) yet.
2. Considered for removal from [Cucumber TCK](/~https://github.com/cucumber/cucumber-tck).
3. Simple *Around*, *Before* and *After* hooks are available.
+4. Missing 'matches' attributes. Simple wrapper for Gherkin JsonFormatter pending porting of:
+
+* /~https://github.com/cucumber/gherkin/blob/master/lib/gherkin/listener/formatter_listener.rb
+* /~https://github.com/cucumber/gherkin/blob/master/lib/gherkin/formatter/filter_formatter.rb
+
+In Gherkin itself
### Cucumber.js-specific features
diff --git a/features/cli.feature b/features/cli.feature
index 5b3424f71..277b24790 100644
--- a/features/cli.feature
+++ b/features/cli.feature
@@ -102,4 +102,4 @@ Feature: Command line interface
Scenario: display help (short flag)
When I run `cucumber.js -h`
- Then I see the help of Cucumber
+ Then I see the help of Cucumber
\ No newline at end of file
diff --git a/features/json_formatter.feature b/features/json_formatter.feature
new file mode 100644
index 000000000..f4984f35d
--- /dev/null
+++ b/features/json_formatter.feature
@@ -0,0 +1,1449 @@
+Feature: JSON Formatter
+ In order to simplify processing of Cucumber features and results
+ Developers should be able to consume features as JSON
+
+ Scenario: output JSON for a feature with no scenarios
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id":"some-feature",
+ "name":"some feature",
+ "description":"",
+ "line":1,
+ "keyword":"Feature",
+ "uri":"/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature"
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with one undefined scenario
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I havn't done anything yet
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id":"some-feature",
+ "name":"some feature",
+ "description":"",
+ "line":1,
+ "keyword":"Feature",
+ "uri":"/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements":[
+ {"name":"I havn't done anything yet",
+ "id":"some-feature;i-havn't-done-anything-yet",
+ "line":3,
+ "keyword":"Scenario",
+ "description":"",
+ "type":"scenario"
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with one scenario with one undefined step
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I've declaired one step but not yet defined it
+ Given I have not defined this step
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri":"/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "I've declaired one step but not yet defined it",
+ "id": "some-feature;i've-declaired-one-step-but-not-yet-defined-it",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps":[
+ {"name":"I have not defined this step",
+ "line":4,
+ "keyword":"Given ",
+ "result":
+ {"status":"undefined"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with one undefined step and subsequent defined steps which should be skipped
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: One pending step and two following steps which will be skipped
+ Given This step is undefined
+ Then this step should be skipped
+
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Then(/^this step should be skipped$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "One pending step and two following steps which will be skipped",
+ "id": "some-feature;one-pending-step-and-two-following-steps-which-will-be-skipped",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is undefined",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "undefined"
+ },
+ "match": {
+ }
+ },
+ {
+ "name": "this step should be skipped",
+ "line": 5,
+ "keyword": "Then ",
+ "result": {
+ "status": "skipped"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with one scenario with one pending step
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I've declaired one step which is pending
+ Given This step is pending
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is pending$/, function(callback) { callback.pending(); });
+ };
+ module.exports = cucumberSteps;
+ """
+
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri":"/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "I've declaired one step which is pending",
+ "id": "some-feature;i've-declaired-one-step-which-is-pending",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is pending",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "pending"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+ Scenario: output JSON for a feature with one scenario with failing step
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I've declaired one step but it is failing
+ Given This step is failing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is failing$/, function(callback) { callback.fail(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri":"/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "I've declaired one step but it is failing",
+ "id": "some-feature;i've-declaired-one-step-but-it-is-failing",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is failing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "error_message": "ERROR_MESSAGE",
+ "status": "failed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+ Scenario: output JSON for a feature with one scenario with passing step
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I've declaired one step which passes
+ Given This step is passing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri":"/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "I've declaired one step which passes",
+ "id": "some-feature;i've-declaired-one-step-which-passes",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a scenario with a passing step follwed by one that is pending and one that fails
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I've declaired one step which is passing, one pending and one failing.
+ Given This step is passing
+ And This step is pending
+ And This step fails but will be skipped
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ this.Given(/^This step is pending$/, function(callback) { callback.pending(); });
+ this.Given(/^This step fails but will be skipped$/, function(callback) { callback.fail(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "I've declaired one step which is passing, one pending and one failing.",
+ "id": "some-feature;i've-declaired-one-step-which-is-passing,-one-pending-and-one-failing.",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ },
+ {
+ "name": "This step is pending",
+ "line": 5,
+ "keyword": "And ",
+ "result": {
+ "status": "pending"
+ },
+ "match": {
+ }
+ },
+ {
+ "name": "This step fails but will be skipped",
+ "line": 6,
+ "keyword": "And ",
+ "result": {
+ "status": "skipped"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a scenario with a pending step follwed by one that passes and one that fails
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: I've declaired one step which is passing, one pending and one failing.
+ Given This step is pending
+ And This step is passing but will be skipped
+ And This step fails but will be skipped
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is pending$/, function(callback) { callback.pending(); });
+ this.Given(/^This step is passing but will be skipped$/, function(callback) { callback(); });
+ this.Given(/^This step fails but will be skipped$/, function(callback) { callback.fail(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "I've declaired one step which is passing, one pending and one failing.",
+ "id": "some-feature;i've-declaired-one-step-which-is-passing,-one-pending-and-one-failing.",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is pending",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "pending"
+ },
+ "match": {
+ }
+ },
+ {
+ "name": "This step is passing but will be skipped",
+ "line": 5,
+ "keyword": "And ",
+ "result": {
+ "status": "skipped"
+ },
+ "match": {
+ }
+ },
+ {
+ "name": "This step fails but will be skipped",
+ "line": 6,
+ "keyword": "And ",
+ "result": {
+ "status": "skipped"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for one feature, one passing scenario, one failing scenario
+ Given a file named "features/a.feature" with:
+ """
+ Feature: one passes one fails
+
+ Scenario: This one passes
+ Given This step is passing
+ Scenario: This one fails
+ Given This step is failing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ this.Given(/^This step is failing$/, function(callback) { callback.fail(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "one-passes one fails",
+ "name": "one passes one fails",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "This one passes",
+ "id": "one-passes one fails;this-one-passes",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This one fails",
+ "id": "one-passes one fails;this-one-fails",
+ "line": 5,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is failing",
+ "line": 6,
+ "keyword": "Given ",
+ "result": {
+ "error_message": "ERROR_MESSAGE",
+ "status": "failed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+ Scenario: output JSON for multiple features
+ Given a file named "features/a.feature" with:
+ """
+ Feature: feature a
+
+ Scenario: This is the first feature
+ Given This step is passing
+ """
+ And a file named "features/b.feature" with:
+ """
+ Feature: feature b
+
+ Scenario: This is the second feature
+ Given This step is passing
+ """
+ And a file named "features/c.feature" with:
+ """
+ Feature: feature c
+
+ Scenario: This is the third feature
+ Given This step is passing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "feature-a",
+ "name": "feature a",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "This is the first feature",
+ "id": "feature-a;this-is-the-first-feature",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "feature-b",
+ "name": "feature b",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/b.feature",
+ "elements": [
+ {
+ "name": "This is the second feature",
+ "id": "feature-b;this-is-the-second-feature",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "feature-c",
+ "name": "feature c",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/c.feature",
+ "elements": [
+ {
+ "name": "This is the third feature",
+ "id": "feature-c;this-is-the-third-feature",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+ Scenario: output JSON for multiple features each with multiple scenarios
+ Given a file named "features/a.feature" with:
+ """
+ Feature: feature a
+
+ Scenario: This is the feature a scenario one
+ Given This step is passing
+
+ Scenario: This is the feature a scenario two
+ Given This step is passing
+
+ Scenario: This is the feature a scenario three
+ Given This step is passing
+ """
+ And a file named "features/b.feature" with:
+ """
+ Feature: feature b
+
+ Scenario: This is the feature b scenario one
+ Given This step is passing
+
+ Scenario: This is the feature b scenario two
+ Given This step is passing
+
+ Scenario: This is the feature b scenario three
+ Given This step is passing
+ """
+ And a file named "features/c.feature" with:
+ """
+ Feature: feature c
+
+ Scenario: This is the feature c scenario one
+ Given This step is passing
+
+ Scenario: This is the feature c scenario two
+ Given This step is passing
+
+ Scenario: This is the feature c scenario three
+ Given This step is passing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "feature-a",
+ "name": "feature a",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "This is the feature a scenario one",
+ "id": "feature-a;this-is-the-feature-a-scenario-one",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This is the feature a scenario two",
+ "id": "feature-a;this-is-the-feature-a-scenario-two",
+ "line": 6,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 7,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This is the feature a scenario three",
+ "id": "feature-a;this-is-the-feature-a-scenario-three",
+ "line": 9,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 10,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "feature-b",
+ "name": "feature b",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/b.feature",
+ "elements": [
+ {
+ "name": "This is the feature b scenario one",
+ "id": "feature-b;this-is-the-feature-b-scenario-one",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This is the feature b scenario two",
+ "id": "feature-b;this-is-the-feature-b-scenario-two",
+ "line": 6,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 7,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This is the feature b scenario three",
+ "id": "feature-b;this-is-the-feature-b-scenario-three",
+ "line": 9,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 10,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "id": "feature-c",
+ "name": "feature c",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/c.feature",
+ "elements": [
+ {
+ "name": "This is the feature c scenario one",
+ "id": "feature-c;this-is-the-feature-c-scenario-one",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 4,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This is the feature c scenario two",
+ "id": "feature-c;this-is-the-feature-c-scenario-two",
+ "line": 6,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 7,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ },
+ {
+ "name": "This is the feature c scenario three",
+ "id": "feature-c;this-is-the-feature-c-scenario-three",
+ "line": 9,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 10,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with a background
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Background:
+ Given This applies to all scenarios
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This applies to all scenarios$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "",
+ "keyword": "Background",
+ "description": "",
+ "type": "background",
+ "line": 3,
+ "steps": [
+ {
+ "name": "This applies to all scenarios",
+ "line": 4,
+ "keyword": "Given "
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with a failing background
+ # Since the background step is re-evaluated for each scenario that is where the result of the step is currently recorded in the JSON output
+ # If the background is being reevaluated for each scenario then it would be misleading to only output the result for the first time it was evaluated.
+
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Background:
+ Given This applies to all scenarios but fails
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This applies to all scenarios but fails$/, function(callback) { callback.fail(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "",
+ "keyword": "Background",
+ "description": "",
+ "type": "background",
+ "line": 3,
+ "steps": [
+ {
+ "name": "This applies to all scenarios but fails",
+ "line": 4,
+ "keyword": "Given "
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with a DocString
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: Scenario with DocString
+ Given we have this DocString:
+ \"\"\"
+ This is a DocString
+ \"\"\"
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^we have this DocString:$/, function(string, callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "Scenario with DocString",
+ "id": "some-feature;scenario-with-docstring",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "we have this DocString:",
+ "line": 4,
+ "keyword": "Given ",
+ "doc_string":
+ {
+ "value": "This is a DocString",
+ "line": 5,
+ "content_type": ""
+ },
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for background step with a DocString
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Background: Background with DocString
+ Given we have this DocString:
+ \"\"\"
+ This is a DocString
+ \"\"\"
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^we have this DocString:$/, function(string, callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "Background with DocString",
+ "keyword": "Background",
+ "description": "",
+ "type": "background",
+ "line": 3,
+ "steps": [
+ {
+ "name": "we have this DocString:",
+ "line": 4,
+ "keyword": "Given ",
+ "doc_string": {
+ "value": "This is a DocString",
+ "line": 5,
+ "content_type": ""
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a feature with tags
+
+ Given a file named "features/a.feature" with:
+ """
+ @alpha @beta @gamma
+ Feature: some feature
+
+ Scenario: This scenario has no tags
+ Given This step is passing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 2,
+ "keyword": "Feature",
+ "tags": [
+ {
+ "name": "@alpha",
+ "line": 1
+ },
+ {
+ "name": "@beta",
+ "line": 1
+ },
+ {
+ "name": "@gamma",
+ "line": 1
+ }
+ ],
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "This scenario has no tags",
+ "id": "some-feature;this-scenario-has-no-tags",
+ "line": 4,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 5,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for scenario with tags
+
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ @one @two @three
+ Scenario: This scenario has tags
+ Given This step is passing
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "This scenario has tags",
+ "id": "some-feature;this-scenario-has-tags",
+ "line": 4,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "tags": [
+ {
+ "name": "@one",
+ "line": 3
+ },
+ {
+ "name": "@two",
+ "line": 3
+ },
+ {
+ "name": "@three",
+ "line": 3
+ }
+ ],
+ "steps": [
+ {
+ "name": "This step is passing",
+ "line": 5,
+ "keyword": "Given ",
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for a step with table
+ # Rows do not appear to support line attribute yet.
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Scenario: This scenario contains a step with a table
+ Given This table:
+ |col 1|col 2|col 3|
+ |one |two |three|
+ |1 |2 |3 |
+ |! |~ |@ |
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This table:$/, function(table, callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "This scenario contains a step with a table",
+ "id": "some-feature;this-scenario-contains-a-step-with-a-table",
+ "line": 3,
+ "keyword": "Scenario",
+ "description": "",
+ "type": "scenario",
+ "steps": [
+ {
+ "name": "This table:",
+ "line": 4,
+ "keyword": "Given ",
+ "rows": [
+ {
+ "cells": [
+ "col 1",
+ "col 2",
+ "col 3"
+ ]
+ },
+ {
+ "cells": [
+ "one",
+ "two",
+ "three"
+ ]
+ },
+ {
+ "cells": [
+ "1",
+ "2",
+ "3"
+ ]
+ },
+ {
+ "cells": [
+ "!",
+ "~",
+ "@"
+ ]
+ }
+ ],
+ "result": {
+ "status": "passed"
+ },
+ "match": {
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
+
+ Scenario: output JSON for background with table
+ # Rows do not appear to support line attribute yet.
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+
+ Background:
+ Given This table:
+ |col 1|col 2|col 3|
+ |one |two |three|
+ |1 |2 |3 |
+ |! |~ |@ |
+ """
+ And a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ this.Given(/^This table:$/, function(table, callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js -f json`
+ Then it should output this json:
+ """
+ [
+ {
+ "id": "some-feature",
+ "name": "some feature",
+ "description": "",
+ "line": 1,
+ "keyword": "Feature",
+ "uri": "/path/to/sandbox/tmp/cucumber-js-sandbox/features/a.feature",
+ "elements": [
+ {
+ "name": "",
+ "keyword": "Background",
+ "description": "",
+ "type": "background",
+ "line": 3,
+ "steps": [
+ {
+ "name": "This table:",
+ "line": 4,
+ "keyword": "Given ",
+ "rows": [
+ {
+ "cells": [
+ "col 1",
+ "col 2",
+ "col 3"
+ ]
+ },
+ {
+ "cells": [
+ "one",
+ "two",
+ "three"
+ ]
+ },
+ {
+ "cells": [
+ "1",
+ "2",
+ "3"
+ ]
+ },
+ {
+ "cells": [
+ "!",
+ "~",
+ "@"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ """
\ No newline at end of file
diff --git a/features/step_definitions/cli_steps.js b/features/step_definitions/cli_steps.js
index c731abdf3..ee1bd855d 100644
--- a/features/step_definitions/cli_steps.js
+++ b/features/step_definitions/cli_steps.js
@@ -8,7 +8,7 @@ var cliSteps = function cliSteps() {
var tmpDir = baseDir + "/tmp/cucumber-js-sandbox";
var cleansingNeeded = true;
- var lastRun;
+ var lastRun = { error: null, stdout: "", stderr: "" };
function tmpPath(path) {
return (tmpDir + "/" + path);
@@ -64,8 +64,60 @@ var cliSteps = function cliSteps() {
this.Then(/^it should pass with:$/, function(expectedOutput, callback) {
var actualOutput = lastRun['stdout'];
+
+ var actualError = lastRun['error'];
+ var actualStderr = lastRun['stderr'];
+
if (actualOutput.indexOf(expectedOutput) == -1)
- throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
+ throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.\n" +
+ "Error:\n'" + actualError + "'.\n" +
+ "stderr:\n'" + actualStderr +"'.");
+ callback();
+ });
+
+ this.Given(/^CUCUMBER_JS_HOME environment variable has been set to the cucumber\-js install dir$/, function(callback) {
+ // This is needed to allow us to check the error_message produced for a failed steps which includes paths which
+ // contain the location where cucumber-js is installed.
+ if (process.env.CUCUMBER_JS_HOME) {
+ callback();
+ } else {
+ callback.fail(new Error("CUCUMBER_JS_HOME has not been set."));
+ }
+ });
+
+ this.Then(/^it should output this json:$/, function(expectedOutput, callback) {
+ var actualOutput = lastRun['stdout'];
+
+ var actualError = lastRun['error'];
+ var actualStderr = lastRun['stderr'];
+
+ try {
+ var actualJson = JSON.parse(actualOutput);
+ }
+ catch(err) {
+ throw new Error("Error parsing actual JSON:\n" + actualOutput);
+ }
+
+ try {
+ var expectedJson = JSON.parse(expectedOutput);
+ }
+ catch(err) {
+ throw new Error("Error parsing expected JSON:\n" + expectedOutput);
+ }
+
+ var actualJsonString = JSON.stringify(actualJson, null, 2);
+
+ // remove path to sandbox from uris
+ actualJsonString = actualJsonString.replace(/(\"uri\"\:\ \")(.+?)(\/tmp\/cucumber-js-sandbox\/)/g, "$1/path/to/sandbox$3");
+ // remove location specific error messages
+ actualJsonString = actualJsonString.replace(/(\"error_message\"\:\ \")(.+?)(\"\,)/g, "$1ERROR_MESSAGE$3");
+
+ var expectedJsonString = JSON.stringify(expectedJson, null, 2);
+
+ if (actualJsonString != expectedJsonString)
+ throw new Error("Expected output to match the following:\n'" + expectedJsonString + "'\nGot:\n'" + actualJsonString + "'.\n" +
+ "Error:\n'" + actualError + "'.\n" +
+ "stderr:\n'" + actualStderr +"'.");
callback();
});
diff --git a/lib/cucumber/ast/feature.js b/lib/cucumber/ast/feature.js
index d2077a912..30048d8ca 100644
--- a/lib/cucumber/ast/feature.js
+++ b/lib/cucumber/ast/feature.js
@@ -1,4 +1,4 @@
-var Feature = function(keyword, name, description, uri, line) {
+ var Feature = function(keyword, name, description, uri, line) {
var Cucumber = require('../../cucumber');
var background;
@@ -26,6 +26,14 @@ var Feature = function(keyword, name, description, uri, line) {
return line;
},
+ getUri: function getUri() {
+ if (!uri) {
+ return undefined;
+ } else {
+ return uri;
+ }
+ },
+
addBackground: function addBackground(newBackground) {
background = newBackground;
},
diff --git a/lib/cucumber/cli.js b/lib/cucumber/cli.js
index a69da1b0f..2413e1910 100644
--- a/lib/cucumber/cli.js
+++ b/lib/cucumber/cli.js
@@ -59,11 +59,11 @@ var Cli = function(argv) {
\n\
-f, --format FORMAT How to format features (default: progress).\n\
Available formats:\n\
-\n\
- pretty : prints the feature as is\n\
- progress: prints one character per scenario\n\
- summary : prints a summary only, after all\n\
- scenarios were executed\n\
+ pretty : prints the feature as is\n\
+ progress : prints one character per scenario\n\
+ json : Prints the feature as JSON\n\
+ summary : prints a summary only, after all\n\
+ scenarios were executed\n\
\n\
-v, --version Display Cucumber.js's version.\n\
\n\
diff --git a/lib/cucumber/cli/argument_parser.js b/lib/cucumber/cli/argument_parser.js
index 9f9c17ee0..e9c283b79 100644
--- a/lib/cucumber/cli/argument_parser.js
+++ b/lib/cucumber/cli/argument_parser.js
@@ -112,11 +112,11 @@ ArgumentParser.FEATURE_FILENAME_REGEXP = /[\/\\][^\/\\]+\.feature$/i;
ArgumentParser.LONG_OPTION_PREFIX = "--";
ArgumentParser.REQUIRE_OPTION_NAME = "require";
ArgumentParser.REQUIRE_OPTION_SHORT_NAME = "r";
-ArgumentParser.TAGS_OPTION_NAME = "tags";
-ArgumentParser.TAGS_OPTION_SHORT_NAME = "t";
ArgumentParser.FORMAT_OPTION_NAME = "format";
ArgumentParser.FORMAT_OPTION_SHORT_NAME = "f";
ArgumentParser.DEFAULT_FORMAT_VALUE = "progress";
+ArgumentParser.TAGS_OPTION_NAME = "tags";
+ArgumentParser.TAGS_OPTION_SHORT_NAME = "t";
ArgumentParser.HELP_FLAG_NAME = "help";
ArgumentParser.HELP_FLAG_SHORT_NAME = "h";
ArgumentParser.DEFAULT_HELP_FLAG_VALUE = false;
diff --git a/lib/cucumber/cli/configuration.js b/lib/cucumber/cli/configuration.js
index dca494e57..0c82d6003 100644
--- a/lib/cucumber/cli/configuration.js
+++ b/lib/cucumber/cli/configuration.js
@@ -15,6 +15,9 @@ var Configuration = function(argv) {
case Configuration.PRETTY_FORMAT_NAME:
formatter = Cucumber.Listener.PrettyFormatter();
break;
+ case Configuration.JSON_FORMAT_NAME:
+ formatter = Cucumber.Listener.JsonFormatter();
+ break;
case Configuration.SUMMARY_FORMAT_NAME:
formatter = Cucumber.Listener.SummaryFormatter();
break;
@@ -63,10 +66,12 @@ var Configuration = function(argv) {
var isVersionRequested = argumentParser.isVersionRequested();
return isVersionRequested;
}
+
};
return self;
};
Configuration.PRETTY_FORMAT_NAME = "pretty";
Configuration.PROGRESS_FORMAT_NAME = "progress";
+Configuration.JSON_FORMAT_NAME = "json";
Configuration.SUMMARY_FORMAT_NAME = "summary";
module.exports = Configuration;
diff --git a/lib/cucumber/listener.js b/lib/cucumber/listener.js
index 09f9082df..bf04e36c8 100644
--- a/lib/cucumber/listener.js
+++ b/lib/cucumber/listener.js
@@ -36,6 +36,7 @@ Listener.EVENT_HANDLER_NAME_SUFFIX = 'Event';
Listener.Formatter = require('./listener/formatter');
Listener.PrettyFormatter = require('./listener/pretty_formatter');
Listener.ProgressFormatter = require('./listener/progress_formatter');
+Listener.JsonFormatter = require('./listener/json_formatter');
Listener.StatsJournal = require('./listener/stats_journal');
Listener.SummaryFormatter = require('./listener/summary_formatter');
module.exports = Listener;
diff --git a/lib/cucumber/listener/json_formatter.js b/lib/cucumber/listener/json_formatter.js
new file mode 100644
index 000000000..7351bef85
--- /dev/null
+++ b/lib/cucumber/listener/json_formatter.js
@@ -0,0 +1,157 @@
+var JsonFormatter = function() {
+
+ var GherkinJsonFormatter = require('gherkin/lib/gherkin/formatter/json_formatter');
+ var gherkinJsonFormatter = new GherkinJsonFormatter(process.stdout);
+
+ var currentFeatureId = 'undefined';
+
+ var Cucumber = require('../../cucumber');
+
+ var self = Cucumber.Listener();
+
+ var parentFeatureTags;
+
+ self.getGherkinFormatter = function() {
+ return gherkinJsonFormatter;
+ }
+
+ self.formatStep = function formatStep(step) {
+
+ var stepProperties = {name: step.getName(), line: step.getLine(), keyword: step.getKeyword()};
+ if (step.hasDocString()) {
+ var docString = step.getDocString();
+ stepProperties['doc_string'] = {value: docString.getContents(), line: docString.getLine(), content_type: docString.getContentType()};
+ }
+ if (step.hasDataTable()) {
+ var tableContents = step.getDataTable().getContents();
+ var raw = tableContents.raw();
+ var tableProperties = [];
+ for (rawRow in raw) {
+ var row = {line: undefined, cells: raw[rawRow]};
+ tableProperties.push(row);
+ }
+ stepProperties['rows'] = tableProperties;
+ }
+
+ gherkinJsonFormatter.step(stepProperties);
+
+ }
+
+ self.formatTags = function formatTags(tags, parentTags) {
+ var tagsProperties = [];
+ for (tag in tags) {
+ var isParentTag = false;
+ for (parentTag in parentTags) {
+ if ((tags[tag].getName() == parentTags[parentTag].getName()) && (tags[tag].getLine() == parentTags[parentTag].getLine())) {
+ isParentTag = true;
+ }
+ }
+ if (!isParentTag) {
+ tagsProperties.push({name: tags[tag].getName(), line: tags[tag].getLine()});
+ }
+ }
+ return tagsProperties;
+ }
+
+ self.handleBeforeFeatureEvent = function handleBeforeFeatureEvent(event, callback) {
+
+ var feature = event.getPayloadItem('feature');
+ currentFeatureId = feature.getName().replace(' ','-');
+
+ gherkinJsonFormatter.uri(feature.getUri());
+
+ var featureProperties = {id: currentFeatureId,
+ name: feature.getName(),
+ description: feature.getDescription(),
+ line: feature.getLine(),
+ keyword: feature.getKeyword()}
+
+ var tags = feature.getTags();
+ if (tags.length > 0) {
+ featureProperties['tags'] = self.formatTags(tags, []);
+ }
+
+ gherkinJsonFormatter.feature(featureProperties);
+
+ parentFeatureTags = tags;
+
+ callback();
+ }
+
+ self.handleBackgroundEvent = function handleBackgroundEvent(event, callback) {
+ var background = event.getPayloadItem('background');
+ gherkinJsonFormatter.background({name: background.getName(), keyword: "Background", description: background.getDescription(), type: 'background', line: background.getLine()})
+ var steps = background.getSteps();
+ steps.forEach(function(value, index, ar) { self.formatStep(value); });
+ callback();
+ }
+
+ self.handleBeforeScenarioEvent = function handleBeforeScenarioEvent(event, callback) {
+
+ var scenario = event.getPayloadItem('scenario');
+
+ var id = currentFeatureId + ';' + scenario.getName().replace(/ /g, '-').toLowerCase();
+ var scenarioProperties = {name: scenario.getName(), id: id, line: scenario.getLine(), keyword: 'Scenario', description: scenario.getDescription(), type: 'scenario'};
+
+ var tags = scenario.getTags();
+ if (tags.length > 0) {
+ var tagsProperties = self.formatTags(tags, parentFeatureTags);
+ if (tagsProperties.length > 0) {
+ scenarioProperties['tags'] = tagsProperties;
+ }
+ }
+
+ gherkinJsonFormatter.scenario(scenarioProperties);
+
+ callback();
+ }
+
+ self.handleStepResultEvent = function handleStepResult(event, callback) {
+ var stepResult = event.getPayloadItem('stepResult');
+
+ var step = stepResult.getStep();
+ self.formatStep(step);
+
+ var stepOutput = {};
+ var resultStatus = 'failed';
+
+ if (stepResult.isSuccessful()) {
+ resultStatus = 'passed';
+ }
+ else if (stepResult.isPending()) {
+ resultStatus = 'pending';
+ stepOutput['error_message'] = undefined;
+ }
+ else if (stepResult.isSkipped()) {
+ resultStatus = 'skipped';
+ }
+ else if (stepResult.isUndefined()) {
+ resultStatus = 'undefined';
+ }
+ else {
+ var failureMessage = stepResult.getFailureException();
+ if (failureMessage) {
+ stepOutput['error_message'] = (failureMessage.stack || failureMessage);
+ }
+ }
+
+ stepOutput['status'] = resultStatus;
+
+ gherkinJsonFormatter.result(stepOutput);
+ gherkinJsonFormatter.match({location: undefined});
+ callback();
+ }
+
+ self.handleAfterFeaturesEvent = function handleAfterFeaturesEvent(event, callback) {
+
+ gherkinJsonFormatter.eof();
+ gherkinJsonFormatter.done();
+
+ callback();
+ }
+
+ return self;
+};
+
+module.exports = JsonFormatter;
+
diff --git a/package.json b/package.json
index 7771cef91..a998a0f3b 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
"node": "0.6 || 0.7 || 0.8"
},
"dependencies": {
- "gherkin": "2.11.0",
+ "gherkin": "2.11.1",
"jasmine-node": "1.0.26",
"connect": "2.3.2",
"browserify": "1.13.2",
diff --git a/spec/cucumber/cli/argument_parser_spec.js b/spec/cucumber/cli/argument_parser_spec.js
index ed165bf6a..de9ce1f4d 100644
--- a/spec/cucumber/cli/argument_parser_spec.js
+++ b/spec/cucumber/cli/argument_parser_spec.js
@@ -78,6 +78,12 @@ describe("Cucumber.Cli.ArgumentParser", function () {
var knownOptionDefinitions = argumentParser.getKnownOptionDefinitions();
expect(knownOptionDefinitions[Cucumber.Cli.ArgumentParser.VERSION_FLAG_NAME]).toEqual(Boolean);
});
+
+ it("defines an --output option to specify output format", function() {
+ var knownOptionDefinitions = argumentParser.getKnownOptionDefinitions();
+ expect(knownOptionDefinitions[Cucumber.Cli.ArgumentParser.FORMAT_OPTION_NAME]).toEqual(String);
+ });
+
});
describe("getShortenedOptionDefinitions()", function () {
@@ -109,6 +115,7 @@ describe("Cucumber.Cli.ArgumentParser", function () {
var shortenedOptionDefinitions = argumentParser.getShortenedOptionDefinitions();
expect(shortenedOptionDefinitions[aliasName]).toEqual(aliasValue);
});
+
});
describe("getFeatureFilePaths()", function () {
diff --git a/spec/cucumber/cli/configuration_spec.js b/spec/cucumber/cli/configuration_spec.js
index 93334a9a7..d6175bfde 100644
--- a/spec/cucumber/cli/configuration_spec.js
+++ b/spec/cucumber/cli/configuration_spec.js
@@ -261,4 +261,20 @@ describe("Cucumber.Cli.Configuration", function () {
expect(configuration.isVersionRequested()).toBe(isVersionRequested);
});
});
+
+ describe("getFormatter()", function() {
+ beforeEach(function() {
+ spyOnStub(argumentParser, 'getFormat').andReturn("progress");
+ });
+
+ it("asks the argument parser which format we should be outputting", function() {
+ configuration.getFormatter();
+ expect(argumentParser.getFormat).toHaveBeenCalled();
+ });
+//
+// it("returns the corresponding formatter for us to use", function() {
+// // TODO: Fill this out when my head is in a better place
+// });
+ });
+
});
diff --git a/spec/cucumber/listener/json_formatter_spec.js b/spec/cucumber/listener/json_formatter_spec.js
new file mode 100644
index 000000000..a2afbb0f2
--- /dev/null
+++ b/spec/cucumber/listener/json_formatter_spec.js
@@ -0,0 +1,473 @@
+require('../../support/spec_helper');
+
+describe("Cucumber.Listener.JsonFormatterWrapper", function() {
+ var Cucumber = requireLib('cucumber');
+ var listener, failedStepResults;
+
+ beforeEach(function() {
+ spyOn(process.stdout, 'write'); // prevent actual output during spec execution
+ listener = Cucumber.Listener.JsonFormatter(process.stdout);
+ formatter = listener.getGherkinFormatter();
+
+ spyOn(formatter, 'uri');
+ spyOn(formatter, 'feature');
+ spyOn(formatter, 'step');
+ spyOn(formatter, 'background');
+ spyOn(formatter, 'scenario');
+ spyOn(formatter, 'result');
+ spyOn(formatter, 'match');
+ spyOn(formatter, 'eof');
+ spyOn(formatter, 'done');
+
+ });
+
+ // Handle Feature
+
+ describe("handleBeforeFeatureEvent()", function() {
+ var event, feature, callback;
+
+ beforeEach(function() {
+ feature = createSpyWithStubs("feature",
+ {getKeyword: 'Feature',
+ getName: 'A Name',
+ getDescription: 'A Description',
+ getLine: 3,
+ getUri: undefined,
+ getTags: false});
+
+ event = createSpyWithStubs("event", {getPayloadItem: feature});
+
+ callback = createSpy("callback");
+ });
+
+ it("adds the feature attributes to the output", function() {
+ listener.handleBeforeFeatureEvent(event, callback);
+ expect(formatter.uri).toHaveBeenCalledWith(undefined);
+ expect(formatter.feature).toHaveBeenCalledWith({id: 'A-Name',
+ name: 'A Name',
+ description: 'A Description',
+ line: 3,
+ keyword: 'Feature'});
+
+ });
+
+ });
+
+ // Handle Background
+
+ describe("handleBackgroundEvent()", function() {
+
+ var parent_feature_event, background, step, steps, event, callback;
+
+ beforeEach(function() {
+ feature = createSpyWithStubs("feature",
+ {getKeyword: 'Feature',
+ getName: 'A Name',
+ getDescription: 'A Description',
+ getLine: 3,
+ getUri: 'feature-uri',
+ getTags: false});
+
+ parent_feature_event = createSpyWithStubs("event", {getPayloadItem: feature});
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ steps = [step];
+
+ background = createSpyWithStubs("background",
+ {getKeyword: 'Background',
+ getName: 'A Name',
+ getDescription: 'A Description',
+ getLine: 3,
+ getSteps: steps});
+
+ event = createSpyWithStubs("event", {getPayloadItem: background});
+ callback = createSpy("callback");
+ });
+
+ it("adds the background attributes to the output", function() {
+ listener.handleBackgroundEvent(event, callback);
+ expect(formatter.background).toHaveBeenCalledWith({name: 'A Name',
+ keyword: 'Background',
+ description: 'A Description',
+ type: 'background',
+ line: 3 });
+ });
+
+ });
+
+ // Handle Scenario
+
+ describe("handleBeforeScenarioEvent()", function() {
+ var parent_feature_event, scenario, callback;
+
+ beforeEach(function() {
+ feature = createSpyWithStubs("feature",
+ {getKeyword: 'Feature',
+ getName: 'A Name',
+ getDescription: 'A Description',
+ getLine: 3,
+ getUri: 'feature-uri',
+ getTags: false});
+
+ parent_feature_event = createSpyWithStubs("event", {getPayloadItem: feature});
+
+ scenario = createSpyWithStubs("scenario",
+ {getKeyword: 'Scenario',
+ getName: 'A Name',
+ getDescription: 'A Description',
+ getLine: 3,
+ getTags: false});
+
+ event = createSpyWithStubs("event", {getPayloadItem: scenario});
+ callback = createSpy("callback");
+ });
+
+ it("adds the scenario attributes to the output", function() {
+ listener.handleBeforeScenarioEvent(event, callback);
+ expect(formatter.scenario).toHaveBeenCalledWith({name: 'A Name',
+ id: 'undefined;a-name',
+ line: 3,
+ keyword: 'Scenario',
+ description: 'A Description',
+ type: 'scenario' });
+ });
+
+ });
+
+ // Step Formatting
+
+ describe("formatStep()", function() {
+
+ it("adds name, line and keyword to the step properties", function(){
+
+ var step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ listener.formatStep(step);
+ expect(formatter.step).toHaveBeenCalledWith({ name : 'Step', line : 3, keyword : 'Step'});
+
+ });
+
+ it("if the step has one, adds a DocString to the step properties", function(){
+
+ var fakeDocString = createSpyWithStubs("docString", {
+ getContents: "This is a DocString",
+ getLine: 3,
+ getContentType: null});
+
+ var step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: true,
+ hasDataTable: false,
+ getDocString: fakeDocString
+ });
+
+ listener.formatStep(step);
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step',
+ line: 3,
+ keyword: 'Step',
+ doc_string: {value: 'This is a DocString', line: 3, content_type: null}
+ });
+
+ });
+
+ it("if the step has one, adds a DataTable to the step properties", function(){
+
+ var fakeContents = createSpyWithStubs("row", {
+ raw: [['a:1', 'a:2', 'a:3'],['b:1', 'b:2', 'b:3'],['c:1', 'c:2', 'c:3']]
+ })
+
+ var fakeDataTable = createSpyWithStubs("dataTable", {
+ getContents: fakeContents
+ });
+
+ var step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: true,
+ getDataTable: fakeDataTable
+ });
+
+ listener.formatStep(step);
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step',
+ line: 3,
+ keyword: 'Step',
+ rows: [{line : undefined, cells: ['a:1', 'a:2', 'a:3'] },
+ {line : undefined, cells: ['b:1', 'b:2', 'b:3'] },
+ {line : undefined, cells: ['c:1', 'c:2', 'c:3'] }]
+ });
+ });
+
+ });
+
+ // Tag Formatting
+
+ describe("formatTags()", function() {
+
+ it("returns the given tags in the format expected by the JSON formatter", function(){
+
+ var tags = [createSpyWithStubs("tag", {getName: "tag_one", getLine:1}),
+ createSpyWithStubs("tag", {getName: "tag_two", getLine:2}),
+ createSpyWithStubs("tag", {getName: "tag_three", getLine:3})];
+
+ expect(listener.formatTags(tags, null)).toEqual([{name: 'tag_one', line :1},
+ {name: 'tag_two', line :2},
+ {name: 'tag_three', line :3}]);
+
+ });
+
+ it("filters out any tags it is told to ignore - e.g. those of the parent feature", function(){
+
+ var tags = [createSpyWithStubs("tag", {getName: "tag_one", getLine:1}),
+ createSpyWithStubs("tag", {getName: "tag_two", getLine:2}),
+ createSpyWithStubs("tag", {getName: "parent_one", getLine:3}),
+ createSpyWithStubs("tag", {getName: "parent_two", getLine:3})];
+
+ var parent_tags = [createSpyWithStubs("tag", {getName: "parent_one", getLine:3}),
+ createSpyWithStubs("tag", {getName: "parent_two", getLine:3})];
+
+
+ expect(listener.formatTags(tags, parent_tags)).toEqual([{name: 'tag_one', line :1},
+ {name: 'tag_two', line :2}]);
+ });
+
+ });
+
+ // Handle Step Results
+
+ describe("handleStepResultEvent()", function() {
+ var parent_feature_event, feature, parent_scenario_event, scenario, event, callback, stepResult;
+
+ beforeEach(function() {
+ callback = createSpy("Callback");
+ });
+
+ it("outputs a step with failed status where no result has been defined", function(){
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ stepResult = createSpyWithStubs("stepResult", {
+ isSuccessful: undefined,
+ isPending: undefined,
+ isFailed: undefined,
+ isSkipped: undefined,
+ isUndefined: undefined,
+ getFailureException: false,
+ getStep: step
+ });
+
+ fakeEvent = createSpyWithStubs("event", {getPayloadItem: stepResult});
+
+ listener.handleStepResultEvent(fakeEvent, callback);
+
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step', line: 3, keyword: 'Step'});
+ expect(formatter.result).toHaveBeenCalledWith({status: 'failed'});
+ expect(formatter.match).toHaveBeenCalledWith({location: undefined});
+
+ });
+
+ it("outputs a step with passed status for a successful step", function(){
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ stepResult = createSpyWithStubs("stepResult", {
+ isSuccessful: undefined,
+ isPending: undefined,
+ isFailed: undefined,
+ isSkipped: undefined,
+ isUndefined: undefined,
+ getFailureException: false,
+ getStep: step
+ });
+
+ stepResult.isSuccessful.andReturn(true);
+ fakeEvent = createSpyWithStubs("event", {getPayloadItem: stepResult});
+
+ listener.handleStepResultEvent(fakeEvent, callback);
+
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step', line: 3, keyword: 'Step'});
+ expect(formatter.result).toHaveBeenCalledWith({status: 'passed'});
+ expect(formatter.match).toHaveBeenCalledWith({location: undefined});
+
+ });
+
+ it("outputs a step with pending status where step is pending", function(){
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ stepResult = createSpyWithStubs("stepResult", {
+ isSuccessful: undefined,
+ isPending: undefined,
+ isFailed: undefined,
+ isSkipped: undefined,
+ isUndefined: undefined,
+ getFailureException: false,
+ getStep: step
+ });
+
+ stepResult.isPending.andReturn(true);
+ fakeEvent = createSpyWithStubs("event", {getPayloadItem: stepResult});
+
+ listener.handleStepResultEvent(fakeEvent, callback);
+
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step', line: 3, keyword: 'Step'});
+ expect(formatter.result).toHaveBeenCalledWith({status: 'pending', error_message: undefined});
+ expect(formatter.match).toHaveBeenCalledWith({location: undefined});
+
+ });
+
+ it("outputs a step with failed status where step fails", function(){
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ stepResult = createSpyWithStubs("stepResult", {
+ isSuccessful: undefined,
+ isPending: undefined,
+ isFailed: undefined,
+ isSkipped: undefined,
+ isUndefined: undefined,
+ getFailureException: false,
+ getStep: step
+ });
+
+ stepResult.isFailed.andReturn(true);
+ fakeEvent = createSpyWithStubs("event", {getPayloadItem: stepResult});
+
+ listener.handleStepResultEvent(fakeEvent, callback);
+
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step', line: 3, keyword: 'Step'});
+ expect(formatter.result).toHaveBeenCalledWith({status: 'failed'});
+ expect(formatter.match).toHaveBeenCalledWith({location: undefined});
+
+ });
+
+ it("outputs a step with skipped status where step should be skipped", function(){
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ stepResult = createSpyWithStubs("stepResult", {
+ isSuccessful: undefined,
+ isPending: undefined,
+ isFailed: undefined,
+ isSkipped: undefined,
+ isUndefined: undefined,
+ getFailureException: false,
+ getStep: step
+ });
+
+ stepResult.isSkipped.andReturn(true);
+ fakeEvent = createSpyWithStubs("event", {getPayloadItem: stepResult});
+
+ listener.handleStepResultEvent(fakeEvent, callback);
+
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step', line: 3, keyword: 'Step'});
+ expect(formatter.result).toHaveBeenCalledWith({status: 'skipped'});
+ expect(formatter.match).toHaveBeenCalledWith({location: undefined});
+
+ });
+
+ it("outputs a step with undefined status where step is undefined", function(){
+
+ step = createSpyWithStubs("step", {
+ getName: 'Step',
+ getLine: 3,
+ getKeyword: 'Step',
+ hasDocString: false,
+ hasDataTable: false
+ });
+
+ stepResult = createSpyWithStubs("stepResult", {
+ isSuccessful: undefined,
+ isPending: undefined,
+ isFailed: undefined,
+ isSkipped: undefined,
+ isUndefined: undefined,
+ getFailureException: false,
+ getStep: step
+ });
+
+ stepResult.isUndefined.andReturn(true);
+ fakeEvent = createSpyWithStubs("event", {getPayloadItem: stepResult});
+
+ listener.handleStepResultEvent(fakeEvent, callback);
+
+ expect(formatter.step).toHaveBeenCalledWith({name: 'Step', line: 3, keyword: 'Step'});
+ expect(formatter.result).toHaveBeenCalledWith({status: 'undefined'});
+ expect(formatter.match).toHaveBeenCalledWith({location: undefined});
+
+ });
+
+ });
+
+ // We're all done. Output the JSON.
+
+ describe("handleAfterFeaturesEvent()", function() {
+ var features, callback;
+
+ beforeEach(function() {
+ event = createSpy("Event");
+ callback = createSpy("Callback");
+
+ });
+
+ it("finalises output", function() {
+ listener.handleAfterFeaturesEvent(event, callback);
+ expect(formatter.eof).toHaveBeenCalled();
+ expect(formatter.done).toHaveBeenCalled();
+ });
+
+ it("calls back", function() {
+ listener.handleAfterFeaturesEvent(event, callback);
+ expect(callback).toHaveBeenCalled();
+ });
+
+ });
+
+});
+
diff --git a/spec/tmp.txt b/spec/tmp.txt
new file mode 100644
index 000000000..b1511927b
--- /dev/null
+++ b/spec/tmp.txt
@@ -0,0 +1,728 @@
+{
+ "name": "",
+ "id": ""
+}
+Cucumber.Listener.JsonFormatter
+
+ constructor
+[32m creates a collection to store the failed steps[0m
+
+ hear()
+[32m checks wether there is a handler for the event[0m
+
+ when there is a handler for that event
+[32m gets the handler for that event[0m
+[32m calls the handler with the event and the callback[0m
+[32m does not callback[0m
+
+ when there are no handlers for that event
+[32m calls back[0m
+[32m does not get the handler for the event[0m
+
+ hasHandlerForEvent
+[32m builds the name of the handler for that event[0m
+
+ when the handler exists
+[32m returns true[0m
+
+ when the handler does not exist
+[32m returns false[0m
+
+ buildHandlerNameForEvent
+[32m gets the name of the event[0m
+[32m returns the name of the event with prefix 'handle' and suffix 'Event'[0m
+
+ getHandlerForEvent()
+[32m gets the name of the handler for the event[0m
+
+ when an event handler exists for the event
+[32m returns the event handler[0m
+
+ when no event handlers exist for the event
+[32m returns nothing[0m
+
+ handleStepResultEvent()
+[32m gets the step result from the event payload[0m
+[32m checks wether the step was successful or not[0m
+
+ when the step passed
+[32m handles the successful step result[0m
+
+ when the step did not pass
+[32m does not handle a successful step result[0m
+[32m checks wether the step is pending[0m
+
+ when the step was pending
+[32m handles the pending step result[0m
+
+ when the step was not pending
+[32m does not handle a pending step result[0m
+[32m checks wether the step was skipped[0m
+
+ when the step was skipped
+[32m handles the skipped step result[0m
+
+ when the step was not skipped
+[32m does not handle a skipped step result[0m
+[32m checks wether the step was undefined[0m
+
+ when the step was undefined
+[32m handles the undefined step result[0m
+
+ when the step was not undefined
+[32m does not handle a skipped step result[0m
+[32m handles a failed step result[0m
+[32m calls back[0m
+
+ handleSuccessfulStepResult()
+[32m witnesses a passed step[0m
+
+ handlePendingStepResult()
+[32m witnesses a pending step[0m
+[32m marks the current scenario as pending[0m
+
+ handleSkippedStepResult()
+[32m counts one more skipped step[0m
+
+ handleUndefinedStepResult()
+[32m gets the step from the step result[0m
+[32m stores the undefined step[0m
+[32m witnesses an undefined step[0m
+[32m marks the current scenario as undefined[0m
+
+ handleFailedStepResult()
+[32m stores the failed step result[0m
+[32m witnesses a failed step[0m
+[32m marks the current scenario as failing[0m
+
+ handleBeforeScenarioEvent
+[32m prepares for a new scenario[0m
+[32m calls back[0m
+
+ handleAfterFeaturesEvent()
+[32m calls back[0m
+
+ handleAfterScenarioEvent()
+[32m checks wether the current scenario failed[0m
+
+ when the current scenario failed
+[32m witnesses a failed scenario[0m
+[32m gets the scenario from the payload[0m
+[32m stores the failed scenario[0m
+
+ when the current scenario did not fail
+[32m checks wether the current scenario is undefined[0m
+
+ when the current scenario is undefined
+[32m witnesses an undefined scenario[0m
+
+ when the current scenario is not undefined
+[32m checks wether the current scenario is pending[0m
+
+ when the current scenario is pending
+[32m witnesses a pending scenario[0m
+
+ when the current scenario is not pending (passed)
+[32m witnesses a passed scenario[0m
+[32m calls back[0m
+
+ isCurrentScenarioFailing()
+[32m returns false when the current scenario did not fail yet[0m
+[32m returns true when a step in the current scenario failed[0m
+
+ isCurrentScenarioPending()
+[32m returns false when the current scenario was not set pending yet[0m
+[32m returns true when the current scenario was set pending[0m
+
+ isCurrentScenarioUndefined()
+[32m returns false when the current scenario was not set undefined yet[0m
+[32m returns true when the current scenario was set undefined[0m
+
+ prepareBeforeScenario()
+[32m unmarks the current scenario as pending[0m
+[32m unmarks the current scenario as failing[0m
+[32m unmarks the current scenario as undefined[0m
+
+ storeFailedStepResult()
+[32m adds the result to the failed step result collection[0m
+
+ storeFailedScenario()
+[32m gets the name of the scenario[0m
+[32m gets the line of the scenario[0m
+[32m appends the scenario details to the failed scenario log buffer[0m
+
+ storeUndefinedStep()
+[32m creates a new step definition snippet builder[0m
+[32m builds the step definition[0m
+[32m appends the snippet to the undefined step log buffer[0m
+
+ getFailedScenarioLogBuffer() [appendStringToFailedScenarioLogBuffer()]
+[32m returns the logged failed scenario details[0m
+[32m returns all logged failed scenario lines joined with a line break[0m
+
+ getUndefinedStepLogBuffer() [appendStringToUndefinedStepLogBuffer()]
+[32m returns the logged undefined step details[0m
+[32m returns all logged failed scenario lines joined with a line break[0m
+
+ appendStringToUndefinedStepLogBuffer() [getUndefinedStepLogBuffer()]
+[32m does not log the same string twice[0m
+
+ getScenarioCount()
+[32m gets the number of passed scenarios[0m
+[32m gets the number of undefined scenarios[0m
+[32m gets the number of pending scenarios[0m
+[32m gets the number of failed scenarios[0m
+[32m returns the sum of passed, undefined, pending aand failed scenarios[0m
+
+ getStepCount()
+[32m gets the number of passed steps[0m
+[32m gets the number of undefined steps[0m
+[32m gets the number of skipped steps[0m
+[32m gets the number of pending steps[0m
+[32m gets the number of failed steps[0m
+[32m returns the sum of passed steps and failed steps[0m
+
+ passed scenario counting
+
+ witnessPassedScenario()
+[32m counts one more passed scenario[0m
+
+ getPassedScenarioCount()
+[32m returns 0 when no scenario passed[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when three scenarios passed[0m
+
+ undefined scenario counting
+
+ getUndefinedScenarioCount()
+[32m returns 0 when no scenarios undefined[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when two scenarios passed[0m
+
+ pending scenario counting
+
+ getPendingScenarioCount()
+[32m returns 0 when no scenarios pending[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when two scenarios passed[0m
+
+ failed scenario counting
+
+ getFailedScenarioCount()
+[32m returns 0 when no scenarios failed[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when two scenarios passed[0m
+
+ passed step counting
+
+ witnessPassedStep()
+[32m counts one more passed step[0m
+
+ getPassedStepCount()
+[32m returns 0 when no step passed[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when three steps passed[0m
+
+ failed step counting
+
+ getFailedStepCount()
+[32m returns 0 when no steps failed[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ skipped step counting
+
+ getSkippedStepCount()
+[32m returns 0 when no steps skipped[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ undefined step counting
+
+ getUndefinedStepCount()
+[32m returns 0 when no steps undefined[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ pending step counting
+
+ getPendingStepCount()
+[32m returns 0 when no steps pending[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ witnessedAnyFailedStep()
+[32m returns false when no failed step were encountered[0m
+[32m returns true when one or more steps were witnessed[0m
+
+ witnessedAnyUndefinedStep()
+[32m returns false when no undefined step were encountered[0m
+[32m returns true when one or more steps were witnessed[0m
+
+Cucumber.Listener.ProgressFormatter
+
+ constructor
+[32m creates a collection to store the failed steps[0m
+
+ log()
+[32m records logged strings[0m
+[32m outputs the logged string to STDOUT by default[0m
+
+ when asked to output to STDOUT
+[32m outputs the logged string to STDOUT[0m
+
+ when asked to not output to STDOUT
+[32m does not output anything to STDOUT[0m
+
+ when asked to output to a function
+[32m calls the function with the logged string[0m
+
+ getLogs()
+[32m returns the logged buffer[0m
+[32m returns an empty string when the listener did not log anything yet[0m
+
+ hear()
+[32m checks wether there is a handler for the event[0m
+
+ when there is a handler for that event
+[32m gets the handler for that event[0m
+[32m calls the handler with the event and the callback[0m
+[32m does not callback[0m
+
+ when there are no handlers for that event
+[32m calls back[0m
+[32m does not get the handler for the event[0m
+
+ hasHandlerForEvent
+[32m builds the name of the handler for that event[0m
+
+ when the handler exists
+[32m returns true[0m
+
+ when the handler does not exist
+[32m returns false[0m
+
+ buildHandlerNameForEvent
+[32m gets the name of the event[0m
+[32m returns the name of the event with prefix 'handle' and suffix 'Event'[0m
+
+ getHandlerForEvent()
+[32m gets the name of the handler for the event[0m
+
+ when an event handler exists for the event
+[32m returns the event handler[0m
+
+ when no event handlers exist for the event
+[32m returns nothing[0m
+
+ handleStepResultEvent()
+[32m gets the step result from the event payload[0m
+[32m checks wether the step was successful or not[0m
+
+ when the step passed
+[32m handles the successful step result[0m
+
+ when the step did not pass
+[32m does not handle a successful step result[0m
+[32m checks wether the step is pending[0m
+
+ when the step was pending
+[32m handles the pending step result[0m
+
+ when the step was not pending
+[32m does not handle a pending step result[0m
+[32m checks wether the step was skipped[0m
+
+ when the step was skipped
+[32m handles the skipped step result[0m
+
+ when the step was not skipped
+[32m does not handle a skipped step result[0m
+[32m checks wether the step was undefined[0m
+
+ when the step was undefined
+[32m handles the undefined step result[0m
+
+ when the step was not undefined
+[32m does not handle a skipped step result[0m
+[32m handles a failed step result[0m
+[32m calls back[0m
+
+ handleSuccessfulStepResult()
+[32m witnesses a passed step[0m
+[32m logs the passing step character[0m
+
+ handlePendingStepResult()
+[32m witnesses a pending step[0m
+[32m marks the current scenario as pending[0m
+[32m logs the pending step character[0m
+
+ handleSkippedStepResult()
+[32m counts one more skipped step[0m
+[32m logs the skipped step character[0m
+
+ handleUndefinedStepResult()
+[32m gets the step from the step result[0m
+[32m stores the undefined step[0m
+[32m witnesses an undefined step[0m
+[32m marks the current scenario as undefined[0m
+[32m logs the undefined step character[0m
+
+ handleFailedStepResult()
+[32m stores the failed step result[0m
+[32m witnesses a failed step[0m
+[32m marks the current scenario as failing[0m
+[32m logs the failed step character[0m
+
+ handleBeforeScenarioEvent
+[32m prepares for a new scenario[0m
+[32m calls back[0m
+
+ handleAfterFeaturesEvent()
+[32m displays a summary[0m
+[32m calls back[0m
+
+ handleAfterScenarioEvent()
+[32m checks wether the current scenario failed[0m
+
+ when the current scenario failed
+[32m witnesses a failed scenario[0m
+[32m gets the scenario from the payload[0m
+[32m stores the failed scenario[0m
+
+ when the current scenario did not fail
+[32m checks wether the current scenario is undefined[0m
+
+ when the current scenario is undefined
+[32m witnesses an undefined scenario[0m
+
+ when the current scenario is not undefined
+[32m checks wether the current scenario is pending[0m
+
+ when the current scenario is pending
+[32m witnesses a pending scenario[0m
+
+ when the current scenario is not pending (passed)
+[32m witnesses a passed scenario[0m
+[32m calls back[0m
+
+ isCurrentScenarioFailing()
+[32m returns false when the current scenario did not fail yet[0m
+[32m returns true when a step in the current scenario failed[0m
+
+ isCurrentScenarioPending()
+[32m returns false when the current scenario was not set pending yet[0m
+[32m returns true when the current scenario was set pending[0m
+
+ isCurrentScenarioUndefined()
+[32m returns false when the current scenario was not set undefined yet[0m
+[32m returns true when the current scenario was set undefined[0m
+
+ prepareBeforeScenario()
+[32m unmarks the current scenario as pending[0m
+[32m unmarks the current scenario as failing[0m
+[32m unmarks the current scenario as undefined[0m
+
+ storeFailedStepResult()
+[32m adds the result to the failed step result collection[0m
+
+ storeFailedScenario()
+[32m gets the name of the scenario[0m
+[32m gets the line of the scenario[0m
+[32m appends the scenario details to the failed scenario log buffer[0m
+
+ storeUndefinedStep()
+[32m creates a new step definition snippet builder[0m
+[32m builds the step definition[0m
+[32m appends the snippet to the undefined step log buffer[0m
+
+ getFailedScenarioLogBuffer() [appendStringToFailedScenarioLogBuffer()]
+[32m returns the logged failed scenario details[0m
+[32m returns all logged failed scenario lines joined with a line break[0m
+
+ getUndefinedStepLogBuffer() [appendStringToUndefinedStepLogBuffer()]
+[32m returns the logged undefined step details[0m
+[32m returns all logged failed scenario lines joined with a line break[0m
+
+ appendStringToUndefinedStepLogBuffer() [getUndefinedStepLogBuffer()]
+[32m does not log the same string twice[0m
+
+ logSummary()
+[32m logs two line feeds[0m
+[32m checks wether there are failed steps or not[0m
+
+ when there are failed steps
+[32m logs the failed steps[0m
+
+ when there are no failed steps
+[32m does not log failed steps[0m
+[32m logs the scenarios summary[0m
+[32m logs the steps summary[0m
+[32m checks wether there are undefined steps or not[0m
+
+ when there are undefined steps
+[32m logs the undefined step snippets[0m
+
+ when there are no undefined steps
+[32m does not log the undefined step snippets[0m
+
+ logFailedStepResults()
+[32m logs a failed steps header[0m
+[32m iterates synchronously over the failed step results[0m
+
+ for each failed step result
+[32m tells the visitor to visit the feature and call back when finished[0m
+[32m logs a failed scenarios header[0m
+[32m gets the failed scenario details from its log buffer[0m
+[32m logs the failed scenario details[0m
+[32m logs a line break[0m
+
+ logFailedStepResult()
+[32m gets the failure exception from the step result[0m
+
+ when the failure exception has a stack
+[32m logs the stack[0m
+
+ when the failure exception has no stack
+[32m logs the exception itself[0m
+[32m logs two line breaks[0m
+
+ logScenariosSummary()
+[32m gets the number of scenarios[0m
+
+ when there are no scenarios
+[32m logs 0 scenarios[0m
+[32m does not log any details[0m
+
+ when there are scenarios
+
+ when there is one scenario
+[32m logs one scenario[0m
+
+ when there are 2 or more scenarios
+[32m logs two or more scenarios[0m
+[32m gets the number of failed scenarios[0m
+
+ when there are no failed scenarios
+[32m does not log failed scenarios[0m
+
+ when there is one failed scenario
+[32m logs a failed scenario[0m
+
+ when there are two or more failed scenarios
+[32m logs the number of failed scenarios[0m
+[32m gets the number of undefined scenarios[0m
+
+ when there are no undefined scenarios
+[32m does not log passed scenarios[0m
+
+ when there is one undefined scenario
+[32m logs one undefined scenario[0m
+
+ when there are two or more undefined scenarios
+[32m logs the undefined scenarios[0m
+[32m gets the number of pending scenarios[0m
+
+ when there are no pending scenarios
+[32m does not log passed scenarios[0m
+
+ when there is one pending scenario
+[32m logs one pending scenario[0m
+
+ when there are two or more pending scenarios
+[32m logs the pending scenarios[0m
+[32m gets the number of passed scenarios[0m
+
+ when there are no passed scenarios
+[32m does not log passed scenarios[0m
+
+ when there is one passed scenario
+[32m logs 1 passed scenarios[0m
+
+ when there are two or more passed scenarios
+[32m logs the number of passed scenarios[0m
+
+ logStepsSummary()
+[32m gets the number of steps[0m
+
+ when there are no steps
+[32m logs 0 steps[0m
+[32m does not log any details[0m
+
+ when there are steps
+
+ when there is one step
+[32m logs 1 step[0m
+
+ when there are two or more steps
+[32m logs the number of steps[0m
+[32m gets the number of failed steps[0m
+
+ when there are no failed steps
+[32m does not log failed steps[0m
+
+ when there is one failed step
+[32m logs one failed step[0m
+
+ when there is two or more failed steps
+[32m logs the number of failed steps[0m
+[32m gets the number of undefined steps[0m
+
+ when there are no undefined steps
+[32m does not log undefined steps[0m
+
+ when there is one undefined step
+[32m logs one undefined steps[0m
+
+ when there are two or more undefined steps
+[32m logs the number of undefined steps[0m
+[32m gets the number of pending steps[0m
+
+ when there are no pending steps
+[32m does not log pending steps[0m
+
+ when there is one pending step
+[32m logs one pending steps[0m
+
+ when there are two or more pending steps
+[32m logs the number of pending steps[0m
+[32m gets the number of skipped steps[0m
+
+ when there are no skipped steps
+[32m does not log skipped steps[0m
+
+ when there is one skipped step
+[32m logs one skipped steps[0m
+
+ when there are two or more skipped steps
+[32m logs the number of skipped steps[0m
+[32m gets the number of passed steps[0m
+
+ when there are no passed steps
+[32m does not log passed steps[0m
+
+ when there is one passed step
+[32m logs one passed step[0m
+
+ when there is two or more passed steps
+[32m logs the number of passed steps[0m
+
+ logUndefinedStepSnippets()
+[32m logs a little explanation about the snippets[0m
+[32m gets the undefined steps log buffer[0m
+[32m logs the undefined steps[0m
+
+ getScenarioCount()
+[32m gets the number of passed scenarios[0m
+[32m gets the number of undefined scenarios[0m
+[32m gets the number of pending scenarios[0m
+[32m gets the number of failed scenarios[0m
+[32m returns the sum of passed, undefined, pending aand failed scenarios[0m
+
+ getStepCount()
+[32m gets the number of passed steps[0m
+[32m gets the number of undefined steps[0m
+[32m gets the number of skipped steps[0m
+[32m gets the number of pending steps[0m
+[32m gets the number of failed steps[0m
+[32m returns the sum of passed steps and failed steps[0m
+
+ passed scenario counting
+
+ witnessPassedScenario()
+[32m counts one more passed scenario[0m
+
+ getPassedScenarioCount()
+[32m returns 0 when no scenario passed[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when three scenarios passed[0m
+
+ undefined scenario counting
+
+ getUndefinedScenarioCount()
+[32m returns 0 when no scenarios undefined[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when two scenarios passed[0m
+
+ pending scenario counting
+
+ getPendingScenarioCount()
+[32m returns 0 when no scenarios pending[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when two scenarios passed[0m
+
+ failed scenario counting
+
+ getFailedScenarioCount()
+[32m returns 0 when no scenarios failed[0m
+[32m returns 1 when one scenario passed[0m
+[32m returns 2 when two scenarios passed[0m
+[32m returns 3 when two scenarios passed[0m
+
+ passed step counting
+
+ witnessPassedStep()
+[32m counts one more passed step[0m
+
+ getPassedStepCount()
+[32m returns 0 when no step passed[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when three steps passed[0m
+
+ failed step counting
+
+ getFailedStepCount()
+[32m returns 0 when no steps failed[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ skipped step counting
+
+ getSkippedStepCount()
+[32m returns 0 when no steps skipped[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ undefined step counting
+
+ getUndefinedStepCount()
+[32m returns 0 when no steps undefined[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ pending step counting
+
+ getPendingStepCount()
+[32m returns 0 when no steps pending[0m
+[32m returns 1 when one step passed[0m
+[32m returns 2 when two steps passed[0m
+[32m returns 3 when two steps passed[0m
+
+ witnessedAnyFailedStep()
+[32m returns false when no failed step were encountered[0m
+[32m returns true when one or more steps were witnessed[0m
+
+ witnessedAnyUndefinedStep()
+[32m returns false when no undefined step were encountered[0m
+[32m returns true when one or more steps were witnessed[0m
+
+Finished in 0.095 seconds
+[32m338 tests, 339 assertions, 0 failures
+[0m
+