JavaScript Cucumber Other

README.md

Cucumber.js

Build Status Dependencies Code Climate

Cucumber is a tool for running automated tests written in plain language. Because they're written in plain language, they can be read by anyone on your team. Because they can be read by anyone, you can use them to help improve communication, collaboration and trust on your team.

Cucumber.js is the JavaScript implementation of Cucumber and runs on both Node.js and modern web browsers.

Try it now

We've put a demo of Cucumber.js to run in your browser. Why don't you give it a try before anything else?

Help & support

Contributing

See CONTRIBUTING.md for info on contributing to Cucumber.js.

Code of Conduct

Everyone interacting in this codebase and issue tracker is expected to follow the Cucumber code of conduct.

Install

Node

Cucumber.js is available as an npm module.

$ npm install cucumber

Browser

  • Grab the latest browserified code from the release folder

Usage

Features

Features are written with the Gherkin syntax

# features/my_feature.feature

Feature: Example feature
  As a user of Cucumber.js
  I want to have documentation on Cucumber
  So that I can concentrate on building awesome applications

  Scenario: Reading documentation
    Given I am on the Cucumber.js GitHub repository
    When I go to the README file
    Then I should see "Usage" as the page title

Support files

Support files let you setup the environment in which steps will be run, and define step definitions.

World

World is a constructor function with utility properties, destined to be used in step definitions:

// features/support/world.js
var zombie = require('zombie');
function World() {
  this.browser = new zombie(); // this.browser will be available in step definitions

  this.visit = function (url, callback) {
    this.browser.visit(url, callback);
  };
}

module.exports = function() {
  this.World = World;
};

If you need to perform operations before/after every scenario, use hooks.

Breaking Change: The World constructor is now strictly synchronous and does not receive a callback from Cucumber anymore... This is a breaking change that was introduced in release v0.8.0

Step definitions

Step definitions are the glue between features written in Gherkin and the actual system under test. They are written in JavaScript.

All step definitions will run with this set to what is known as the World in Cucumber. It's an object exposing useful methods, helpers and variables to your step definitions. A new instance of World is created before each scenario.

Step definitions are contained within one or more wrapper functions.

Those wrappers are run before executing the feature suite. this is an object holding important properties like the Given(), When() and Then() functions. Another notable property is World; it contains a default World constructor that can be either extended or replaced.

Step definitions are run when steps match their name. this is an instance of World.

// features/step_definitions/my_step_definitions.js

module.exports = function () {
  this.Given(/^I am on the Cucumber.js GitHub repository$/, function (callback) {
    // Express the regexp above with the code you wish you had.
    // `this` is set to a World instance.
    // i.e. you may use this.browser to execute the step:

    this.visit('https://github.com/cucumber/cucumber-js', callback);

    // The callback is passed to visit() so that when the job's finished, the next step can
    // be executed by Cucumber.
  });

  this.When(/^I go to the README file$/, function (callback) {
    // Express the regexp above with the code you wish you had. Call callback() at the end
    // of the step, or callback(null, 'pending') if the step is not yet implemented:

    callback(null, 'pending');
  });

  this.Then(/^I should see "(.*)" as the page title$/, function (title, callback) {
    // matching groups are passed as parameters to the step definition

    var pageTitle = this.browser.text('title');
    if (title === pageTitle) {
      callback();
    } else {
      callback(new Error("Expected to be on page with title " + title));
    }
  });
};
Promises

Instead of Node.js-style callbacks, promises can be returned by step definitions:

this.Given(/^I am on the Cucumber.js GitHub repository$/, function () {
  // Notice how `callback` is omitted from the parameters
  return this.visit('https://github.com/cucumber/cucumber-js');

  // A promise, returned by zombie.js's `visit` method is returned to Cucumber.
});

Simply omit the last callback parameter and return the promise.

If the promise resolves to the string 'pending', the step will be marked as pending.

Synchronous step definitions

Often, asynchronous behaviour is not needed in step definitions. Simply omit the callback parameter, do not return anything and Cucumber will treat the step definition function as synchronous:

this.Given(/^I add one cucumber$/, function () {
  // Notice how `callback` is omitted from the parameters
  this.cucumberCount += 1;
});

If the step returns the string 'pending', the step will be marked as pending.

Generators (ES6)

If your language supports generator functions, you can define step definitions with generator functions

this.When(/^I add one cucumber$/, function *() {
  this.cucumberCount += yield 1;
});
Strings instead of regular expressions

