Skip to content

Commit

Permalink
Merge pull request #53 from storybookjs/feat/non-eject-customization
Browse files Browse the repository at this point in the history
Test-runner file for hook configuration
  • Loading branch information
yannbf authored Feb 16, 2022
2 parents e13b424 + 755f06f commit 2980d2b
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 60 deletions.
21 changes: 21 additions & 0 deletions .storybook/test-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { toMatchImageSnapshot } from 'jest-image-snapshot';
import type { TestRunnerConfig } from '../dist/ts';

const customSnapshotsDir = `${process.cwd()}/__snapshots__`;

const config: TestRunnerConfig = {
setup() {
expect.extend({ toMatchImageSnapshot });
},
async postRender(page, context) {
const image = await page.screenshot();
expect(image).toMatchImageSnapshot({
customSnapshotsDir,
customSnapshotIdentifier: context.id,
failureThreshold: 0.03,
failureThresholdType: 'percent',
});
},
};

export default config;
91 changes: 50 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
# Storybook Test Runner
<h1>Storybook Test Runner</h1>

Storybook test runner turns all of your stories into executable tests.

## Table of Contents

- [Storybook Test Runner](#storybook-test-runner)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Getting started](#getting-started)
- [CLI Options](#cli-options)
- [Configuration](#configuration)
- [Running against a deployed Storybook](#running-against-a-deployed-storybook)
- [Stories.json mode](#storiesjson-mode)
- [Running in CI](#running-in-ci)
- [1. Running against deployed Storybooks on Github Actions deployment](#1-running-against-deployed-storybooks-on-github-actions-deployment)
- [2. Running against locally built Storybooks in CI](#2-running-against-locally-built-storybooks-in-ci)
- [Experimental test hook API](#experimental-test-hook-api)
- [Image snapshot recipe](#image-snapshot-recipe)
- [Troubleshooting](#troubleshooting)
<h2>Table of Contents</h2>

- [Features](#features)
- [Getting started](#getting-started)
- [CLI Options](#cli-options)
- [Configuration](#configuration)
- [Running against a deployed Storybook](#running-against-a-deployed-storybook)
- [Stories.json mode](#storiesjson-mode)
- [Running in CI](#running-in-ci)
- [1. Running against deployed Storybooks on Github Actions deployment](#1-running-against-deployed-storybooks-on-github-actions-deployment)
- [2. Running against locally built Storybooks in CI](#2-running-against-locally-built-storybooks-in-ci)
- [Experimental test hook API](#experimental-test-hook-api)
- [Image snapshot recipe](#image-snapshot-recipe)
- [Render lifecycle](#render-lifecycle)
- [Troubleshooting](#troubleshooting)
- [The test runner seems flaky and keeps timing out](#the-test-runner-seems-flaky-and-keeps-timing-out)
- [Adding the test runner to other CI environments](#adding-the-test-runner-to-other-ci-environments)
- [Future work](#future-work)
- [Future work](#future-work)

## Features

Expand Down Expand Up @@ -255,10 +254,42 @@ The test runner renders a story and executes its [play function](https://storybo

To enable use cases like visual or DOM snapshots, the test runner exports test hooks that can be overridden globally. These hooks give you access to the test lifecycle before and after the story is rendered.

The hooks, `preRender` and `postRender`, are functions that take a [Playwright Page](https://playwright.dev/docs/pages) and a context object with the current story `id`, `title`, and `name`. They are globally settable by `@storybook/test-runner`'s `setPreRender` and `setPostRender` APIs.
There are three hooks: `setup`, `preRender`, and `postRender`. `setup` executes once before all the tests run. `preRender` and `postRender` execute within a test before and after a story is rendered.

The render functions are async functions that receive a [Playwright Page](https://playwright.dev/docs/pages) and a context object with the current story `id`, `title`, and `name`. They are globally settable by `@storybook/test-runner`'s `setPreRender` and `setPostRender` APIs.

All three functions can be set up in the configuration file `.storybook/test-runner.js` which can optionally export any of these functions.

> **NOTE:** These test hooks are experimental and may be subject to breaking changes. We encourage you to test as much as possible within the story's play function.

### Image snapshot recipe

Consider, for example, the following recipe to take image snapshots:

```js
// .storybook/test-runner.js
const { toMatchImageSnapshot } = require('jest-image-snapshot');
const customSnapshotsDir = `${process.cwd()}/__snapshots__`;

module.exports = {
setup() {
expect.extend({ toMatchImageSnapshot });
},
async postRender(page, context) {
const image = await page.screenshot();
expect(image).toMatchImageSnapshot({
customSnapshotsDir,
customSnapshotIdentifier: context.id,
});
},
};
```

There is also an exported `TestRunnerConfig` type available for TypeScript users.

### Render lifecycle

To visualize the test lifecycle, consider a simplified version of the test code automatically generated for each story in your Storybook:

```js
Expand All @@ -280,28 +311,6 @@ it('button--basic', async () => {
});
```

### Image snapshot recipe

If you want to make the test runner take image snapshots, the following recipe uses test hooks in `jest-setup.js` to do it:

```js
const { toMatchImageSnapshot } = require('jest-image-snapshot');
const { setPostRender } = require('@storybook/test-runner');
expect.extend({ toMatchImageSnapshot });
// use custom directory/id to align CSF and stories.json mode outputs
const customSnapshotsDir = `${process.cwd()}/__snapshots__`;

setPostRender(async (page, context) => {
const image = await page.screenshot();
expect(image).toMatchImageSnapshot({
customSnapshotsDir,
customSnapshotIdentifier: context.id,
});
});
```

## Troubleshooting

#### The test runner seems flaky and keeps timing out
Expand Down
17 changes: 0 additions & 17 deletions jest-setup.js

This file was deleted.

14 changes: 14 additions & 0 deletions playwright/jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { getTestRunnerConfig, setPreRender, setPostRender } = require('../dist/cjs');

const testRunnerConfig = getTestRunnerConfig(process.env.STORYBOOK_CONFIG_DIR);
if (testRunnerConfig) {
if (testRunnerConfig.setup) {
testRunnerConfig.setup();
}
if (testRunnerConfig.preRender) {
setPreRender(testRunnerConfig.preRender);
}
if (testRunnerConfig.postRender) {
setPostRender(testRunnerConfig.postRender);
}
}
2 changes: 1 addition & 1 deletion src/config/jest-playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getJestConfig = () => {
globalSetup: '@storybook/test-runner/playwright/global-setup.js',
globalTeardown: '@storybook/test-runner/playwright/global-teardown.js',
testEnvironment: '@storybook/test-runner/playwright/custom-environment.js',
// @TODO: setupFilesAfterEnv: ['@storybook/test-runner/setup']
setupFilesAfterEnv: ['@storybook/test-runner/playwright/jest-setup.js'],
};

if (TEST_MATCH) {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './playwright/hooks';
export * from './config/jest-playwright';
export * from './util/getTestRunnerConfig';
6 changes: 6 additions & 0 deletions src/playwright/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export type TestContext = {

export type TestHook = (page: Page, context: TestContext) => Promise<void>;

export interface TestRunnerConfig {
setup?: () => void;
preRender?: TestHook;
postRender?: TestHook;
}

export const setPreRender = (preRender: TestHook) => {
global.__sbPreRender = preRender;
};
Expand Down
17 changes: 17 additions & 0 deletions src/util/getTestRunnerConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { join, resolve } from 'path';
import { serverRequire } from '@storybook/core-common';
import { TestRunnerConfig } from '../playwright/hooks';

let testRunnerConfig: TestRunnerConfig;
let loaded = false;

export const getTestRunnerConfig = (configDir: string): TestRunnerConfig | undefined => {
// testRunnerConfig can be undefined
if (loaded) {
return testRunnerConfig;
}

testRunnerConfig = serverRequire(join(resolve(configDir), 'test-runner'));
loaded = true;
return testRunnerConfig;
};
1 change: 1 addition & 0 deletions src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './getCliOptions';
export * from './getTestRunnerConfig';
export * from './getStorybookMain';
export * from './getStorybookMetadata';
export * from './getParsedCliOptions';
2 changes: 1 addition & 1 deletion test-runner-jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ module.exports = {
globalSetup: './playwright/global-setup.js',
globalTeardown: './playwright/global-teardown.js',
testEnvironment: './playwright/custom-environment.js',
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
setupFilesAfterEnv: ['./playwright/jest-setup.js'],
};

0 comments on commit 2980d2b

Please sign in to comment.