Playwright

An addon to visually test the stories in the multiple browsers within storybook environment

View on Github

storybook-addon-playwright

An addon to visually test the stories in the multiple browsers within storybook environment.

Addon will not work in storybook static build, but the screenshots can be tested against the static build files.

This package has been tested with react framework only, therefore it may not work with other frameworks.

Works with Component Story Format (CSF) only.

addon-screenshot

Compatibility

Package Version
storybook ~6.4
playwright ~1.17

Motivation

Being able to make components that feel and look same in all browser were always a challenge, it's required that developer keep switching between browsers and visually checking the components. It's also important to keep track of the changes and be able to detect changes as quickly as possible. That's why this add-on has been created. With the help of playwright and storybook now it's possible to visually check components and be notified of changes all in one place.

Example

storybook-addon-playwright-example

Getting Started

Required packages:

  • storybook-addon-playwright
  • @storybook/addon-knobs
yarn add storybook-addon-playwright @storybook/addon-knobs --dev

Configuration

within .storybook/main.js

module.exports = {
  stories: ['../**/*.stories.[tj]sx'],
  addons: [
    '@storybook/addon-knobs/register',
    'storybook-addon-playwright/preset',
    'storybook-addon-playwright/register',
  ],
};

If story book complain that it can not find register in @storybook/addon-knobs/register path you may need to point it to destination folder: @storybook/addon-knobs/dist/register

within .storybook/main.js OR .storybook/middleware.js:

const { setConfig } = require('storybook-addon-playwright/configs');
const playwright = require('playwright');

(async () => {
  let browser = {
    chromium: await playwright['chromium'].launch(),
    firefox: await playwright['firefox'].launch(),
    webkit: await playwright['webkit'].launch(),
  };
  setConfig({
    storybookEndpoint: `http://localhost:6006/`,
    getPage: async (browserType, options) => {
      const page = await browser[browserType].newPage(options);
      return page;
    },
    afterScreenshot: async (page) => {
      await page.close();
    },
  });
})();

within .storybook/middleware.js :

const middleware = require('storybook-addon-playwright/middleware');
module.exports = middleware;

setConfig Options

  • storybookEndpoint
  • enableMigration
  • beforeScreenshot
  • afterScreenshot
  • beforeStoryImageDiff
  • afterStoryImageDiff
  • beforeFileImageDiff
  • afterFileImageDiff
  • beforeAllImageDiff
  • afterAllImageDiff
  • pageGotoOptions
  • afterUrlConstruction
  • afterNavigation
  • releaseModifierKey
  • screenshotOptions
  • theme

storybookEndpoint

storybookEndpoint must match the ip/port of storybook.

For docker and none locale browsers, the public ip address of storybook required.

enableMigration

If set true will apply changes to the json file generate by add-on, read more in Migration section.

beforeScreenshot

Will be called before taking a screenshot, useful to manipulate the page.

afterScreenshot

Will be called after a screenshot taken.

afterStoryImageDiff

Will be called before/after running image diff test on whole application screenshot.

beforeStoryImageDiff/afterStoryImageDiff

Will be called before/after running image diff on particular story.

beforeFileImageDiff/afterFileImageDiff

Will be called before/after running image diff on particular file.

beforeAllImageDiff/afterAllImageDiff

Will be called after imageDiff process of all stories screenshots complete.

pageGotoOptions

please refer top Playwright API page.goto option

afterUrlConstruction

Will be called before page.goto, can be used to manipulate url.

afterNavigation

Will be called when page navigated to story.

releaseModifierKey

When set to true, will execute keyboard.up for modifier key, Shift, Meta, Control, or Alt, after screenshot taken.

screenshotOptions

Default Options to apply when taking screenshot.

theme

It overrides the default theme of the addon. It is a json of type material-ui Theme object.

.
.
.
setConfig({
  theme: {
    palette: {
      primary: {
        main: '#0052cc',
      },
      secondary: {
        main: '#edf2ff',
      },
    },
  }
});
.
.
.

How it works

This add-on is basically an interface between playwright and storybook stories. Add-on executes user instruction on the page provided in configuration.

Instructions created by user will save in a json file next to the story file. therefore its available for the next load.

This add-on consist of there parts:

  • Action list panel
  • Screenshots list panel
  • Screenshots preview panel

Action list panel:

Action panel act like a playground, it consists of the list of action sets that created by user for specific story and will be executed in the browser page when selected.

An action set can have single or multiple actions.

Actions are referred to the playwright page methods such as click, mouse move etc...

Screenshots list panel

This panel holds the screenshots taken previously by user, here you can manage screenshots such as delete edit or sort screenshots.

Screenshots preview panel

The preview panel displays the latest screenshots taken by the playwright, it can selectively display all or some of the supported browser by playwright.

Here you can save and change the screenshots settings such as with, height etc.

The screenshots are saved in the folder named __screenshots__ under the story folder.

Add or extend playwright page methods

To add or extend the playwright method, the following properties are available in the setConfig method:

  • customActionSchema

customActionSchema

