From dfbb7061628670c223ce416a4b03229493d3ada1 Mon Sep 17 00:00:00 2001 From: Filipp Riabchun Date: Sun, 24 Nov 2019 03:25:23 +0100 Subject: [PATCH 1/7] storyshots-puppeteer: introduce puppeteerTest and axeTest exports --- .../storyshots-puppeteer/package.json | 1 + .../storyshots-puppeteer/src/axeTest.ts | 23 ++++ .../storyshots-puppeteer/src/config.ts | 63 ++++++++++ .../storyshots-puppeteer/src/imageSnapshot.ts | 119 +++--------------- .../storyshots-puppeteer/src/index.ts | 4 +- .../storyshots-puppeteer/src/puppeteerTest.ts | 87 +++++++++++++ examples/official-storybook/package.json | 6 +- .../stories/addon-storyshots.stories.js | 24 +++- ...apshots-addons-storyshots-block-1-snap.png | Bin .../storyshots-puppeteer/axe-tests.runner.js | 29 +++++ .../jest.config.js | 2 +- .../puppeteer-tests.runner.js | 29 +++++ .../storyshots-image.runner.js | 2 +- yarn.lock | 17 ++- 14 files changed, 292 insertions(+), 114 deletions(-) create mode 100644 addons/storyshots/storyshots-puppeteer/src/axeTest.ts create mode 100644 addons/storyshots/storyshots-puppeteer/src/config.ts create mode 100644 addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts rename examples/official-storybook/{image-snapshots => storyshots-puppeteer}/__image_snapshots__/storyshots-image-runner-js-image-snapshots-addons-storyshots-block-1-snap.png (100%) create mode 100644 examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js rename examples/official-storybook/{image-snapshots => storyshots-puppeteer}/jest.config.js (75%) create mode 100644 examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js rename examples/official-storybook/{image-snapshots => storyshots-puppeteer}/storyshots-image.runner.js (92%) diff --git a/addons/storyshots/storyshots-puppeteer/package.json b/addons/storyshots/storyshots-puppeteer/package.json index ecc6ade62dd8..180714858766 100644 --- a/addons/storyshots/storyshots-puppeteer/package.json +++ b/addons/storyshots/storyshots-puppeteer/package.json @@ -29,6 +29,7 @@ "prepare": "node ../../../scripts/prepare.js" }, "dependencies": { + "@hypnosphi/jest-puppeteer-axe": "^1.4.0", "@storybook/node-logger": "5.3.0-beta.5", "@storybook/router": "5.3.0-beta.5", "@types/jest-image-snapshot": "^2.8.0", diff --git a/addons/storyshots/storyshots-puppeteer/src/axeTest.ts b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts new file mode 100644 index 000000000000..920af43c09e9 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/axeTest.ts @@ -0,0 +1,23 @@ +import '@hypnosphi/jest-puppeteer-axe'; +import { defaultCommonConfig, CommonConfig } from './config'; +import { puppeteerTest } from './puppeteerTest'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace,no-redeclare + namespace jest { + interface Matchers { + toPassAxeTests(parameters: any): R; + } + } +} + +export const axeTest = (customConfig: Partial = {}) => + puppeteerTest({ + ...defaultCommonConfig, + ...customConfig, + async testBody(page, options) { + const parameters = options.context.parameters.a11y; + const include = parameters?.element ?? '#root'; + await expect(page).toPassAxeTests({ ...parameters, include }); + }, + }); diff --git a/addons/storyshots/storyshots-puppeteer/src/config.ts b/addons/storyshots/storyshots-puppeteer/src/config.ts new file mode 100644 index 000000000000..bb9aa5815f85 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/config.ts @@ -0,0 +1,63 @@ +import { MatchImageSnapshotOptions } from 'jest-image-snapshot'; +import { Base64ScreenShotOptions, Browser, DirectNavigationOptions, Page } from 'puppeteer'; + +export interface Context { + kind: string; + story: string; + parameters: { + [key: string]: any; + }; +} + +export interface CommonConfig { + storybookUrl: string; + chromeExecutablePath: string; + getGotoOptions: (options: { context: Context; url: string }) => DirectNavigationOptions; + customizePage: (page: Page) => Promise; + getCustomBrowser: () => Promise; + setupTimeout: number; + testTimeout: number; +} + +export interface PuppeteerTestConfig extends CommonConfig { + testBody: (page: Page, options: { context: Context; url: string }) => void | Promise; +} + +export interface ImageSnapshotConfig extends CommonConfig { + getMatchOptions: (options: { context: Context; url: string }) => MatchImageSnapshotOptions; + getScreenshotOptions: (options: { context: Context; url: string }) => Base64ScreenShotOptions; + beforeScreenshot: (page: Page, options: { context: Context; url: string }) => void; +} + +const noop: () => undefined = () => undefined; +const asyncNoop: () => Promise = async () => undefined; + +export const defaultCommonConfig: CommonConfig = { + storybookUrl: 'http://localhost:6006', + chromeExecutablePath: undefined, + getGotoOptions: noop, + customizePage: asyncNoop, + getCustomBrowser: undefined, + setupTimeout: 15000, + testTimeout: 15000, +}; + +export const defaultPuppeteerTestConfig: PuppeteerTestConfig = { + ...defaultCommonConfig, + testBody(page, options) { + const testBody = options.context.parameters.puppeteerTest; + if (testBody != null) { + return testBody(page, options); + } + return null; + }, +}; + +// We consider taking the full page is a reasonable default. +const defaultScreenshotOptions = () => ({ fullPage: true, encoding: 'base64' } as const); +export const defaultImageSnapshotConfig: ImageSnapshotConfig = { + ...defaultCommonConfig, + getMatchOptions: noop, + getScreenshotOptions: defaultScreenshotOptions, + beforeScreenshot: noop, +}; diff --git a/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts b/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts index 626f3b9e825c..c2ceaed274df 100644 --- a/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts +++ b/addons/storyshots/storyshots-puppeteer/src/imageSnapshot.ts @@ -1,111 +1,20 @@ -import { Browser, Page } from 'puppeteer'; import { toMatchImageSnapshot } from 'jest-image-snapshot'; -import { logger } from '@storybook/node-logger'; -import { constructUrl } from './url'; -import { ImageSnapshotConfig } from './ImageSnapshotConfig'; +import { defaultImageSnapshotConfig, ImageSnapshotConfig } from './config'; +import { puppeteerTest } from './puppeteerTest'; expect.extend({ toMatchImageSnapshot }); -// We consider taking the full page is a reasonable default. -const defaultScreenshotOptions = () => ({ fullPage: true, encoding: 'base64' } as const); - -const noop: () => undefined = () => undefined; -const asyncNoop: () => Promise = async () => undefined; - -const defaultConfig: ImageSnapshotConfig = { - storybookUrl: 'http://localhost:6006', - chromeExecutablePath: undefined, - getMatchOptions: noop, - getScreenshotOptions: defaultScreenshotOptions, - beforeScreenshot: noop, - getGotoOptions: noop, - customizePage: asyncNoop, - getCustomBrowser: undefined, - setupTimeout: 15000, - testTimeout: 15000, -}; - export const imageSnapshot = (customConfig: Partial = {}) => { - const { - storybookUrl, - chromeExecutablePath, - getMatchOptions, - getScreenshotOptions, - beforeScreenshot, - getGotoOptions, - customizePage, - getCustomBrowser, - setupTimeout, - testTimeout, - } = { ...defaultConfig, ...customConfig }; - - let browser: Browser; // holds ref to browser. (ie. Chrome) - let page: Page; // Hold ref to the page to screenshot. - - const testFn = async ({ context }: any) => { - const { kind, framework, name } = context; - if (framework === 'react-native') { - // Skip tests since we de not support RN image snapshots. - logger.error( - "It seems you are running imageSnapshot on RN app and it's not supported. Skipping test." - ); - - return; - } - const url = constructUrl(storybookUrl, kind, name); - - if (!browser || !page) { - logger.error( - `Error when generating image snapshot for test ${kind} - ${name} : It seems the headless browser is not running.` - ); - - throw new Error('no-headless-browser-running'); - } - - expect.assertions(1); - - let image; - try { - await customizePage(page); - await page.goto(url, getGotoOptions({ context, url })); - await beforeScreenshot(page, { context, url }); - image = await page.screenshot(getScreenshotOptions({ context, url })); - } catch (e) { - logger.error( - `Error when connecting to ${url}, did you start or build the storybook first? A storybook instance should be running or a static version should be built when using image snapshot feature.` - ); - throw e; - } - - expect(image).toMatchImageSnapshot(getMatchOptions({ context, url })); - }; - testFn.timeout = testTimeout; - - testFn.afterAll = async () => { - if (getCustomBrowser && page) { - await page.close(); - } else if (browser) { - await browser.close(); - } - }; - - const beforeAll = async () => { - if (getCustomBrowser) { - browser = await getCustomBrowser(); - } else { - // eslint-disable-next-line global-require - const puppeteer = require('puppeteer'); - // add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507 - browser = await puppeteer.launch({ - args: ['--no-sandbox ', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], - executablePath: chromeExecutablePath, - }); - } - - page = await browser.newPage(); - }; - beforeAll.timeout = setupTimeout; - testFn.beforeAll = beforeAll; - - return testFn; + const config = { ...defaultImageSnapshotConfig, ...customConfig }; + const { getMatchOptions, getScreenshotOptions, beforeScreenshot } = config; + + return puppeteerTest({ + ...config, + async testBody(page, options) { + expect.assertions(1); + await beforeScreenshot(page, options); + const image = await page.screenshot(getScreenshotOptions(options)); + expect(image).toMatchImageSnapshot(getMatchOptions(options)); + }, + }); }; diff --git a/addons/storyshots/storyshots-puppeteer/src/index.ts b/addons/storyshots/storyshots-puppeteer/src/index.ts index f5c722b4283c..dd2e0b715fee 100644 --- a/addons/storyshots/storyshots-puppeteer/src/index.ts +++ b/addons/storyshots/storyshots-puppeteer/src/index.ts @@ -1,2 +1,4 @@ -export * from './ImageSnapshotConfig'; +export * from './config'; +export * from './puppeteerTest'; +export * from './axeTest'; export * from './imageSnapshot'; diff --git a/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts new file mode 100644 index 000000000000..bd59a5842601 --- /dev/null +++ b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts @@ -0,0 +1,87 @@ +import { Browser, Page } from 'puppeteer'; +import { logger } from '@storybook/node-logger'; +import { constructUrl } from './url'; +import { defaultPuppeteerTestConfig, PuppeteerTestConfig } from './config'; + +export const puppeteerTest = (customConfig: Partial = {}) => { + const { + storybookUrl, + chromeExecutablePath, + getGotoOptions, + customizePage, + getCustomBrowser, + testBody, + setupTimeout, + testTimeout, + } = { ...defaultPuppeteerTestConfig, ...customConfig }; + + let browser: Browser; // holds ref to browser. (ie. Chrome) + let page: Page; // Hold ref to the page to screenshot. + + const testFn = async ({ context }: any) => { + const { kind, framework, name } = context; + if (framework === 'react-native') { + // Skip tests since RN is not a browser environment. + logger.error( + "It seems you are running puppeteer test on RN app and it's not supported. Skipping test." + ); + + return; + } + const url = constructUrl(storybookUrl, kind, name); + + if (!browser || !page) { + logger.error( + `Error when running puppeteer test for ${kind} - ${name} : It seems the headless browser is not running.` + ); + + throw new Error('no-headless-browser-running'); + } + + try { + await customizePage(page); + await page.goto(url, getGotoOptions({ context, url })); + } catch (e) { + logger.error( + `Error when connecting to ${url}, did you start or build the storybook first? A storybook instance should be running or a static version should be built when using puppeteer test feature.` + ); + throw e; + } + await testBody(page, { context, url }); + }; + testFn.timeout = testTimeout; + + const cleanup = async () => { + if (getCustomBrowser && page) { + await page.close(); + } else if (browser) { + await browser.close(); + } + }; + + process.on('SIGINT', async () => { + await cleanup(); + process.exit(); + }); + testFn.afterAll = cleanup; + + const beforeAll = async () => { + if (getCustomBrowser) { + browser = await getCustomBrowser(); + } else { + // eslint-disable-next-line global-require + const puppeteer = require('puppeteer'); + // add some options "no-sandbox" to make it work properly on some Linux systems as proposed here: https://github.com/Googlechrome/puppeteer/issues/290#issuecomment-322851507 + browser = await puppeteer.launch({ + args: ['--no-sandbox ', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + executablePath: chromeExecutablePath, + }); + } + + page = await browser.newPage(); + }; + beforeAll.timeout = setupTimeout; + testFn.beforeAll = beforeAll; + + return testFn; +}; diff --git a/examples/official-storybook/package.json b/examples/official-storybook/package.json index 5ebab2fc34a7..977713f366c1 100644 --- a/examples/official-storybook/package.json +++ b/examples/official-storybook/package.json @@ -4,12 +4,12 @@ "private": true, "scripts": { "build-storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true build-storybook -c ./", - "do-image-snapshots": "../../node_modules/.bin/jest --projects=./image-snapshots", + "do-storyshots-puppeteer": "../../node_modules/.bin/jest --projects=./storyshots-puppeteer", "generate-addon-jest-testresults": "jest --config=tests/addon-jest.config.json --json --outputFile=stories/addon-jest.testresults.json", "graphql": "node ./graphql-server/index.js", - "image-snapshots": "yarn run build-storybook && yarn run do-image-snapshots", "packtracker": "yarn storybook --smoke-test --quiet && cross-env PT_PROJECT_TOKEN=1af1d41b-d737-41d4-ac00-53c8f3913b53 packtracker-upload --stats=./node_modules/.cache/storybook/manager-stats.json", - "storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll" + "storybook": "cross-env STORYBOOK_DISPLAY_WARNING=true DISPLAY_WARNING=true start-storybook -p 9011 -c ./ --no-dll", + "storyshots-puppeteer": "yarn run build-storybook && yarn run do-storyshots-puppeteer" }, "devDependencies": { "@packtracker/webpack-plugin": "^2.0.1", diff --git a/examples/official-storybook/stories/addon-storyshots.stories.js b/examples/official-storybook/stories/addon-storyshots.stories.js index 18eeb9eda95d..904ec48f807e 100644 --- a/examples/official-storybook/stories/addon-storyshots.stories.js +++ b/examples/official-storybook/stories/addon-storyshots.stories.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { styled } from '@storybook/theming'; const Block = styled.div({ @@ -12,4 +12,24 @@ export default { title: 'Addons/Storyshots', }; -export const block = () => ; +export const block = () => { + const [hover, setHover] = useState(false); + + return ( + setHover(true)} onMouseLeave={() => setHover(false)}> + {hover && 'I am hovered'} + + ); +}; +block.story = { + parameters: { + async puppeteerTest(page) { + const element = await page.$('[data-test-block]'); + await element.hover(); + const textContent = await element.getProperty('textContent'); + const text = await textContent.jsonValue(); + // eslint-disable-next-line jest/no-standalone-expect + expect(text).toBe('I am hovered'); + }, + }, +}; diff --git a/examples/official-storybook/image-snapshots/__image_snapshots__/storyshots-image-runner-js-image-snapshots-addons-storyshots-block-1-snap.png b/examples/official-storybook/storyshots-puppeteer/__image_snapshots__/storyshots-image-runner-js-image-snapshots-addons-storyshots-block-1-snap.png similarity index 100% rename from examples/official-storybook/image-snapshots/__image_snapshots__/storyshots-image-runner-js-image-snapshots-addons-storyshots-block-1-snap.png rename to examples/official-storybook/storyshots-puppeteer/__image_snapshots__/storyshots-image-runner-js-image-snapshots-addons-storyshots-block-1-snap.png diff --git a/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js b/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js new file mode 100644 index 000000000000..566e5916a272 --- /dev/null +++ b/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js @@ -0,0 +1,29 @@ +/* This file is not suffixed by ".test.js" to not being run with all other test files. + * This test needs the static build of the storybook to run. + * `yarn run storyshots-puppeteer` generates the static build & uses storyshots-puppeteer. + * */ +import path from 'path'; +import fs from 'fs'; +import initStoryshots from '@storybook/addon-storyshots'; +import { axeTest } from '@storybook/addon-storyshots-puppeteer'; +import { logger } from '@storybook/node-logger'; + +// We run puppeteer tests on the static build of the storybook. +// For this test to be meaningful, you must build the static version of the storybook *before* running this test suite. +const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); + +if (!fs.existsSync(pathToStorybookStatic)) { + logger.error( + 'You are running puppeteer tests without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' + ); +} else { + initStoryshots({ + suite: 'Puppeteer tests', + storyKindRegex: /^Basics|UI/, + framework: 'react', + configPath: path.join(__dirname, '..'), + test: axeTest({ + storybookUrl: `file://${pathToStorybookStatic}`, + }), + }); +} diff --git a/examples/official-storybook/image-snapshots/jest.config.js b/examples/official-storybook/storyshots-puppeteer/jest.config.js similarity index 75% rename from examples/official-storybook/image-snapshots/jest.config.js rename to examples/official-storybook/storyshots-puppeteer/jest.config.js index 8202dd461f06..427991f8b937 100644 --- a/examples/official-storybook/image-snapshots/jest.config.js +++ b/examples/official-storybook/storyshots-puppeteer/jest.config.js @@ -5,7 +5,7 @@ const finalJestConfig = { ...globalJestConfig }; finalJestConfig.rootDir = path.join(__dirname, '../../..'); finalJestConfig.testMatch = [ - '/examples/official-storybook/image-snapshots/storyshots-image.runner.js', + '/examples/official-storybook/storyshots-puppeteer/*.runner.js', ]; module.exports = finalJestConfig; diff --git a/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js b/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js new file mode 100644 index 000000000000..df15d10043eb --- /dev/null +++ b/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js @@ -0,0 +1,29 @@ +/* This file is not suffixed by ".test.js" to not being run with all other test files. + * This test needs the static build of the storybook to run. + * `yarn run storyshots-puppeteer` generates the static build & uses storyshots-puppeteer. + * */ +import path from 'path'; +import fs from 'fs'; +import initStoryshots from '@storybook/addon-storyshots'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; +import { logger } from '@storybook/node-logger'; + +// We run puppeteer tests on the static build of the storybook. +// For this test to be meaningful, you must build the static version of the storybook *before* running this test suite. +const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); + +if (!fs.existsSync(pathToStorybookStatic)) { + logger.error( + 'You are running puppeteer tests without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' + ); +} else { + initStoryshots({ + suite: 'Puppeteer tests', + storyKindRegex: /^Addons\/Storyshots/, + framework: 'react', + configPath: path.join(__dirname, '..'), + test: puppeteerTest({ + storybookUrl: `file://${pathToStorybookStatic}`, + }), + }); +} diff --git a/examples/official-storybook/image-snapshots/storyshots-image.runner.js b/examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js similarity index 92% rename from examples/official-storybook/image-snapshots/storyshots-image.runner.js rename to examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js index 7f95e14267ef..636408bc00d4 100644 --- a/examples/official-storybook/image-snapshots/storyshots-image.runner.js +++ b/examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js @@ -1,6 +1,6 @@ /* This file is not suffixed by ".test.js" to not being run with all other test files. * This test needs the static build of the storybook to run. - * `yarn run image-snapshots` generates the static build & uses the image snapshots behavior of storyshots. + * `yarn run storyshots-puppeteer` generates the static build & uses storyshots-puppeteer. * */ import path from 'path'; import fs from 'fs'; diff --git a/yarn.lock b/yarn.lock index 65f9e0abc156..3b74ade126e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2301,6 +2301,14 @@ dependencies: "@hapi/hoek" "^8.3.0" +"@hypnosphi/jest-puppeteer-axe@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@hypnosphi/jest-puppeteer-axe/-/jest-puppeteer-axe-1.4.0.tgz#aa7a348934178fcb41defb688ebd493970e3d660" + integrity sha512-sQ1BpqNE9C2d0afEtm3LLQWfQjITuxHXaLF79sGDUGa7/DPnfn2qgzcQOtL9uPu7KnZOz95dmsUgoHXkyQbrmQ== + dependencies: + "@babel/runtime" "^7.4.4" + axe-puppeteer "^1.0.0" + "@hypnosphi/jscodeshift@^0.6.4": version "0.6.4" resolved "https://registry.yarnpkg.com/@hypnosphi/jscodeshift/-/jscodeshift-0.6.4.tgz#49a3be6ac515af831f8a3e630380e3511c8c0fb7" @@ -5586,11 +5594,18 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -axe-core@^3.3.2: +axe-core@^3.1.2, axe-core@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.4.0.tgz#a57ee620c182d5389aff229586aaae06bc541abe" integrity sha512-5C0OdgxPv/DrQguO6Taj5F1dY5OlkWg4SVmZIVABFYKWlnAc5WTLPzG+xJSgIwf2fmY+NiNGiZXhXx2qT0u/9Q== +axe-puppeteer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/axe-puppeteer/-/axe-puppeteer-1.0.0.tgz#cebbeec2c65a2e0cb7d5fd1e7aef26c5f71895a4" + integrity sha512-hTF3u4mtatgTN7fsLVyVgbRdNc15ngjDcTEuqhn9A7ugqLhLCryJWp9fzqZkNlrW8awPcxugyTwLPR7mRdPZmA== + dependencies: + axe-core "^3.1.2" + axios-retry@^3.0.2: version "3.1.2" resolved "https://registry.yarnpkg.com/axios-retry/-/axios-retry-3.1.2.tgz#4f4dcbefb0b434e22b72bd5e28a027d77b8a3458" From 370285a106c58a75d3a238b800aa14262dd44d62 Mon Sep 17 00:00:00 2001 From: Filipp Riabchun Date: Sun, 24 Nov 2019 12:58:15 +0100 Subject: [PATCH 2/7] Add docs. Don't open story page is there are no tests defined in it --- addons/a11y/README.md | 2 + addons/storyshots/README.md | 2 +- .../storyshots/storyshots-puppeteer/README.md | 231 +++++++++++------- .../storyshots-puppeteer/src/config.ts | 37 ++- .../storyshots-puppeteer/src/puppeteerTest.ts | 9 +- 5 files changed, 171 insertions(+), 110 deletions(-) diff --git a/addons/a11y/README.md b/addons/a11y/README.md index d4d2638a9e6c..c6e541e4e109 100755 --- a/addons/a11y/README.md +++ b/addons/a11y/README.md @@ -44,6 +44,8 @@ storiesOf('button', module) )); ``` +## Parameters + For more customizability. Use the `addParameters` function to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure). You can override these options [at story level too](https://storybook.js.org/docs/configurations/options-parameter/#per-story-options). diff --git a/addons/storyshots/README.md b/addons/storyshots/README.md index c6d792f1cd7b..474bf391921a 100644 --- a/addons/storyshots/README.md +++ b/addons/storyshots/README.md @@ -1,4 +1,4 @@ # StoryShots - [addon-storyshots](storyshots-core) - Basic StoryShots api -- [addon-storyshots-puppeteer](storyshots-puppeteer) - Image Snapshots addition to StoryShots based on [puppeteer](https://github.com/GoogleChrome/puppeteer) +- [addon-storyshots-puppeteer](storyshots-puppeteer) - Integration of StoryShots with [puppeteer](https://github.com/GoogleChrome/puppeteer) diff --git a/addons/storyshots/storyshots-puppeteer/README.md b/addons/storyshots/storyshots-puppeteer/README.md index c56afe0132cd..7012559315ca 100644 --- a/addons/storyshots/storyshots-puppeteer/README.md +++ b/addons/storyshots/storyshots-puppeteer/README.md @@ -1,33 +1,50 @@ +# StoryShots + [Puppeteer](https://github.com/GoogleChrome/puppeteer) + ## Getting Started -Add the following module into your app. +Add the following modules into your app. ```sh -npm install @storybook/addon-storyshots-puppeteer --save-dev +npm install @storybook/addon-storyshots-puppeteer puppeteer --save-dev ``` -## Configure Storyshots for image snapshots +## Configure Storyshots for Puppeteeer tests /\*\ **React-native** is **not supported** by this test function. -Internally, it uses [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot). - -When willing to generate and compare image snapshots for your stories, you have two options: +When willing to run Puppeteer tests for your stories, you have two options: - Have a storybook running (ie. accessible via http(s), for instance using `npm run storybook`) - Have a static build of the storybook (for instance, using `npm run build-storybook`) Then you will need to reference the storybook URL (`file://...` if local, `http(s)://...` if served) -### Using default values for _imageSnapshots_ +## _puppeteerTest_ +Allows to define arbitrary Puppeteer tests as `story.parameters.puppeteerTest` function. -Then you can either create a new Storyshots instance or edit the one you previously used: +You can either create a new Storyshots instance or edit the one you previously used: ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; -initStoryshots({ suite: 'Image storyshots', test: imageSnapshot() }); +initStoryshots({ suite: 'Puppeteer storyshots', test: puppeteerTest() }); +``` + +Then, in your stories: +```js +export const myExample = () => { + ... +}; +myExample.story = { + parameters: { + async puppeteerTest(page) { + const element = await page.$(''); + await element.click(); + expect(something).toBe(something); + }, + }, +}; ``` This will assume you have a storybook running on at __. @@ -35,7 +52,7 @@ Internally here are the steps: - Launches a Chrome headless using [puppeteer](https://github.com/GoogleChrome/puppeteer) - Browses each stories (calling __ URL), -- Take screenshots & save all images under \_\_image_snapshots\_\_ folder. +- Runs the `parameters.puppeteerTest` function if it's defined. ### Specifying the storybook URL @@ -43,59 +60,29 @@ If you want to set specific storybook URL, you can specify via the `storybookUrl ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://my-specific-domain.com:9010' }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://my-specific-domain.com:9010' }), }); ``` -The above config will use __ for screenshots. You can also use query parameters in your URL (e.g. for setting a different background for your storyshots, if you use `@storybook/addon-backgrounds`). +The above config will use __ for tests. You can also use query parameters in your URL (e.g. for setting a different background for your storyshots, if you use `@storybook/addon-backgrounds`). You may also use a local static build of storybook if you do not want to run the webpack dev-server: ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; - -initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'file:///path/to/my/storybook-static' }), -}); -``` +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; -### Specifying options to _jest-image-snapshots_ - -If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured. - -```js -import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; -const getMatchOptions = ({ context: { kind, story }, url }) => { - return { - failureThreshold: 0.2, - failureThresholdType: 'percent', - }; -}; -const beforeScreenshot = (page, { context: { kind, story }, url }) => { - return new Promise(resolve => - setTimeout(() => { - resolve(); - }, 600) - ); -}; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'file:///path/to/my/storybook-static' }), }); ``` -`getMatchOptions` receives an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. - -`beforeScreenshot` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeScreenshot` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the screenshot and can be used avoid regressions due to mounting animations. - -### Specifying options to _goto()_ (puppeteer API) +### Specifying options to _goto()_ (Puppeteer API) You might use `getGotoOptions` to specify options when the storybook is navigating to a story (using the `goto` method). Will be passed to [Puppeteer .goto() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options) @@ -108,63 +95,42 @@ const getGotoOptions = ({ context, url }) => { }; }; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getGotoOptions }), -}); -``` - -### Specifying options to _screenshot()_ (puppeteer API) - -You might use `getScreenshotOptions` to specify options for screenshot. Will be passed to [Puppeteer .screenshot() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions) - -```js -import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; -const getScreenshotOptions = ({ context, url }) => { - return { - encoding: 'base64', // encoding: 'base64' is a property required by puppeteer - fullPage: false, // Do not take the full page screenshot. Default is 'true' in Storyshots., - }; -}; -initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getScreenshotOptions }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', getGotoOptions }), }); ``` -`getScreenshotOptions` receives an object `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. - -### Specifying custom Chrome executable path (puppeteer API) +### Specifying custom Chrome executable path (Puppeteer API) You might use `chromeExecutablePath` to specify the path to a different version of Chrome, without downloading Chromium. Will be passed to [Runs a bundled version of Chromium](https://github.com/GoogleChrome/puppeteer#default-runtime-settings) ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; const chromeExecutablePath = '/usr/local/bin/chrome'; initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ storybookUrl: 'http://localhost:6006', chromeExecutablePath }), + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', chromeExecutablePath }), }); ``` -### Specifying a custom puppeteer `browser` instance +### Specifying a custom Puppeteer `browser` instance -You might use the async `getCustomBrowser` function to obtain a custom instance of a puppeteer `browser` object. This will prevent `storyshots-puppeteer` from creating its own `browser`. It will create and close pages within the `browser`, and it is your responsibility to manage the lifecycle of the `browser` itself. +You might use the async `getCustomBrowser` function to obtain a custom instance of a Puppeteer `browser` object. This will prevent `storyshots-puppeteer` from creating its own `browser`. It will create and close pages within the `browser`, and it is your responsibility to manage the lifecycle of the `browser` itself. ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; import puppeteer from 'puppeteer'; (async function() { initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', - getCustomBrowser: async () => puppeteer.connect({ browserWSEndpoint: 'ws://yourUrl' }), + getCustomBrowser: () => puppeteer.connect({ browserWSEndpoint: 'ws://yourUrl' }), }), }); })(); @@ -178,7 +144,7 @@ An example of device emulation: ```js import initStoryshots from '@storybook/addon-storyshots'; -import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; const devices = require('puppeteer/DeviceDescriptors'); const iPhone = devices['iPhone 6']; @@ -188,8 +154,8 @@ function customizePage(page) { } initStoryshots({ - suite: 'Image storyshots', - test: imageSnapshot({ + suite: 'Puppeteer storyshots', + test: puppeteerTest({ storybookUrl: 'http://localhost:6006', customizePage, }), @@ -201,12 +167,12 @@ initStoryshots({ By default, `@storybook/addon-storyshots-puppeteer` uses 15 second timeouts for browser setup and test functions. Those can be customized with `setupTimeout` and `testTimeout` parameters. -### Integrate image storyshots with regular app +### Integrate Puppeteer storyshots with regular app -You may want to use another Jest project to run your image snapshots as they require more resources: Chrome and Storybook built/served. +You may want to use another Jest project to run your Puppeteer storyshots as they require more resources: Chrome and Storybook built/served. You can find a working example of this in the [official-storybook](https://github.com/storybookjs/storybook/tree/master/examples/official-storybook) example. -### Integrate image storyshots with [Create React App](https://github.com/facebookincubator/create-react-app) +### Integrate Puppeteer storyshots with [Create React App](https://github.com/facebookincubator/create-react-app) You have two options here, you can either: @@ -214,30 +180,105 @@ You have two options here, you can either: - Create a custom test file using Jest outside of the CRA scope: - A more robust approach would be to separate existing test files ran by create-react-app (anything `(test|spec).js` suffixed files) from the test files to run storyshots with image snapshots. - This use case can be achieved by using a custom name for the test file, ie something like `image-storyshots.runner.js`. This file will contains the `initStoryshots` call with image snapshots configuration. + A more robust approach would be to separate existing test files ran by create-react-app (anything `(test|spec).js` suffixed files) from the test files to run Puppeteer storyshots. + This use case can be achieved by using a custom name for the test file, ie something like `puppeteer-storyshots.runner.js`. This file will contain the `initStoryshots` call with Puppeteer storyshots configuration. Then you will create a separate script entry in your package.json, for instance ```json { "scripts": { - "image-snapshots": "jest image-storyshots.runner.js --config path/to/custom/jest.config.json" + "puppeteer-storyshots": "jest puppeteer-storyshots.runner.js --config path/to/custom/jest.config.json" } } ``` Note that you will certainly need a custom config file for Jest as you run it outside of the CRA scope and thus you do not have the built-in config. - Once that's setup, you can run `npm run image-snapshots`. + Once that's setup, you can run `npm run puppeteer-storyshots`. ### Reminder -An image snapshot is a screenshot taken by a web browser (in our case, Chrome). +Puppeteer launches a web browser (Chrome) internally. The browser opens a page (either using the static build of storybook or a running instance of Storybook) If you run your test without either the static build or a running instance, this wont work. -To make sure your screenshots are taken from latest changes of your Storybook, you must keep your static build or running Storybook up-to-date. +To make sure your tests run against the latest changes of your Storybook, you must keep your static build or running Storybook up-to-date. This can be achieved by adding a step before running the test ie: `npm run build-storybook && npm run image-snapshots`. -If you run the image snapshots against a running Storybook in dev mode, you don't have to worry about the snapshots being up-to-date because the dev-server is watching changes and rebuilds automatically. +If you run the Puppeteer storyshots against a running Storybook in dev mode, you don't have to worry about the stories being up-to-date because the dev-server is watching changes and rebuilds automatically. + +## _axeTest_ +Runs [Axe](https://www.deque.com/axe/) accessibility checks and verifies that they pass using [jest-puppeteer-axe](https://github.com/WordPress/gutenberg/tree/master/packages/jest-puppeteer-axe). + +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { axeTest } from '@storybook/addon-storyshots-puppeteer'; + +axeTest({ suite: 'A11y checks', test: axeTest() }); +``` + +For configuration, it uses the same `story.parameters.a11y` parameter as [`@storybook/addon-a11y`](https://github.com/storybookjs/storybook/tree/next/addons/a11y#parameters) + +## _imageSnapshots_ +Generates and compares screenshots of your stories using [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot). + +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; + +initStoryshots({ suite: 'Image storyshots', test: imageSnapshot() }); +``` + +It saves all images under \_\_image_snapshots\_\_ folder. + +### Specifying options to _jest-image-snapshots_ + +If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured. + +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +const getMatchOptions = ({ context: { kind, story }, url }) => { + return { + failureThreshold: 0.2, + failureThresholdType: 'percent', + }; +}; +const beforeScreenshot = (page, { context: { kind, story }, url }) => { + return new Promise(resolve => + setTimeout(() => { + resolve(); + }, 600) + ); +}; +initStoryshots({ + suite: 'Image storyshots', + test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getMatchOptions, beforeScreenshot }), +}); +``` + +`getMatchOptions` receives an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. + +`beforeScreenshot` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeScreenshot` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the screenshot and can be used avoid regressions due to mounting animations. + +### Specifying options to _screenshot()_ (Puppeteer API) + +You might use `getScreenshotOptions` to specify options for screenshot. Will be passed to [Puppeteer .screenshot() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions) + +```js +import initStoryshots from '@storybook/addon-storyshots'; +import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; +const getScreenshotOptions = ({ context, url }) => { + return { + encoding: 'base64', // encoding: 'base64' is a property required by puppeteer + fullPage: false, // Do not take the full page screenshot. Default is 'true' in Storyshots., + }; +}; +initStoryshots({ + suite: 'Image storyshots', + test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getScreenshotOptions }), +}); +``` + +`getScreenshotOptions` receives an object `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. diff --git a/addons/storyshots/storyshots-puppeteer/src/config.ts b/addons/storyshots/storyshots-puppeteer/src/config.ts index bb9aa5815f85..df3d70c46a16 100644 --- a/addons/storyshots/storyshots-puppeteer/src/config.ts +++ b/addons/storyshots/storyshots-puppeteer/src/config.ts @@ -9,10 +9,15 @@ export interface Context { }; } +interface Options { + context: Context; + url: string; +} + export interface CommonConfig { storybookUrl: string; chromeExecutablePath: string; - getGotoOptions: (options: { context: Context; url: string }) => DirectNavigationOptions; + getGotoOptions: (options: Options) => DirectNavigationOptions; customizePage: (page: Page) => Promise; getCustomBrowser: () => Promise; setupTimeout: number; @@ -20,13 +25,15 @@ export interface CommonConfig { } export interface PuppeteerTestConfig extends CommonConfig { - testBody: (page: Page, options: { context: Context; url: string }) => void | Promise; + testBody: ((page: Page, options: Options) => void | Promise) & { + filter?: (options: Options) => boolean; + }; } export interface ImageSnapshotConfig extends CommonConfig { - getMatchOptions: (options: { context: Context; url: string }) => MatchImageSnapshotOptions; - getScreenshotOptions: (options: { context: Context; url: string }) => Base64ScreenShotOptions; - beforeScreenshot: (page: Page, options: { context: Context; url: string }) => void; + getMatchOptions: (options: Options) => MatchImageSnapshotOptions; + getScreenshotOptions: (options: Options) => Base64ScreenShotOptions; + beforeScreenshot: (page: Page, options: Options) => void; } const noop: () => undefined = () => undefined; @@ -42,15 +49,21 @@ export const defaultCommonConfig: CommonConfig = { testTimeout: 15000, }; +const getTestBody = (options: Options) => options.context.parameters.puppeteerTest; + +function defaultTestBody(page: Page, options: Options) { + const testBody = getTestBody(options); + if (testBody != null) { + return testBody(page, options); + } + return null; +} + +defaultTestBody.filter = (options: Options) => getTestBody(options) != null; + export const defaultPuppeteerTestConfig: PuppeteerTestConfig = { ...defaultCommonConfig, - testBody(page, options) { - const testBody = options.context.parameters.puppeteerTest; - if (testBody != null) { - return testBody(page, options); - } - return null; - }, + testBody: defaultTestBody, }; // We consider taking the full page is a reasonable default. diff --git a/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts index bd59a5842601..49083c46ea66 100644 --- a/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts +++ b/addons/storyshots/storyshots-puppeteer/src/puppeteerTest.ts @@ -28,7 +28,12 @@ export const puppeteerTest = (customConfig: Partial = {}) = return; } + const url = constructUrl(storybookUrl, kind, name); + const options = { context, url }; + if (testBody.filter != null && !testBody.filter(options)) { + return; + } if (!browser || !page) { logger.error( @@ -40,14 +45,14 @@ export const puppeteerTest = (customConfig: Partial = {}) = try { await customizePage(page); - await page.goto(url, getGotoOptions({ context, url })); + await page.goto(url, getGotoOptions(options)); } catch (e) { logger.error( `Error when connecting to ${url}, did you start or build the storybook first? A storybook instance should be running or a static version should be built when using puppeteer test feature.` ); throw e; } - await testBody(page, { context, url }); + await testBody(page, options); }; testFn.timeout = testTimeout; From 9ee47eb7fde150f81171150ce7048b3702f45375 Mon Sep 17 00:00:00 2001 From: Filipp Riabchun Date: Sun, 24 Nov 2019 16:50:53 +0100 Subject: [PATCH 3/7] Add CI job --- .github/workflows/tests-puppeteer.yml | 32 ++++++++++++++++++++++++ examples/official-storybook/package.json | 4 +-- lib/api/src/version.ts | 2 +- scripts/test.js | 8 +++--- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/tests-puppeteer.yml diff --git a/.github/workflows/tests-puppeteer.yml b/.github/workflows/tests-puppeteer.yml new file mode 100644 index 000000000000..e7590c6e644d --- /dev/null +++ b/.github/workflows/tests-puppeteer.yml @@ -0,0 +1,32 @@ +name: Puppeteer & A11y tests + +on: [push] + +jobs: + build: + + name: Puppeteer & A11y tests + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v1 + with: + node-version: '10.x' + - uses: actions/checkout@v1 + - name: Cache node modules + uses: actions/cache@v1 + with: + path: node_modules + key: ${{ runner.OS }}-build-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.OS }}-build-${{ env.cache-name }}- + ${{ runner.OS }}-build- + ${{ runner.OS }}- + - name: install, bootstrap + run: | + yarn bootstrap --core + - name: build storybook + run: | + yarn --cwd examples/official-storybook build-storybook + - name: test + run: | + yarn test --puppeteer diff --git a/examples/official-storybook/package.json b/examples/official-storybook/package.json index 069968b39281..628e6255c078 100644 --- a/examples/official-storybook/package.json +++ b/examples/official-storybook/package.json @@ -54,6 +54,7 @@ "lodash": "^4.17.15", "paths.macro": "^2.0.2", "prop-types": "^15.7.2", + "puppeteer": "^2.0.0", "react": "^16.8.3", "react-dom": "^16.8.3", "storybook-chromatic": "^3.0.0", @@ -61,8 +62,5 @@ "ts-loader": "^6.0.0", "uuid": "^3.3.2", "webpack": "^4.33.0" - }, - "optionalDependencies": { - "puppeteer": "^2.0.0" } } diff --git a/lib/api/src/version.ts b/lib/api/src/version.ts index 7ea61ab7e509..da0b027db8cc 100644 --- a/lib/api/src/version.ts +++ b/lib/api/src/version.ts @@ -1 +1 @@ -export const version = '5.3.0-beta.5'; +export const version = '5.3.0-beta.6'; diff --git a/scripts/test.js b/scripts/test.js index 60f58c62c232..dc936233bfa9 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -50,11 +50,11 @@ const tasks = { projectLocation: '', isJest: true, }), - image: createProject({ - name: `Image snapshots for Official storybook ${chalk.gray('(image)')}`, + puppeteer: createProject({ + name: `Puppeteer and A11y tests for Official storybook ${chalk.gray('(puppeteer)')}`, defaultValue: false, - option: '--image', - projectLocation: path.join(__dirname, '..', 'examples/official-storybook/image-snapshots'), + option: '--puppeteer', + projectLocation: path.join(__dirname, '..', 'examples/official-storybook/storyshots-puppeteer'), isJest: true, }), cli: createProject({ From 4439243f56ecf194add8fcf762bd005d998c3cd0 Mon Sep 17 00:00:00 2001 From: Filipp Riabchun Date: Sun, 24 Nov 2019 20:00:51 +0100 Subject: [PATCH 4/7] Fix some of the a11y violations --- .../storyshots-puppeteer/axe-tests.runner.js | 18 ++----- .../storyshots-puppeteer/getStorybookUrl.js | 18 +++++++ .../puppeteer-tests.runner.js | 18 ++----- .../storyshots-image.runner.js | 17 ++----- lib/components/src/Button/Button.stories.tsx | 4 +- lib/components/src/ScrollArea/ScrollArea.tsx | 6 +-- lib/components/src/form/form.stories.tsx | 50 +++++++++++-------- lib/components/src/tabs/tabs.stories.tsx | 4 +- lib/components/src/tabs/tabs.tsx | 2 +- .../src/typography/link/link.stories.tsx | 2 +- .../src/components/sidebar/SidebarHeading.tsx | 4 +- .../sidebar/SidebarSearch.stories.tsx | 2 +- lib/ui/src/settings/about.js | 1 + 13 files changed, 73 insertions(+), 73 deletions(-) create mode 100644 examples/official-storybook/storyshots-puppeteer/getStorybookUrl.js diff --git a/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js b/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js index 566e5916a272..05dde656719d 100644 --- a/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js +++ b/examples/official-storybook/storyshots-puppeteer/axe-tests.runner.js @@ -3,27 +3,17 @@ * `yarn run storyshots-puppeteer` generates the static build & uses storyshots-puppeteer. * */ import path from 'path'; -import fs from 'fs'; import initStoryshots from '@storybook/addon-storyshots'; import { axeTest } from '@storybook/addon-storyshots-puppeteer'; -import { logger } from '@storybook/node-logger'; +import getStorybookUrl from './getStorybookUrl'; -// We run puppeteer tests on the static build of the storybook. -// For this test to be meaningful, you must build the static version of the storybook *before* running this test suite. -const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); - -if (!fs.existsSync(pathToStorybookStatic)) { - logger.error( - 'You are running puppeteer tests without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' - ); -} else { +const storybookUrl = getStorybookUrl(); +if (storybookUrl != null) { initStoryshots({ suite: 'Puppeteer tests', storyKindRegex: /^Basics|UI/, framework: 'react', configPath: path.join(__dirname, '..'), - test: axeTest({ - storybookUrl: `file://${pathToStorybookStatic}`, - }), + test: axeTest({ storybookUrl }), }); } diff --git a/examples/official-storybook/storyshots-puppeteer/getStorybookUrl.js b/examples/official-storybook/storyshots-puppeteer/getStorybookUrl.js new file mode 100644 index 000000000000..913acc6c592c --- /dev/null +++ b/examples/official-storybook/storyshots-puppeteer/getStorybookUrl.js @@ -0,0 +1,18 @@ +import path from 'path'; +import fs from 'fs'; +import { logger } from '@storybook/node-logger'; + +export default function getStorybookUrl() { + if (process.env.USE_DEV_SERVER) { + return 'http://localhost:9011'; + } + + const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); + if (!fs.existsSync(pathToStorybookStatic)) { + logger.error( + 'You are running puppeteer tests without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' + ); + return null; + } + return `file://${pathToStorybookStatic}`; +} diff --git a/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js b/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js index df15d10043eb..df5db691816a 100644 --- a/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js +++ b/examples/official-storybook/storyshots-puppeteer/puppeteer-tests.runner.js @@ -3,27 +3,17 @@ * `yarn run storyshots-puppeteer` generates the static build & uses storyshots-puppeteer. * */ import path from 'path'; -import fs from 'fs'; import initStoryshots from '@storybook/addon-storyshots'; import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'; -import { logger } from '@storybook/node-logger'; +import getStorybookUrl from './getStorybookUrl'; -// We run puppeteer tests on the static build of the storybook. -// For this test to be meaningful, you must build the static version of the storybook *before* running this test suite. -const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); - -if (!fs.existsSync(pathToStorybookStatic)) { - logger.error( - 'You are running puppeteer tests without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' - ); -} else { +const storybookUrl = getStorybookUrl(); +if (storybookUrl != null) { initStoryshots({ suite: 'Puppeteer tests', storyKindRegex: /^Addons\/Storyshots/, framework: 'react', configPath: path.join(__dirname, '..'), - test: puppeteerTest({ - storybookUrl: `file://${pathToStorybookStatic}`, - }), + test: puppeteerTest({ storybookUrl }), }); } diff --git a/examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js b/examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js index 636408bc00d4..8f69249353de 100644 --- a/examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js +++ b/examples/official-storybook/storyshots-puppeteer/storyshots-image.runner.js @@ -3,28 +3,19 @@ * `yarn run storyshots-puppeteer` generates the static build & uses storyshots-puppeteer. * */ import path from 'path'; -import fs from 'fs'; import initStoryshots from '@storybook/addon-storyshots'; import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer'; -import { logger } from '@storybook/node-logger'; +import getStorybookUrl from './getStorybookUrl'; -// Image snapshots -// We do screenshots against the static build of the storybook. -// For this test to be meaningful, you must build the static version of the storybook *before* running this test suite. -const pathToStorybookStatic = path.join(__dirname, '../', 'storybook-static'); - -if (!fs.existsSync(pathToStorybookStatic)) { - logger.error( - 'You are running image snapshots without having the static build of storybook. Please run "yarn run build-storybook" before running tests.' - ); -} else { +const storybookUrl = getStorybookUrl(); +if (storybookUrl != null) { initStoryshots({ suite: 'Image snapshots', storyKindRegex: /^Addons\/Storyshots/, framework: 'react', configPath: path.join(__dirname, '..'), test: imageSnapshot({ - storybookUrl: `file://${pathToStorybookStatic}`, + storybookUrl, getMatchOptions: () => ({ failureThreshold: 0.02, // 2% threshold, failureThresholdType: 'percent', diff --git a/lib/components/src/Button/Button.stories.tsx b/lib/components/src/Button/Button.stories.tsx index 2d6f048a3fb1..9eb18d888821 100644 --- a/lib/components/src/Button/Button.stories.tsx +++ b/lib/components/src/Button/Button.stories.tsx @@ -15,7 +15,7 @@ storiesOf('Basics/Button', module).add('all buttons', () => (

Buttons that are used for everything else

-
@@ -42,7 +42,7 @@ storiesOf('Basics/Button', module).add('all buttons', () => ( - ))} @@ -65,7 +75,7 @@ storiesOf('Basics/Form/Button', module) .add('validations', () => ( {['error', 'warn', 'valid', null].map(valid => ( - + @@ -78,7 +88,7 @@ storiesOf('Basics/Form/Textarea', module) .add('sizes', () => ( {['auto', 'flex', '100%'].map(size => ( - +