It is also possible to use simple strings instead of regexps as step definition patterns:

this.Then('I should see "$title" as the page title', function (title, callback) {
  // the above string is converted to the following Regexp by Cucumber:
  // /^I should see "([^"]*)" as the page title$/

  var pageTitle = this.browser.text('title');
  if (title === pageTitle) {
    callback();
  } else {
    callback(new Error("Expected to be on page with title " + title));
  }
});

'I have $count "$string"' would translate to /^I have (.*) "([^"]*)"$/.

Data table

When steps have a data table, they are passed an object with methods that can be used to access the data.

  • with column headers
    • hashes: returns an array of objects where each row is converted to an object (column header is the key)
    • rows: returns the table as a 2-D array, without the first row
  • without column headers
    • raw: returns the table as a 2-D array
    • rowsHash: returns an object where each row corresponds to an entry (first column is the key, second column is the value)

See this feature for examples

Timeouts

By default, asynchronous hooks and steps timeout after 5000 milliseconds. This can be modified globally with:

// features/support/env.js

var configure = function () {
  this.setDefaultTimeout(60 * 1000);
};

module.exports = configure;

A specific step's timeout can be set with:

// features/step_definitions/my_steps.js

var mySteps = function () {
  this.Given(/^a slow step$/, {timeout: 60 * 1000}, function(callback) {
    // Does some slow browser/filesystem/network actions
  });
};

module.exports = mySteps;

Hooks

Hooks can be used to prepare and clean the environment before and after each scenario is executed. Hooks can use callbacks, return promises, or be synchronous. The first argument to hooks is always the current scenario. See Cucumber.Api.Scenario for more information.

Before hooks

To run something before every scenario, use before hooks:

// features/support/hooks.js (this path is just a suggestion)

var myHooks = function () {
  this.Before(function (scenario) {
    // Just like inside step definitions, "this" is set to a World instance.
    // It's actually the same instance the current scenario step definitions
    // will receive.

    // Let's say we have a bunch of "maintenance" methods available on our World
    // instance, we can fire some to prepare the application for the next
    // scenario:

    this.bootFullTextSearchServer();
    this.createSomeUsers();
  });
};

module.exports = myHooks;

If you need to run asynchronous code, simply accept a callback in your hook function and run it when you're done:

this.Before(function (scenario, callback) {
  this.createUsers(callback);
});

Or return a promise:

this.Before(function (scenario) {
  // assuming this.createUsers returns a promise:
  return this.createUsers();
});
After hooks

The before hook counterpart is the after hook. It's similar in shape but is executed, well, after every scenario:

// features/support/after_hooks.js

var myAfterHooks = function () {
  this.After(function (scenario) {
    // Again, "this" is set to the World instance the scenario just finished
    // playing with.

    // We can then do some cleansing:

    this.emptyDatabase();
    this.shutdownFullTextSearchServer();
  });
};

module.exports = myAfterHooks;
Tagged hooks

Hooks can be conditionally selected for execution based on the tags of the scenario.

// features/support/hooks.js (this path is just a suggestion)

var myHooks = function () {
  this.Before({tags: ["@foo", "@bar,@baz"]}, function (scenario) {
    // This hook will be executed before scenarios tagged with @foo and either
    // @bar or @baz.

    // ...
  });
};

module.exports = myHooks;
Hook timeouts

Hooks timeout the same as steps and a specific hooks's timeout can be set with:

// features/step_definitions/hooks.js

var myHooks = function () {
  this.Before({timeout: 60 * 1000}, function (scenario) {
    // Does some slow initialization
  });
};

module.exports = myHooks;
Attachments

You can attach text, images and files to the Cucumber report using the scenario object:

this.After(function (scenario) {
  scenario.attach('Some text');
});

By default, text is saved with a MIME type of text/plain. You can also specify a different MIME type:

this.After(function (scenario) {
  scenario.attach('{"name": "some JSON"}', 'application/json');
});

Images and other binary data can be attached using a stream.Readable. In that case, passing a callback to attach() becomes mandatory:

this.After(function (scenario, callback) {
  if (scenario.isFailed()) {
    var stream = getScreenshotOfError();
    scenario.attach(stream, 'image/png', function(err) {
      callback(err);
    });
  }
  else {
    callback();
  }
});

Images and binary data can also be attached using a Buffer:

this.After(function (scenario) {
  if (scenario.isFailed()) {
    var buffer = getScreenshotOfError();
    scenario.attach(buffer, 'image/png');
  }
});