This property enables developer to add a new method to the playwright page. every entries in the customActionSchema property will appear in the 'Add Actions' menu under the Actions panel.

This property follows the json-schema rules with one additional property named parameters, therefore clear understanding of json-schema required.

Here is an example to add a box to the page:

//async function addBox(this: Page, position: { x: number; y: number })
async function addBox(position) {
  await this.evaluate((pos) => {
    if (!pos) return;
    const div = document.createElement('div');
    div.style.backgroundColor = '#009EEA';
    div.style.width = '200px';
    div.style.height = '200px';
    div.style.position = 'absolute';
    div.style.top = pos.y + 'px';
    div.style.left = pos.x + 'px';
    document.body.append(div);
  }, position);
}

(async () => {
  let browser = {
    chromium: await playwright['chromium'].launch(),
    firefox: await playwright['firefox'].launch(),
    webkit: await playwright['webkit'].launch(),
  };
  setConfig({
    storybookEndpoint: `http://localhost:6006/`,
    getPage: async (browserType, options) => {
      const page = await browser[browserType].newPage(options);
      page.addBox = addBox;
      return page;
    },
    afterScreenshot: async (page) => {
      await page.close();
    },
    customActionSchema: {
      addBox: {
        type: 'promise',
        parameters: {
          position: {
            type: 'object',
            properties: {
              x: {
                type: 'number',
              },
              y: {
                type: 'number',
              },
            },
            required: ['x', 'y'],
          },
        },
      },
    },
  });
})();

Additional Page Methods

The following custom methods has been added to the playwright page:

  • clearInput,
  • mouseDownOnSelector
  • mouseMoveToSelector
  • setSelectorSize
  • scrollSelector
  • dragDropSelector
  • takeScreenshot
  • takeScreenshotOptions
  • selectorMouseWheel
  • mouseFromTo

clearInput

This method fetches an element with selector, waits for actionability checks, focuses the element, clear it and triggers an input event.

mouseDownOnSelector

This method fetches an element with selector, and perform mousedown on the center of selector.

mouseMoveToSelector

This method fetches an element with selector, and move the mouse to center of selector.

setSelectorSize

This method fetches an element with selector, and set the selector with or height.

scrollSelector

This method fetches an element with selector, and set the selector scrollLeft and scrollTop.

dragDropSelector

This method fetches an element with selector, and move it to the position given by user.

takeScreenshot

This method will take a screenshot between actions, its useful for taking a screenshot in sequence for events/actions. In the end the screenshots will be merged with the final screenshot.

takeScreenshotOptions

The purpose of this action is to have centralized options for all screenshots. This action can be used in conjunction with takeScreenshot action only. Only one instance can be used.

selectorMouseWheel

This method fetches an element with selector, and dispatch WheelEvent.

mouseFromTo

This method will perform mouse down, move,and up from to selected location.

Testing

Screenshots saved with the addon can also be tested with the test framework like jest. to do so configure the jest as follow:

add setup file to jest.config.js

module.exports = {
  setupFilesAfterEnv: ['./jest.setup.js'],
};

within jest.setup.js

const playwright = require('playwright');
const { setConfig } = require('storybook-addon-playwright/configs');
const { toMatchScreenshots } = require('storybook-addon-playwright');

expect.extend({ toMatchScreenshots });

let browser = {};

beforeAll(async () => {
  browser = {
    chromium: await playwright['chromium'].launch(),
    firefox: await playwright['firefox'].launch(),
    webkit: await playwright['webkit'].launch(),
  };
  setConfig({
    storybookEndpoint: `http://localhost:6006/`, // or  `./storybook-static`
    getPage: async (browserType, options) => {
      const page = await browser[browserType].newPage(options);
      return page;
    },
    afterScreenshot: async (page) => {
      await page.close();
    },
  });
});

afterAll(async () => {
  const promises = Object.keys(browser).map((browserType) =>
    browser[browserType].close(),
  );
  await Promise.resolve(promises);
});

and within the test file:

describe('test screenshots', () => {
  it('should pass image diff', async () => {
    await expect('*').toMatchScreenshots();
  }, 10000);
});

Or with toMatchImageSnapshot:

const { getScreenshots } = require('storybook-addon-playwright');

describe('test screenshots manually', () => {
  it('should pass image diff', async () => {
    await getScreenshots({
      onScreenshotReady: (screenshotBuffer, baselineScreenshotPath) => {
        expect(screenshotBuffer).toMatchImageSnapshot({
          customSnapshotIdentifier: baselineScreenshotPath.screenshotIdentifier,
          customSnapshotsDir: baselineScreenshotPath.screenshotsDir,
        });
      },
    });
  }, 10000);
});

Make sure to set appropriate timeout for your tests.

Typescript

If your editor does not recognise the toMatchScreenshots matcher, add a global.d.ts file to your project with:

import 'storybook-addon-playwright';

Migration

It is possible that the structure of the json file generated by addon change because of the new features, to fix and apply the changes set the enableMigration to true in setConfig and run the storybook.

Make sure after migration set the enableMigration to false