Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@kamem

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

More than 1 year has passed since last update.

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

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

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

手順

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

Cypressでキャプチャを撮る

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

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

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

/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/"
}

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

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

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

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

3
Help us understand the problem. What is going on with this article?
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
kamem
学校の講師とか、プロジェクトに参加してサイト作ったりしてます。 Design/Photoshop/Illustrator/HTML/CSS/Sass/Postcss/Javascript/ES6/Babel/jQuery/Typescript/React/Vue/Node

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?