cucumber-js Documentation

TypeScript javascript & typescript repository by @cucumber. Model Context Protocol (MCP) server & tools with 5.4k stars.

5,379 stars TypeScript TypeScript 1 file ยท ~8,505 tokens #cucumber#javascript#typescript
RAW Doc

1. Project Overview & Quickstart (cucumber/cucumber-js)

File: README.md

Cucumber

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.

This is the JavaScript implementation of Cucumber. It runs on maintained versions of Node.js. You can quickly try it via CodeSandbox, or read on to get started locally in a couple of minutes.

Looking to contribute? Read our code of conduct first, then check the contributing guide to get up and running.

Install

Cucumber is available on npm:

shell
npm install @cucumber/cucumber

Get Started

Let's take this example of something to test:

First, write your main code in src/index.js:

js
class Greeter {
  sayHello() {
    return 'hello'
  }
}

module.exports = {
  Greeter
}

Then, write your feature in features/greeting.feature:

gherkin
Feature: Greeting

  Scenario: Say hello
    When the greeter says hello
    Then I should have heard "hello"

Next, implement your steps in features/support/steps.js:

js
const assert = require('assert')
const { When, Then } = require('@cucumber/cucumber')
const { Greeter } = require('../../src')

When('the greeter says hello', function () {
  this.whatIHeard = new Greeter().sayHello()
})

Then('I should have heard {string}', function (expectedResponse) {
  assert.equal(this.whatIHeard, expectedResponse)
})

Finally, run Cucumber:

shell
npx cucumber-js

And see the output:

If you learn best by example, we have a repo with several example projects, that might help you get going.

Documentation

The following documentation is for main, which might contain some unreleased features. See documentation for older versions if you need it.

Support

Support is available from the community if you need it.


File: docs/retro/2022/01/14.md

What we liked:

  • small pieces / small steps
  • screensharing - made it easy to switch over, like when Demi left
  • liked talking about cars! - personal connection is really important.
  • liked the explanation of the World. Hugely valuable for Blaise! Cucumber ecosystem struggles defining these concepts for newcomers.
  • A clear objective with many similar, small examples of it. Easy to shift context from one problem to the next, even though they were all slightly different.
  • we just kept rolling when Blaise arrived and Demi left.
  • Kate coming along even though she didn't feel she had a lot to contribute

Anything we would do differently? Puzzles?

  • Took half an hour to get started - setting up the environment, etc. A script to get everything set up beforehand? GitPod? 15 minutes was just spent chatting but we value that!
  • Deciding what to work on took some time too - could we do that in Slack beforehand?
  • We could log how time is spent. Blaise has acted as "the scribe" beforehand.
  • VS Code linting was broken on Matt's machine at least.
  • Matt's GPG keychain
  • Tests run slowly

Actions

  • Kate: be the scribe next week, and spot how we spend our time
  • Matt: Fix GPG key and linting thing
  • Blaise: Come along next week!

File: docs/retro/2022/01/21.md

2022-01-21

Who

  • Kate Dames
  • Blaise Pabon
  • Matt Wynne

What happened

Insights

  • Kate liked that we're using a real-world example.
  • Kate liked going through the example mapping looking at actual data in the examples - e.g. Greg, Julien etc.
  • Kate liked that we used Miro for collaboration to pin-down our thoughts - park some things for later etc.
  • Blaise liked doing example mapping in action, so concretely. Learning the distinctions between a rule / example and a story.
  • Blaise: Materials used to teach cucumber don't emphasise the end result (e.g. the intermediate progress towards a goal). Also don't emphasis how you can use a single test to run against multiple implementations.
  • Matt: Our docs don't give people enough context as they arrive - straight into the guts of installation without any signposts
  • Matt: How much we discovered about a problem I thought was simple. I love example mapping!
  • Kate: I wanted to be more actively participating in the miro board. More hands on. Was it a lack of familiarity? Should we have mad more formal mob roles? What are the rules of the example mapping game?
  • Kate: It was fun, thank you!

