Github ActionsでPlaywright/PupeteerのE2Eテスト

## はじめに
今回はE2Eテストツールである[Playwright](https://github.com/microsoft/playwright)と[Pupeteer](https://github.com/puppeteer/puppeteer)をGithub Actionsのワークフロー上で動くように設定した際の手順の整理です。PlaywrightとPupeteerどちらも使ってみましたが、テストコードに使う関数や設定周りほとんど同じような印象でした。

## 1. Typescript + Jest + Playwright/Pupeteerの設定

まずはTypescriptでPlaywright/Pupeteerのテストを実行するのに必要な設定周りです。

### 1.1 Playwright

#### 必要なライブラリのインストール

```bash
npm i -D playwright jest ts-jest jest-playwright-preset jest-image-snapshot @types/jest-image-snapshot
```

#### jest-playwright.config.js 

https://github.com/playwright-community/jest-playwright/#configuration

`serverOptions` に別プロセスで実行するコマンドを指定することができる（下記 `npm start` でPlaywrightがアクセスする、フロントエンドのリソースを返すWebサーバを起動）

```js
module.exports = {
  launchOptions: {
    headless: true,
  },
  serverOptions: {
    command: "npm start",
    port: 3000,
  },
  browsers: ["chromium"],
};
```

#### jest.config.js

```js
module.exports = {
  preset: "jest-playwright-preset",
  transform: {
    "^.+\\.ts?$": "ts-jest",
  },
  setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
};
```

#### jest.setup.ts

`jest.setTimeout`は各テスト実行に必要な時間に応じて変更。

```ts
import { toMatchImageSnapshot } from "jest-image-snapshot";

jest.setTimeout(30000);
expect.extend({ toMatchImageSnapshot });
```


#### tsconfig.json

```json
{
  "compilerOptions": {
    "types": [
      "@types/jest",
      "@types/jest-image-snapshot",
      "jest-playwright-preset",
      "expect-playwright"
    ],
    ...
  }
}
```

### 1.2 Pupeteer

#### 必要なライブラリのインストール

```bash
npm i -D puppeteer jest ts-jest jest-puppeteer jest-image-snapshot @types/puppeteer @types/jest-environment-puppeteer @types/jest-image-snapshot
```

#### jest-puppeteer.config.js

https://github.com/smooth-code/jest-puppeteer#configure-puppeteer

```javascript
module.exports = {
  launch: {
    dumpio: true,
    headless: process.env.HEADLESS !== "false",
  },
  server: {
    command: "npm start",
    port: 3000,
  },
}
```

#### jest.config.js

```js
module.exports = {
  preset: "jest-puppeteer",
  transform: {
    "^.+\\.ts?$": "ts-jest",
  },
  setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
  globalSetup: "./jest.global-setup.js",
};
```

#### jest.global-setup.js

```js
const { setup: setupPuppeteer } = require("jest-environment-puppeteer");

module.exports = async function globalSetup(globalConfig) {
  await setupPuppeteer(globalConfig);
};
```

#### jest.setup.js
`jest.setTimeout`は各テスト実行に必要な時間に応じて変更。

```js
require("expect-puppeteer");
const { toMatchImageSnapshot } = require("jest-image-snapshot");

jest.setTimeout(30000);
expect.extend({ toMatchImageSnapshot });
```

## 2. テストコードを書く
[jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) を使ってページのレンダリング結果のスナップショット生成し、前回同じテストが走った際とスナップショットに差異があればエラーが発生するようにします。デフォルトの設定だと `__image_snapshots__` ディレクトリにスナップショットのpngと差異があった際のdiffのpngが保存されます。

```ts
beforeAll(async () => {
  await page.goto("https://www.example.com/")
})

test("Should display the page correctly", async () => {
  const image = await page.screenshot({
    fullPage: true,
  });

  expect(image).toMatchImageSnapshot({
    customSnapshotIdentifier: "ExamplePage",
  });
});
```

## 3. Playwright/Pupeteer用のDockerの準備
最初にDockerを使わずにMacのローカルでテストを実行してスナップショットを生成し、GithubのレポジトリにpushしてGithub Actions上で同じテストを実行してみました。しかしOSが違うことによりフォントのレンダリングに微妙な差異が出てテストがfailしてしまい、同じ環境を確保するためDockerを使用する必要がありました。ここから先はPlaywrightしか試していないのですが、Pupeteerも基本同様の設定で問題ないはずです。

Playwrightには[公式のDockerイメージ](https://hub.docker.com/_/microsoft-playwright)があるのですが、こちら日本語のフォントがインストールされていないため、そのまま使うと日本語の文字が豆腐文字になってしまいます。そのため日本語フォントのインストールをDockerfileに含めます。

#### Dockerfile

```docker
FROM mcr.microsoft.com/playwright:v1.5.1-bionic

# 日本語フォントのインストール
RUN apt-get install -y curl fontconfig fonts-noto-cjk \
  && fc-cache -fv

ARG UPDATE_SNAPSHOTS
ENV UPDATE_SNAPSHOTS "$UPDATE_SNAPSHOTS"

...

# スナップショットを更新する際は UPDATE_SNAPSHOTS=true を環境変数で渡す
CMD sh -c "if $UPDATE_SNAPSHOTS == 'true'; then npm run test:update; else npm run test; fi"
```

## 4 Github Actionsの設定
[upload-artifact](upload-artifact)を使ってテスト失敗時にスナップショットのDiffがダウンロードできるようにします。

```
...
- run npm e2e # 上記DockerfileでE2Eテストを実行

- if: failure()
  uses: actions/upload-artifact@v1
  with:
    name: image_snapshots_${{ github.sha }}
    path: __tests__/medium/e2e/__image_snapshots__
```

## まとめ
以上、Github ActionsでPlaywright/PupeteerのE2Eテストを行った際の手順でした。ご参考になればと思います。