ESLint plugins
There is one ESLint plugin per framework. Use the plugin for your test runner to enforce executable-stories patterns at lint time.
| Framework | Package | Rules |
|---|---|---|
| Jest | eslint-plugin-executable-stories-jest | require-init-before-steps, require-story-context-for-steps, require-test-context-for-doc-story |
| Vitest | eslint-plugin-executable-stories-vitest | require-task-for-story-init, require-test-context-for-story-init, require-init-before-steps |
| Playwright | eslint-plugin-executable-stories-playwright | require-init-before-steps, require-story-context-for-steps, require-test-context-for-doc-story |
Requires ESLint 9+ (flat config).
Install
Section titled “Install”Vitest:
pnpm add -D eslint-plugin-executable-stories-vitestJest:
pnpm add -D eslint-plugin-executable-stories-jestPlaywright:
pnpm add -D eslint-plugin-executable-stories-playwrightUsage (flat config)
Section titled “Usage (flat config)”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', }, },];Jest rules
Section titled “Jest rules”require-init-before-steps
Section titled “require-init-before-steps”story.init() must run before story.given, story.when, story.then, and the doc methods in the same Jest test.
require-story-context-for-steps
Section titled “require-story-context-for-steps”Legacy top-level step helpers (given, when, then, and, but and aliases) must only be called from an allowed story context.
require-test-context-for-doc-story
Section titled “require-test-context-for-doc-story”If you use doc.story(title) in a framework-native test, it must be inside a test() or it() callback.
Vitest rules
Section titled “Vitest rules”require-task-for-story-init
Section titled “require-task-for-story-init”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');});require-test-context-for-story-init
Section titled “require-test-context-for-story-init”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.
require-init-before-steps
Section titled “require-init-before-steps”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');});Playwright rules
Section titled “Playwright rules”require-init-before-steps
Section titled “require-init-before-steps”story.init(testInfo) must run before story.given, story.when, story.then, and doc methods in the same test.
require-story-context-for-steps
Section titled “require-story-context-for-steps”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'); });});require-test-context-for-doc-story
Section titled “require-test-context-for-doc-story”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.