clover.blue

CypressとResamble.jsを使ったwebサイトの差分画像比較

Data
2019/10/02
Tag

VueやReactなどを使って運用していると色んな所に影響するようなコンポーネントをさわるケースが出てくると思います。

そのときに影響範囲がある画面をポチポチして確認するのがしんどいので
予め確認したいページを指定してキャプチャを取れる状態にしておき

差分があった場合に、画像で出力してくれる仕組みを作ってみました。

手順

  1. Cypressで比較したい画像のキャプチャを撮っておく
  2. 1で撮ったキャプチャと比較したい画像のキャプチャを撮る
  3. Resamble.jsで1と2の画像を比較して差分がある場合、差分画像を出力する

Cypressでキャプチャを撮る

Cypressで下記のようにページごとに、キャプチャを撮って現時点の日付で保存しておきます。(/e2e/screenshot/内の指定のフォルダに保存されます。)

画面サイズごとにキャプチャを取りたい場合は、それぞれ画面ごとに保存したいフォルダをかえて保存しておきます。

画面サイズを変更する場合はcy.viewportを指定します。

https://github.com/kamem/clover.blue2/blob/master/test/diff/index.js

/test/e2e/integration/screenshot.js
import moment from 'moment'
const host = 'http://localhost:1341/'

const pages = [
  {
    name: 'top',
    path: ''
  },
  {
    name: 'about',
    path: 'about'
  }
]

pages.forEach(({ name, path }) => {
  context(name, () => {
    beforeEach(() => {
      cy.visit(`${host}${path}`)
    })

    it(`${name} screenshot`, () => {
      cy.screenshot(`${name}/normal/${moment().format()}`)
    })
  })
})

pages.forEach(({ name, path }) => {
  context(`small_${name}`, () => {
    beforeEach(() => {
      cy.viewport(320, 480)
      cy.visit(`${host}${path}`)
    })

    it(`${name} screenshot`, () => {
      cy.screenshot(`${name}/small/${moment().format()}`)
    })
  })
})

Resamble.jsを使って差分画像の出力

/e2e/screenshot/内のCypressで保存したファイルをフォルダ単位で検索して。
そのフォルダ内の最新の2件を比較する仕組みにしました。

package.jsonに下記を記述して、nodeでResemble.jsで画像の差分を出力を動作するようにしました。

package.js
"scripts": {
  "diff": "node test/diff/"
}

コードはざっくりなので参考程度に...

https://github.com/kamem/clover.blue2/blob/master/test/e2e/integration/screenshot.js

/test/diff/index.js
const fs = require('fs')
const path = require('path')
const compareImages = require('resemblejs/compareImages')
const _ = require('lodash')
const mkdirp = require('mkdirp')

const getDiff = async (img1, img2, output = './output.png', options = {}) => {
  const data = await compareImages(
    fs.readFileSync(img1, () => {}),
    fs.readFileSync(img2, () => {}),
    options
  )

  if (data.misMatchPercentage >= 0.01) {
    mkdirp(path.dirname(output), err => {
      if (err) return console.error(err)
      fs.writeFile(output, data.getBuffer(), () => {})
    })
  }
}

const searchDir = dir => {
  return fs
    .readdirSync(dir)
    .filter(item => !fs.existsSync(item))
    .map(item => {
      const filePath = `${dir}/${item}`
      return {
        dir,
        item: fs.statSync(filePath).isDirectory() ? searchDir(filePath) : item
      }
    })
}

const diffPngFiles = dirObj => {
  const pngFiles = getDiffFiles(dirObj)

  if (pngFiles.length === 2) {
    getDiff(
      `${pngFiles[0].dir}/${pngFiles[0].item}`,
      `${pngFiles[1].dir}/${pngFiles[1].item}`,
      `${pngFiles[0].dir.replace('e2e', 'diff')}.png`
    )
  }

  dirObj.forEach(({ dir, item }) => {
    if (Array.isArray(item)) {
      diffPngFiles(item)
    }
  })
}

const getDiffFiles = dirObj => {
  const pngFiles = dirObj.filter(({ item }) => ~item.indexOf('png'))

  return _.sortBy(pngFiles, ({ dir, item }) => {
    return -fs.statSync(`${dir}/${item}`).ctimeMs
  }).slice(0, 2)
}

diffPngFiles(searchDir('./test/e2e/screenshots'))

「すごい頑張って差分テストを!」って感じではないですが。
一旦サクッと差分ポイントが確認できるーぐらいで使ってみるのはいいかなという感じです。

今後もこのあたりもちょっとうまい方法ないかなという部分は考えて行きたいです。