From f7145f027c8391382fe847d618ffd78b4eb073b6 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 1 Jun 2022 11:18:53 +0200 Subject: [PATCH] feat: support DOM snapshot testing --- .storybook/main.js | 2 +- .storybook/test-runner.ts | 6 + README.md | 24 +- package.json | 1 + src/config/jest-playwright.ts | 1 + .../__snapshots__/Button.stories.js.snap | 39 +++ .../__snapshots__/Header.stories.js.snap | 88 +++++++ .../pages/__snapshots__/Page.stories.js.snap | 226 ++++++++++++++++++ yarn.lock | 61 ++++- 9 files changed, 442 insertions(+), 6 deletions(-) create mode 100644 stories/atoms/__snapshots__/Button.stories.js.snap create mode 100644 stories/molecules/__snapshots__/Header.stories.js.snap create mode 100644 stories/pages/__snapshots__/Page.stories.js.snap diff --git a/.storybook/main.js b/.storybook/main.js index 451b70b6..9b65858b 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -11,7 +11,7 @@ const stories = [ directory: '../stories/molecules', }, // general glob - '../stories/pages/**/*.stories.*', + '../stories/pages/**/*.stories.@(js|jsx|ts|tsx)', ]; if (process.env.STRESS_TEST) { diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts index ca9f5bea..d8a03d43 100644 --- a/.storybook/test-runner.ts +++ b/.storybook/test-runner.ts @@ -9,6 +9,7 @@ const config: TestRunnerConfig = { expect.extend({ toMatchImageSnapshot }); }, async postRender(page, context) { + // Visual snapshot tests const image = await page.screenshot({ fullPage: true }); expect(image).toMatchImageSnapshot({ customSnapshotsDir, @@ -16,6 +17,11 @@ const config: TestRunnerConfig = { failureThreshold: 0.03, failureThresholdType: 'percent', }); + + const elementHandler = await page.$('#root'); + const innerHTML = await elementHandler.innerHTML(); + // HTML snapshot tests + expect(innerHTML).toMatchSnapshot(); }, }; diff --git a/README.md b/README.md index 0054ccb9..dcd96ac9 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ Read the announcement: [Interaction Testing with Storybook](https://storybook.js - [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 @@ -170,7 +170,7 @@ module.exports = { }; ``` -Once you have a valid `stories.json` file, your Storybook will be compatible with the "stories.json mode". +Once you have a valid `stories.json` file, your Storybook will be compatible with the "stories.json mode". By default, the test runner will detect whether your Storybook URL is local or remote, and if it is remote, it will run in "stories.json mode" automatically. To disable it, you can pass the `--no-stories-json` flag: @@ -265,9 +265,25 @@ All three functions can be set up in the configuration file `.storybook/test-run > **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. +### DOM snapshot recipe + +The `postRender` function provides a [Playwright page](https://playwright.dev/docs/api/class-page) instance, of which you can use for DOM snapshot testing: + +```js +// .storybook/test-runner.js +module.exports = { + async postRender(page, context) { + // the #root element wraps the story + const elementHandler = await page.$('#root'); + const innerHTML = await elementHandler.innerHTML(); + expect(innerHTML).toMatchSnapshot(); + }, +}; +``` + ### Image snapshot recipe -Consider, for example, the following recipe to take image snapshots: +Here's a slightly different recipe for image snapshot testing: ```js // .storybook/test-runner.js diff --git a/package.json b/package.json index 7319a0f6..87079d5b 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "global": "^4.4.0", "is-localhost-ip": "^1.4.0", "jest-playwright-preset": "^1.7.0", + "jest-serializer-html": "^7.1.0", "jest-watch-typeahead": "^1.0.0", "node-fetch": "^2", "playwright": "^1.14.0", diff --git a/src/config/jest-playwright.ts b/src/config/jest-playwright.ts index f8c2a58b..e2dad3b6 100644 --- a/src/config/jest-playwright.ts +++ b/src/config/jest-playwright.ts @@ -14,6 +14,7 @@ export const getJestConfig = () => { globalTeardown: '@storybook/test-runner/playwright/global-teardown.js', testEnvironment: '@storybook/test-runner/playwright/custom-environment.js', setupFilesAfterEnv: ['@storybook/test-runner/playwright/jest-setup.js'], + snapshotSerializers: ['jest-serializer-html'], testEnvironmentOptions: { 'jest-playwright': { browsers: TEST_BROWSERS.split(',') diff --git a/stories/atoms/__snapshots__/Button.stories.js.snap b/stories/atoms/__snapshots__/Button.stories.js.snap new file mode 100644 index 00000000..dab58ccb --- /dev/null +++ b/stories/atoms/__snapshots__/Button.stories.js.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Atoms/Button Demo play-test 2`] = ` + +`; + +exports[`Atoms/Button FindBy play-test 2`] = ` + +`; + +exports[`Atoms/Button Primary smoke-test 2`] = ` + +`; + +exports[`Atoms/Button WaitFor play-test 2`] = ` + +`; + +exports[`Atoms/Button WaitForElementToBeRemoved play-test 2`] = ` + +`; + +exports[`Atoms/Button WithLoaders play-test 2`] = ` + +`; diff --git a/stories/molecules/__snapshots__/Header.stories.js.snap b/stories/molecules/__snapshots__/Header.stories.js.snap new file mode 100644 index 00000000..2953a50a --- /dev/null +++ b/stories/molecules/__snapshots__/Header.stories.js.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Molecules/Header LoggedIn smoke-test 2`] = ` +
+
+
+ + + + + + + + + + +

