index.js

'use strict'

var wd = require('webdriverio')
var wdiosc = require('wdio-screenshot')
var isObject = require('lodash/isObject')
var bb = require('bluebird')
var set = require('lodash/set')
var get = require('lodash/get')
var mkdirp = bb.promisify(require('mkdirp'))
var path = require('path')
var Differ = require('webjerk-image-set-diff')

var DEFAULT_WINDOW_EXEC =  function () { return {}; } // eslint-disable-line

/**
 * @module webjerk-snaps
 * @description website visual regression testing plugin.  on `main`,
 * webjerk-snaps queues up browsers to be run serially. each browser is
 * launched, screenshots captured. on `post`, the image directories are sent to
 * `webjerk-image-set-diff` for executing the comparison algorithm.
 */
module.exports = function registerSnaps () {
  return {
    name: 'snaps',
    /**
     * launches a browser and captures screenshots
     * @param {*} webdriverioConf
     * @param {*} pluginConfig
     * @param {*} webjerkconfig
     * @returns {Promise}
     */
    capture (webdriverioConf, pluginConfig, webjerkconfig) {
      var { testName, snapDefinitions, snapDefinitionsFromWindow, url } = pluginConfig
      if (!testName) throw new Error('missing testName')
      if (!snapDefinitions && !snapDefinitionsFromWindow) throw new Error('snapDefinitions or snapDefinitionsFromWindow must be set')
      var client = wd.remote(webdriverioConf)
      wdiosc.init(client)
      console.log(`booting ${webdriverioConf.desiredCapabilities.browserName}`)
      return client
      .init()
      .url(url)
      .execute(snapDefinitionsFromWindow || DEFAULT_WINDOW_EXEC, '<msg>')
      .then(function ({ value }) {
        var browser = `${webdriverioConf.desiredCapabilities.browserName}-${webdriverioConf.desiredCapabilities.version || 'latest'}`
        var snapDefs = snapDefinitionsFromWindow ? value : snapDefinitions
        if (!snapDefs) throw new Error('no snapDefinitions were found')
        var snapRoot = path.resolve(process.cwd(), 'snaps')
        var snapRunRoot = path.resolve(snapRoot, 'runs')
        var snapRefRoot = path.resolve(snapRoot, 'ref')
        var snapRunDir = path.resolve(snapRunRoot, `run-${pluginConfig.runId}`)
        return Promise.resolve()
        .then(() => Promise.all([mkdirp(snapRunDir), mkdirp(snapRefRoot)]))
        .then(() => {
          return snapDefs.reduce((client, sd) => {
            var snapFilename = path.join(snapRunDir, `${sd.name}-${browser}.png`)
            return client.saveElementScreenshot(snapFilename, sd.elem)
          }, client)
        })
        .then(() => client.end())
        .then(() => ({ snapRefRoot, snapRunDir }))
        .catch(err => client.end().then(() => { throw err }))
      })
    },
    /**
     * webjerk main hook
     * @param {*} pluginConfig
     * @param {*} webjerkconfig
     * @param {*} results
     */
    main (pluginConfig, webjerkconfig, results) {
      var capabilities = pluginConfig.desiredCapabilities || [{ browserName: 'chrome' }]
      pluginConfig = pluginConfig || {}
      if (!Array.isArray(capabilities)) capabilities = [capabilities]
      if (!pluginConfig.runId) pluginConfig.runId = Date.now().toString()
      // run each capability, i.e. run each requested browser
      return bb.map(
        capabilities,
        capability => {
          var webdriverioConf = pluginConfig.webdriverio || {}
          if (!isObject(capability)) throw new Error('desiredCapabilities must be an object')
          set(webdriverioConf, 'desiredCapabilities', capability)
          if (!webdriverioConf.desiredCapabilities.browserName) throw new Error('browserName missing')
          return this.capture(webdriverioConf, pluginConfig, webjerkconfig)
        },
        { concurrency: pluginConfig.concurrency || 1 }
      )
    },
    /**
     * webjerk post hook
     * @param {*} pluginConfig
     * @param {*} webjerkconfig
     * @param {*} results
     */
    post (pluginConfig, webjerkconfig, results) {
      var res = get(results, `main[${this.name}]`)
      if (!res || !res.length) return console.error('unable to find run & ref images')
      var { snapRefRoot, snapRunDir } = res[0]
      if (!snapRunDir || !snapRefRoot) throw new Error('expected snapRunDir & snapRefRoot from main results')
      return Differ.factory({ refDir: snapRefRoot, runDir: snapRunDir, report: pluginConfig.report || true }).run()
    }
  }
}