こんにちは、薦田です(@toshiya_komoda)。 今回はChromeブラウザのブラウザテスト自動化ツールであるPuppeteerに関するTipsを紹介します。
Puppeteerによるフルページスクリーンショット
Puppeteerは、Chrome DevTools ProtocolのNode.jsクライアントであり、Chrome DevToolsチームがメンテナンスしています。 その主な用途の1つにブラウザ自動テストが挙げられます。 PuppeteerはChromeの操作に特化しており、操作対象がChromeに限定されるもののSeleniumにはない便利なAPIを利用できます。
Puppeteerで利用できる便利なAPIの1つに、フルページスクリーンショットがあります。 フルページスクリーンショットとは、ウェブページの上端から下端までを1つなぎの画像としてスクリーンショットを撮ったものです。
例えば、以下の画像はフルページスクリーンショットの例です。DeNA Testing Blogの過去の記事です。
DeNA Testing Blog記事のフルページスクリーンショット
Seleniumでもスクリーンショットを取得可能ですが、取得対象の領域がviewportに制限されます。 このためSeleniumでフルページスクリーンショットを実現しようとすると、スクロールしながら複数枚のスクリーンショットを撮った後、1枚につなぎ合わせるといった実装が必要になります。
Puppeteerを利用した場合の実装は以下のように簡潔なものになります。 Node.js v8.11.1, Puppeteer 1.3.0で動作確認しています。
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch({headless: true}); const page = await browser.newPage(); page.setViewport({width: 1200, height: 800}) const url = 'http://swet.dena.com/entry/2018/02/16/104605' await page.goto(url); await page.waitForNavigation({waitUntil:'networkidle2', timeout:5000}) .catch(e => console.log('timeout exceed. proceed to next operation')) await page.screenshot({path: 'testing-blog.png', fullPage:true}) console.log("save screenshot: " + url) await browser.close() })();
補足:Puppeteerの以前のバージョンではフルページスクリーンショットの高さに16384pxの制限がありましたが、1.2.0以降のバージョンではこちらの制限はなくなっているようです。
遅延読み込み対策
Puppeteerでは、前述のようにフルページスクリーンショットを簡単に利用することができ、Visual Regression Testなどに利用することが可能です。 しかし、画像の遅延読み込みに対応したウェブページでは、フルページスクリーンショットをうまく取得できないことがあります。
こちらの画像がそのような例です。 lazysizesという画像遅延読み込みライブラリのデモページで別に画像を用意して試しています。
使用画像: Test by Ray Bouknight is licensed under CC BY 2.0
ページ中にロードしきれていない画像が存在しており、正しくフルページスクリーンショット画像が撮れていません。
この問題はPuppeteerのissueにも報告されており、 不具合扱いではありませんが、Feature RequestとしてPuppeteer側での対応が提案されている状況です。 現時点では以下のように一度ページ下端までウィンドウをスクロールさせることで問題を回避できます。
const puppeteer = require('puppeteer'); async function scrollToBottom(page, viewportHeight) { const getScrollHeight = () => { return Promise.resolve(document.documentElement.scrollHeight) } let scrollHeight = await page.evaluate(getScrollHeight) let currentPosition = 0 let scrollNumber = 0 while (currentPosition < scrollHeight) { scrollNumber += 1 const nextPosition = scrollNumber * viewportHeight await page.evaluate(function (scrollTo) { return Promise.resolve(window.scrollTo(0, scrollTo)) }, nextPosition) await page.waitForNavigation({waitUntil: 'networkidle2', timeout: 5000}) .catch(e => console.log('timeout exceed. proceed to next operation')); currentPosition = nextPosition; console.log(`scrollNumber: ${scrollNumber}`) console.log(`currentPosition: ${currentPosition}`) // 2 scrollHeight = await page.evaluate(getScrollHeight) console.log(`ScrollHeight ${scrollHeight}`) } } (async () => { const viewportHeight = 1200 const viewportWidth = 1600 const browser = await puppeteer.launch({headless: true}); const page = await browser.newPage(); page.setViewport({width: viewportWidth, height: viewportHeight}) const url = 'http://afarkas.github.io/lazysizes/#examples' await page.goto(url); // 1 await page.waitForNavigation({waitUntil: 'networkidle2', timeout: 5000}) .catch(e => console.log('timeout exceed. proceed to next operation')); await scrollToBottom(page, viewportHeight) await page.screenshot({path:'scroll-sc.png', fullPage: true}) console.log("save screenshot: " + url) await browser.close() })();
こちらのコードは、Vasilev氏のPuppeteer Screenshotsの実装を参考にしつつ、以下2点の改良を加えたものです。
page.waitForNavigation()
関数の条件として、networkidle2
(ネットワーク接続が2本以下の状態が500ms以上続く状態)を 指定しています。 ただしウェブサイトによっては、 画像ロード以外のネットワーク接続の影響でこの条件が恒久的に満たされないため、timeout
オプション(デフォルトは30sec)を明示的に指定しています。- また、
scrollToBottom()
関数の中ではwhile
ループの中でスクロールしながら毎回scrollHeight
の値を更新しています。 これは、スクロール操作によってページのscrollHeight
の値が動的に変わる場合があるためです。
ページを一度下端までスクロールしておくことで、さきほどのページのフルページスクリーンショットは以下のように改善されます。
遅延読み込みページのフルページスクリーンショット(遅延読み込み対応版)
画像が全てロードされ、きれいなフルページスクリーンショットが取得されています。
補足Tips
以前のバージョンにあった高さ制限の問題とは別に、Puppeteerでは非常に大きなフルページスクリーンショット画像を取得しようとすると、その下端がかけてしまうことがあります。 こちらの現象は遅延読み込みとは別の問題ですが、こちらのissueで説明されています。 こちらの問題はChromium側での対応が必要ということで、Chromiumの不具合としてあがっていますが、Puppeteer1.3.0にバンドルされたChromiumでは未対応です。
この現象が発生した場合には、スクロールしながらスクリーンショットを複数枚取得し最後につなぎ合わせる、といったSeleniumの場合と同様のワークアラウンドが必要になります。