+ Acme +

+
+
+ +
+
+
+`; + +exports[`Molecules/Header LoggedOut smoke-test 2`] = ` +
+
+
+ + + + + + + + + + +

+ Acme +

+
+
+ + +
+
+
+`; diff --git a/stories/pages/__snapshots__/Page.stories.js.snap b/stories/pages/__snapshots__/Page.stories.js.snap new file mode 100644 index 00000000..88e8e0b0 --- /dev/null +++ b/stories/pages/__snapshots__/Page.stories.js.snap @@ -0,0 +1,226 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Pages/Page LoggedIn smoke-test 2`] = ` +
+
+
+
+ + + + + + + + + + +

+ Acme +

+
+
+ +
+
+
+
+

+ Pages in Storybook +

+

+ We recommend building UIs with a + + + component-driven + + + process starting with atomic components and ending with pages. +

+

+ Render pages with mock data. This makes it easy to build and review page states without needing to navigate to them in your app. Here are some handy patterns for managing page data in Storybook: +

+
    +
  • + Use a higher-level connected component. Storybook helps you compose such data from the "args" of child component stories +
  • +
  • + Assemble data in the page component from your services. You can mock these services out using Storybook. +
  • +
+

+ Get a guided tutorial on component-driven development at + + Learn Storybook + + . Read more in the + + docs + + . +

+
+ + Tip + + Adjust the width of the canvas with the + + + + + + + Viewports addon in the toolbar +
+
+
+`; + +exports[`Pages/Page LoggedOut smoke-test 2`] = ` +
+
+
+
+ + + + + + + + + + +

+ Acme +

+
+
+ + +
+
+
+
+

+ Pages in Storybook +

+

+ We recommend building UIs with a + + + component-driven + + + process starting with atomic components and ending with pages. +

+

+ Render pages with mock data. This makes it easy to build and review page states without needing to navigate to them in your app. Here are some handy patterns for managing page data in Storybook: +

+
    +
  • + Use a higher-level connected component. Storybook helps you compose such data from the "args" of child component stories +
  • +
  • + Assemble data in the page component from your services. You can mock these services out using Storybook. +
  • +
+

+ Get a guided tutorial on component-driven development at + + Learn Storybook + + . Read more in the + + docs + + . +

+
+ + Tip + + Adjust the width of the canvas with the + + + + + + + Viewports addon in the toolbar +
+
+
+`; diff --git a/yarn.lock b/yarn.lock index 868fc507..33fd05cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5665,6 +5665,13 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diffable-html@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/diffable-html/-/diffable-html-4.1.0.tgz#e7a2d1de187c4e23a59751b4e4c17483a058c696" + integrity sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g== + dependencies: + htmlparser2 "^3.9.2" + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -5707,6 +5714,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -5726,6 +5741,11 @@ domain-browser@^1.1.1: resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== +domelementtype@1, domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + domelementtype@^2.0.1, domelementtype@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" @@ -5738,6 +5758,13 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== + dependencies: + domelementtype "1" + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -5745,6 +5772,14 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -5879,6 +5914,11 @@ enquirer@^2.3.4: dependencies: ansi-colors "^4.1.1" +entities@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -7212,6 +7252,18 @@ html-webpack-plugin@^4.0.0: tapable "^1.1.3" util.promisify "1.0.0" +htmlparser2@^3.9.2: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== + dependencies: + domelementtype "^1.3.1" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.1.1" + htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -8336,6 +8388,13 @@ jest-runtime@^27.5.1: slash "^3.0.0" strip-bom "^4.0.0" +jest-serializer-html@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz#0cfea8a03b9b82bc420fd2cb969bd76713a87c08" + integrity sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA== + dependencies: + diffable-html "^4.1.0" + jest-serializer@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" @@ -10579,7 +10638,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.6.0: +readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==