フロントのE2EテストをJest+jest-puppeteerでやる

Jest+jest-puppeteerを使ったE2Eテストをする

本業の方でECサイトのフロントエンドをやっているのだが、いい加減手作業でのリグレッションテストにキレそうになってきたので、フロントエンドの自動テストを試みてみた。

目次

目的と概要

ブラウザで特定のURLに行って、ポチポチと手作業で特定の操作を行って、〇〇が起こること、みたいなテストケースに対して自動でOK、NGを出すようにする。

Jestとjest-puppeteerを使って上記を実現する。

最終的にはTerminalを使って下記のようにテストケースごとにOK/NGを出せるようにする。

環境インストール

Jestとpuppeteer、puppeteerをJestで使うために最適化したjest-puppeteerをインストールする。

$ mkdir jest_test
$ cd jest_test
$ vim package.json 
{
  "dependencies": {
    "jest": "^26.6.3",
    "jest-puppeteer": "^4.4.0",
    "puppeteer": "^2.1.0"
  }
}

jest-puppeteerとpuppeteer間でバージョンの依存があるっぽいので、package.jsonで明示的に示したほうが良い。

後は普通にnpm installで入れる。

$ npm install

コンフィグを書く

$ vim jest.config.js
module.exports = {
    preset: 'jest-puppeteer',
}

テストコードを書く

しょうもないサンプルではなく、実際のプロジェクトで確認していることを例にしてテストコードを書いてみる。

例えば、このブログのWordPressテーマは

トップページのみ、スラグがhabbisの記事を3記事表示して、Moreリンクを表示するという仕様がある。

こちらをテストしてみる。

__tests__ディレクトリ配下がテスト対象のコードを格納するところ。

なので作る。

$ mkdir __tests__
$ vim __tests__/_index.js
let page;
const host = 'https://yhei-web-design.com';

beforeAll(async () => {
  jest.setTimeout(1000000);
  page = await browser.newPage();
})

test('トップにRoutineカテゴリーの一覧の見出しが存在すること', async () => {
  // トップに遷移
  await page.goto(`${host}`);
  // 最初のセクションにRoutineカテゴリーのタイトルが表示されていること
  const el = await page.$('h2.heading');
  expect(await (await el.getProperty('innerHTML')).jsonValue()).toBe('Routine<span class="heading__caption">日々の積み重ね</span>');
})

test('トップにRoutineカテゴリーの記事が3つ存在すること', async () => {
  // トップに遷移
  await page.goto(`${host}`);
  const articleClassLists = await page.evaluate(() => {
    const dataList = [];
    // 一つ目のカテゴリー一覧の記事リストを取得
    const nodeList = Array.from(document.querySelector('.site-main').children);
    nodeList.forEach(_node => {
      dataList.push(Array.from(_node.classList));
    });
    return dataList;
  });
  for (let i=0; i < articleClassLists.length; i++) {
    await expect(articleClassLists[i].includes('category-habbits')).toBe(true);
  }
})

test('RoutineカテゴリーのMoreリンクを押すとRoutineカテゴリー一覧に遷移すること', async () => {
  // トップに遷移
  await page.goto(`${host}`);
  await page.$eval('a.top-category-link', link => link.click());
  await expect(page.url()).toBe(`${host}/habbits/`);
})

test('2ページ目にはRoutineカテゴリーの一覧が存在しないこと', async () => {
  // トップに遷移
  await page.goto(`${host}/page/2/`);
  const el = await page.$('h2.heading');
  expect(await (await el.getProperty('innerHTML')).jsonValue()).not.toBe('Routine<span class="heading__caption">日々の積み重ね</span>');
})

querySelectorで要素を取得するのが異常にめんどい。変に加工せずにdocument要素だけくださいって感じ。

最終的なファイル構成は下記。

$ tree -L 1
.
├── __tests__
├── jest.config.js
├── node_modules
├── package-lock.json
└── package.json

テスト実行

$ ./node_modules/.bin/jest
 PASS  __tests__/_index.js (9.979 s)
  ✓ トップにRoutineカテゴリーの一覧の見出しが存在すること (4082 ms)
  ✓ トップにRoutineカテゴリーの記事が3つ存在すること (1286 ms)
  ✓ RoutineカテゴリーのMoreリンクを押すとRoutineカテゴリー一覧に遷移すること (2546 ms)
  ✓ 2ページ目にはRoutineカテゴリーの一覧が存在しないこと (1367 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        10.094 s, estimated 11 s
Ran all test suites.

ターミナルで実際に実行したイメージは下記。仕様ごとに色とかマークがついててわかりやす〜い。

まとめ

jest-puppeteer自体にちょっと癖があって慣れるまで大変ではある。

が、視認で確認することはほぼすべてできる感触があった。

また、1メソッド1アサーションがテスト駆動開発の定石だが、実行に少し時間がかかるので、あまりURL遷移とかはさせない方がいい気がしている。ときには1メソッドでたくさんアサートしてもいいと思う。

運用イメージとしては仕様を決めたらその仕様通り動くかどうかのテストを書く。

pre-push前やマージ前にこのテストを自動で走らせて、NGであればpushやマージさせないようにする。

そうすると、テストが陳腐化しなくて済む。

最終的にテストに仕様がすべて書いてあり、テスト自体がQAチームかつ仕様書みたいなノリになるのがゴールだと思う。

次回は運用に載せるための準備とかを書いてみる。