1 | # Cucumber.js
|
2 |
|
3 | **Important** This is not the main Cucumberjs project. That is at https://www.npmjs.com/package/cucumber
|
4 |
|
5 | *Cucumber*, the [popular Behaviour-Driven Development tool](https://cucumber.io), brought to your JavaScript stack.
|
6 |
|
7 | It runs on both Node.js and *modern* web browsers.
|
8 |
|
9 | ## Prerequesites
|
10 |
|
11 | * [Node.js](https://nodejs.org) or [io.js](https://iojs.org)
|
12 | * [NPM](https://www.npmjs.com)
|
13 |
|
14 | Cucumber.js is tested on:
|
15 |
|
16 | * Node.js 4.x, 0.12, 0.10, and io.js (see [CI builds](https://travis-ci.org/cucumber/cucumber-js))
|
17 | * Google Chrome
|
18 | * Firefox
|
19 | * Safari
|
20 | * Opera
|
21 |
|
22 | ## Usage
|
23 |
|
24 | ### Install
|
25 |
|
26 | Cucumber.js is available as an npm module.
|
27 |
|
28 | Install globally with:
|
29 |
|
30 | ``` shell
|
31 | $ npm install -g cucumber
|
32 | ```
|
33 |
|
34 | Install as a development dependency of your application with:
|
35 |
|
36 | ``` shell
|
37 | $ npm install --save-dev cucumber
|
38 | ```
|
39 |
|
40 |
|
41 | ### Features
|
42 |
|
43 | Features are written with the [Gherkin syntax](https://github.com/cucumber/cucumber/wiki/Gherkin)
|
44 |
|
45 | ``` gherkin
|
46 | # features/myFeature.feature
|
47 |
|
48 | Feature: Example feature
|
49 | As a user of cucumber.js
|
50 | I want to have documentation on cucumber
|
51 | So that I can concentrate on building awesome applications
|
52 |
|
53 | Scenario: Reading documentation
|
54 | Given I am on the Cucumber.js GitHub repository
|
55 | When I go to the README file
|
56 | Then I should see "Usage" as the page title
|
57 | ```
|
58 |
|
59 | ### Support Files
|
60 |
|
61 | Support files let you setup the environment in which steps will be run, and define step definitions.
|
62 |
|
63 | #### World
|
64 |
|
65 | *World* is a constructor function with utility properties, destined to be used in step definitions:
|
66 |
|
67 | ```javascript
|
68 | // features/support/world.js
|
69 | var zombie = require('zombie');
|
70 | function World() {
|
71 | this.browser = new zombie(); // this.browser will be available in step definitions
|
72 |
|
73 | this.visit = function (url, callback) {
|
74 | this.browser.visit(url, callback);
|
75 | };
|
76 | }
|
77 |
|
78 | // Should you need asynchronous operations when World is intantiated (i.e. before every scenario), use a hook with a callback or returning a promise (see Hooks below for more information):
|
79 | function Before(callback) {
|
80 | var server = require('http').createServer();
|
81 | server.listen(8080, callback);
|
82 | }
|
83 |
|
84 | module.exports = function() {
|
85 | this.World = World;
|
86 | this.Before = Before;
|
87 | };
|
88 | ```
|
89 |
|
90 | #### Step Definitions
|
91 |
|
92 | Step definitions are the glue between features written in Gherkin and the actual *SUT* (*system under test*). They are written in JavaScript.
|
93 |
|
94 | All step definitions will run with `this` set to what is known as the *[World](https://github.com/cucumber/cucumber/wiki/A-Whole-New-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.
|
95 |
|
96 | Step definitions are contained within one or more wrapper functions.
|
97 |
|
98 | 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.
|
99 |
|
100 | Step definitions are run when steps match their name. `this` is an instance of `World`.
|
101 |
|
102 | ``` javascript
|
103 | // features/step_definitions/myStepDefinitions.js
|
104 |
|
105 | module.exports = function () {
|
106 | this.Given(/^I am on the Cucumber.js GitHub repository$/, function (callback) {
|
107 | // Express the regexp above with the code you wish you had.
|
108 | // `this` is set to a World instance.
|
109 | // i.e. you may use this.browser to execute the step:
|
110 |
|
111 | this.visit('https://github.com/cucumber/cucumber-js', callback);
|
112 |
|
113 | // The callback is passed to visit() so that when the job's finished, the next step can
|
114 | // be executed by Cucumber.
|
115 | });
|
116 |
|
117 | this.When(/^I go to the README file$/, function (callback) {
|
118 | // Express the regexp above with the code you wish you had. Call callback() at the end
|
119 | // of the step, or callback.pending() if the step is not yet implemented:
|
120 |
|
121 | callback.pending();
|
122 | });
|
123 |
|
124 | this.Then(/^I should see "(.*)" as the page title$/, function (title, callback) {
|
125 | // matching groups are passed as parameters to the step definition
|
126 |
|
127 | var pageTitle = this.browser.text('title');
|
128 | if (title === pageTitle) {
|
129 | callback();
|
130 | } else {
|
131 | callback(new Error("Expected to be on page with title " + title));
|
132 | }
|
133 | });
|
134 | };
|
135 | ```
|
136 |
|
137 | ##### Promises
|
138 |
|
139 | Instead of Node.js-style callbacks, promises can be returned by step definitions:
|
140 |
|
141 | ``` javascript
|
142 | this.Given(/^I am on the Cucumber.js GitHub repository$/, function () {
|
143 | // Notice how `callback` is omitted from the parameters
|
144 | return this.visit('https://github.com/cucumber/cucumber-js');
|
145 |
|
146 | // A promise, returned by zombie.js's `visit` method is returned to Cucumber.
|
147 | });
|
148 | ```
|
149 |
|
150 | Simply omit the last `callback` parameter and return the promise.
|
151 |
|
152 | ##### Synchronous step definitions
|
153 |
|
154 | 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:
|
155 |
|
156 | ``` javascript
|
157 | this.Given(/^I add one Cucumber$/, function () {
|
158 | // Notice how `callback` is omitted from the parameters
|
159 | this.cucumberCount += 1;
|
160 | });
|
161 |
|
162 | ```
|
163 |
|
164 | ##### Strings instead of regular expressions
|
165 |
|
166 | It is also possible to use simple strings instead of regexps as step definition patterns:
|
167 |
|
168 | ```javascript
|
169 | this.Then('I should see "$title" as the page title', function (title, callback) {
|
170 | // the above string is converted to the following Regexp by Cucumber:
|
171 | // /^I should see "([^"]*)" as the page title$/
|
172 |
|
173 | var pageTitle = this.browser.text('title');
|
174 | if (title === pageTitle) {
|
175 | callback();
|
176 | } else {
|
177 | callback(new Error("Expected to be on page with title " + title));
|
178 | }
|
179 | });
|
180 | ```
|
181 |
|
182 | `'I have $count "$string"'` would translate to `/^I have (.*) "([^"]*)"$/`.
|
183 |
|
184 | ##### Data Table
|
185 |
|
186 | When steps have a data table, they are passed an object with methods that can be used to access the data.
|
187 |
|
188 | - with column headers
|
189 | - `hashes`: returns an array of objects where each row is converted to an object (column header is the key)
|
190 | - `rows`: returns the table as a 2-D array, without the first row
|
191 | - without column headers
|
192 | - `raw`: returns the table as a 2-D array
|
193 | - `rowsHash`: returns an object where each row corresponds to an entry (first column is the key, second column is the value)
|
194 |
|
195 | See this [feature](/features/data_tables.feature) for examples
|
196 |
|
197 | ##### Timeouts
|
198 |
|
199 | By default, asynchronous hooks and steps timeout after 5000 milliseconds.
|
200 | This can be modified globally with:
|
201 |
|
202 | ```js
|
203 | // features/support/env.js
|
204 |
|
205 | var configure = function () {
|
206 | this.setDefaultTimeout(60 * 1000);
|
207 | };
|
208 |
|
209 | module.exports = configure;
|
210 | ```
|
211 |
|
212 | A specific step's timeout can be set with:
|
213 |
|
214 | ```js
|
215 | // features/step_definitions/my_steps.js
|
216 |
|
217 | var mySteps = function () {
|
218 | this.Given(/^a slow step$/, {timeout: 60 * 1000}, function(callback) {
|
219 | // Does some slow browser/filesystem/network actions
|
220 | });
|
221 | };
|
222 |
|
223 | module.exports = mySteps;
|
224 | ```
|
225 |
|
226 | #### Hooks
|
227 |
|
228 | Hooks can be used to prepare and clean the environment before and after each scenario is executed.
|
229 | Hooks can use callbacks, return promises, or be synchronous.
|
230 | The first argument to hooks is always the current scenario. See
|
231 | [Cucumber.Api.Scenario](https://github.com/cucumber/cucumber-js/blob/master/lib/cucumber/api/scenario.js)
|
232 | for more information.
|
233 |
|
234 | ##### Before hooks
|
235 |
|
236 | To run something before every scenario, use before hooks:
|
237 |
|
238 | ``` javascript
|
239 | // features/support/hooks.js (this path is just a suggestion)
|
240 |
|
241 | var myHooks = function () {
|
242 | this.Before(function (scenario) {
|
243 | // Just like inside step definitions, "this" is set to a World instance.
|
244 | // It's actually the same instance the current scenario step definitions
|
245 | // will receive.
|
246 |
|
247 | // Let's say we have a bunch of "maintenance" methods available on our World
|
248 | // instance, we can fire some to prepare the application for the next
|
249 | // scenario:
|
250 |
|
251 | this.bootFullTextSearchServer();
|
252 | this.createSomeUsers();
|
253 | });
|
254 | };
|
255 |
|
256 | module.exports = myHooks;
|
257 | ```
|
258 |
|
259 | If you need to run asynchronous code, simply accept a callback in your hook function and run it when you're done:
|
260 |
|
261 | ``` javascript
|
262 | this.Before(function (scenario, callback) {
|
263 | this.createUsers(callback);
|
264 | });
|
265 | ```
|
266 |
|
267 | Or return a promise:
|
268 |
|
269 | ```javascript
|
270 | this.Before(function (scenario) {
|
271 | // assuming this.createUsers returns a promise:
|
272 | return this.createUsers();
|
273 | });
|
274 | ```
|
275 |
|
276 | ##### After hooks
|
277 |
|
278 | The *before hook* counterpart is the *after hook*. It's similar in shape but is executed, well, *after* every scenario:
|
279 |
|
280 | ```javascript
|
281 | // features/support/after_hooks.js
|
282 |
|
283 | var myAfterHooks = function () {
|
284 | this.After(function (scenario) {
|
285 | // Again, "this" is set to the World instance the scenario just finished
|
286 | // playing with.
|
287 |
|
288 | // We can then do some cleansing:
|
289 |
|
290 | this.emptyDatabase();
|
291 | this.shutdownFullTextSearchServer();
|
292 | });
|
293 | };
|
294 |
|
295 | module.exports = myAfterHooks;
|
296 | ```
|
297 |
|
298 | ##### Around hooks
|
299 |
|
300 | It's also possible to combine both before and after hooks in one single definition with the help of *around hooks*:
|
301 |
|
302 | ```javascript
|
303 | // features/support/advanced_hooks.js
|
304 |
|
305 | myAroundHooks = function () {
|
306 | this.Around(function (scenario, runScenario) {
|
307 | // "this" is - as always - an instance of World promised to the scenario.
|
308 |
|
309 | // First do the "before scenario" tasks:
|
310 |
|
311 | this.bootFullTextSearchServer();
|
312 | this.createSomeUsers();
|
313 |
|
314 | // When the "before" duty is finished, tell Cucumber to execute the scenario
|
315 | // and pass a function to be called when the scenario is finished:
|
316 |
|
317 | // The first argument to runScenario is the error, if any, of the before tasks
|
318 | // The second argument is a function which performs the after tasks
|
319 | // it can use callbacks, return a promise or be synchronous
|
320 | runScenario(null, function () {
|
321 | // Now, we can do our "after scenario" stuff:
|
322 |
|
323 | this.emptyDatabase();
|
324 | this.shutdownFullTextSearchServer();
|
325 | });
|
326 | });
|
327 | };
|
328 |
|
329 | module.exports = myAroundHooks;
|
330 | ```
|
331 |
|
332 | As with `Before` and `After` hooks, `Around` hooks functions (both pre- and post-scenario functions) can accept a callback or return a promise if you need asynchronous operations.
|
333 |
|
334 | ##### Tagged hooks
|
335 |
|
336 | Hooks can be conditionally elected for execution based on the tags of the scenario.
|
337 |
|
338 | ``` javascript
|
339 | // features/support/hooks.js (this path is just a suggestion)
|
340 |
|
341 | var myHooks = function () {
|
342 | this.Before("@foo", "@bar,@baz", function (scenario) {
|
343 | // This hook will be executed before scenarios tagged with @foo and either
|
344 | // @bar or @baz.
|
345 |
|
346 | // ...
|
347 | });
|
348 | };
|
349 |
|
350 | module.exports = myHooks;
|
351 | ```
|
352 |
|
353 | ##### Attachments
|
354 |
|
355 | You can attach text, images and files to the Cucumber report using the scenario object:
|
356 |
|
357 | ``` javascript
|
358 | this.After(function (scenario) {
|
359 | scenario.attach('Some text');
|
360 | });
|
361 | ```
|
362 |
|
363 | By default, text is saved with a MIME type of `text/plain`. You can also specify
|
364 | a different MIME type:
|
365 |
|
366 | ``` javascript
|
367 | this.After(function (scenario) {
|
368 | scenario.attach('{"name": "some JSON"}', 'application/json');
|
369 | });
|
370 | ```
|
371 |
|
372 | Images and other binary data can be attached using a [stream.Readable](https://nodejs.org/api/stream.html). In that case, passing a callback to `attach()` becomes mandatory:
|
373 |
|
374 | ``` javascript
|
375 | this.After(function (scenario, callback) {
|
376 | if (scenario.isFailed()) {
|
377 | var stream = getScreenshotOfError();
|
378 | scenario.attach(stream, 'image/png', function(err) {
|
379 | callback(err);
|
380 | });
|
381 | }
|
382 | else {
|
383 | callback();
|
384 | }
|
385 | });
|
386 | ```
|
387 |
|
388 | Images and binary data can also be attached using a [Buffer](https://nodejs.org/api/buffer.html):
|
389 |
|
390 | ``` javascript
|
391 | this.After(function (scenario) {
|
392 | if (scenario.isFailed()) {
|
393 | var buffer = getScreenshotOfError();
|
394 | scenario.attach(buffer, 'image/png');
|
395 | }
|
396 | });
|
397 | ```
|
398 |
|
399 | Here is an example of saving a screenshot using [WebDriver](https://www.npmjs.com/package/selenium-webdriver)
|
400 | when a scenario fails:
|
401 |
|
402 | ``` javascript
|
403 | this.After(function (scenario, callback) {
|
404 | if (scenario.isFailed()) {
|
405 | webDriver.takeScreenshot().then(stream) {
|
406 | scenario.attach(stream, 'image/png', callback);
|
407 | }, function(err) {
|
408 | callback(err);
|
409 | });
|
410 | }
|
411 | else {
|
412 | callback();
|
413 | }
|
414 | });
|
415 | ```
|
416 |
|
417 | ##### After features event
|
418 |
|
419 | 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/).
|
420 |
|
421 | 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".
|
422 |
|
423 | ```javascript
|
424 | // features/support/after_hooks.js
|
425 | var myAfterHooks = function () {
|
426 | this.registerHandler('AfterFeatures', function (event, callback) {
|
427 | // clean up!
|
428 | // Be careful, there is no World instance available on `this` here
|
429 | // because all scenarios are done and World instances are long gone.
|
430 | callback();
|
431 | });
|
432 | }
|
433 |
|
434 | module.exports = myAfterHooks;
|
435 | ```
|
436 |
|
437 | ### CLI
|
438 |
|
439 | Cucumber.js includes a binary file to execute the features.
|
440 |
|
441 | If you installed cucumber.js globally, you may run it with:
|
442 |
|
443 | ``` shell
|
444 | $ cucumber.js
|
445 | ```
|
446 |
|
447 | If you installed Cucumber locally, you may need to specify the path to the binary:
|
448 |
|
449 | ``` shell
|
450 | $ ./node_modules/.bin/cucumber.js
|
451 | ```
|
452 |
|
453 | **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 so-called file extension.
|
454 |
|
455 | #### Running specific features
|
456 |
|
457 | * Specify a feature file
|
458 | * `$ cucumber.js features/my_feature.feature`
|
459 | * Specify a scenario by its line number
|
460 | * `$ cucumber.js features/my_feature.feature:3`
|
461 | * Use [Tags](#tags)
|
462 |
|
463 | #### Requiring support files
|
464 |
|
465 | Use `--require <FILE|DIR>` to require files before executing the features.
|
466 | If not used, all "*.js" files (and other extensions specifed by `--compiler`) that are siblings
|
467 | or below the features will be loaded automatically. Automatic
|
468 | loading is disabled when this option is specified, and all loading becomes explicit.
|
469 | Files under directories named "support" are always loaded first
|
470 |
|
471 | #### Formatters
|
472 |
|
473 | Use `--format <TYPE[:PATH]>` to specify the format of the output.
|
474 | If PATH is not supplied, the formatter prints to stdout.
|
475 | If PATH is supplied, it prints to the given file.
|
476 | If multiple formats are specified with the same output, only the last is used.
|
477 |
|
478 | Built-in formatters
|
479 | * pretty - prints the feature as is (default)
|
480 | * progress - prints one character per scenario
|
481 | * json - prints the feature as JSON
|
482 | * summary - prints a summary only, after all scenarios were executed
|
483 |
|
484 | #### Tags
|
485 |
|
486 | Use `--tags <EXPRESSION>` to run specific features or scenarios.
|
487 |
|
488 | * `--tag @dev`: tagged with @dev
|
489 | * `--tag ~@dev`: NOT tagged with `@dev`
|
490 | * `--tags @foo,@bar`: tagged with `@foo` OR `bar`
|
491 | * `--tags @foo --tags @bar`: tagged with `@foo` AND `bar`
|
492 |
|
493 | #### Transpilers
|
494 |
|
495 | Step definitions and support files can be written in other languages that transpile to javascript.
|
496 | This done with the CLI option `--compiler <file_extension>:<module_name>`.
|
497 | Below are some examples
|
498 |
|
499 | * [CoffeeScript](https://www.npmjs.com/package/coffee-script): `--compiler coffee:coffee-script/register`
|
500 | * [TypeScript](https://www.npmjs.com/package/ts-node): `--compiler ts:ts-node/register`
|
501 | * [Pogo](https://www.npmjs.com/package/pogo): `--compiler pogo:pogo`
|
502 |
|
503 | ### Custom Snippet Syntax
|
504 |
|
505 | Undefined steps snippets are printed in javascript by default.
|
506 | Custom snippet snytaxes can be used with `--snippet-syntax <FILE>`.
|
507 | See [here](/features/step_definition_snippets_custom_syntax.feature) for an example.
|
508 |
|
509 | ##### Building a custom snippet syntax
|
510 |
|
511 | * See the [JavaScript syntax](/lib/cucumber/support_code/step_definition_snippet_builder/javascript_syntax.js) for an example. Please open an issue if you need more information.
|
512 | * Please add the keywords `cucumber` and `snippets` to your package,
|
513 | so it can easily be found by searching [npm](https://www.npmjs.com/search?q=cucumber+snippets).
|
514 |
|
515 | ### Examples
|
516 |
|
517 | A few example apps are available for you to browse:
|
518 |
|
519 | * [Rails app serving features in the browser](https://github.com/jbpros/cucumber-js-example)
|
520 | * [Express.js app running features in the cli](https://github.com/olivoil/NodeBDD)
|
521 |
|
522 | ## Contribute
|
523 |
|
524 | See [CONTRIBUTING](https://github.com/cucumber/cucumber-js/blob/master/CONTRIBUTING.md).
|
525 |
|
526 | ## Help & support
|
527 |
|
528 | * Twitter: [@cucumber_js](https://twitter.com/cucumber_js/)
|
529 | * IRC: [#cucumber](http://webchat.freenode.net?channels=cucumber&uio=d4) on Freenode
|
530 | * Google Groups: [cukes](https://groups.google.com/group/cukes)
|
531 | * Website: [cucumber.io](https://cucumber.io)
|