Skip to content

Commit

Permalink
feat: Automated visual regression tests (#1157)
Browse files Browse the repository at this point in the history
feat: Automated visual regression tests
  • Loading branch information
ptbrowne authored Sep 17, 2019
2 parents 3cf8107 + c74a80f commit 4696fa2
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 16 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ desktop.ini
.cache
transpiled

# Generated files
react/Icon/icons-sprite.js
react/palette.js
screenshots/
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ script:
- yarn lint
- yarn test
- yarn build:css:all
- yarn build:doc:react
- yarn screenshots
- yarn argos --token $ARGOS_TOKEN --branch $TRAVIS_BRANCH --commit $TRAVIS_COMMIT
after_success:
- test $TRAVIS_BRANCH = "master" && test $TRAVIS_REPO_SLUG = "cozy/cozy-ui" && yarn
build:doc && yarn deploy:doc -- --username cozycloud --email [email protected]
build:doc:kss && yarn deploy:doc -- --username cozycloud --email [email protected]
--repo=https://[email protected]/cozy/cozy-ui.git
- test $TRAVIS_BRANCH = "master" && test $TRAVIS_REPO_SLUG = "cozy/cozy-ui" && yarn travis-deploy-once "yarn semantic-release"

Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"homepage": "https://github.com/cozy/cozy-ui",
"scripts": {
"argos": "argos upload screenshots/",
"build:css:all": "yarn build:css && yarn build:css:utils",
"build:css": "env CSSMODULES=false stylus -c --include stylus -o dist/cozy-ui.min.css stylus/cozy-ui/build.styl --use ./node_modules/autoprefixer-stylus",
"build:css:utils": "env CSSMODULES=false stylus -c --include stylus -o dist/cozy-ui.utils.min.css stylus/cozy-ui/utils.styl --use ./node_modules/autoprefixer-stylus",
Expand All @@ -40,6 +41,7 @@
"semantic-release": "semantic-release",
"sprite": "scripts/make-icon-sprite.sh",
"jest": "yarn test:jest",
"screenshots": "node scripts/screenshots.js",
"test": "yarn test:jest",
"test:jest": "jest --verbose",
"transpile": "env BABEL_ENV=transpilation babel react/ --out-dir transpiled/react --verbose",
Expand Down Expand Up @@ -116,12 +118,14 @@
},
"dependencies": {
"@babel/runtime": "^7.3.4",
"argos-cli": "0.1.1",
"body-scroll-lock": "^2.5.8",
"classnames": "^2.2.5",
"date-fns": "^1.28.5",
"hammerjs": "^2.0.8",
"node-polyglot": "^2.2.2",
"normalize.css": "^7.0.0",
"puppeteer": "1.20.0",
"react-hot-loader": "^4.3.11",
"react-markdown": "^4.0.8",
"react-pdf": "^4.0.5",
Expand Down
140 changes: 140 additions & 0 deletions scripts/screenshots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
const puppeteer = require('puppeteer')
const path = require('path')
const fs = require('fs')
const sortBy = require('lodash/sortBy')

const DEFAULT_PUPPETEER_VIEWPORT = {
width: 800,
height: 600
}

const emptyDirectory = directory => {
for (const filename of fs.readdirSync(directory)) {
fs.unlinkSync(path.join(directory, filename))
}
}

/**
* Screenshot a component to the screenshot directory, taking care of
* resizing the viewport before-hand so that the viewport fits the
* component.
*/
const screenshotComponent = async (page, { link, name }, screenshotDir) => {
await page.goto(link, { waitUntil: 'load', timeout: 0 })

// Getting the dimensions of the root wrapper
const rootBcr = await page.evaluate(() => {
const { width, height } = document
.querySelector('#rsg-root')
.getBoundingClientRect()
return { width, height }
})

// Fitting the viewport to the component
await page.setViewport({
width: Math.ceil(rootBcr.width),
height: Math.ceil(rootBcr.height)
})

console.log(`Screenshotting ${name} at ${rootBcr.width}x${rootBcr.height}`)
await page.screenshot({
path: path.join(screenshotDir, `${name}.png`),
fullPage: true
})

// Reset dimensions otherwise the page stays at the same dimensions as the
// tallest/widest component page
await page.setViewport(DEFAULT_PUPPETEER_VIEWPORT)
}

/**
* Fetch all available components on the styleguide and returns an array
* of { name, link } describing each component.
* Components are sorted by name.
*/
const fetchAllComponents = async (page, styleguideIndexURL) => {
console.log(`Opening page ${styleguideIndexURL}`)
await page.goto(styleguideIndexURL, {
waitUntil: 'load',
timeout: 0
})

console.log('Extracting links')
const links = await page.evaluate(() => {
return Array.from(
document.querySelectorAll('h2 + * [title="Open isolated"]')
).map(x => x.href)
})

return sortBy(
links.map(link => ({
link,
name: link.split('/').slice(-1)[0]
})),
x => x.name
)
}

/**
* Ensure directories are ready for taking screenshots.
*
* - Throws if styleguide has not been built
* - Creates the screenshot dir if it does not exist
* - Empties the screenshot dir if it exists
*
*/
const prepareFS = async (styleguideDir, screenshotDir) => {
if (!fs.existsSync(styleguideDir)) {
throw new Error(
`Styleguide does not seem to have been built (searching in ${styleguideDir}). Please run yarn build:doc:react.`
)
}

if (!fs.existsSync(screenshotDir)) {
console.log(`Creating screenshot directory ${screenshotDir}`)
fs.mkdirSync(screenshotDir)
} else {
console.log(`Emptying screenshot directory ${screenshotDir}`)
emptyDirectory(screenshotDir)
}
}

/**
* Opens and configure browser and page.
* Returns { browser, page }
*/
const prepareBrowser = async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setDefaultNavigationTimeout(0)
console.log('Browser opened and set up')
return { browser, page }
}

/**
* 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 { browser, page } = await prepareBrowser()

const styleguideIndexURL = `file://${STYLEGUIDE_DIR}/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 browser.close()
}

if (require.main === module) {
main().catch(e => {
console.error(e)
process.exit(1)
})
}
Loading

0 comments on commit 4696fa2

Please sign in to comment.