2019.6.22
静的サイトを作っていて結構思うことなのですが、こういうこと↓が結構あります。
これらを回避するために、デプロイする前に全ページ確認する作業をやらなくてはいけません。これはかなり面倒です。サイト全体のスクショを開発前と開発後で差分を抽出して、意図しない見た目の変更をキャッチできるようにすれば確認する手間が減ります。
そんな手間を省くためにサイト全体のスクリーンショットの差分を取得できる環境を作ってみました。
npm i -D puppeteer
GitHub - GoogleChrome/puppeteer: Headless Chrome Node API
npm i -D @types/puppeteer
Using Puppeteer in TypeScript |
npm install -D ts-node
Node.js QuickStart - TypeScript Deep Dive 日本語版
puppeteerでスクリーンショットは下記の流れで撮ります。
const browser = await puppeteer.launch()
でブラウザを立ち上げるconst page = await browser.newPage()
でページを開く(Chromeで言うタブを開く)await page.goto(url)
でページにアクセスするawait page.screenshot()
でスクリーンショットを保存するbrowser.close()
でブラウザを終了する今回はスクリーンショットを撮るためだけのこういうクラスを作りました。browser.newPage
ではなくcontext.newPage
を使っています。これはブラウザキャッシュを効かせないようにするためにシークレットモードを使用するためにbrowser.createIncognitoBrowserContext()
を使っているからです。
import puppeteer from "puppeteer";
export class ScreenShotSaver {
private browser: puppeteer.Browser;
private context: puppeteer.BrowserContext;
async init() {
this.browser = await puppeteer.launch({ headless: true });
this.context = await this.browser.createIncognitoBrowserContext();
}
async close() {
await this.browser.close();
}
async saveScreenshot(url: string, dist: string) {
console.log("start saving screenshot", url);
const page = await this.context.newPage();
await page.goto(url, {
waitUntil: "networkidle2",
timeout: 60000
});
await page.screenshot({ path: dist, fullPage: true });
console.log("saved screenshot", url, "as", dist);
return dist;
}
}
今回はローカルに起動したサイトのトップページを保存します。./regression-test/screenshots
配下に保存されます。
import { ScreenShotSaver } from "./utils/screenshot";
const save = async () => {
const ssSaver = new ScreenShotSaver();
await ssSaver.init();
const ssDir = `./regression-test/screenshots`;
const url = `http://localhost:8000`;
const newSs = await ssSaver.saveScreenshot(url, `${ssDir}/index.png`);
console.log(newSs, "saved");
ssSaver.close();
};
save();
実行はts-node
を使います。
./node_modules/.bin/ts-node ./regression-test/save-screenshots.ts
このブログのトップを試しに保存してみました。
縦に長いのでここに置いてあります。
resemblejsという画像を比較してくれるライブラリがあるのでそれを使います。
npm i -D resemblejs
差分情報のとり方はresemble(image1).compareTo(image2)
でCompareResult
が非同期で渡ってきます。
比較する画像のパスと差分画像のファイル名を引数に、非同期で差分情報を返す関数を作成しました。
const resemble = require("resemblejs");
import fs from "fs";
interface CompareResult {
isSameDimensions: boolean;
dimensionDifference: { width: number; height: number };
rawMisMatchPercentage: number;
misMatchPercentage: string;
diffBounds: { top: number; left: number; bottom: number; right: number };
analysisTime: number;
getImageDataUrl: () => any;
getBuffer: () => any;
}
export const compareImages = (
path1: string,
path2: string,
diffFileName: string
) => {
const image1 = fs.readFileSync(path2);
const image2 = fs.readFileSync(path1);
return new Promise<CompareResult>(res => {
resemble(image1)
.compareTo(image2)
.onComplete((data: CompareResult) => {
fs.writeFileSync(diffFileName, data.getBuffer());
res(data);
});
});
};
今回はトップページの差分を取ります。差分がなければ新しい画像と差分画像は削除されるようにしました。
import fs from "fs";
import path from "path";
import { ScreenShotSaver, compareImages } from "./utils/screenshot";
const diffScreenshot = async () => {
let ssSaver: ScreenShotSaver;
ssSaver = new ScreenShotSaver();
await ssSaver.init();
const url = "http://localhost:8000";
const ssDir = "./regression-test/screenshots";
const newSs = await ssSaver.saveScreenshot(
url,
path.join(ssDir, `index.new.png`)
);
const diff = path.join(ssDir, `index.diff.png`);
const result = await compareImages(
newSs,
path.join(ssDir, `index.png`),
diff
);
console.log(
"result rawMisMatchPercentage",
"index",
result.rawMisMatchPercentage
);
if (result.rawMisMatchPercentage > 0.1) {
throw new Error("rawMisMatchPercentage > 0.1");
}
fs.unlinkSync(newSs);
fs.unlinkSync(diff);
await ssSaver.close();
};
diffScreenshot();
実行はts-node
を使用します。
./node_modules/.bin/ts-node ./regression-test/diff-screenshots.ts
試しにこの記事のデプロイ前後の差分をとってみました。差分があるところはピンクになります。いい感じにできたのではないでしょうか。CIと組み合わせてプルリクエストごとにテストが走るともっと良いですね。
ここまでのソースコードはここにあります。
blog/regression-test at c6edd241f810aa61ff771fc366d9aed2ad7f3542 · SatoshiKawabata/blog · GitHub