Cypress + REG CLI ではじめたビジュアルリグレッションテスト


WEBアプリケーション検証でビジュアルリグレッションテストの導入を検討しています。
cypress と reg-cli を利用して before/after スクリーンショット比較環境をトライしたときの備忘録です。
導入し易く本格的に活用していきたいと思います。

# 目次
* [やること](https://qiita.com/yodyman/items/3a2fbb106eaaca3aca47#やること)
* [cypress環境構築](https://qiita.com/yodyman/items/3a2fbb106eaaca3aca47#cypress環境構築)
* [スクリーンショット取得実装](https://qiita.com/yodyman/items/3a2fbb106eaaca3aca47#スクリーンショット取得実装)
* [reg-cliの追加インストール](https://qiita.com/yodyman/items/3a2fbb106eaaca3aca47#reg-cliの追加インストール)
* [スクリーンショット画像を比較](https://qiita.com/yodyman/items/3a2fbb106eaaca3aca47#スクリーンショット画像を比較)
* [参考にさせていただいたページ](https://qiita.com/yodyman/items/3a2fbb106eaaca3aca47#参考にさせていただいたページ)

## やること
* システム改修前後のスクリーンショットを cypress で before/after に保存する
* reg-cli で before/after 内画像を比較し差分を確認する

## cypress環境構築

nodejs,npm インストールは、環境に合わせて実施ください

### cypress のインストール
```
mkdir -p /myproduct
cd /myproduct
npm init -y
npm install cypress --save-dev
```

### cypress 実行コマンドを package.json に追加
```package.json
"scripts": {
  "cypress:open": "cypress open",
  "cypress:run": "cypress run"
},
```

### Cypress Test Runner を実行
```
npm run cypress:open
```
![cypress01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/d63478c5-962e-64e3-cdd9-8ac33b7656d5.png)

### Cypress 構成
cypress test runner を実行すると cypress ディレクトリが生成されます。

```
cypress
  |- fixtures　//テストデータを置く
  |- integration //spec ファイルを置く
  |- plugins
  |- support
  |- screenshots　//スクリーンショットが保存される
  |- package.json
  |- package-lock.json
  |- cypress.json

# サンプル spec を削除
rm integration/examples
```

## スクリーンショット取得実装

### cypress 環境変数の作成
スクリーンショットサイズ・キャプチャモード、ウェイトの環境変数を cypress.json に指定する

```cypress.json
{
  "env": {
    "ss_wait_msec": 1000,
    "ss_view_width": 1340,
    "ss_view_height": 1000,
    "ss_capture": "fullPage"
  }
}
```


### スクリーンショットを取得するアクセスリスト（テストデータ）の作成

`fixtures/visual-regression-test/pages.json`

```pages.json
  [
    {
      "test_id": "001",
      "uri": "/001"
    },
    {
      "test_id": "002",
      "uri": "/002"
    },
    {
      "test_id": "003",
      "uri": "/003"
    }
  ]
```


### スクリーンショットを取得する spec ファイルの実装

改修前のスクリーンショット保存
`integration/visual-regression-test/before.spec.js`
スクリーンショット対象は、テストデータに指定

```before.spec.js

describe('before screenshot', function() {
    const pages    = require('../../fixtures/visual-regression-test/pages.json')
    const domain   = 'https://before.com'
    const ss_dir   = 'before'
    var test_id    = ''
    var ss_path    = ''
    var url        = ''

    pages.forEach(({ test_id, uri }) => {
        context(test_id, () => {
            beforeEach(() => {
                url = domain + uri
                cy.visit(url)
            })

            it('take screenshot', function() {
                ss_path = ss_dir + '/' + test_id
                cy.wait(Cypress.env('ss_wait_msec'))
                cy.viewport(Cypress.env('ss_view_width'), Cypress.env('ss_view_height'))
                cy.screenshot(ss_path, {
                    capture: Cypress.env('ss_capture')
                })
            })
        })
    })
})
```

改修後のスクリーンショット保存
`integration/visual-regression-test/after.spec.js`
リクエスト先は、before と同じテストデータ pages.json を再利用

```after.spec.js

describe('after screenshot', function() {
    const pages    = require('../../fixtures/visual-regression-test/pages.json')
    const domain   = 'https://after.com'
    const ss_dir   = 'after'
    var test_id    = ''
    var ss_path    = ''
    var url        = ''

    pages.forEach(({ test_id, uri }) => {
        context(test_id, () => {
            beforeEach(() => {
                url = domain + uri
                cy.visit(url)
            })

            it('take screenshot', function() {
                ss_path = ss_dir + '/' + test_id
                cy.wait(Cypress.env('ss_wait_msec'))
                cy.viewport(Cypress.env('ss_view_width'), Cypress.env('ss_view_height'))
                cy.screenshot(ss_path, {
                    capture: Cypress.env('ss_capture')
                })
            })
        })
    })
})
```

### スクリーンショット保存先の変更

デフォルトではスクリーンショット保存先が spec.js ファイル毎に別れます

* screenshots/visual-regression-test/`before.spec.js`/before/001.png
* screenshots/visual-regression-test/`after.spec.js`/after/001.png

reg-cli でbefore/after を指定しやすいパスにしたいので spec.js ファイル名をパスから除く形に plugin で対応

* screenshots/visual-regression-test/before/001.png
* screenshots/visual-regression-test/after/001.png

`plugins/index.js` に追加

```index.js
const path = require('path');
const fs = require('fs')

module.exports = (on, config) => {
    on('after:screenshot', (details) => {
        var str = details.path
        var new_path = str.replace(/[a-z-]+\.spec\.js/, '')
        var new_dir  = path.dirname(new_path)
        fs.mkdirSync(new_dir, { recursive: true })
        fs.renameSync(details.path, new_path)
        return { path: new_path }
    })
}
```

### 例外発生時にテストが中断されないように対応

デフォルトの動作として例外発生時は、テストが中断されるようです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/78d21aa9-29fa-2ae1-184d-ff1c3b9c61d7.png)

テストが中断されないように `uncaught exceptions` イベントで対応

`support/index.js` に追加

```index.js
Cypress.on('uncaught:exception', (err, runnable) => {
    // returning false here prevents Cypress from
    // failing the test
    return false
})
```

### before/after スクリーンショットを取得する
実装した before/after.spec.js を実行します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/021aadd7-7145-7512-ec6b-d308bdfdbf0b.png)






## reg-cliの追加インストール

```
cd ./myproduct
npm install reg-cli --save-dev
```

## スクリーンショット画像を比較

`$ reg-cli /path/to/actual-dir /path/to/expected-dir /path/to/diff-dir -R ./report.html`

```
cd ./myproduct
./node_modules/.bin/reg-cli \
  ./cypress/screenshots/visual-regression-test/before \
  ./cypress/screenshots/visual-regression-test/after \
  ./cypress/screenshots/visual-regression-test/diff \
  -R ./cypress/screenshots/visual-regression-test/report/index.html
```

比較結果が出力されます

```
✘ change  cypress\screenshots\visual-regression-test\before\001.png
✔ pass    cypress\screenshots\visual-regression-test\before\003.png
✘ change  cypress\screenshots\visual-regression-test\before\002.png


✘ 2 file(s) changed.
✔ 1 file(s) passed.
```

### スクリーンショット差分ファイル

コマンド引数で指定した diff ディレクトリに差分画像が出力される
差分があるスクリーンショットのみ出力されるようです

`./cypress/screenshots/visual-regression-test/diff`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/f0a21167-3f14-a92b-14e4-a7d283116cb8.png)



### スクリーンショット結果レポート

コマンド引数で指定した report ファイルに全ての比較結果が出力されます

`./cypress/screenshots/visual-regression-test/report/index.html`

比較された画像の結果がわかりやすくリスト表示されます

<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100110/2441735c-9fb2-f00b-406a-56de357231e2.png">

画像毎の比較結果は、複数の表示モードで確認することができます。

* Diff
  * 2枚の差分を表示する
* Slide
  * 画像上でマウスを左右にスライドすることで1枚目（左）、2枚目（右）を表示する
* 2up
  * 左右に並べて表示する 
* Blend
  * 2枚を重ねて表示する
* Toggle
  * toggle ボタンで画像を切り替えて表示する

### 比較精度を調整
-M, --matchingThreshold オプションを指定することで人の見た目に合わせた比較をすることができました（すごい！）

```
./node_modules/.bin/reg-cli \
  ./cypress/screenshots/visual-regression-test/before \
  ./cypress/screenshots/visual-regression-test/after \
  ./cypress/screenshots/visual-regression-test/diff \
  -R ./cypress/screenshots/visual-regression-test/report/index.html
  -M 0.2
```
`-M 0.2` 0 - 1 の範囲を指定できるようで、小数点指定してみました。

## 参考にさせていただいたページ
https://qiita.com/natsu_mikan/items/692018262b617bb87859
https://qiita.com/hurikake06/items/9930a9ad223e86320c8f
https://www.cypress.io/
https://github.com/reg-viz/reg-cli