2.0.0-beta.18 • Published 6 years ago

picturebook v2.0.0-beta.18

Weekly downloads
1
License
MIT
Repository
github
Last release
6 years ago

🤖📗 PictureBook

Automated React Storybook Setup

Simplify React Storybook story creation and cross-browser image comparison testing

💡 Rationale

Setting up storybook and implement cross-browser image comparison testing on multiple projects is time consuming.

Instead of providing a wrapper on top of existing projects that will fall out of date, Picturebook lets you retain control of the Storybook and Nightwatch packages.

This project aims to provide utility methods to simplify Storybook, SauceLabs and Nightwatch configuration and screenshot comparison. Specifically:

  • Creation of storybook stories: They are created based on your file system structure
  • Saucelabs tunnel setup: Reduce SauceConnect config to the tunnel id and username / accessKey
  • Screenshot: Take screenshots of every story on different browsers using SauceLabs and Nightwatch
  • Image Comparison: Compare and update screenshots to baselines collocated with your stories.

⚙️ Install

  1. You will need a SauceLabs account. If you don't have one you can sign up for a trial here or request a free one for open source projects here.
  2. If you want to run tests against Safari and IE11 from localhost, add localtest.dev 127.0.0.1 to your /etc/hosts (the exact steps may vary depending on your platform, see here for more details).
  3. Add picturebook and its peer dependencies:
yarn add --dev picturebook @storybook/react nightwatch react

If you don't have Storybook set it up yet, follow these instructions first.

Some of Picturebook utility methods rely on the output of require.context. This is a webpack construct and it's not available in Node or test environments. There are multiple ways to mock it. If you are using babel you could add the require-context-hook plugin. For instance, if you want to enable it only for tests, you can do something like:

{
  "env": {
    "test": {
      "plugins": ["require-context-hook"]
    }
  }
}

and on your test setup:

require('babel-plugin-require-context-hook/register')()

Alternatively, there's also the require-context npm package that will also emulate webpack's require.context.

📚 API

type StoryPaths = {|
  +name: string,
  +parents: $ReadOnlyArray<string>,
  +title: string,
  +path: string,
  +screenshots: {|
    +[extension: string]: string,
  |},
  +tests: {|
    +[extension: string]: string,
  |},
  +doc: ?string,
  +url: ?string,
|}

type LoadedStory = {|
  ...$Exact<StoryPaths>,
  +main: () => React.Node,
|}

type Options = {|
  flattenFolders: $ReadOnlyArray<string>,
  storiesOf: any,
  stories: any,
  baseUrl?: string,
  decorators: $ReadOnlyArray<Function>,
  storyFiles: $ReadOnlyArray<string>,
|}

type ImgLog = {|
  +imgFileName: string,
  +name: string,
  +platform: string,
  +browser: string
|}

function getFiles(userOptions: $Shape<Options>): Array<StoryPaths>

function loadStories(userOptions: $Shape<Options>): Array<LoadedStory>

function nightwatchConfig(params: {
  desiredCapabilities?: {},     // nightwatch desired capabilities object
  files: Array<StoryPaths>,     // output of getFiles()
  username: string,             // SauceLabs username
  access_key: string,           // SauceLabs accessKey
  browsers?: {                  // List of browsers to test. Default list
    [browserName: string]: {    //   includes chrome, firefox, edge, ie11, iphone7
      desiredCapabilities: {    //   and safari but any valid SauceLabs config is
        platform: string,       //   valid
        version: string,
        browserName: string,
        screenResolution: string,
      },
      custom_vars: {|           // Custom properties required per browser config:
        name: string,           // Must match "browserName"
        platform: string,       // "mobile" | "desktop" but any string is allowed
        extract?: {|            // If cropping the output, specify crop
          top: number,
          left: number,
          width: number,
          height: number,
        |},
      |},
    },
  },
  // Alias for browsers where localhost cannot be accessed from a tunnel,
  // defaults to "localtest.dev"
  localhostAlias?: string,
  // List of browsers that can't access localhost from a tunnel, defaults to:
  // ["ie11", "safari"]
  localhostAliasBrowsers?: Array<string>,
  resultPath?: string,           // Where to output the result file
  proxy?: {},                    // Nightwatch proxy object
}): Object

function compareImages(params: {
    screenshots: Array<ImgLog>,  // output of runTests() or runTestsWithTunnel()
    files: Array<StoryPaths>,    // output of getFiles()
    root: string,                // base path for the stories
    threshold?: number,          // max number of different pixels allowed
    overwrite?: boolean          // update image instead of failing
  }): Promise<{
    +name: string,
    +status: 'CREATED' | 'SUCCESS' | 'FAILED',
    +error: ?string,
    +diffPath: ?string,
    +referencePath: ?string,
    +screenshotPath: ?string,
    +diffThreshold: number,
    +browser: string,
    +platform: string,
  }>

function runTests(configPath: string): Promise<Array<ImgLog>>

function runTestsWithTunnel(tunnelId: string, configPath: string): Promise<Array<ImgLog>>

🎪 Sample App

This project includes a sample app to demo the behavior. You can view it running yarn start and opening localhost:6006 on your browser.

To run the image comparison tests, keep yarn start running and call SAUCE_ACCESS_KEY={YOUR_ACCESS_KEY} SAUCE_USERNAME={YOUR_USERNAME} yarn test:app.

️✏️ Storybook Usage

To take advantage of the automated storybook folder config (and testing) modify your storybook config.js to look like this:

import { storiesOf, configure } from '@storybook/react'
import * as picturebook from 'picturebook'

function loadStories() {
  picturebook.loadStories({
    storiesOf,
    stories: require.context('../stories', true, /\.(js|md|png)/),
  })
}

configure(loadStories, module)

If you want to see a more advanced use case on how to use it with decorators and other plugins, check out the sample project.

📸 Screenshot Usage

To take screenshots, picturebook relies on nightwatch. With the exception of a few required fields, configuration is generated for you. To take advantage of it you should create a nightwatch.conf.js file on your root (or customize it following instructions here).

require('babel-register')

const { resolve } = require('path')
const { nightwatchConfig, getFiles } = require('picturebook')
const requireContext = require('require-context')

const config = nightwatchConfig({
  username: process.env.SAUCE_USERNAME,
  access_key: process.env.SAUCE_ACCESS_KEY,
  files: getFiles({
    baseUrl: 'http://localhost:6006',
    stories: requireContext(resolve(__dirname, './stories'), true, /\.js/),
  }),
  resultPath: resolve(__dirname, './picturebook-img.log'),
  desiredCapabilities: {
    build: 'local',
    'tunnel-identifier': 'picturebook-sample',
  },
})

module.exports = config

To run image comparison, after screenshots have been taken you can call:

compareImages({
  screenshots,
  root: storyRoot,
  overwrite: true,
  files: getFiles({
    stories: requireContext(storyRoot, true, /\.(js|png)/),
  }),
})

Where screenshots is the output of runTestsWithTunnel or runTests (if running with or without a tunnel, respectively).

🙋 QYMA (Questions you may ask)

Why do I need to add localtest.dev as a localhost alias?

This is a SauceLabs issue. To work around it, safari and edge browsers replace localhost from urls to localtest.dev as described here.

You can customize the target browsers and localhost alias with the localhostAliasBrowsers and localhostAlias, respectively.

How do you skip some files?

Picturebook will only take screenshots of the files you tell it exists. You can filter the output from require.context by specifying a different regExp to filter by.

SauceConnect ENOENT error

If you see the following error after a couple unsuccessful runs of SauceConnect:

events.js:167
      throw er; // Unhandled 'error' event
      ^

Error: spawn ~/picturebook/node_modules/node-sauce-connect/lib/sc ENOENT

Sometimes it gets stuck. Try cleaning node_modules and re-running yarn:

rm -rf node_modules
yarn
3.0.0-alpha.2

6 years ago

3.0.0-alpha.1

6 years ago

2.0.0-beta.24

6 years ago

2.0.0-beta.23

6 years ago

