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

spdt

v2.0.6

Published

spdt stands for Storybook Puppeteer Declarative Testing. Declarative testing of isolated React components using storybook as a renderer and puppeteer+jest as a test runner

Downloads

135

Readme

SPDT - Storybook Puppeteer Declarative Testing

TL;DR

npm i -D spdt or yarn add spdt -D

Overview

Declarative testing of isolated React components using storybook (v4) as a renderer and puppeteer+jest as a test runner.

It's not yet another testing framework. It's just the way how to implement DRY (don't repeat yourself) principle for testing React components.

  • write a fixture - props for React component
  • add a declaration - a single line which describes what to test
  • write a simple storybook story
  • call npm run spdt to generate tests and run storybook server
  • call npm run spdt:test to run generated jest tests - puppeteer reads react components in storybook and calls assertions

Here is an example.

We have a React component to display a list of products.

// file: src/example/ProductList.jsx
const ProductList = ({list}) => (
  <div>
    {list.map((item, index) => <div key={index} className="itemElement">{item}</div>)}
  </div>
)
export default ProductList

We have some testing data (fixture) to render: ['apricot', 'banana', 'carrot'] We need to write a test to check that three items (with class .itemElement) will be rendered. Let's write a fixture in such a form:

// file: src/example/__tests__/ProductList.fixture.js
export default {
  listOfThree: {
    props: {
      items: ['apricot', 'banana', 'carrot']
    },
    spdt: {
      checkSelector: {selector: '.itemElement', length: 3} // <----
    }
  }
}

That simple declaration checkSelector: {selector: '.itemElement', length: 3} makes spdt library to generate a test for you. No need to copy-paste unit tests any more. Just write a test pattern (declaration) once and reuse it over and over (see example testH1 below)

You can add as many fixtures as you want:

// file: src/example/__tests__/ProductList.fixture.js
export default {
  listOfThree: {
    props: {
      items: ['apricot', 'banana', 'carrot']
    },
    spdt: {
      checkSelector: {selector: '.itemElement', length: 3} // <----
    }
  },
  listOfTwo: {
    props: {
      items: ['orange', 'tangerine']
    },
    spdt: {
      checkSelector: {selector: '.itemElement', length: 2} // <----
    }
  }
}

The idea behind this module was to make testing of React+D3 components based on fixtures. However spdt can speed up testing of any React application

Here is a short description of the workflow:

  • create a React component, e.g Comment.js
  • create a fixture file with a set of properties for your component: Comment.fixture.js
  • create a story file Comment.story.js which is used by Storybook to generate versions of your component based on fixture file:
  • run npm run spdt:generate-story-index to generate .spdt/index.js file for Storybook
  • add asserts/expectations for the component in fixture file (see examples below)
  • run npm run spdt:generate-test-index to generate .spdt/test-index.generated.js file.
  • run npm run spdt:generate-tests to generate test files for each React component (which has story and fixture files), e.g. Comment.generated.spdt.js
  • run Storybook server npm run spdt:storybook
  • run generated tests using jest + puppeteer npm run spdt:test (in another terminal tab)

How to install spdt

  • Install it from npm npm i -D spdt
  • Run initialization node_modules/.bin/spdt:init It will copy config files (jest, puppeteer, storybook) to predefined folder (by default ./spdt )
  • Copy generated scripts from terminal to your package.json file
    "spdt:generate-story-index": "./node_modules/.bin/spdt:generate-story-index",
    "spdt:generate-test-index": "./node_modules/.bin/spdt:generate-test-index",
    "spdt:generate-tests": "./node_modules/.bin/spdt:generate-tests",
    "spdt:test": "jest --detectOpenHandles --config ./.spdt/jest.spdt.config.js",
    "spdt:test:chrome": "HEADLESS=false jest --detectOpenHandles --config ./.spdt/jest.spdt.config.js",
    "spdt:test:chrome:slow": "SLOWMO=1000 HEADLESS=false jest --detectOpenHandles --config ./.spdt/jest.spdt.config.js",
    "spdt:storybook": "start-storybook -p 9009 -c ./.spdt",
    "spdt": "npm run spdt:generate-story-index && npm run spdt:generate-test-index && npm run spdt:generate-tests && npm run spdt:storybook"

Which npm modules need to be installed

  • @storybook/[email protected] (if you still using storybook v4 please use version [email protected])
  • @babel/node@^7.2 (@babel libs are used for generating test files)
  • @babel/core@^7.3
  • @babel/plugin-transform-runtime@^7.3
  • puppeteer@^1.20.0
  • jest-puppeteer@^4.2.0
  • [email protected]

How to use

For example you have a react component like this

// src/components/SimpleComponent+.js

import React from 'react'

export default function(props) {
  const { title, children } = props
  return (
    <div className="simple-component">
      {title}
      <br />
      {children}
    </div>
  )
}

Create a fixture file SimpleComponent.fixture.js inside of __tests__ folder near the component

// file src/components/__tests__/SimpleComponent.fixture.js

export default {
  fixtureOne: {
    props: {
      title: 'Component Title',
      children: 'Some children components',
    },
    spdt: {
      checkSelector: 'div.simple-component',
    },
  },
  fixtureTwo: {
    props: {
      title: 'Another Title',
      children: 'Some children components',
    },
    spdt: {
      checkSelector: 'div.simple-component',
    },
  },
}

Here is a schema of the fixture file

{
    [unique name of fixture]: {
        props: { ... }, // list of all props for your component
        spdt: { ... }, // list of assertions such as checkSelector, checkAxes, checkBars, checkArcs, etc.
    },
    [another fixture]: ...

}

Create a file SimpleComponent.story.js inside of __tests__ folder

// file src/components/__tests__/SimpleComponent.story.js

const { storiesOf } = require('@storybook/react')
import SimpleComponent from '../SimpleComponent'
import fixtures from './SimpleComponent.fixture'

export default (storyGenerator) =>
  storyGenerator({
    storiesOf,
    title: 'SimpleComponent',
    Component: SimpleComponent,
    fixtures,
  })

Now run npm run spdt to call four commands sequencially

  • spdt:generate-story-index - it will generate ./.spdt/index.js for Storybook
  • spdt:generate-test-index - it will generate a ./.spdt/test-index.generated.js for TestGenerator
  • spdt:generate-tests - it will generate <your component name>.generated.spdt.js files based on pairs (story.js + fixture.js)
  • spdt:storybook - it will run Storybook server, available at http://localhost:9009

If everything went well and Storybook started to work you can run generated tests

npm run spdt:test

example of a complicated component with dependencies such as Redux, Router

// file src/components/Article/Comment.js

import DeleteButton from './DeleteButton'
import { Link } from 'react-router-dom'
import React from 'react'

const Comment = (props) => {
  const comment = props.comment
  const show = props.currentUser && props.currentUser.username === comment.author.username
  return (
    <div className="card">
      <div className="card-block">
        <p className="card-text">{comment.body}</p>
      </div>
      <div className="card-footer">
        <Link to={`/@${comment.author.username}`} className="comment-author">
          <img src={comment.author.image} className="comment-author-img" alt={comment.author.username} />
        </Link>
        &nbsp;
        <Link to={`/@${comment.author.username}`} className="comment-author">
          {comment.author.username}
        </Link>
        <span className="date-posted">{new Date(comment.createdAt).toDateString()}</span>
        <DeleteButton show={show} slug={props.slug} commentId={comment.id} />
      </div>
    </div>
  )
}

export default Comment

A file Comment.fixture.js inside of __tests__ folder

// file src/components/Article/__tests__Comment.fixture.js

export default {
  fixture1: {
    props: {
      comment: {
        id: 'id123',
        body: 'some text',
        author: {
          username: 'Author name',
          image: 'https://i.pinimg.com/originals/b6/89/81/b6898148bfa9df9e67330fca31571f9b.png',
        },
        createdAt: 'Sat Aug 25 2018',
      },
      slug: 'slug123',
      currentUser: {
        username: 'user name',
      },
    },
    spdt: {
      checkSelector: ['div.card', 'div.card-footer', 'img.comment-author-img']
    },
  },
}

A file Comment.story.js inside of __tests__ folder

import React from 'react'
import { Route, BrowserRouter, browserHistory } from 'react-router-dom'
import { Provider } from 'react-redux'
import reducer from '../../../reducer'
import { createStore } from 'redux'
import Comment from '../Comment'
import fixtures from './Comment.fixture'
const { storiesOf } = require('@storybook/react')

const Component = (props) => (
  <div>
    <Provider store={createStore(reducer)}>
      <BrowserRouter history={browserHistory}>
        <Route path="/" component={() => <Comment {...props} />} />
      </BrowserRouter>
    </Provider>
  </div>
)

export default (storyGenerator) =>
  storyGenerator({
    storiesOf,
    title: 'Comment',
    Component: Component,
    fixtures,
  })

Run npm run spdt and then npm run spdt:test in another terminal tab and you should see results of jest test runner

> npm run spdt:test

> [email protected] spdt:test .../react-redux-realworld-example-app
> jest --detectOpenHandles --config ./.spdt/jest.spdt.config.js

Setup Test Environment.
 PASS  src/components/Article/__tests__/Comment.generated.spdt.js
  Comment - fixture fixture1
    ✓ should find component matching selector [div.card] 1 time(s) (16ms)
    ✓ should find component matching selector [div.card-footer] 1 time(s) (8ms)
    ✓ should find component matching selector [img.comment-author-img] 1 time(s) (4ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.372s, estimated 2s
Ran all test suites.
Teardown Puppeteer
Teardown Test Environment.

SPDT Declarations

Use these declarations as keys in fixtures

export default {
  [fixture name]: {
    props: { ... },
    spdt: {
      [declaration name]: [declaration value]
    }
  }
}

checkSelector

Value can be

  • string , e.g. 'div.className'
  • object , e.g. {selector: 'div.className', length: 0}
  • array of strings or objects, e.g. ['div.className', {selector: 'li', length: 5}]

This assertion will generate a separate it test to check provided selector

    it('should find component matching selector [div.card] 1 time(s)', async () => {
      const components = await page.$$('div.card')
      const expected = 1
      expect(components).toHaveLength(expected)
    }),

checkAttr

Value can be

  • object, with required keys { selector: 'div.className', expected: 'value-of-attribute', attribute: 'name-of-attribute'}
  • array of objects, e.g. [{selector:'div', expected:'x', attribute:'y'}]

checkSvg

Value can be of Boolean type

  • value true means that it test will be generated to check svg tag is present on the page
  • value false means that it test won't be generated
    it('should load component as <svg>', async () => {
      const component = await page.$('svg')
      expect(component._remoteObject.description).toMatch('svg') // eslint-disable-line no-underscore-dangle
    })`

checkAxes

Value can be of Number type

  • value means the number of expected axes of D3 chart based on selector g.axis
    it('should have ${checkAxes} axes', async () => {
      const axes = await page.$$('g.axis')
      const expected = ${checkAxes}
      expect(axes).toHaveLength(expected)
    })`

checkBars

Value can be of Boolean type

  • value true means that it test will be generated to check selector rect.bar has found elements as many as in array fixture.props.data
  • value false means that it test won't be generated
    it('should have ${checkBarsValue} bars according to fixture data', async () => {
      const bars = await page.$$('rect.bar')
      const expected = ${checkBarsValue} // fixture.props.data.length
      expect(bars).toHaveLength(expected)
    })`

checkArcs

Value can be of Boolean type

  • value true means that it test will be generated to check selector path.arc has found elements as many as in array fixture.props.data
  • value false means that it test won't be generated
    it('should have ${checkArcsValue} arcs according to fixture data', async () => {
      const arcs = await page.$$('path.arc')
      const expected = ${checkArcsValue} // fixture.props.data.length
      expect(arcs).toHaveLength(expected)
    })`

Custom Declarations

When you run initialization node_modules/.bin/spdt:init it creates the file test-declarations.js and a directory custom-declarations in the folder .spdt Use the example testH1 declaration as a guideline to add more custom declarations

General requirements

  • The file test-declarations.js should export an object.
  • The key of the object is the name of a custom declaration
  • The value of the object is a function which takes a fixture and returns a string - generated it test for puppeteer+jest environment

Example

const declarationTestH1 = (fixture) => {
  const { testH1 } = (fixture && fixture.spdt) || {}
  let selector
  let value
  if (typeof testH1 === 'string') {
    selector = 'h1'
    value = testH1
  }
  if (!selector || !value) {
    return null
  }
  return `
    it('testH1: should find component matching selector [${selector}] with value ${value}', async () => {
      const components = await page.$$eval('[id=root] ${selector}', elements => elements.map(e => e.innerText))
      const expected = '${value}'
      expect(components).toContain(expected)
    })`
}

module.exports = {
  testH1: declarationTestH1,
}

Continuous Integration workflow

  • make sure you have installed the module start-server-and-test
npm i -D start-server-and-test
  • check package.json icludes these script commands
    "spdt:storybook:ci": "start-storybook --ci --quiet -p 9009 -c ./.spdt",
    "spdt:ci": "npm run spdt:generate-story-index && npm run spdt:generate-test-index && npm run spdt:generate-tests && npm run spdt:storybook:ci",
    "ci": "start-server-and-test spdt:ci 9009 spdt:test"
  • just run npm run ci

  • module start-server-and-test does the magic:

    • it executes npm run spdt:ci
    • it listens when the port 9009 is available (storybook is up and running)
    • it runs the tests npm run spdt:test
    • it stops storybook when tests end

New features

Check out helpers folder with several high order functions

  • withParent - simple way to wrap a component with a div tag with some css styles
  • withRouter - it wraps a component with Router from react-router-dom
  • withStoreFromFixture - simple way to provide some values to redux store from the fixture
  • withTheme - a wrapper in case you use material-ui

Check out new custom declarations in custom-declarations folder

  • checkInnerText
  • checkInnerTexts
  • checkValue