Actions

  • Matt: figure out where we should implement this - in the existing commitbit repo or somewhere else?
  • Kate: think about the process and formalising example mapping

File: docs/retro/2021/08/06.md

2021-08-06

This session was streamed live and recorded. Watch the video here.

Who

What happened

  • Discussed what to do.
  • Worked from Marju's fork, using our own local machines and VSCode each time we switched driver.
  • Re-did the work we did last time on the mobbing machine, using Marju's machine to submit a new PR (#1764) cleanly. We learned about git cherry-pick.
  • Started working through the pre-prepared issue #1136 in a branch on Marju's fork
  • Arti had to leave a bit early

Insights

  • Preferred working on own machine, not using the live share / mobbing machine.
  • Good to prepare. But, wonder if you over-prepare, it doesn't leave space for people to explore and learn.
  • Good to have an issue to focus on, and perhaps a failing test (but we could also write this ourselves).
    • It would help to share what we're goin g to work on beforehand so people watching can also know ahead of time. Decide now for next session?
  • Matt didn't like being in charge of the timer.
    • Could use different mechanism for changing turns, like when we make a commit, or when a test is passing?
  • More people would be nice. Should we make it easier for people to join? How? How often can we run it?

Actions / decisions

  • Next time, continue with fixing #1136

File: docs/retro/2021/07/17.md

2021-07-17

This retrospective was from the first new contributors mob/ensemble. Sadly @mattwynne failed to record the stream, so there's no video from this session.

Who

What happened?

What worked? What do you appreciate?

  • I learned general concepts that I could use in other open source projects
  • The issues were good for first timers. I'm keen to explore more now I've looked through them!
  • It was so much fun! All the intimidation I had about starting to do this vanished. Low stakes way of learning new things. A nice way of learning new stuff!
  • The team gelled almost instantly

What was puzzling or frustrating?

  • Why did the tests pass on your device but not mine?
  • Live Share vs Screen Share it was a bit confusing
  • Would it be easier to just run it locally on one of our machines? More realistic.
  • Twitch setup (Matt heard his own voice the whole way through, and we started late)

What should we decide / change for next time?

  • Do this regularly but on any codebase, not just Cucumber
  • Would it be easier to just run it locally on one of our machines?
  • Matt do more homework on twitch setup
  • Let's do this again!
  • Update CONTRIBUTING.md guide to include pointers to basics about forking, how to name your branch etc., and some words of comfort and encouragement to make mistakes, ask for help etc.

File: docs/retro/README.md

Retrospective notes

From time to time, we run a mobbing/ensemble session on this codebase. At the end of each session., we
run a brief retrospective conversation in an effort to "turn up the good" โ€“ Woody Zuill.

Each retro we run is recorded in this folder as a dated Markdown file.

e.g. To find the retrospective for 23rd May 2020 you would look in the file 2020/05/23.md

We use retro-tools to help manage these files.


File: docs/support_files/api_reference.md

Support Files

API Reference

Each method can be destructed from the object returned by require('@cucumber/cucumber').


`defineParameterType({name, preferForRegexpMatch, regexp, transformer, useForSnippets})`

Define a new parameter type and optionally convert an output parameter into something else.

  • name: String used to refer to this type in cucumber expressions.
  • regexp: A regular expression (or array of regular expressions) that match the parameter.
  • transformer: An optional function which transforms the captured argument from a string into what is passed to the step definition.
    If no transform function is specified, the captured argument is left as a string.
    The function can be synchronous or return a Promise of the transformed value. The value of this is the current world, so the function can delegate to world functions.
    Note that your transformer functions cannot reference the world as this if you use
    arrow functions. See FAQ for details.
  • useForSnippets: Defaults to true. That means this parameter type will be used to generate snippets for undefined steps. If the regexp frequently matches text you don't intend to be used as arguments, disable its use for snippets with false.
  • preferForRegexpMatch: Defaults to false. Set to true if you use regular expressions and you want this parameter type's regexp to take precedence over others during a match.

The built in parameter types are:

  • int
  • float
  • string
    • contained in single or double quotes
    • the transformer removes the quotes
  • word