Here is an example of saving a screenshot using WebDriver when a scenario fails:

this.After(function (scenario, callback) {
  if (scenario.isFailed()) {
    webDriver.takeScreenshot().then(stream) {
      scenario.attach(stream, 'image/png', callback);
    }, function(err) {
      callback(err);
    });
  }
  else {
    callback();
  }
});
Event Handlers

You can register event handlers for the following events within the cucumber lifecycle.

Event Object
BeforeFeatures array of Features
BeforeFeature Feature
BeforeScenario Scenario
BeforeStep Step
StepResult StepResult
AfterStep Step
ScenarioResult ScenarioResult
AfterScenario Scenario
AfterFeature Feature
FeaturesResult FeaturesResult
AfterFeatures array of Features

Hooks also trigger BeforeStep, StepResult, and AfterStep events with the object HookStep

Handlers will be passed the associated object as the first argument. Handlers can be synchronous, return a promise, accept an additional callback argument, or use generators.

// features/support/handlers.js
var myHandlers = function () {
  this.registerHandler('AfterFeatures', function (features, callback) {
    // clean up!
    // There is no World instance available on `this`
    // because all scenarios are done and World instances are long gone.
    callback();
  });
}

module.exports = myHandlers;

Handlers timeout the same as steps / hooks and can have their timeout changed by passing in an options object.

// features/support/handlers.js
var myHandlers = function () {
  this.registerHandler('AfterFeatures', {timeout: 10000}, function (features, callback) {
    //...
  });
}

module.exports = myHandlers;

CLI

Cucumber.js includes a executable file to run the features.

If you installed Cucumber.js globally, you may run it with:

$ cucumber.js

If you installed Cucumber locally, you may need to specify the path to the executable:

$ ./node_modules/.bin/cucumber.js

Note to Windows users: invoke Cucumber.js with cucumber-js instead of cucumber.js. The latter is causing the operating system to invoke JScript instead of Node.js, because of the file extension.

Running specific features

  • Specify a feature file
    • $ cucumber.js features/my_feature.feature
  • Specify a scenario by its line number
    • $ cucumber.js features/my_feature.feature:3
  • Specify a scenario by its name matching a regular expression
    • $ cucumber.js --name "topic 1"
    • If used multiple times, the scenario name needs to match only one of the names supplied
  • Use Tags

Requiring support files

Use --require <FILE|DIR> to require files before executing the features. If not used, all *.js files (and other extensions specifed by --compiler) that are siblings or below the features will be loaded automatically. Automatic loading is disabled when this option is specified, and all loading becomes explicit. Files under directories named "support" are always loaded first

Formatters

Use --format <TYPE[:PATH]> to specify the format of the output. If PATH is not supplied, the formatter prints to stdout. If PATH is supplied, it prints to the given file. If multiple formats are specified with the same output, only the last is used.

Built-in formatters

  • json - prints the feature as JSON
  • pretty - prints the feature as is (default)
  • progress - prints one character per scenario
  • rerun - prints the paths of the failing scenarios (example)
    • suggested use: add the rerun formatter to your default profile and the output file to your .gitignore
  • summary - prints a summary only, after all scenarios were executed

Tags

Use --tags <EXPRESSION> to run specific features or scenarios.

  • --tags @dev: tagged with @dev
  • --tags ~@dev: NOT tagged with @dev
  • --tags @foo,@bar: tagged with @foo OR bar
  • --tags @foo --tags @bar: tagged with @foo AND bar

Transpilers

Step definitions and support files can be written in other languages that transpile to javascript. To do this use the CLI option --compiler <file_extension>:<module_name>. Running require("<module_name>"), should make it possible to require files with the given extension. As an example, load CoffeeScript support files with --compiler coffee:coffee-script/register.

Custom snippet syntax

Undefined steps snippets are printed in javascript by default. Custom snippet syntaxes can be used with --snippet-syntax <FILE>. See here for an example.

Building a custom snippet syntax
  • See the JavaScript syntax for an example. Please open an issue if you need more information.
  • Please add the keywords cucumber and snippets to your package, so it can easily be found by searching npm.

Profiles

In order to store and reuse commonly used CLI options, you can add a cucumber.js file to your project root directory. The file should export an object where the key is the profile name and the value is a string of CLI options. The profile can be applied with -p <NAME> or --profile <NAME>. This will prepend the profile's CLI options to the ones provided by the command line. Multiple profiles can be specified at a time. If no profile is specified and a profile named default exists, it will be applied.