NuxtアプリをGitLabにpushしたときに自動でE2Eテストする(JestとPuppeteer)

Git, Vue.js/Nuxt.js

WEBアプリをリリースするたびに、いつも人力でフロント結合テストを上から下までガンバってやってたんですが、さすがにエンジニアの工数割くのももったいないし、外部に委託できる内容でもないしで開発のネックになってたんですよね。

コード書いて自動でテストしてくれたら楽だなーと思って、腰を据えて書いてみたら想像の3倍苦労したので、備忘録がてら記事にしてみました。
あ〜あ、自動テストも自動で書いてくれる世界になったらいいのに。

まず、これからやることを整理すると…

  1. Nuxt.jsのプロジェクトをGitLabにpush/mergeすると、
  2. E2E(End to End)テストが自動で走って、
  3. すべてOKだったらスクショをDLできる(うまくDLできず、保留中です。。)

フロントサイドのデグレ・スタイル崩れを減らしたいんじゃ!というのと、結合テストの項目を減らしたいという目的から、今回自動テスト導入に至りました。

JestというFacebook製のテストツールと、PuppeteerというChromeをHeadless(ブラウザGUIなし)で動かせるライブラリ。
これらをガッチャンコしたプリセット、それが jest-puppeteer です!

スポンサーリンク

jest-puppeteerを導入する

$ yarn add -D puppeteer jest-puppeteer

まずはjest-puppeteerを導入します。

それからプロジェクトにいくつかファイルを作成していきます。
jest-puppeteer.config.js をプロジェクトのルート直下に作成します。

続いてルート直下に jest.e2e.config.js を追加します。

package.json にテスト実行用のスクリプトを追加します。

実際にe2eテストを動かす test:e2e スクリプトを用意し、
テスト実行時にテストサーバーが自動で立つように、 testServer スクリプトを用意します。

jest-puppeteerのコードを書く

プロジェクト/test/e2e/ ディレクトリを作成し、その中に index.spec.js を作成します。
こちらが全体のサンプル。あくまで挙動を確認する用なのでheadlessではない(ブラウザが表示される)し、テストサーバーも使いません。

※行末セミコロンは省略してます。

サンプルコードではGoogleのトップページのレンダリングと、検索窓に文字を入力して検索実行できるかという2項目の簡易的なテストをしています。

jest-puppeteerの全体の構成

ここで、index.spec.js の構成についてざっくり説明します。

jest-puppeteerの大まかな構成はjestのルールに従っています。
describeを一つの塊として「E2E TESTを実施するよ〜」と宣言し、その中にテストコードと、テストに付随する前処理・後処理を書いていくイメージです。

注意してほしいのは、テストケース以外の関数は上から実行されるわけではなく、決まった順番で呼ばれるということです。

  1. beforeAll
  2. beforeEach
    1. (テストごと)
    2. afterEach
    3. it
    4. beforeEach
  3. afterEach
  4. afterAll

続いて、関数の説明とサンプルコードを紹介します。構成とテストマッチャーについて詳しく知りたい方はこちらのQiitaの記事が非常に参考になるのでどうぞ(丸投げ)

beforeAll(describe内で最初に1度だけ呼ばれる関数)

テストの前に、ブラウザの起動とページの用意、ベーシック認証の設定などをあらかじめ準備しなければいけません。テスト内で最初に行う処理を beforeAll に書いていきます。

beforeEach(テストケース実行前に毎回呼ばれる関数)

テストケースの前に毎回呼ばれる関数です。僕は特にやることが思いつかず使いませんでした。

テストケース後に毎回呼ばれる afetrEach も存在します。

it または test(テストケース)

ここにテストケースを記述していきます。
ブラウザ上で行う操作(クリック、入力、ページ遷移など)はPuppeteerを使って書きます。

Puppeteerの記述方法については別記事にまとめる予定なので、少々お待ちを。
またはこちらのQiitaの記事にほとんど書いてあるので参考にしてみてください(またか!)

テスト完了後(afterAll)

テスト完了後には必ずブラウザプロセスを終了します。
そうしないと実行しているサーバーやコンテナ内にプロセスが残り続けてしまうためです。
waitForを入れているのは、テストケースがすべて完了する前にブラウザが閉じられてしまうことがあったためです。

ローカルでテスト実行してみる

$ yarn test:e2e

だいたいのテストコードを書き終えたら、実際にローカルでテストを動かしてみましょう。

すべての項目がOKだったとき

テスト中にPuppeteerが撮影したスクリーンショットもちゃんとローカルに保存されています。(何故かこの検索結果のページはfullPage指定でも全画面にならなかったけど)

google-search.png

NG項目があったとき

わざと結果と異なるようにして、テストを失敗させてみました。

きちんとどこでなぜNGになったかが表示されます。
ページのタイトルが期待する文字列と違うと、以下のように表示されます。

※実行時にエラーが出る場合はPolyfillを入れる必要があるかもです。ちょっとこのへんは記憶が曖昧です。

$ yarn add @babel/polyfill

GitLab-CI上でテスト実行してみる

.gitlab-ci.yml

GitLab CIでテストを実行するには、.gitlab-ci.yml に test の STAGE を用意して、scriptの中で yarn test:e2e を実行する記述をすれば、自動でパイプラインに追加されます。
失敗したときも、先程コンソールで出たように詳細なエラー内容がログから辿れるのでとても便利です。

しかしrulesを設定しないと、commitごとに毎回テストが走ってしまいます。
なので僕は

「developブランチとstagingブランチにマージするとき」 → 必ず自動実行
「featureとつくブランチをcommitしたとき」 → マニュアル実行

というルールを作りました。

CIのパイプラインからスクリーンショットをDLする

テストはCIのコンテナ内で行われ、テスト中に撮影されたスクリーンショットたちもそこに保存されているので、確認することができません。

artifacts にパスを設定すると、ジョブの成果物を取り出す(DLする)ことができる…はずなんですが、パスの設定が悪いのか、知識不足な僕はまだうまく取れたことがないです。。
分かり次第更新します。すみません。

CIのテスト結果を通知する

CIがコケたときにSlackで通知したりメール送ったりすることも可能です。
今回の記事の内容からは逸れるのでこちらの記事を参考にしてください。

ここまでできれば、E2Eテストは完成したも同然です。テストの粒度を細かくしたり、精度を高めたりしながら、フロントの結合テストをしなくてよくなる日も近いかも。

参考にさせていただいたサイト

Jest
Getting Started · Jest
Facebook製のJavaScriptテストツール「Jest」の逆引き使用例 – Qiita

puppeteer
Nuxt.js に後から E2E テスト (puppeteer, jest-puppeteer) を入れる – Qiita
【Node.js】puppeteer基本情報&逆引き – Qiita
JestとPuppeteerでお手軽(Visual)レグレッションテスト – Qiita

GitLab CI
GitLab CIとPuppeteerを使ってはてなブログのデザインを継続的にデプロイする – pixiv inside – pixiv inside
GitLab CI/CDパイプライン設定リファレンス(日本語訳:GitLab CI/CD Pipeline Configuration Reference) – Qiita