diff --git a/.npmignore b/.npmignore index e0de789c96..933438a0cd 100644 --- a/.npmignore +++ b/.npmignore @@ -8,7 +8,7 @@ build .travis.yml renovate.json yarn.lock -/scripts + .changelog examples docs @@ -21,3 +21,5 @@ loaders # Since parcel users will use the transpiled version of cozy-ui # that has already been processed by postcss, it is not necessary. postcss.config.js + +screenshots/ diff --git a/docs/README.md b/docs/README.md index 9f385a0c7d..f198911cfc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -83,6 +83,26 @@ yarn build:doc:react yarn deploy:doc --repo git@github.com:USERNAME/cozy-ui.git ``` +## UI regression testing + +Components in `cozy-ui` are showcased with [React Styleguidist][]. To prevent UI regressions, +for each PR, each component is screenshotted and compared to the master version to find any +regression (thanks [Argos][] !). + +If your app uses [React Styleguidist][], `cozy-ui` provides `rsg-screenshots`, a CLI tool to take +screenshots of your components (uses Puppeteer under the hood). + +```bash +$ yarn add cozy-ui +$ # The rsg-screenshots binary is now installed +$ yarn build:doc:react # Build our styleguide, the output directory is docs/react +$ rsg-screenshots --screenshot-dir screenshots/ --styleguide-dir docs/react +# Each component in the styleguide will be screenshotted and saved inside the +# screenshots/ directory +``` + +See our [travis configuration](./travis.yml) for more information. + ## License Cozy UI is developed by Cozy Cloud and distributed under the AGPL-3.0 license. @@ -107,3 +127,5 @@ You can reach the Cozy Community by: * Mentioning us on [Twitter](https://twitter.com/cozycloud) [stylus]: http://stylus-lang.com/ +[React Styleguidist]: https://react-styleguidist.js.org/ +[Argos]: https://github.com/argos-ci/argos diff --git a/package.json b/package.json index 34ee7852d2..24c564f9da 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "0.0.0-development", "description": "Cozy apps UI SDK", "main": "./index.js", + "bin": { + "rsg-screenshots": "./scripts/screenshots.js" + }, "repository": { "type": "git", "url": "https://github.com/cozy/cozy-ui.git" @@ -42,7 +45,7 @@ "semantic-release": "semantic-release", "sprite": "scripts/make-icon-sprite.sh", "jest": "yarn test:jest", - "screenshots": "node scripts/screenshots.js", + "screenshots": "node scripts/screenshots.js --screenshot-dir ./screenshots --styleguide-dir ./build/react", "test": "yarn test:jest", "test:jest": "jest --verbose", "transpile": "env BABEL_ENV=transpilation babel react/ --out-dir transpiled/react --verbose", @@ -133,6 +136,7 @@ "react-select": "2.2.0" }, "peerDependencies": { + "puppeteer": "1.20.0", "@material-ui/core": "3.9.3", "cozy-client": "*", "cozy-device-helper": "1.7.5", diff --git a/scripts/screenshots.js b/scripts/screenshots.js index 94862f5448..f407408373 100644 --- a/scripts/screenshots.js +++ b/scripts/screenshots.js @@ -1,7 +1,17 @@ -const puppeteer = require('puppeteer') +#!/usr/bin/env node + +let puppeteer +try { + puppeteer = require('puppeteer') +} catch (e) { + console.error(e) + console.log('Could not import puppeteer, you should install it if you want to take screenshots') + process.exit(1) +} const path = require('path') const fs = require('fs') const sortBy = require('lodash/sortBy') +const { ArgumentParser } = require('argparse') const emptyDirectory = directory => { for (const filename of fs.readdirSync(directory)) { @@ -95,22 +105,34 @@ const prepareBrowser = async () => { return { browser, page } } +const pathArgument = p => { + if (p.startsWith('/')) { + return p + } else { + return path.join(process.cwd(), p) + } +} + /** * Fetches all components from styleguide and takes a screenshot of each. */ const main = async () => { - const SCREENSHOT_DIR = path.join(__dirname, '../screenshots') - const STYLEGUIDE_DIR = path.join(__dirname, '../build/react') - - await prepareFS(STYLEGUIDE_DIR, SCREENSHOT_DIR) + const parser = new ArgumentParser() + + parser.addArgument('--screenshot-dir', { required: true, dest: 'screenshotDir', type: pathArgument }) + parser.addArgument('--styleguide-dir', { required: true, dest: 'styleguideDir', type: pathArgument }) + + const args = parser.parseArgs() + + await prepareFS(args.styleguideDir, args.screenshotDir) const { browser, page } = await prepareBrowser() - const styleguideIndexURL = `file://${STYLEGUIDE_DIR}/index.html` + const styleguideIndexURL = `file://${path.join(args.styleguideDir, '/index.html')}` const components = await fetchAllComponents(page, styleguideIndexURL) console.log('Screenshotting all components') for (const component of components) { - await screenshotComponent(page, component, SCREENSHOT_DIR) + await screenshotComponent(page, component, args.screenshotDir) } await browser.close()