`After([options,] fn)`

Defines a hook which is run after each scenario.

  • options: An object with the following keys:
    • name: An optional name for this hook
    • tags: String tag expression used to apply this hook to only specific scenarios. See cucumber-tag-expressions for more information.
    • timeout: A hook-specific timeout, to override the default timeout.
  • fn: A function, defined as follows:
    • The first argument will be an object of the form {pickle, gherkinDocument, result, error, willBeRetried, testCaseStartedId}
      • The pickle object comes from the gherkin library. See testdata/good/*.pickles.ndjson for examples of its structure.
    • When using the asynchronous callback interface, have one final argument for the callback function.

options can also be a string as a shorthand for specifying tags.

Multiple After hooks are executed in the reverse order that they are defined.


`AfterAll([options,] fn)`

Defines a hook which is run after all scenarios have completed.

  • options: An object with the following keys:
    • name: An optional name for this hook
    • on: In parallel mode, where the hook should run - HookTarget.WORKER (the default) to run once per worker, or HookTarget.COORDINATOR to run just once on the coordinator. Has no effect in serial mode.
    • timeout: A hook-specific timeout, to override the default timeout.
  • fn: A function, defined as follows:
    • When using the asynchronous callback interface, have one argument for the callback function.

Multiple AfterAll hooks are executed in the reverse order that they are defined.


`AfterStep([options,] fn)`

Defines a hook which is run after each step.

  • options: An object with the following keys:
    • tags: String tag expression used to apply this hook to only specific scenarios. See cucumber-tag-expressions for more information.
    • timeout: A hook-specific timeout, to override the default timeout.
  • fn: A function, defined as follows:
    • The first argument will be an object of the form {pickle, pickleStep, gherkinDocument, result, testCaseStartedId, testStepId}
      • The pickle object comes from the gherkin library. See testdata/good/*.pickles.ndjson for examples of its structure.
      • The pickleStep is the step in the pickle that this hook has been invoked for
    • When using the asynchronous callback interface, have one final argument for the callback function.

options can also be a string as a shorthand for specifying tags.

Multiple AfterStep hooks are executed in the reverse order that they are defined.


`Before([options,] fn)`

Defines a hook which is run before each scenario. Same interface as After except the first argument passed to fn will not have the result property.

Multiple Before hooks are executed in the order that they are defined.


`BeforeAll([options,] fn)`

Defines a hook which is run before all scenarios. Same interface as AfterAll.

Multiple BeforeAll hooks are executed in the order that they are defined.


`BeforeStep([options,] fn)`

Defines a hook which is run before each step. Same interface as AfterStep except the first argument passed to fn will not have the result property.

Multiple BeforeStep hooks are executed in the order that they are defined.


`Given(pattern[, options], fn)`

Define a "Given" step.

Aliases: defineStep (deprecated and will be removed in a future release; use the appropriate Given/When/Then keyword to define your step).

  • pattern: A regex or string pattern to match against a gherkin step.
  • options: An object with the following keys:
    • timeout: A step-specific timeout, to override the default timeout.
    • wrapperOptions: Step-specific options that are passed to the definition function wrapper.
  • fn: A function, which should be defined as follows:
    • Should have one argument for each capture in the regular expression.
    • May have an additional argument if the gherkin step has a docstring or data table.
    • When using the asynchronous callback interface, have one final argument for the callback function.

`setDefaultTimeout(milliseconds)`

Set the default timeout for asynchronous steps. Defaults to 5000 milliseconds.


`setDefinitionFunctionWrapper(wrapper)`

Note: the usage of setDefinitionFunctionWrapper is discouraged in favor of BeforeStep and AfterStep hooks.

Set a function used to wrap step / hook definitions.

The wrapper function is expected to take 2 arguments:

  • fn is the original function defined for the step - needs to be called in order for the step to be run.
  • options is the step specific wrapperOptions and may be undefined.

Example:

javascript
setDefinitionFunctionWrapper(function(fn, options) {
  return function(...args) {
    // call original function with correct `this` and arguments
    // ensure return value of function is returned
    return fn.apply(this, args)
      .catch(error => {
        // rethrow error to avoid swallowing failure
        throw error;
      });
  }
})

When used, the result is wrapped again to ensure it has the same length of the original step / hook definition.


setParallelCanAssign(canAssignFn)

Set the function used to determine if a pickle can be executed based on currently executing pickles.

The canAssignFn function is expected to take 2 arguments:

  • pickleInQuestion is the a pickle we are checking if its okay to run
  • picklesInProgress is an array of pickles currently being executed

And returns true if the pickle can be executed, false otherwise.

See examples in our parallel documentation.


`setWorldConstructor(constructor)`

Set a custom world constructor, to override the default world constructor:

javascript
function World({attach, parameters}) {
  this.attach = attach;
  this.parameters = parameters;
}
  • attach - a function hooks / steps can use to add attachments
  • parameters - world parameters passed in through the cli

Note: The World constructor was made strictly synchronous in v0.8.0.


`Then(pattern[, options], fn)`

Define a "Then" step. Same interface as Given


`When(pattern[, options], fn)`

Define a "When" step. Same interface as Given


File: docs/support_files/attachments.md

Attachments

Text, images and other data can be added to the output of the messages and JSON formatters with attachments.
The world constructor is passed an attach function,
which the default world constructor assigns to this.attach. If using a custom world constructor,
you need to do this as well if you want to add attachments.

javascript
var {After} = require('@cucumber/cucumber');

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

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

javascript
var {After} = require('@cucumber/cucumber');

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

If you'd like, you can also specify a filename to be used if the attachment is made available to download as a file via a formatter:

javascript
var {After} = require('@cucumber/cucumber');

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

Images and other binary data can be attached using a stream.Readable.
The data will be base64 encoded in the output.
You should wait for the stream to be read before continuing by awaiting the returned promise or providing a callback.

javascript
var {After, Status} = require('@cucumber/cucumber');

// Awaiting the promise
After(async function (testCase) {
  if (testCase.result.status === Status.FAILED) {
    var stream = getScreenshotOfError();
    await this.attach(stream, { mediaType: 'image/png' });
  }
});

// Passing a callback
After(function (testCase, callback) {
  if (testCase.result.status === Status.FAILED) {
    var stream = getScreenshotOfError();
    this.attach(stream, { mediaType: 'image/png' }, callback);
  }
  else {
    callback();
  }
});

Images and binary data can also be attached using a Buffer.
The data will be base64 encoded in the output.

javascript
var {After, Status} = require('@cucumber/cucumber');

After(function (testCase) {
  if (testCase.result.status === Status.FAILED) {
    var buffer = getScreenshotOfError();
    this.attach(buffer, { mediaType: 'image/png' });
  }
});

If you've already got a base64-encoded string, you can prefix your mime type with base64: to indicate this. Here's an example of saving a screenshot using Selenium WebDriver
when a scenario fails:

javascript
var {After, Status} = require('@cucumber/cucumber');

After(async function (testCase) {
  if (testCase.result.status === Status.FAILED) {
    const screenshot = await driver.takeScreenshot()
    this.attach(screenshot, { mediaType: 'base64:image/png' })
  }
})

Attachments are also printed by the progress, progress-bar and summary formatters.
They appear right after the step and only text/plain content is visible.
It can be used to debug scenarios, especially in parallel mode.

javascript
// Step definition
Given('a basic step', async function() {
  this.attach('Some info.')
  this.attach('{"some": "JSON"}}', { mediaType: 'application/json' })
  this.attach((await driver.takeScreenshot()), {
    mediaType: 'base64:image/png',
    fileName: 'screenshot.png'
  })
})

// Result format
// โœ” Given a basic step # path:line
//    Attachment (text/plain): Some info.
//    Attachment (application/json)
//    Attachment (image/png): screenshot.png

Logging

You can log useful information from your support code with the simple log function:

javascript
var {After} = require('@cucumber/cucumber');

After(function () {
  this.log('Something interesting happened!');
});

Anything you log will be attached as a string with a MIME type of text/x.cucumber.log+plain

You can attach one or more links from your support code with the link function:

javascript
var {Before, After} = require('@cucumber/cucumber');

Before(function () {
  this.link('https://cucumber.io');
});

After(function () {
  this.link(
    'https://github.com/cucumber/cucumber-js',
    'https://github.com/cucumber/cucumber-jvm',
    'https://github.com/cucumber/cucumber-ruby'
  );
});

Links will be attached as a string with a MIME type of text/uri-list


File: docs/support_files/data_table_interface.md

Data tables

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)
    • transpose: returns a new instance with the data transposed

See this feature for examples


File: docs/support_files/hooks.md

Hooks

Hooks are used for setup and teardown the environment before and after each scenario. See the API reference for the specification of the first argument passed to hooks. Multiple Before hooks are executed in the order that they were defined. Multiple After hooks are executed in the reverse order that they were defined.

Note that your hook functions cannot reference the world as this if you use
arrow functions. See FAQ for details.

javascript
const {After, Before} = require('@cucumber/cucumber');

// Synchronous
Before(function () {
  this.count = 0;
});

// Asynchronous Callback
Before(function (testCase, callback) {
  var world = this;
  tmp.dir({unsafeCleanup: true}, function(error, dir) {
    if (error) {
      callback(error);
    } else {
      world.tmpDir = dir;
      callback();
    }
  });
});

// Asynchronous Promise
After(function () {
  // Assuming this.driver is a selenium webdriver
  return this.driver.quit();
});

Named hooks

โ„น๏ธ Added in v8.1.0

Hooks can optionally be named:

javascript
const {Before} = require('@cucumber/cucumber');

Before({name: "Set up some test state"}, function () {
// do stuff here
});

Such hooks will then be referenced by name in formatter output, which can be useful to help you understand what's happening with your tests.

Tagged hooks

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

javascript
const {After, Before} = require('@cucumber/cucumber');

Before(function () {
  // This hook will be executed before all scenarios
});

Before({tags: "@foo"}, function () {
  // This hook will be executed before scenarios tagged with @foo
});

Before({tags: "@foo and @bar"}, function () {
  // This hook will be executed before scenarios tagged with @foo and @bar
});

Before({tags: "@foo or @bar"}, function () {
  // This hook will be executed before scenarios tagged with @foo or @bar
});

// You can use the following shorthand when only specifying tags
Before("@foo", function () {
  // This hook will be executed before scenarios tagged with @foo
});

See more documentation on tag expressions

Skipping in a Before Hook

If you need to imperatively skip a test using a Before hook, this can be done using any of the constructs defined in skipped steps

This includes using: a synchronous return, an asynchronous callback, or an asynchronous promise

javascript
// Synchronous
Before(function() {
  // perform some runtime check to decide whether to skip the proceeding scenario
  return 'skipped'
});

BeforeAll / AfterAll

If you have some setup / teardown that needs to be done before or after all scenarios, use BeforeAll / AfterAll. Like hooks and steps, these can be synchronous, accept a callback, or return a promise.

javascript
const {AfterAll, BeforeAll} = require('@cucumber/cucumber');

// Synchronous
BeforeAll(function () {
  // perform some shared setup
});

// Asynchronous Callback
BeforeAll(function (callback) {
  // perform some shared setup

  // execute the callback (optionally passing an error when done)
});

// Asynchronous Promise
AfterAll(function () {
  // perform some shared teardown
  return Promise.resolve()
});

World parameters in BeforeAll/AfterAll

โ„น๏ธ Added in v10.1.0

BeforeAll/AfterAll hooks aren't given a World instance bound to this like other hooks and steps. But they can access World parameters via this.parameters in order to:

  • Use the parameters as configuration to drive automation
  • Update the parameters with extra context which will then be available to other hooks and steps

Here's a fictional example of obtaining an auth token that can then be used by all tests:

javascript
const {AfterAll, BeforeAll} = require('@cucumber/cucumber');

BeforeAll(async function () {
  this.parameters.accessToken = await getAccessToken(this.parameters.oauth) 
});

Running on the coordinator in parallel mode

โ„น๏ธ Added in v13.2.0

In parallel mode, BeforeAll/AfterAll hooks run once per worker by default. This is helpful where each worker needs its own separate setup (e.g. its own browser instance).

Sometimes though, you have setup/teardown that should happen just once, centrally - like starting a shared server or seeding a database. For those, set the on option to HookTarget.COORDINATOR so the hook runs a single time on the coordinator process instead of on each worker:

javascript
const {AfterAll, BeforeAll, HookTarget} = require('@cucumber/cucumber');

BeforeAll({on: HookTarget.COORDINATOR}, async function () {
  // runs once, before any workers start their scenarios
  await startSharedServer();
});

AfterAll({on: HookTarget.COORDINATOR}, async function () {
  // runs once, after all workers have finished
  await stopSharedServer();
});

Coordinator BeforeAll hooks run before the workers begin, and coordinator AfterAll hooks run after all workers have finished, so they wrap the entire run.

The default is HookTarget.WORKER, which you can also specify explicitly. In serial (non-parallel) mode there's a single process, so all BeforeAll/AfterAll hooks run once regardless of the on option.

BeforeStep / AfterStep

If you have some code execution that needs to be done before or after all steps, use BeforeStep / AfterStep. Like the Before / After hooks, these also have a world instance as 'this', and can be conditionally selected for execution based on the tags of the scenario.

javascript
const {AfterStep, BeforeStep} = require('@cucumber/cucumber');

BeforeStep({tags: "@foo"}, function () {
  // This hook will be executed before all steps in a scenario with tag @foo
});

AfterStep( function ({result}) {
  // This hook will be executed after all steps, and take a screenshot on step failure
  if (result.status === Status.FAILED) {
    this.driver.takeScreenshot();
  }
});

2. Official Technical Reference & Guides (cucumber/docs)

Cucumber Documentation

๐Ÿšจ This repository is archived. the Cucumber documentation is now at https://github.com/cucumber/website. ๐Ÿšจ

Contributing Content

For small edits, the recommended way to contribute is via GitHub.
Each page in the documentation will have an Edit link for this purpose.

Working locally

For bigger modifications, the recommended workflow is to edit the documentation
locally on your machine, and seeing the results in a web browser.

This involves getting the source code and building the documentation yourself.

Get the source code

text
git clone https://github.com/cucumber/docs
cd docs

View the site

The website is built with several tools that are distributed as a Docker image.

Install Docker if you don't already have
it on your machine.

Build and serve the website:

text
./docker_shell.sh

Open a browser:

text
http://localhost:1313

Changes to the contents will be automatically updated in the browser.

Try editing one of the pages under content and see for yourself!

When you are done editing, build the site and check links:

text
./docker_shell.sh make clean
./docker_shell.sh make
./docker_shell.sh make htmlproofer

By default, external links are only checked in CI (because it takes a while).
To check external links locally:

text
CI=true ./docker_shell.sh make htmlproofer

Links that are broken should be removed or replaced, even if they are only
temporarily broken. The reason for this is that broken links negatively affect
search engine rankings.

Modify theme

See the theme README

Architecture

The site is built with Hugo, a fast static site generator.

We have a custom-built theme for the site in themes/cucumber-hugo. This theme
is based on Bulma - a lightweight CSS framework.

The online site is rebuild automatically whenever the git repository receives
new commits, either via a git push, or by modifying contents in the CMS.

The Netlify CMS saves contents straight to GitHub
using the GitHub API.

Continuous Deployment

GitHub will notify Netlify for every git push thanks to a webhook.

Netlify will then build the site and
deploy it if the build is successful.

The build will check for broken links and other problems. Internal and external links are checked by HTMLProofer.
Occasionally, the build will fail due to external links being unavailable or giving a timeout. When that happens, please check if these external links are available and if so, 'Retry build' on Netlify.

You can discuss the documentation in the Community Discord.