2.0.0-beta.21

6 years ago

2.0.0-beta.20

6 years ago

2.0.0-beta.19

6 years ago

2.0.0-beta.18

6 years ago

2.0.0-beta.17

6 years ago

2.0.0-beta.16

6 years ago

2.0.0-beta.15

6 years ago

2.0.0-beta.14

6 years ago

2.0.0-beta.13

6 years ago

2.0.0-beta.12

6 years ago

2.0.0-beta.11

6 years ago

2.0.0-beta.10

6 years ago

2.0.0-beta.9

6 years ago

2.0.0-beta.8

6 years ago

2.0.0-beta.7

6 years ago

2.0.0-beta.6

6 years ago

2.0.0-beta.5

6 years ago

2.0.0-beta.4

6 years ago

2.0.0-beta.3

6 years ago

2.0.0-beta.2

6 years ago

2.0.0-beta.1

6 years ago

1.2.6

7 years ago

1.2.5

7 years ago

1.2.4

7 years ago

1.2.3

7 years ago

1.2.2

7 years ago

1.2.1

7 years ago

1.2.0

7 years ago

1.1.9-test14

7 years ago

1.1.9-test13

7 years ago

1.1.9-test12

7 years ago

1.1.9-test11

7 years ago

1.1.9-test10

7 years ago

1.1.9-test9

7 years ago

1.1.9-test8

7 years ago

1.1.9-test7

7 years ago

1.1.9-test6

7 years ago

1.1.9-test5

7 years ago

1.1.9-test4

7 years ago

1.1.9-test3

7 years ago

1.1.9-test2

7 years ago

1.1.9-test

7 years ago

1.1.9

7 years ago

1.1.8

7 years ago

1.1.7

7 years ago

1.1.6

7 years ago

1.1.5

7 years ago

1.1.4

7 years ago

1.1.3

7 years ago

1.1.2

7 years ago

1.1.1

7 years ago

1.1.0

7 years ago

1.0.35

7 years ago

1.0.34

7 years ago

1.0.33

7 years ago

1.0.32

7 years ago

1.0.31

7 years ago

1.0.30

7 years ago

1.0.29

7 years ago

1.0.28-beta.21

7 years ago

1.0.28-beta.20

7 years ago

1.0.28-beta.19

7 years ago

1.0.28-beta.18

7 years ago

1.0.28-beta.17

7 years ago

1.0.28-beta.16

7 years ago

1.0.28-beta.15

7 years ago

1.0.28-beta.14

7 years ago

1.0.28-beta.13

7 years ago

1.0.28-beta.12

7 years ago

1.0.28-beta.11

7 years ago

1.0.28-beta.10

7 years ago

1.0.28-beta.9

7 years ago

1.0.28-beta.8

7 years ago

1.0.28-beta.7

7 years ago

1.0.28-beta.6

7 years ago

1.0.28-beta.5

7 years ago

1.0.28-beta.4

7 years ago

1.0.28-beta.3

7 years ago

1.0.28-beta.2

7 years ago

1.0.28-beta.1

7 years ago

1.0.28

7 years ago

1.0.27

7 years ago

1.0.26

7 years ago

1.0.25

7 years ago

1.0.24

7 years ago

1.0.23

7 years ago

1.0.22

7 years ago

1.0.21

7 years ago

1.0.20

7 years ago

1.0.19

7 years ago

1.0.18-test.3

7 years ago

1.0.18-test.2

7 years ago

1.0.18-test.1

7 years ago

1.0.18

7 years ago

1.0.17

7 years ago

1.0.16

7 years ago

1.0.15

7 years ago

1.0.14

7 years ago

1.0.13

7 years ago

1.0.12

7 years ago

1.0.11

7 years ago

1.0.10

7 years ago

1.0.9

7 years ago

1.0.8

7 years ago

1.0.7

7 years ago

1.0.6

7 years ago

1.0.5

7 years ago

1.0.4

7 years ago

1.0.3

7 years ago

1.0.2

7 years ago

1.0.1

7 years ago

1.0.0

7 years ago