Skip to main content

Specflow-emulator

Write your Gherkin steps once, bind them everywhere

Declare your Gherkin step definitions once. specflow-emulator keeps them in a pool and binds them to every .feature scenario automatically — shared steps, scoped steps and a scenario context included. Runs on Jest, Vitest and Vitest browser mode.

🎯

One step pool

Declare each Given / When / Then once. The pool is matched against every scenario in your .feature files — no test() boilerplate.

♻️

Shared steps, zero glue

A step reused across scenarios or features just works. Share across feature files with a tag instead of exporting helper functions.

🏷️

Scoped on purpose

Restrict a step to a feature, a scenario or a tag so identically worded steps never collide.

🧩

Scenario context

Every scenario gets a fresh context object to pass data between steps — no module-level mutable state.

🧭

Errors that point at the line

When a step matches zero or several definitions you get a formatted error naming the feature, scenario, step and file.

Jest, Vitest & the browser

Same API on Jest and Vitest. A dedicated specflow-emulator/browser entry point covers Vitest browser mode (alpha).

Three files, no wiring

Write your steps in a *.stepdefinitions file, point defineFeature at your .feature, and the matcher does the rest.

calculator.stepdefinitions.js

import { defineSteps } from "specflow-emulator";

export const stepDefinitions = defineSteps(
[{ feature: "Simple Calculator" }],
({ Given, When, Then }) => {
Given(/^number "(.*)"$/, (ctx) => (n) => {
ctx.numbers = [...(ctx.numbers ?? []), n];
});

When("I add them", (ctx) => () => {
ctx.result = ctx.numbers.reduce((a, b) => a + +b, 0);
});

Then(/^the result should be "(.*)"$/, (ctx) => (expected) => {
expect(ctx.result).toBe(+expected);
});
}
);

calculator.steps.js

import { defineFeature } from "specflow-emulator";

// The step pool is bound to every scenario automatically.
defineFeature("./calculator.feature");

Stop rewiring the same steps

Before

// raw jest-cucumber: rewire given/when/then in every test()
const addThem = (when) =>
when("I add them", () => {/* ... */});

test("Simple addition", ({ given, when, then }) => {
given(/^number "(.*)"$/, (n) => {/* ... */});
given(/^number "(.*)"$/, (n) => {/* ... */});
addThem(when);
then(/^the result should be "(.*)"$/, (r) => {/* ... */});
});

test("Simple multiplication", ({ given, when, then }) => {
// ...copy every step again...
});

After

// specflow-emulator: declare each step once
defineSteps([{ feature: "Simple Calculator" }], ({ Given, When, Then }) => {
Given(/^number "(.*)"$/, (ctx) => (n) => {/* ... */});

When("I add them", (ctx) => () => {/* ... */});
When("I multiply them", (ctx) => () => {/* ... */});

Then(/^the result should be "(.*)"$/, (ctx) => (r) => {/* ... */});
});
// every matching scenario is wired for you

Ready to try it?

npm i -D specflow-emulator