Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

headless Chromeでお手軽にWebページのE2Eテスト

これは、モバイルファクトリー Advent Calendar 2018 の11日目の記事です。

こんにちは、id:toricorです。好きな分野はソフトウェアの品質向上・テスト・自動化です。

今日の記事はブラウザ操作自動化ツールを使い、E2Eテストを小さく試したときのお話です。

要約

ブラウザ操作自動化ツールを使いE2Eテストを実行する方法を紹介します(Chromeのみでの実行になります)。

E2Eテストとは

関数などの部品単位を対象にした単体テストに対して、例えばUIを実際に操作してフロントエンド/サーバサイド/DBのすべてが期待通りに動くことを確かめるようなテストはE2Eテスト(End to Endテスト)と呼ばれます。 部品それぞれが正しく動いても組み合わせたときに全体が正しく動くとは限らないので、組み立てたWebページを対象としたテストで動作を見てあげることが必要です。

例えば今日の記事で紹介するようなブラウザ操作自動化ツールを使用することで、ユーザーがWebサービスを利用するときのような一連のブラウザ操作を再現させ、それをテストとして表現することができます。

headless Chromeとpuppeteer

Chromeといえば有名なブラウザですが、headlessモードを使えばGUIなしで高速に動作させる事ができることからE2Eテストで使われています(headless Chrome)。

headless Chromeは開発者が操作しやすいように、DevTools Protocolを介して操作するpuppeteerというNodeのライブラリが提供されており、このライブラリがブラウザ操作を助けてくれます。 github.com

puppeteerの特徴を上げると

  • GoogleのDevToolsチームがメンテしてます(コードが長期間メンテされそうですよね!)
  • ブラウザ操作でできる大体のことはpuppeteerを介してもできます
    • フォームを埋めたり
    • SPAをクロールしたり
    • デバイスサイズに合わせたスクリーンショットを取ったり

テストを動かす

puppeteer自体はただのブラウザ自動操作ツールなので、テストとしてチェック項目を確認できるようにテスト用のツールも導入します。

テストフレームワークの選定

js界ではいくつか有名なテストフレームワークがありますが今回はJestを採用しました。 jsにはまだそれほど慣れていないので

  • ドキュメントが豊富なこと
  • すぐ動かせるサンプルコードがあったこと

を基準に選びました。 今回つくったE2EテストはJestの公式ドキュメントにあったサンプルを利用したので基本ケースをすぐに作ることができました。

テストの設定

Jest, puppeteer, jest-puppeteer(puppeteerを使ったテストの設定をいい感じにしてくれる)をインストールします。

__tests__以下にテストファイルを配置し、yarn testでテストを実行するようにします。

  "scripts": {
    "test": "jest __tests__",
  },

実際にテストを実行する際には、環境変数の設定などを行う処理などとともにシェルスクリプトに記述しておき、シェルスクリプトを実行するようにしました。

テスト例

例えば「駅メモ!」の公式サイトの「駅の思い出」ページをテストします。
駅の思い出ページの検索ボックス

「駅の思い出」ページでは検索ボックスが1つあり、

  • 駅名を検索することで、その駅のページに遷移します
  • 駅のページでは駅の情報を表示することができます

今回のテスト例としては、表示される路線情報・都道府県情報が期待通りなのかを確かめます。

const timeout = 5000
    ...
    it('五反田という駅名で検索できる', async () => {
      const stationName = '五反田'
      await page.type('.text-input', stationName)
      await page.click('.search-btn')
      await page.waitFor('.activity-best-matched-station--result')

      let text = await page.evaluate(() => document.body.textContent)
      // 五反田に乗り入れる各路線が期待通り表示されるかどうか
      expect(text).toContain('JR山手線')
      expect(text).toContain('東急池上線')
      expect(text).toContain('都営浅草線')

      // 五反田は東京都の駅である
      expect(text).toContain('東京都')

      // 五反田駅のページのスクリーンショットを取得する
      await page.screenshot({path: '__tests__/database-gotanda.png'})
    })
  },
  timeout
)

テスト結果

前処理-テスト-後処理の順で実行し、テストを通過させることができました。

テストの実行ログ
yarn testの実行ログ(成功した場合)

またスクリーンショットも以下のように簡単に取得することができました。

五反田駅の検索結果
五反田駅の検索結果

デバッグ方法

もし意図しない結果を得た場合、下の例のようにheadlessモードをオフにしてテストを実行することでブラウザのウィンドウが立ち上がり、puppeteerが自動で操作する様子を目で見ながら確認することができます。

  const browser = await puppeteer.launch({
    headless: false,
    slowMo: 500, // スローモーション
  })

今後の展望

E2Eテストの対象ページを増やしたり、スクリーンショットの差分チェックテストをしたりできるといいですね!

明日は id:narita-cppです。お楽しみに!