UNPKG

16.5 kBMarkdownView Raw
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
7It 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
14Cucumber.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
26Cucumber.js is available as an npm module.
27
28Install globally with:
29
30``` shell
31$ npm install -g cucumber
32```
33
34Install as a development dependency of your application with:
35
36``` shell
37$ npm install --save-dev cucumber
38```
39
40
41### Features
42
43Features are written with the [Gherkin syntax](https://github.com/cucumber/cucumber/wiki/Gherkin)
44
45``` gherkin
46# features/myFeature.feature
47
48Feature: 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
61Support 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
69var zombie = require('zombie');
70function 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):
79function Before(callback) {
80 var server = require('http').createServer();
81 server.listen(8080, callback);
82}
83
84module.exports = function() {
85 this.World = World;
86 this.Before = Before;
87};
88```
89
90#### Step Definitions
91
92Step definitions are the glue between features written in Gherkin and the actual *SUT* (*system under test*). They are written in JavaScript.
93
94All 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
96Step definitions are contained within one or more wrapper functions.
97
98Those 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
100Step definitions are run when steps match their name. `this` is an instance of `World`.
101
102``` javascript
103// features/step_definitions/myStepDefinitions.js
104
105module.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
139Instead of Node.js-style callbacks, promises can be returned by step definitions:
140
141``` javascript
142this.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
150Simply omit the last `callback` parameter and return the promise.
151
152##### Synchronous step definitions
153
154Often, 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
157this.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
166It is also possible to use simple strings instead of regexps as step definition patterns:
167
168```javascript
169this.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
186When 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
195See this [feature](/features/data_tables.feature) for examples
196
197##### Timeouts
198
199By default, asynchronous hooks and steps timeout after 5000 milliseconds.
200This can be modified globally with:
201
202```js
203// features/support/env.js
204
205var configure = function () {
206 this.setDefaultTimeout(60 * 1000);
207};
208
209module.exports = configure;
210```
211
212A specific step's timeout can be set with:
213
214```js
215// features/step_definitions/my_steps.js
216
217var mySteps = function () {
218 this.Given(/^a slow step$/, {timeout: 60 * 1000}, function(callback) {
219 // Does some slow browser/filesystem/network actions
220 });
221};
222
223module.exports = mySteps;
224```
225
226#### Hooks
227
228Hooks can be used to prepare and clean the environment before and after each scenario is executed.
229Hooks can use callbacks, return promises, or be synchronous.
230The 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)
232for more information.
233
234##### Before hooks
235
236To run something before every scenario, use before hooks:
237
238``` javascript
239// features/support/hooks.js (this path is just a suggestion)
240
241var 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
256module.exports = myHooks;
257```
258
259If you need to run asynchronous code, simply accept a callback in your hook function and run it when you're done:
260
261``` javascript
262this.Before(function (scenario, callback) {
263 this.createUsers(callback);
264});
265```
266
267Or return a promise:
268
269```javascript
270this.Before(function (scenario) {
271 // assuming this.createUsers returns a promise:
272 return this.createUsers();
273});
274```
275
276##### After hooks
277
278The *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
283var 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
295module.exports = myAfterHooks;
296```
297
298##### Around hooks
299
300It'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
305myAroundHooks = 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
329module.exports = myAroundHooks;
330```
331
332As 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
336Hooks 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
341var 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
350module.exports = myHooks;
351```
352
353##### Attachments
354
355You can attach text, images and files to the Cucumber report using the scenario object:
356
357``` javascript
358this.After(function (scenario) {
359 scenario.attach('Some text');
360});
361```
362
363By default, text is saved with a MIME type of `text/plain`. You can also specify
364a different MIME type:
365
366``` javascript
367this.After(function (scenario) {
368 scenario.attach('{"name": "some JSON"}', 'application/json');
369});
370```
371
372Images 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
375this.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
388Images and binary data can also be attached using a [Buffer](https://nodejs.org/api/buffer.html):
389
390``` javascript
391this.After(function (scenario) {
392 if (scenario.isFailed()) {
393 var buffer = getScreenshotOfError();
394 scenario.attach(buffer, 'image/png');
395 }
396});
397```
398
399Here is an example of saving a screenshot using [WebDriver](https://www.npmjs.com/package/selenium-webdriver)
400when a scenario fails:
401
402``` javascript
403this.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
419The *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
421note: 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
425var 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
434module.exports = myAfterHooks;
435```
436
437### CLI
438
439Cucumber.js includes a binary file to execute the features.
440
441If you installed cucumber.js globally, you may run it with:
442
443``` shell
444$ cucumber.js
445```
446
447If 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
465Use `--require <FILE|DIR>` to require files before executing the features.
466If not used, all "*.js" files (and other extensions specifed by `--compiler`) that are siblings
467or below the features will be loaded automatically. Automatic
468loading is disabled when this option is specified, and all loading becomes explicit.
469Files under directories named "support" are always loaded first
470
471#### Formatters
472
473Use `--format <TYPE[:PATH]>` to specify the format of the output.
474If PATH is not supplied, the formatter prints to stdout.
475If PATH is supplied, it prints to the given file.
476If multiple formats are specified with the same output, only the last is used.
477
478Built-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
486Use `--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
495Step definitions and support files can be written in other languages that transpile to javascript.
496This done with the CLI option `--compiler <file_extension>:<module_name>`.
497Below 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
505Undefined steps snippets are printed in javascript by default.
506Custom snippet snytaxes can be used with `--snippet-syntax <FILE>`.
507See [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,
513so it can easily be found by searching [npm](https://www.npmjs.com/search?q=cucumber+snippets).
514
515### Examples
516
517A 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
524See [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)