こんにちは!
6月から23卒内定者インターン生として株式会社Hajimariに入社した、江端 凌です。
普段は、TUKURÜS事業部で受託開発の業務に携わっています。
今回は受託開発している既存サイトに、bootstrapを導入することになったので、どのような影響が出るのかを調査することになりました。
背景としては、既存サイトのCSSの複雑化と適切なブレークポイントの設置が出来ていなかったことから導入に至りました。
そこでbootstrapの導入前と導入後を比較するために、node.js製のライブラリであるpuppeteerを利用して全ページのスクリーンショットと差分画像を生成しました!
今回はpuppeteerの概要から、実装までの流れを紹介させていただきたいと思います!
puppeteerとは?
puppeteerとは、自動でブラウザ操作を行えるnode.jsのライブラリです。
npmでインストールすると、操作するchromiumも一緒についてきます。
puppeteerは、このchromiumでブラウザを操作していきます。
puppeteerの概要としては、
- ヘッドレスで操作できる。
- 動的に生成されるページ(SPAなど)でも操作ができる。
といった特徴があります。
puppeteerを使ってやったこと
では、今回puppeteerを使ってやりたいことを確認していきます。
bootstrapを既存のサイトに導入した場合、すでに存在しているCSSと競合してデザイン崩れを起こす可能性があります。
その時、どのような影響が出るのかを調査したいため、
- テスト環境(bootstrap未実装)
- local環境(bootstrap導入済み)
の二つを利用して、puppeteerを使って両方の環境でスクリーンショットを撮りました。
その後、looks-sameというライブラリで差分画像を生成をします。
looks-sameもnode.jsのライブラリですので、puppeteerと一緒にインストールできますが、長くなるので今回は割愛させていただきます。
例として今回は、弊社のホームページのスクリーンショットを自動で撮影するプログラムを作りましょう!
puppeteerを導入してみる
では早速、npmを使ってpuppeteerをインストールしていきます!
node.jsをインストールをしていない場合は、node.jsのインストールを行なってください。
node.jsがインストールされているかどうか*1は、node -v
コマンドで確認できます。
npmでインストールする
まず、puppeteerを使うフォルダを作ります。
適当にデスクトップなど(どこでもいいです!)に移動して、以下のコマンドを実行してください。
$ mkdir puppeteer && cd puppeteer
そうしたらフォルダが作成されていると思います。
続いて、npmでpuppeteerをインストールしていきます!
$ npm init && npm install puppeteer
そうするとREPLで色々聞かれるので、全部Enterキーで答えましょう。
使うバージョンなどを聞かれています。
画像のような表示になったらインストールは完了です。
puppeteer単体をインストールしたい方は、
$ npm install puppeteer-core
で単体でインストールすることが可能です。*2
実際にコードを書いてみる
先ほど作成したpuppeteerフォルダ配下にindex.js
と画像を保存するdist
フォルダを作成しましょう。
$ touch index.js && mkdir dist
このindex.js
の中に処理を書いていきます。
index.js
ファイルがエントリーポイントになります。
本当は全ページ撮影するなら、ConstsファイルにURLやログイン情報などを保存しておく方が便利ですが、今回は簡易的にindex.js
だけで書いていきます。
index.js
const puppeteer = require('puppeteer'); /** * 引数に渡した数 * ミリ秒待機 * @param {int} msec * @returns setTimeout */ function sleep(msec) { return new Promise(function (resolve) { setTimeout(function () { resolve(); }, msec); }); } // puppeteerの設定 (async () => { const browser = await puppeteer.launch({ // ヘッドレスかどうか headless: false, // 10000ミリ秒応答が無かったら終了する timeout: 10000, }); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); // ページに移動 await page.goto('https://www.hajimari.inc/'); // 画面遷移のアニメーションが終わるまで待機 await sleep(8000); // スクリーンショットを取ってdistフォルダに保存する。 await page.screenshot({ path: 'dist/hajimari.png', fullPage: false }); // ブラウザを閉じる await browser.close(); })();
上記のコードを書いて保存したら、node index.js
をターミナルで実行しましょう。
$ node index.js
dist
フォルダ配下にhajimari.png
が保存されているはずです。
こんな感じで ↓
それぞれのコードについて
それぞれのコードを見ていきましょう。
puppeteerは基本的に非同期で動作するので、async
とawait
で処理を書いていきます。
puppeteerの設定
(async () => { const browser = await puppeteer.launch({ headless: false, timeout: 10000, }); const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 });
ここでは、puppeteerの設定ができます。
3行目のheadless: false
は、ヘッドレスにするかどうかを決めています。
今回はわかりやすく、false
にすることでchromiumの動きを見えるようにしました。必要なければtrue
にすることで見えなくなります。
8行目で表示するサイズを変えられます。
await page.setViewport({ width: 1920, height: 1080 });
今回はPC表示にしていますが、スマホサイズで撮影したい場合は、
width
とheight
をスマホのサイズにすることで撮影可能です。
スクリーンショットの処理
// ページに移動 await page.goto('https://www.hajimari.inc/'); // 画面遷移のアニメーションが終わるまで待機 await sleep(8000); // スクリーンショットを取ってdistフォルダに保存する。 await page.screenshot({ path: 'dist/hajimari.png', fullPage: false }); // ブラウザを閉じる await browser.close();
1行目で目的のページに移動します。
page.goto()
に目的のURLを渡してあげましょう。ここを変更すると別のページにも飛ぶことができます。
2行目のsleep()
は上の方で作った関数です。
function sleep(msec) { return new Promise(function (resolve) { setTimeout(function () { resolve(); }, msec); }); }
細かい説明は省きますが、引数に渡した数字 × ミリ秒だけ待機します。
なので、await sleep(8000);
は8秒待機させています。
3行目の処理はスクリーンショットをして、引数に渡した保存先に保存しています。
4行目でブラウザを閉じて、puppeteerの処理を終了しています。
まとめ
自分はこのpuppeteerアプリを作成したことで、何度も確認したりスマホ画面に切り替えたりできて効率化につながりました!
またヘッドレスでも動くので、puppeteerを動かしながら別の業務もできます。
今回は紹介しきれませんでしたが、差分画像を生成するlooks-same
というライブラリを使ったり、ベーシック認証を突破したり、Xpathで要素を取得してボタンを押したりもできたりして、とても幅広いです。
今後もE2Eテスト用に使ったりできそうなので、より良いものにできるように保守していきたいと思っています!
株式会社Hajimariでは、Laravelをメイン言語として自社開発・受託開発を行なっており、
一緒に開発を行なっていただけるエンジニア募集しています!
長野拠点の立ち上げメンバーも大募集しています!
興味のある方は以下の記事をぜひご覧ください!
みなさまとお会いできるのを心からお待ちしております!