@lotun/vitest-cucumber
v0.5.4
Published
Vitest plugin for Cucumber
Maintainers
Readme
@lotun/vitest-cucumber
A Vitest plugin that lets you run Cucumber .feature files as native Vitest tests. Write your scenarios in Gherkin, define step definitions with @cucumber/cucumber, and execute them with the full power of Vitest's runner — parallel execution, watch mode, coverage, and clickable error locations in your feature files.
Features
- Runs on the official Cucumber API — uses
@cucumber/cucumberruntime under the hood, so everything Cucumber supports is supported (Scenarios, Outlines, Backgrounds, Rules, Tags, Hooks, Custom Worlds, etc.) - Native Vitest integration —
.featurefiles appear as regular test files in Vitest's output - Node.js and Browser mode — run scenarios in Node (default), or run step/hook bodies and the World in a real browser via Vitest Browser Mode — same plugin, same step files (see Browser mode)
- Parallel execution — leverages Vitest's worker-based parallelism
- Clickable errors — failures point to the exact line in the
.featurefile - Tag filtering — use
CUCUMBER_OPTIONSenv var to pass any Cucumber CLI flags (e.g.--tags) - Line number targeting — run a specific scenario or outline by appending
:lineto the feature file path (e.g.vitest run features/simple.feature:5)
Requirements
- Node.js >= 22.0.0
- Vitest ^4.x || ^5.x
- @cucumber/cucumber ^12.x || ^13.x
Installation
npm install -D @lotun/vitest-cucumber @cucumber/cucumber vitestNode.js mode
Node mode is the default — Cucumber and your step/hook bodies both run in Node.js
vitest.config.ts
import { defineConfig } from "vitest/config";
import { cucumber } from "@lotun/vitest-cucumber";
export default defineConfig({
plugins: [
cucumber({
import: ["features/support/**/*.ts", "features/steps/**/*.ts"],
}),
],
test: {
include: ["features/**/*.feature"],
},
});The cucumber() plugin accepts an optional partial IConfiguration object — the same options you would pass to Cucumber CLI.
Tip: Setting
isolate: falsein Vitest config allows workers to reuse cached support files (step definitions, hooks, world) across feature files, which can significantly speed up test runs with many feature files.
Browser mode
The same cucumber() plugin can also run your scenarios in a real browser using
Vitest Browser Mode. Cucumber's orchestration
(parsing, step matching, scheduling, hooks, retries) runs in Node, while your
step/hook bodies and the World run in the browser realm — so steps can touch
the DOM, window, and any browser-only imports directly. The bridge uses Vitest's
Commands API only, so it is provider-agnostic (Playwright, WebdriverIO, preview).
Install a browser provider:
npm install -D @vitest/browser-playwright playwrightEnable test.browser — no plugin change is needed:
import { playwright } from "@vitest/browser-playwright";
import { defineConfig } from "vitest/config";
import { cucumber } from "@lotun/vitest-cucumber";
export default defineConfig({
plugins: [
cucumber({
import: ["features/support/**/*.ts", "features/steps/**/*.ts"],
}),
],
test: {
include: ["features/**/*.feature"],
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});cucumber() returns two Vite plugins — vitest-cucumber:node and
vitest-cucumber:browser — and each activates automatically based on whether
test.browser.enabled is set for the (project) config. You can therefore also run
Node and Browser mode side by side using
Vitest projects.
Architecture
There is one Node process (the Vitest server) running the Cucumber runtime for
all browser sessions. Each .feature file is a session with its own task
channel; step/hook bodies are dispatched to the page and executed there, then the
result is reported back. No Node worker threads are used — the Node side is
orchestration only. Browser-level parallelism is handled by the provider; all browser projects share the same Vite server process, but each gets its own independent Cucumber config and support library.
Cucumber API support
Node mode runs the native @cucumber/cucumber runtime, so the entire API is
supported. Browser mode supports the entire API too, via the bridge:
| Cucumber API | Node.js | Browser |
| -------------------------------------------------------------------- | :-----: | :-----: |
| Given / When / Then / defineStep | ✅ | ✅ |
| Before / After / BeforeStep / AfterStep (tags, options, arg) | ✅ | ✅ |
| BeforeAll / AfterAll | ✅ | ✅ |
| setWorldConstructor / World | ✅ | ✅ |
| world (v10.8+) / context (v11+) / version | ✅ | ✅ |
| defineParameterType (incl. async transformer, string/array regexp) | ✅ | ✅ |
| DataTable / DocString step arguments / Status | ✅ | ✅ |
| Callback-interface steps | ✅ | ✅ |
| setDefaultTimeout / per-step { timeout } | ✅ | ✅ |
| wrapPromiseWithTimeout | ✅ | ✅ |
| attach / log / link | ✅ | ✅ |
| setDefinitionFunctionWrapper | ✅ | ✅ |
| setParallelCanAssign | ⚠️ | ⚠️ |
Notes:
setParallelCanAssignis a no-op in both Node and browser modes: theparallelconfig flag is rejected (Vitest owns parallelism), so the serial runtime never invokes the validator. Calling it is harmless.
Writing features & steps
Feature file
Feature: Arithmetic
Scenario: Double a value
Given a value of 3
When I double it
Then the value should be 6World (features/support/world.ts)
import { setWorldConstructor } from "@cucumber/cucumber";
export class ArithmeticWorld {
value = 0;
}
setWorldConstructor(ArithmeticWorld);Step definitions (features/steps/arithmetic.ts)
import { Given, Then, When } from "@cucumber/cucumber";
import { expect } from "vitest";
import type { ArithmeticWorld } from "../support/world.ts";
Given("a value of {int}", function valueOf(this: ArithmeticWorld, n: number) {
this.value = n;
});
When("I double it", function double(this: ArithmeticWorld) {
this.value *= 2;
});
When("I add {int}", function add(this: ArithmeticWorld, n: number) {
this.value += n;
});
Then(
"the value should be {int}",
function shouldBe(this: ArithmeticWorld, expected: number) {
expect(this.value).toBe(expected);
},
);Fully working example projects are available in the example folder:
| Example | Description |
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| example/node | Node.js mode — feature files, step definitions, hooks, custom world |
| example/node-playwright | Node.js mode with Playwright — browser automation from step definitions |
| example/browser | Browser mode — DOM interactions via Playwright/Chromium |
| example/storybook | Storybook integration — run Cucumber scenarios inside Storybook play functions |
Usage
Run tests
npx vitest runRun a single feature file
npx vitest run features/simple.featureRun by line number
You can target a specific scenario or scenario outline by passing a line number:
# Run a specific scenario (line where "Scenario:" appears)
# features/simple.feature line 5 → "A single passing step"
npx vitest run features/simple.feature:5
# Run a specific Scenario Outline (line where "Scenario Outline:" appears — runs all its examples)
# features/outline.feature line 3 → "Doubling <input>" (3 examples)
npx vitest run features/outline.feature:3
# Run a single example row of an outline
# features/outline.feature line 10 → "Doubling 3" only
npx vitest run features/outline.feature:10Environment CLI Options
Use the CUCUMBER_OPTIONS environment variable to pass Cucumber CLI options at runtime. These options are merged with the config passed to the cucumber() plugin using Cucumber's own merge strategy.
CUCUMBER_OPTIONS="--tags @smoke" npx vitest runHow It Works
- The Vitest plugin intercepts
.featurefiles during thetransformphase - Each feature is transformed into a small JS module that calls
runFeatureFile() - The Gherkin source is parsed to extract scenario names and structure
- Support code (step definitions, hooks, world) is loaded via Vitest's module loader
@cucumber/cucumber's runtime executes the scenarios and collects results- Results are mapped back to Vitest's
describe/testblocks with pass/fail/skip status
Note: The
CUCUMBER_WORKER_IDenvironment variable is automatically set to match theVITEST_WORKER_IDenvironment variable.
Unsupported Cucumber Options
Some Cucumber options conflict with how Vitest manages test execution and are not supported:
| Option | Reason |
| ---------- | -------------------------------------------------------------------- |
| parallel | Vitest handles parallelism via workers — use Vitest's config instead |
Passing parallel config option will throw an error at runtime.
