playwrightとGitlab CI/CDでE2Eテストを自動化してみた

この記事は[ニフクラ](https://www.nifcloud.com/) 等を提供している、[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct) の7日目の記事です。

前日は`@heriet` さんの [trivy+conftestで柔軟にライセンスポリシーをチェックする](https://zenn.dev/heriet/articles/license-policy-check-by-trivy-and-conftest)  でした。 
ライセンスポリシーのチェックをより容易に、汎用的にできるというのは魅力的で私自身すごく勉強になりました。

# はじめに
初めまして！
私は富士通クラウドテクノロジーズ2年目の社員で、基幹システムの運用・開発をしております。
今回は実際に私が部署で管理しているWEBアプリのE2Eテストを**Playwight**と**Gitlab CI/CD**で自動化した経験から、自動化までの簡単な流れを書こうと思います。

# E2Eテストとは
E2E（エンドツーエンド）テストは、User Interface Testとも呼ばれております。
WEBアプリにおいて、**ユーザの想定される操作に対し、WEBアプリが正しく動作してくれることを確認する** 上流工程のテストになります。
ユーザの動作に基づいてテストケースを作成し実施していくためアプリの正常性を確認するのにとても重要な工程ですが、それが故にテストケースの作成から実施まで時間がかかってしまうという問題点もあります。

# なんで自動化するのか
E2Eテストに限らず、システムは大きくなればなるほどテストも肥大化し、エンハンスをしたつもりが気づいたらデグレを引き起こすといったことは往々にしてあります。
そのため、さあテストをしていくぞとなったとき、「本当は全機能テストしたいけど工数が、、」といった悩みに苦しむ人はたくさんいると思います。
しかし、ボタン1つ押すと勝手にテストをやってくれて、その証拠（スクリーンショットとか動画）まで採取してくれたらどうでしょう。 **工数の削減・デグレ防止** につながるだけでなく、どうしても単純作業で憂鬱になってしまう（個人差あり）テスト工程をやらなくて済むと良いこと尽くしです。
また、ヒューマンエラーというのはどうしても発生してしまいます。それはテスト工程でも同じように見逃しなどは起きてしまうでしょう。

上記などの理由で皆さん自動化を試みるんだろうと、私は勝手に想像しております。


# Playwrightについて
PlaywrightはNodeのライブラリでブラウザ操作をコマンドラインで実現できるものであり信頼できるE2Eテスト実施のために作られたものです。
[公式](https://playwright.dev/)での特徴として以下が挙げられています。
- クロスブラウザ、クロスプラットフォームでのテストが可能
- イベントが豊富に用意されており、不安定なE2Eテストの原因の一つである人為的なタイムアウトを作る必要がなくなる
- テスト再試行を行う上での画面キャプチャや動画ファイルの作成、実行ログ等が豊富

私がこのライブラリを知ったのは1年ほど前で、その当時は比較的新しいものであまりドキュメントもなかったですが触ってみたいという気持ちも込めてPlaywrightで実装を行いました。


# 手順
では、実際にGitlab CIでE2Eテストを自動化する流れを書いていきます。
## フォルダ構成
以下がフォルダ構成です。 `testcodes`の中にplaywrgihtのテストコードを置いていきます。
```
Gitlabプロジェクト
├── .gitlab-ci.yml
├── docs
    ├── playwright.config.js
    └── testcodes
        ├── 01-01-01.spec.js
        ├── 01-01-02.spec.js
        ├──       ・
        ├──       ・
        ├──       ・
```

## Gitlab CI/CD
以下の.gitlab-ci.ymlを用意しました。
```yml:.gitlab-ci.yml
image: mcr.microsoft.com/playwright:focal

stages:
  - test

e2e:
  stage: test
  script:
    - cd docs/
    - npm install playwright
    - npm install @playwright/test
    - npx playwright test
  retry:
    max: 2
    when:
    - runner_system_failure
  only:
    - web
  artifacts:
    when: always
    name: "results"
    paths:
      - docs/*
    exclude:
      - docs/testcodes/*
      - docs/node_modules/*
      - docs/*.js
```
- `image` : PlaywrightのDockerイメージがあったのでそれを使います。（https://hub.docker.com/_/microsoft-playwright）
- `script` : 動作に必要なNodeのパッケージをインストールしてテストを実行します。
- `retry` : 失敗時のリトライ制御です。
- `only` : いつジョブが作成（実行）されるかを制御します。今回はUIからの実行のみにします。
- `artifacts` : ジョブ実行後に、指定されたパス配下のファイルをzip形式で画面からダウンロードします。 `exclude`を指定すればダウンロードする対象ファイルから指定されたものを除外することが可能です。（今回はPlaywrightの機能で保存されたスクショや動画のみをダウンロードするようにその他を除外している）

詳しくは[公式リファレンス参照](ttps://gitlab-docs.creationline.com/ee/ci/yaml/)

 ## playwrgiht
設定ファイルとして `playwright.config.js` を用意します。実行フォルダに `playwright.config.js`もしくは `playwright.config.ts`を置けば勝手に読み込んでくれるみたいです。
```js:playwright.config.js
module.exports = {

  timeout: 100000,
  workers: process.env.CI ? 1 : undefined,
  testDir:'testcodes',

  projects: [
    //  use Chromium browser.
    
    {
      name: 'chrome',
      use: {
        launchOptions:{
          //args:['--proxy-server=' ],
          headless: true,
        },
        baseURL: '<テストするアプリのURL>',
        browserName: 'chromium',
        viewport: { width: 1280, height: 720 },
        video: 'retain-on-failure',
      },
    },
    

    //  use firefox browser.
    
    {
      name: 'firefox',
      use: {      
        launchOptions:{
          //args:['--proxy-server=' ],
          headless: true,
        },
        baseURL: '<テストするアプリのURL>',
        browserName: 'firefox',
        viewport: { width: 1280, height: 720 },
        video: 'retain-on-failure',
      },
    },
    

    // use webkit browser
    {
      name: 'webkit',
      use: {      
        launchOptions:{
          //args:['--proxy-server=' ],
          headless: true,
        },
        baseURL: '<テストするアプリのURL>',
        browserName: 'webkit',
        viewport: { width: 1280, height: 720 },
        video: 'retain-on-failure',
      },
    },
  
    
  ],
  };


```
`timeout` : テストの制限時間を設定できます。 
`testDir` : テストコードがあるディレクトリを指定します。
`projects` : こちらに各ブラウザの設定をそれぞれ配列で格納することで、各ブラウザでのテストが実行されます。(上記では chrome,firefox,webkit(safari)で実行されます)
`launchOptions` :  プロキシの設定や、headlessでのテスト実行などの設定ができます。
`video` : 録画ファイルをどのような場合に作成するか設定します。上記の設定は **テストが失敗した場合** 録画ファイルが保存されるような設定です。



<br>
<br>

次にテストコードです。
上記で設定した`testcodes`というフォルダにテストコードを保存していきます。
以下にその一例を載せます。
```js:01-01-01.spec.js
const { test, expect } = require('@playwright/test');


test('[01-01-01-a]アカウント1でのログイン', async ({  page, baseURL,browserName }) => {

 // Go to baseURL
  await page.goto(baseURL);

  // Click [placeholder="アカウント名"]
  await page.click('[placeholder="アカウント名"]');

  // Fill [placeholder="アカウント名"]
  await page.fill('[placeholder="アカウント名"]', 'account1');

  // Click [placeholder="パスワード"]
  await page.click('[placeholder="パスワード"]');

  // Fill [placeholder="パスワード"]
  await page.fill('[placeholder="パスワード"]', 'password');

  // Click input:has-text("ログイン")
  await Promise.all([
    page.waitForNavigation(),
    page.click('input:has-text("ログイン")')
  ]);

  await page.waitForSelector('text=ロゴイン成功');
  expect(page.url()).toBe('<遷移後のURL>');
  await page.screenshot({ path: `screenshots/01-01-01-a/${browserName}/1.png`, fullPage: true,});

});
```
上記のテストコードは、`account1`というユーザでログインするというテストです。
以下が流れとなります。

1. ` page.goto(baseURL);`で`playwright.config.js`に設定したbaseURLにアクセスしますが、認証画面にリダイレクト
2. プレスホルダーに**アカウント名**とあるテキストボックスに`account1`,プレスホルダーに**パスワード**とあるテキストボックスに`password`と入力します
3. `waitForNavigation`でページ遷移を待ちつつ、` page.click('input:has-text("ログイン")')`で**ログイン**と記載されているボタンをクリック
4. `waitForSelector`でログイン後のページのテキストにある **ログイン成功**という文字が読み込まれるまで待ち、`page.screenshot`でスクショを撮影する（スクショは`testcodes`ディレクトリから相対パスで、`screenshots`ディレクトリ > テストNoごとのディレクトリ > ブラウザごとのディレクトリに置いていきます）

また、テストコードを自動で生成してくれるような機能も存在します。
今回は詳細については触れませんので、気になる方は[こちら](https://playwright.dev/docs/codegen-intro)をご確認ください。


# 動作確認
では実際に動かします！
Gitlabで上記ファイル群を保存したプロジェクトで、`CI/CD`> `Pipelines`に移動し、 `Run pipeline` を押下します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985502/3e9f12e6-9ba5-5074-8480-2cde9ba3114c.png)


するとテストが実施されます。（実施の様子はe2eジョブの実行時のコンソールを確認してください）
成功すると以下のように`Stages`部分にレ点がつきますので
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985502/dd19e442-bce3-92a0-7e1d-f7ac41c9eb3f.png)

その右のダウンロードっぽいボタンを押してみると、e2eジョブのarchive、、つまりテストで撮影したスクショなどがダウンロードできます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985502/5ee80e94-42d9-97b1-e2e0-1fdc9731e1a4.png)

もちろん失敗した場合も、失敗するまでのスクショと合わせて録画ファイルもダウンロードできるのでなぜ失敗したのか簡単に追うことができます。
以下は実際に失敗したときのコンソールログの様子です。なぜ失敗したか・どこで失敗したかも一目瞭然ですし、ログで分からなくても録画ファイルがあるので確認できますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985502/360f196c-ab41-5470-ed1b-7dc64d8e0d57.png)


# 最後に
今回はGitlab CI/CDとPlaywrightを利用して、E2Eテストを自動化してみました。
やってみた感想として、Playwrightはクロスブラウザでのテストが可能だったり、関数がたくさん用意されていて実装に迷いにくいと思いました。
また、私が実際にPlaywrightでE2Eテストを作成していた1年前から、この記事を作成した間に公式のリファレンス・WEB記事ともに充実していて、期待されていて進化を続けているライブラリの一つだと感じました。
今回の成果物を発展させて、デプロイ起点で自動でCIジョブが起動したり、マージリクエストを発行したらコンテナ上でWEBアプリ起動・そのままE2Eテストが走って成功しないとマージできないようにしたりできたらいいなとも思いました。
これからも機会があれば進化を追い続けていきたいのと、それをまた記事にできればと思います。






この記事は[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct) の7日目の記事でした。

明日は [@nakanosika](https://qiita.com/nakanosika)さんの「Istioでカスタムメトリクスの取得をやってみた」です。
私自身Istrioを知らなかったのですが、マイクロサービスのネットワーク管理のフレームワークということで楽しみにしています。