Puppeteer を使って web ページの画像差分を作成する

ブラウザのオートメーションを使って 2 つの web ページの差分画像を作成してみます。 JavaScript のライブラリだけを使ってこれを実現します。

## ライブラリのインストール

ヘッドレス Chrome (Chromium) を制御するライブラリ [Puppeteer](https://pptr.dev)を使います。
yarn または npm を使い、Puppeteer と他に必要なライブラリをインストールします。

```shell
$ mkdir my-project
$ cd my-project
$ yarn init # or npm init
$ yarn add puppeteer looks-same # or npm install puppeteer looks-same
```

[looks-same](https://www.npmjs.com/package/looks-same) は、2 つの画像を比較して同じ画像なのかを判定したり、差分画像を生成することができるライブラリです。

ここでは、プログラムを作成する前に、生成する画像を保存するディレクトリを作成しておきます。

```shell
$ mkdir dist
```

##  Puppetter を制御する JavaScript を作成する

Puppetter の API を使い、ブラウザを制御するプログラムを作成します。ここでは `URL_A` と `URL_B` という変数名で比較する 2 つの web ページを指定します。
作成するプログラムでは以下の操作を行います。

1. ヘッドレス Chrome を起動する
2. `URL_A` に遷移する
3. `URL_A` のスクリーンショットを作成する
4. `URL_B` に遷移する
5. `URL_B` のスクリーンショットを作成する
6. 作成した 2 個のスクリーンショットから差分画像を生成する

```javascript
// index.js
const puppeteer = require('puppeteer')
const looksSame = require('looks-same')

const URL_A = 'https://example.com/a'
const URL_B = 'https://example.com/b'

;(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.setViewport({ width: 512, height: 100 })

  const pathA = 'dist/a.png'
  const pathB = 'dist/b.png'
  const pathDiff = 'dist/diff.png'
  await page.goto(URL_A)
  await page.screenshot({ path: pathA })
  await page.goto(URL_B)
  await page.screenshot({ path: pathB })

  await new Promise((resolve, reject) =>
    looksSame.createDiff(
      {
        reference: pathA,
        current: pathB,
        diff: pathDiff,
        highlightColor: '#ff00ff'
      },
      error => (error ? reject() : resolve())
    )
  )

  await browser.close()
})()
```

プログラムを実行します。

```shell
$ node index.js
```

プログラムを実行すると、 `dist` ディレクトリの中にスクリーンショットとその差分が生成されているのがわかります。 なお、画像のうちの異なる部分はピンク色（#ff00ff）でハイライトされます。

## 実際の例

以下の微妙に内容が異なる 2 つの web ページのスクリーンショットの差分画像を実際に作成してみます。

```html
<!-- a.html -->
<!DOCTYPE html>
<html>
  <body>
    <h1 class="title">Helloo!</h1>
    <p class="content">This is page A!</p>
  </body>
</html>
```

```html
<!-- b.html -->
<!DOCTYPE html>
<html>
  <body>
    <h1 class="title">Hello!</h1>
    <p class="content">This is page B!</p>
  </body>
</html>
```

テキストの差分はこんな感じ。

```shell
$ diff a.html b.html
1c1
< <!-- a.html -->
---
> <!-- b.html -->
5,6c5,6
<     <h1 class="title">Helloo!</h1>
<     <p class="content">This is page A!</p>
---
>     <h1 class="title">Hello!</h1>
>     <p class="content">This is page B!</p>
```

前述のプログラムにハードコードされている URL を適切に変更します。
別途ウェブサーバーを立ち上げるなどして、上記の HTML に下記の URL でアクセスできるようにしておいてください。

```JavaScript
// index.js
// ...
const URL_A = 'http://127.0.0.1:8080/a.html'
const URL_B = 'http://127.0.0.1:8080/b.html'
// ...
```

プログラムを実行します。

```shell
node index.js
```

以下のように画像が生成されます。

a.png
![a.png](https://qiita-image-store.s3.amazonaws.com/0/125062/f092b77f-065d-8fed-fce2-565402f4911e.png)

b.png
![b.png](https://qiita-image-store.s3.amazonaws.com/0/125062/bc61837b-7ae6-b9f5-f177-e54bfe57dad5.png)

差分画像
![diff.png](https://qiita-image-store.s3.amazonaws.com/0/125062/073079f3-c586-1d2a-fe31-7b6287e2d0b5.png)

## まとめ

Node.js を使って2つの web ページのスクリーンショットの差分画像を作成し、コンテンツの違いやスタイルの違いなどを検出できることが分かりました。Production と 開発環境の間でどのような見た目の変化が生じているかを可視化したり、 CI などで回してチャットツールに差分画像をポストするなども簡単にできそうですね。