Help us understand the problem. What is going on with this article?

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

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'))

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした