npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

storytests-cli

v2.0.1

Published

CLI utility to generate tests from Storybook stories

Readme

Storytests CLI

Framework agnostic CLI Utility to generate test files from matched Storybook files.

Table of Contents

Installation

You can install storytests-cli using npm or yarn:

npm i storytests-cli --save-dev
# or
yarn add -D storytests-cli

Usage

Prerequisites: Node.js (>10.4.0).

Initialize a basic configuration by running storytests-cli with:

npm run storytests init
# or
yarn storytests init

Currently all templates including the default one are preconfigured for React and Storybook@^6.0.0. However this utility is agnostic of framework or Storybook version and you may contribute with your own templates. Existing templates can be used by providing -t, --template argument and they include a hermione preset, puppeteer preset or a playwright one with respective argument names.

You could also create a config file named storytests.config.js yourself, names like storytestsrc.cjs or storytests.conf.js would also work. Read about configuration in detail.

When configured can be run with:

npm run storytests
# or
yarn storytests

Config file in the project root will be hooked up automatically. If you are using a different location or name for your config file, pass relative path to it with -c, --config argument.

yarn storytests -c ./.config/storytests.config.js

By default, if an existing test file is found, it will not be rewritten. If you want to rewrite existing test files, pass -r, --rewrite flag.

You can also display a help message with --help.

Configuration

storytests-cli can be configured with the following properties:

  • strategy: 'component' | 'story';

    When set to 'component' a separate test file will be created for every matched file. When set to 'story' a separate test file will be created for every matched story in a file.

  • testDirectoy: ((component: string, path: string) => string) | string;

    Path to the folder where test files will be created relative to the matched file folder. Can be either a function or a string. Relative paths are supported: in this case they will be resolve against matched file directory.

  • postfixes: string[];

    Postfixes for generated test files. For example, to create hermione and other generic test files you can specify ['hermione', 'test'] as the value.

  • filesGlob: string;

    Absolute path glob pattern to match desired story files.

  • componentPattern: RegExp;

    RegExp to match the component name in a Storybook file.

  • storyPattern: RegExp;

    RegExp to match the story names in a Storybook file.

  • generateTest: (
        component: string,
        story: string | string[],
        postfix: string,
    ) => string | false;

    A function that gets called for every file with every possible combination of stories/postfixes and should return test file content. Recieves matched component name (the result of the match from componentPattern), stories matched from storyPattern in the file or a single story name (if strategy is set to 'story'), as well as the postfix from postfixes. This function could also return false (not any other falsy value though), then no test file for this combination of arguments will be created.

  • generateFileName: (
        component: string,
        story: string | string[],
        postfix: string,
    ) => string;

    A function that gets called before generateTest and should return the file name. Has identical signature to generateTest except it should not return false.

  • validateFileName: (path: string, component: string, stories: string[]) =>
        boolean;

    A function that gets called for every unvalidated file when running cleanup command. path parameter stores relative path from test directory (calculated using testDirectory). component and stories parameters store matched component names and all matched stories (matches from componentPattern and storyPattern). Should return true if file is valid and false if file shoudl get removed (e.g. a screenshot from a removed story).

Example

Let's imagine we have a simple Button component story:

// button.stories.tsx

// ...

export default {
  title: "Components/Button",
  component: Button,
} as Meta;

const Template: Story = ({ label, ...args }) => (
  <Button {...args}>{label}</Button>
);

// @storytests-ignore
export const Playground = Template.bind({});

export const Primary = Template.bind({});
Primary.args = {
  view: "primary",
};

// ...

We want to create hermione and playwright test files from this story. Take a look at a sufficient storytests.config.js.

// storytests.config.js

const path = require('path');

const hermioneTemplate = require('./storytests/hermione.template');
const playwrightTemplate = require('./storytests/playwright.template');

module.exports = {
    /**
     * Should match `Components/Button`
     * ```
     * export default {
     *   title: "Components/Button",
     *   component: Button,
     * } as Meta;
     * ```
     */
    componentPattern: /(?<=title: ")[a-z/]+/gi,

    /**
     * Should match `Primary`
     * ```
     * export const Primary = Template.bind({});
     * ```
     *
     * Should not match `Playground`
     * ```
     * // @storytests-ignore
     * export const Playground = Template.bind({});
     * ```
     */
    storyPattern: /(?<!\/\/ @storytests-ignore[ \r\n]export const )\b[a-z]+(?= = Template.bind\()/gi,

    /**
     * Generate a single test file for a single component, not for every story
     */
    strategy: 'component',

    /**
     * Generate test files in the same directory as stories file
     */
    testDirectory: './',

    /**
     * Generate `hermione` and `playwright` (though we can use any names here, they get passed to our hooks)
     */
    postfixes: ['hermione', 'playwright'],

    /**
     * Glob pattern to match story files
     */
    filesGlob: path.resolve(__dirname, './src/**/*.stories.tsx'),

    /**
     * A hook function to generate test file contents
     * @param {string} componentPath component name (match from `componentPattern`)
     * @param {string[]} stories story names as an array (matches from `storyPattern`, could be empty)
     * @param {string} postfix test file postfix
     * @returns {string|false} could return false then this file will not be generated
     */
    generateTest: (componentPath, stories, postfix) => {
        switch (postfix) {
            case 'hermione':
                return hermioneTemplate(componentPath, stories);
            case 'playwright':
                return playwrightTemplate(componentPath, stories);
            default:
                return false;
        }
    },

    /**
     * A hook function to generate file name
     */
    generateFileName: (componentPath, _stories, postfix) => {
        const componentParts = componentPath.split('/');

        const component = componentParts[
            componentParts.length - 1
        ].toLowerCase();

        const isPlaywright = postfix === 'playwright';

        const type = isPlaywright ? 'spec' : postfix;

        const extention = isPlaywright ? 'ts' : 'js';

        // Even though we specified `playwright` as a postfix in the config we are free to use any names we want
        return `${component}.${type}.${extention}`;
    },
};

Now when we run yarn storytests in the project we should see button.hermione.js and button.spec.ts generated in the same folder as button.stories.tsx according to imported template functions which could look like this:

/**
 * Generates a hermione test file from template
 * @param {string} componentPath component name
 * @param {string[]} stories story names as an array
 */
const hermioneTemplate = (componentPath, stories) => {
    if (stories.length === 0) {
        return false;
    }

    const kebabCaseComponent = componentPath.toLowerCase().replace(/\//g, '-');
    const componentParts = componentPath.split('/');
    const component = componentParts[componentParts.length - 1];
    const kebabCaseStories = stories.map((story) =>
        story.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(),
    );
    const storyNames = stories.map((story) =>
        story.replace(/([a-z])([A-Z])/g, '$1 $2'),
    );

    return `describe("${component}", function () {
  const selector = ".story";
        ${kebabCaseStories
            .map(
                (story, index) => `
  it("${storyNames[index]}", function () {
    return this.browser
      .url("iframe.html?id=${kebabCaseComponent}--${story}")
      .assertView("${story}", selector);
  });`,
            )
            .join('\n')}
});
`;
};

module.exports = hermioneTemplate;

Resulting button.hermione.js could look something like this:

describe('Button', function () {
    const selector = '.story';

    it('Primary', function () {
        return this.browser
            .url('iframe.html?id=components-button--primary')
            .assertView('primary', selector);
    });

    // ...
});

You can check out the repository with this example more in depth at storytests-cli-example

Acknowledgements

Inspired by Storytests Webpack Plugin by baushonok

License

MPL-2.0