Skip to content

Documentation and test for AfterFeatures event #171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,37 @@ var myAfterHooks = function () {
module.exports = myAfterHooks;
```

##### After features event
The *after features event* is emitted once all features have been executed, just before the process exits. It can be used for tasks such as closing your browser after running automated browser tests with [selenium](https://code.google.com/p/selenium/wiki/WebDriverJs) or [phantomjs](http://phantomjs.org/).

note: There are "Before" and "After" events for each of the following: "Features", "Feature", "Scenario", "Step" as well as the standalone events "Background" and "StepResult". e.g. "BeforeScenario".

```javascript
// features/support/world.js
var webdriver = require("selenium-webdriver");

var World = function World(callback) {
this.driver = new webdriver.Builder().
withCapabilities(webdriver.Capabilities.chrome()).
build();

callback();
}

module.exports = World;

// features/support/after_hooks.js
var myAfterHooks = function () {
this.registerHandler('AfterFeatures', function (event, callback) {
this.driver.close();
callback();
});
}

module.exports = myAfterHooks;
```


##### Around hooks

It's also possible to combine both before and after hooks in one single definition with the help of *around hooks*:
Expand Down
35 changes: 17 additions & 18 deletions bin/cucumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,23 @@
var Cucumber = require('../lib/cucumber');
var cli = Cucumber.Cli(process.argv);
cli.run(function(succeeded) {
var code = succeeded ? 0 : 1;
var exitFunction = function() {
process.exit(code);
};

var code = succeeded ? 0 : 1;

// --- exit after waiting for all pending output ---
var waitingIO = false;
process.stdout.on('drain', function() {
if (waitingIO) {
// the kernel buffer is now empty
exitFunction();
process.on('exit', function() {
process.exit(code);
});

var timeoutId = setTimeout(function () {
console.error('Cucumber process timed out after waiting 60 seconds for the node.js event loop to empty. There may be a resource leak. Have all resources like database connections and network connections been closed properly?');
process.exit(code);
}, 60 * 1000);

if (timeoutId.unref) {
timeoutId.unref();
}
});
if (process.stdout.write("")) {
// no buffer left, exit now:
exitFunction();
} else {
// write() returned false, kernel buffer is not empty yet...
waitingIO = true;
}
else {
clearTimeout(timeoutId);
}

});
85 changes: 85 additions & 0 deletions features/cli.feature
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,91 @@ Feature: Command line interface

"""


Scenario: run a single failing feature
Given a file named "features/a.feature" with:
"""
Feature: some feature
Scenario:
When a step is failing
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
var cucumberSteps = function() {
this.When(/^a step is failing$/, function(callback) { callback("forced error"); });
};
module.exports = cucumberSteps;
"""
When I run `cucumber.js features/a.feature`
Then it should fail with:
"""
1 scenario (1 failed)
1 step (1 failed)

"""
And it should exit with code "1"


Scenario: run a single failing feature with an empty hooks file
Given a file named "features/a.feature" with:
"""
Feature: some feature
Scenario:
When a step is failing
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
var cucumberSteps = function() {
this.When(/^a step is failing$/, function(callback) { callback("forced error"); });
};
module.exports = cucumberSteps;
"""
And a file named "features/support/hooks.js" with:
"""
"""
When I run `cucumber.js features/a.feature`
Then it should fail with:
"""
1 scenario (1 failed)
1 step (1 failed)

"""
And it should exit with code "1"


Scenario: run a single failing feature with an AfterFeatures hook
Given a file named "features/a.feature" with:
"""
Feature: some feature
Scenario:
When a step is failing
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
var cucumberSteps = function() {
this.When(/^a step is failing$/, function(callback) { callback("forced error"); });
};
module.exports = cucumberSteps;
"""
And a file named "features/support/hooks.js" with:
"""
var hooks = function() {
this.registerHandler('AfterFeatures', function (event, callback) {
callback();
});
};
module.exports = hooks;
"""
When I run `cucumber.js features/a.feature`
Then it should fail with:
"""
1 scenario (1 failed)
1 step (1 failed)

"""
And it should exit with code "1"


Scenario: run a single feature without step definitions
Given a file named "features/a.feature" with:
"""
Expand Down
35 changes: 29 additions & 6 deletions features/step_definitions/cli_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,41 @@ var cliSteps = function cliSteps() {
});
});

this.Then(/^it passes with:$/, function(expectedOutput, callback) {
this.Then(/^it should (pass|fail) with:$/, function (passOrFail, expectedOutput, callback) {
var actualOutput = lastRun['stdout'];
var actualError = lastRun['error'];
var actualError = lastRun['error'];
var actualStderr = lastRun['stderr'];

if (actualOutput.indexOf(expectedOutput) == -1)


var cleanString = function (str) {
//Strips colour codes and normalise line endings

return str
.replace(/\033\[[0-9;]*m/g, '')
.replace(/\r\n|\r/g, "\n");
};

actualOutput = cleanString(actualOutput);
expectedOutput = cleanString(expectedOutput);

if (actualOutput.indexOf(expectedOutput) === -1)
throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.\n" +
"Error:\n'" + actualError + "'.\n" +
"stderr:\n'" + actualStderr +"'.");
"Error:\n'" + actualError + "'.\n" +
"stderr:\n'" + actualStderr + "'.");

callback();
});

this.Then(/^it should exit with code "([^"]*)"$/, function (code, callback) {
var actualCode = lastRun['error'] ? lastRun['error'].code : "0";

if (actualCode != code) {
throw new Error("Exit code expected: \"" + code + "\"\nGot: \"" + actualCode + "\"\n");
}

callback();
});

this.Then(/^it outputs this json:$/, function(expectedOutput, callback) {
var actualOutput = lastRun['stdout'];
var actualError = lastRun['error'];
Expand Down