Automated tests in plain language, for the Node.js test runner
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 a new implementation of Cucumber built around the Node.js test runner. It's still in the pre-1.0.0 phase, so APIs and behaviour might change. The stable canonical implementation of Cucumber for JavaScript continues to be @cucumber/cucumber for now.
cucumber-node is available on npm:
npm install --save-dev @cucumber/node
You'll need Node.js 22 or 23.
Say we have this small class:
export class Greeter {
sayHello() {
return 'hello'
}
}
Let's write a feature file specifying how it should work, in features/greeting.feature
:
Feature: Greeting
Scenario: Say hello
When the greeter says hello
Then I should have heard "hello"
Next, we need to provide automation to turn that spec into tests, in features/support/steps.js
:
import assert from 'node:assert'
import { When, Then } from '@cucumber/node'
import { Greeter } from '../../lib/Greeter.js'
When('the greeter says hello', (t) => {
t.world.whatIHeard = new Greeter().sayHello()
})
Then('I should have heard {string}', (t, expectedResponse) => {
assert.equal(t.world.whatIHeard, expectedResponse)
})
Finally, run node --test
with some special arguments:
node --import @cucumber/node/bootstrap --test "features/**/*.feature"
Since cucumber-node augments the standard Node.js test runner, you can use many of its options in the same way you would when running tests written in JavaScript, like:
- 🔀
--test-concurrency
to control the number of concurrent processes - 🏃
--test-force-exit
to forcibly exit once all tests have executed - 😷
--test-isolation=none
to have all tests run in a single process - 🔍
--test-name-pattern
to target some scenarios by name - 💎
--test-shard
to shard execution across multiple runs/environments - ⏩
--test-skip-pattern
to omit some scenarios by name - 👀
--watch
to watch for changes and automatically re-run
(In all cases you still need the --import @cucumber/node/bootstrap
so that cucumber-node kicks in when a feature file is encountered.)
Full API documentation is at https://cucumber.github.io/cucumber-node and includes:
Given
,When
andThen
for stepsBefore
andAfter
for hooksParameterType
for custom parameter typesDataTable
for working with data tables
When you write a step or hook function, the first argument will always be a TestCaseContext
object, similar to the one that node --test
gives you when writing tests in JavaScript and with many of the same properties, plus the "world" where you can keep your state, and methods for attaching content.
Discovery of your code is based on the following glob (relative to the working directory):
features/**/*.{cjs,js,mjs,cts,mts,ts}
This isn't configurable (yet).
cucumber-node is an ESM package, but you can write your code in either format:
- ESM - e.g.
import { Given } from '@cucumber/node'
- CommonJS - e.g.
const { Given } = require('@cucumber/node')
You also can write your code in TypeScript.
We recommend bringing in tsx
to handle the transpilation, plus the Node.js types:
npm install --save-dev tsx @types/node
Then, add tsx
as another import when you run:
node --import @cucumber/node/bootstrap --import tsx --test "features/**/*.feature"
Remember to add a tsconfig.json
to your project. If you're not sure what you need, @tsconfig/node22
is a good place to start.
You might even be able to go without any extra dependencies and instead lean on Node.js built-in TypeScript support, although this is still very new and has several limitations.
Some Cucumber formatters are included as Node.js test reporters:
- HTML
--test-reporter=@cucumber/node/reporters/html --test-reporter-destination=./report.html
- JUnit
--test-reporter=@cucumber/node/reporters/junit --test-reporter-destination=./TEST-cucumber.xml
- Message
--test-reporter=@cucumber/node/reporters/message --test-reporter-destination=./messages.ndjson
There are some caveats that apply when using these reporters (but not otherwise):
- Don't mix Cucumber tests with other tests - if you have non-Cucumber tests to run with
node --test
, you should do that in a separate run. - Don't use the
spec
reporter at the same time - because we're abusing thediagnostic
channel to send messages to the reporter, it makes thespec
output very noisy - we recommend thedot
reporter instead.
There are some pretty standard Cucumber features that are missing (but not for long):
- Filtering by tag expression
- BeforeAll/AfterAll hooks
- Regular expression steps
- Customise World creation and type
- Snippets
Some behaviour of cucumber-node is different - and better - than in cucumber-js:
node --test
by default runs each test file in a separate process, and runs them concurrently as much as possible within the constraints of the system. This is markedly different to cucumber-js which is single-process and serial by default. This is also a good thing, helping you identify and fix unintentional dependencies between scenarios.
There's no reliance on this
in your step and hook functions to access state, since we pass a context object as the first argument to all functions. This means you're free to use arrow functions as you normally would in JavaScript.