From 205a44af9256c72799927a9b401fcc2b28eddfe2 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Wed, 16 Feb 2022 15:46:49 +0800 Subject: [PATCH 1/3] Test-runner file for hook configuration --- .storybook/test-runner.js | 18 ++++++++++ README.md | 59 +++++++++++++++++++-------------- jest-setup.js | 17 ---------- playwright/jest-setup.js | 14 ++++++++ src/config/jest-playwright.ts | 2 +- src/index.ts | 1 + src/playwright/hooks.ts | 6 ++++ src/util/getTestRunnerConfig.ts | 17 ++++++++++ src/util/index.ts | 1 + test-runner-jest.config.js | 2 +- 10 files changed, 93 insertions(+), 44 deletions(-) create mode 100644 .storybook/test-runner.js delete mode 100644 jest-setup.js create mode 100644 playwright/jest-setup.js create mode 100644 src/util/getTestRunnerConfig.ts diff --git a/.storybook/test-runner.js b/.storybook/test-runner.js new file mode 100644 index 00000000..41453fce --- /dev/null +++ b/.storybook/test-runner.js @@ -0,0 +1,18 @@ +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, + failureThreshold: 0.03, + failureThresholdType: 'percent', + }); + }, +}; diff --git a/README.md b/README.md index 069aa8d2..016d20a3 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,10 @@ Storybook test runner turns all of your stories into executable tests. - [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) + - [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) ## Features @@ -255,10 +256,40 @@ 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, + }); + }, +}; +``` + +### Render lifecycle + To visualize the test lifecycle, consider a simplified version of the test code automatically generated for each story in your Storybook: ```js @@ -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 diff --git a/jest-setup.js b/jest-setup.js deleted file mode 100644 index 99feca39..00000000 --- a/jest-setup.js +++ /dev/null @@ -1,17 +0,0 @@ -const { toMatchImageSnapshot } = require('jest-image-snapshot'); -const { setPostRender } = require('./dist/cjs'); - -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, - failureThreshold: 0.03, - failureThresholdType: 'percent', - }); -}); diff --git a/playwright/jest-setup.js b/playwright/jest-setup.js new file mode 100644 index 00000000..3e59b20f --- /dev/null +++ b/playwright/jest-setup.js @@ -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); + } +} diff --git a/src/config/jest-playwright.ts b/src/config/jest-playwright.ts index f6c65302..59b68117 100644 --- a/src/config/jest-playwright.ts +++ b/src/config/jest-playwright.ts @@ -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) { diff --git a/src/index.ts b/src/index.ts index 0ce45489..d7ffe9f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './playwright/hooks'; export * from './config/jest-playwright'; +export * from './util/getTestRunnerConfig'; diff --git a/src/playwright/hooks.ts b/src/playwright/hooks.ts index 0b8b7d6d..518a9b91 100644 --- a/src/playwright/hooks.ts +++ b/src/playwright/hooks.ts @@ -9,6 +9,12 @@ export type TestContext = { export type TestHook = (page: Page, context: TestContext) => Promise; +export interface TestRunnerConfig { + setup?: () => void; + preRender?: TestHook; + postRender?: TestHook; +} + export const setPreRender = (preRender: TestHook) => { global.__sbPreRender = preRender; }; diff --git a/src/util/getTestRunnerConfig.ts b/src/util/getTestRunnerConfig.ts new file mode 100644 index 00000000..c8ffe7bf --- /dev/null +++ b/src/util/getTestRunnerConfig.ts @@ -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; +}; diff --git a/src/util/index.ts b/src/util/index.ts index e6a3578a..a8b5e3d1 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,4 +1,5 @@ export * from './getCliOptions'; +export * from './getTestRunnerConfig'; export * from './getStorybookMain'; export * from './getStorybookMetadata'; export * from './getParsedCliOptions'; diff --git a/test-runner-jest.config.js b/test-runner-jest.config.js index 3a05521e..94993cee 100644 --- a/test-runner-jest.config.js +++ b/test-runner-jest.config.js @@ -13,5 +13,5 @@ module.exports = { globalSetup: './playwright/global-setup.js', globalTeardown: './playwright/global-teardown.js', testEnvironment: './playwright/custom-environment.js', - setupFilesAfterEnv: ['/jest-setup.js'], + setupFilesAfterEnv: ['./playwright/jest-setup.js'], }; From 75dc9994bd9d74d8f7a6837c3393ed115fa2da6d Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Wed, 16 Feb 2022 16:09:42 +0800 Subject: [PATCH 2/3] Test out a typescript test-runner config --- .storybook/{test-runner.js => test-runner.ts} | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename .storybook/{test-runner.js => test-runner.ts} (70%) diff --git a/.storybook/test-runner.js b/.storybook/test-runner.ts similarity index 70% rename from .storybook/test-runner.js rename to .storybook/test-runner.ts index 41453fce..f291980f 100644 --- a/.storybook/test-runner.js +++ b/.storybook/test-runner.ts @@ -1,8 +1,9 @@ -const { toMatchImageSnapshot } = require('jest-image-snapshot'); +import { toMatchImageSnapshot } from 'jest-image-snapshot'; +import type { TestRunnerConfig } from '../dist/ts'; const customSnapshotsDir = `${process.cwd()}/__snapshots__`; -module.exports = { +const config: TestRunnerConfig = { setup() { expect.extend({ toMatchImageSnapshot }); }, @@ -16,3 +17,5 @@ module.exports = { }); }, }; + +export default config; From 755f06f761c629578013b0b49b7e0822f35a3e07 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Wed, 16 Feb 2022 22:38:17 +0800 Subject: [PATCH 3/3] Review followup --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 016d20a3..5b667aca 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,25 @@ -# Storybook Test Runner +

Storybook Test Runner

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) - - [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) +

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) + - [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) ## Features @@ -288,6 +286,8 @@ module.exports = { }; ``` +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: