Skip to content

ESLint plugins

There is one ESLint plugin per framework. Use the plugin for your test runner to enforce executable-stories patterns at lint time.

FrameworkPackageRules
Jesteslint-plugin-executable-stories-jestrequire-init-before-steps, require-story-context-for-steps, require-test-context-for-doc-story
Vitesteslint-plugin-executable-stories-vitestrequire-task-for-story-init, require-test-context-for-story-init, require-init-before-steps
Playwrighteslint-plugin-executable-stories-playwrightrequire-init-before-steps, require-story-context-for-steps, require-test-context-for-doc-story

Requires ESLint 9+ (flat config).

Vitest:

Terminal window
pnpm add -D eslint-plugin-executable-stories-vitest

Jest:

Terminal window
pnpm add -D eslint-plugin-executable-stories-jest

Playwright:

Terminal window
pnpm add -D eslint-plugin-executable-stories-playwright

Vitest — spread the recommended config:

import vitestExecutableStories from 'eslint-plugin-executable-stories-vitest';
export default [...vitestExecutableStories.configs.recommended];

Or register the plugin and enable rules manually:

import vitestExecutableStories from 'eslint-plugin-executable-stories-vitest';
export default [
{
plugins: {
'executable-stories-vitest': vitestExecutableStories,
},
rules: {
'executable-stories-vitest/require-task-for-story-init': 'error',
'executable-stories-vitest/require-test-context-for-story-init': 'error',
'executable-stories-vitest/require-init-before-steps': 'error',
},
},
];

Jest — same pattern; spread configs.recommended or add the plugin. Jest now ships three rules:

import jestExecutableStories from 'eslint-plugin-executable-stories-jest';
export default [...jestExecutableStories.configs.recommended];

Playwright — spread the recommended config to enable the three rules:

import playwrightExecutableStories from 'eslint-plugin-executable-stories-playwright';
export default [...playwrightExecutableStories.configs.recommended];

Or enable rules manually:

import playwrightExecutableStories from 'eslint-plugin-executable-stories-playwright';
export default [
{
plugins: {
'executable-stories-playwright': playwrightExecutableStories,
},
rules: {
'executable-stories-playwright/require-init-before-steps': 'error',
'executable-stories-playwright/require-story-context-for-steps': 'error',
'executable-stories-playwright/require-test-context-for-doc-story': 'error',
},
},
];

story.init() must run before story.given, story.when, story.then, and the doc methods in the same Jest test.

Legacy top-level step helpers (given, when, then, and, but and aliases) must only be called from an allowed story context.

If you use doc.story(title) in a framework-native test, it must be inside a test() or it() callback.

story.init() must be called with the task argument from the test callback so the story is attached to the correct test. Use it('...', ({ task }) => { story.init(task); ... }).

Invalid:

it('my test', () => {
story.init(); // reported: missing task
story.given('setup');
});

Valid:

it('my test', ({ task }) => {
story.init(task);
story.given('setup');
});

story.init(task) must be called from inside a Vitest test (e.g. inside an it() callback), not at the top level or in a describe callback. This rule reports when story.init is used outside a test context.

Step functions (story.given, story.when, story.then, etc.) and doc methods (story.note, story.json, etc.) must be called only after story.init(task) in the same test. This rule reports when steps or doc methods are used without a preceding story.init() in scope.

Invalid:

it('my test', ({ task }) => {
story.given('setup'); // reported: story.init() must be called first
});

Valid:

it('my test', ({ task }) => {
story.init(task);
story.given('setup');
});

story.init(testInfo) must run before story.given, story.when, story.then, and doc methods in the same test.

Step functions (story.given, story.when, story.then, etc.) and their aliases must be called inside a Playwright test callback (e.g. inside test('...', async ({ page }) => { ... })), not at the top level or in a describe callback. This rule ensures steps are attached to a test so the reporter can associate them with the correct scenario.

Invalid:

test.describe('Login', () => {
story.given('user is on login page'); // reported: steps must be inside a test
test('user logs in', async ({ page }, testInfo) => {
story.init(testInfo);
story.when('user submits credentials');
story.then('user sees dashboard');
});
});

Valid:

test.describe('Login', () => {
test('user logs in', async ({ page }, testInfo) => {
story.init(testInfo);
story.given('user is on login page');
story.when('user submits credentials');
story.then('user sees dashboard');
});
});

If you use doc.story(title) to attach story metadata to a plain test() (framework-native pattern), it must be called inside the test callback, not at the top level or in a describe. This rule ensures the story title is associated with the correct test.

Invalid:

test.describe('Login', () => {
doc.story('User logs in'); // reported: doc.story must be inside a test
test('user logs in', async ({ page }, testInfo) => {
story.init(testInfo);
story.given('user is on login page');
});
});

Valid:

test('user logs in', async ({ page }, testInfo) => {
doc.story('User logs in');
story.init(testInfo);
story.given('user is on login page');
story.when('user submits credentials');
story.then('user sees dashboard');
});

Using with official framework ESLint plugins

Section titled “Using with official framework ESLint plugins”

Our plugins do not depend on or bundle the official framework ESLint plugins (e.g. eslint-plugin-playwright). You can use both: install the official plugin for framework best practices and our plugin for executable-stories rules. In your flat config, spread or merge both configs.