まずPuppeteerとは
Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.
devToolsのプロトコルを使い(ヘッドレス)Chromeを動かすためのnode.jsのライブラリ、とのことですね。
これを使うことで任意のページのスクリーンショットを撮ったり、パフォーマンスの計測をしたりが自動化できる、というわけです。
色々できるPuppeteerですが、今回は「スクリーンショットを撮る」機能にフォーカスを当てます。
レガシーを乗り切る?
フロントエンド界隈では開発環境を日々いい感じにするムーブが強めなわけですが、はて全てのwebサイトが全てwebpackで構成されているのか、テストを考慮した設計になっているのか、というとそうでもないですよね。
様々な事情があり、いわゆる「レガシー」な環境で作業する機会はまだまだ存在する、というのが実情ではないでしょうか?
例えばそう、よく似た30枚の静的なhtmlを直さねばならない、とかね、、、
よく似た30枚の静的なhtmlを直す
※以下はイメージです。
①正規表現で対象の文言を置換したりする
③30枚を頑張って確認
③「できました!」
④「すまんがここのビジュアル差し替えお願いします」
⑤「はい。。。」30枚を頑張って(略)
⑥「ここのリンク文言変更になったので」
⑦「はい。。。。。。」30枚を(ry
何が起こるか?
※以下はイメージです。
- 目へのダメージ
- 集中力へのダメージ
- 上記から来る確認漏れ
- 確認漏れから来る修正対応
- 修正対応から来る精神へのダメージ...
辛いですね。
(半)自動化しましょう
こんな感じで。
https://6z2zmkp69w.codesandbox.io/
https://k094vklyk7.codesandbox.io/
の画面の差分が赤く表示されています。
ファイル構成
※Puppeteerだけでなく、差分画像の生成用にpixelmatchも併せてインストールします。
screen_diff
├── node_modules
│ └── 略
├── package-lock.json
├── package.json
├── screenshot
│ ├── diff
│ │ └── mypage.png
│ ├── reference
│ │ └── mypage.png
│ └── test
│ └── mypage.png
├── screenshot.js
└── test
├── reference.json
└── test.json
{
"name": "screen_diff",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start": "node screenshot"
},
"dependencies": {
"pingjs": "^0.9.1", // 差分画像の生成に使用
"pixelmatch": "^4.0.2", // 差分画像の生成に使用
"puppeteer": "^1.9.0"
}
}
const puppeteer = require('puppeteer');
const pixelmatch = require('pixelmatch');
const PNG = require('pngjs').PNG;
const fs = require('fs');
const TESTS_JSON = './test/test.json';
const REFERENCE_JSON = './test/reference.json';
const SCREENSHOT_FILE_PATH = './screenshot/';
const tests = JSON.parse(fs.readFileSync(TESTS_JSON, 'utf8'));
const refs = JSON.parse(fs.readFileSync(REFERENCE_JSON, 'utf8'));
const PC_SIZE = {
width: 1200,
height: 800
};
const action = {
'hover': async (options) => {
const page = options.page;
await page.hover(options.trigger);
}
};
const compareScreenshots = (test) => {
const defaultCompare = doneReading({
img1: fs.createReadStream(`screenshot/test/${test.pageName}.png`).pipe(new PNG()).on('parsed', () => {defaultCompare.next()}),
img2: fs.createReadStream(`screenshot/reference/${test.pageName}.png`).pipe(new PNG()).on('parsed', () => {defaultCompare.next()}),
filename: test.pageName
});
if (test.pattern) {
for (let pattern of test.pattern) {
const patternCompare = doneReading({
img1: fs.createReadStream(`screenshot/test/${test.pageName}_${pattern.type}.png`).pipe(new PNG()).on('parsed', () => {patternCompare.next()}),
img2: fs.createReadStream(`screenshot/reference/${test.pageName}_${pattern.type}.png`).pipe(new PNG()).on('parsed', () => {patternCompare.next()}),
filename: `${test.pageName}_${pattern.type}`
});
}
}
}
function* doneReading(options) {
const img1 = options.img1;
const img2 = options.img2;
let readed = 0;
yield readed++;
const diff = new PNG({
width: img1.width,
height: img1.height
});
pixelmatch(
img1.data,
img2.data,
diff.data,
img1.width,
img1.height,
{
threshold: 0.01
}
);
console.log('diff');
return diff.pack().pipe(fs.createWriteStream(`screenshot/diff/${options.filename}.png`));
}
const takeScreenshot = async (options) => {
const page = options.page;
const test = options.test;
await page.goto(options.url);
await page.screenshot({
path: options.dist + test.pageName + '.png',
fullPage: true
})
}
(async () => {
const browser = await puppeteer.launch({
headless: true,
ignoreHTTPSErrors: true,
});
const page = await browser.newPage();
page.setViewport(PC_SIZE)
for (let key in tests) {
await takeScreenshot({
page: page,
url: refs[key].url,
test: tests[key],
dist: SCREENSHOT_FILE_PATH + 'reference/',
});
await takeScreenshot({
page: page,
url: tests[key].url,
test: tests[key],
dist: SCREENSHOT_FILE_PATH + 'test/',
});
// 画像差分を出す
compareScreenshots(tests[key]);
console.log("save screenshot: " + key)
}
await browser.close()
})();
使い方
何は無くとも
$ npm i
比較したいurlをjsonに追加
{
"mypage": {
"pageName": "mypage",
"url": "https://k094vklyk7.codesandbox.io/"
},
"mypage2": {
"pageName": "mypage2",
"url": "[テスト対象のurl]"
}
}
{
"mypage": {
"pageName": "mypage",
"url": "https://6z2zmkp69w.codesandbox.io/"
},
"mypage2": {
"pageName": "mypage2",
"url": "[比較元のurl]"
}
}
実行
$ npm run start
> screen_diff@1.0.0 start
> node screenshot
save screenshot: mypage
diff
save screenshot: mypage2
diff
差分の確認
screenshot/diff/
に差分画像が出ているので確認。
お疲れ様でした。
まとめ
削られた精神でも見逃さないよう、良き感じにdiffってくださるPuppeteerさんは天使。
諸注意
リファクタリングであれば、「差分出てない、OK!」(リグレッションテスト)となりますが、
今回のような使用用途では、差分が出ること前提の運用になっています。
見やすいように表示はされますが、最終的にその差分が「いい差分かどうか」を判断するのは人の目になりますので、油断なきようにしましょう。