【フロントエンドテスト】PlaywrightでE2Eテストを実施する。

## はじめに
はじめまして、河村康治です。HITOTSUアドベントカレンダーの11日目担当します！！
普段はバックエンドを主に担当しているのですが、テスト基盤構築に力をいれて行こうと思い、今回のアドベントカレンダーではフロントのテストに関して記載して行こうと思います！！！
今回は、Playwrightを活用したE2Eテストの記事を書いて行きます！！

## Playwrightとは？？
Microsoft開発中のE2Eのテストライブラリです。もう少しイメージしやすく言うと、SeleniumやPuppeteerなどのスクレイピング実行やコード生成を実行できるツールです。最近の開発では採用される事が増えており、リグレッションテストや受け入れテストを自動化し、テスト工数を削減し品質を高める事に一役買っています！！

![スクリーンショット 2022-12-12 17.47.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/c1e5da22-b46a-4229-0eb4-204feb200033.png)

https://playwright.dev/

## Playwrightの強みは？？
結局、SeleniumやPuppeteerと比べて何がいいかと言うと、一番はコードを自動生成してくれる所です。スクレイピングを実施する場合、Domツリーを解析しコードに落とし込むのが大変。やった人ならばわかるのですが、開発者ツール開いて、class名は〜〜で、idは〜〜みたいな感じでコード解析し、コードに落とし込む。で、トライ＆エラーを繰り返しながら作る。ただ、PlaywrightならばGUIでポチポチしていく事でコードが自動生成されます。百聞は一見にしかずなので、下記動画をご覧ください。

■サンプル動画(iphoneの購入サイトへの画面遷移)

https://twitter.com/kkfactory0705/status/1602198930795954176

## E2Eテスト
では、実際にPlaywrightを利用しE2Eテストの実装をしていきましょう！！今回は、Redmineを元にテストを実施していきたいと思います。



<details><summary>Redmineの準備</summary><div>
RedmineはDockerを使いローカルに立てます。

```yml:docker-compose.yml
version: '3.8'
services:
  redmine:
    image: redmine
    container_name: redmine
    ports:
      - 3010:3000
    volumes:
      - ./data/plugins:/usr/src/redmine/plugins
      - ./data/themes:/usr/src/redmine/public/themes
    environment:
      REDMINE_DB_MYSQL: redmine-db
      REDMINE_DB_PASSWORD: redmine
    depends_on:
      - redmine-db
    restart: always
    
  redmine-db:
    image: mariadb
    container_name: redmine-db
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: redmine
      MYSQL_DATABASE: redmine
    volumes:
      - ./data/db:/var/lib/mysql
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    restart: always
```
これでOK！！
`http://localhost:3010/`でアクセスし、ユーザIDとパスワード共に、adminで入れます。
その後、パスワード変更やプロジェクト作成やトラッカー作成などの準備が必要です。

</div></details>

成果物は下記動画の様になります。

<iframe width="560" height="315" src="https://www.youtube.com/embed/Ur9rCXjnBa0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

### ソースコード

```shell:yarnコマンド
yarn create playwright ←これすると、Playwrightで必要な定義ファイルを作ってくれます。
```


```shell:ディレクトリ構成
~/develop/react/playwright_test$ tree -I node_modules               
.
├── README.md
├── docker-compose.yml
├── package.json
├── playwright-report
│   ├── data
│   │   └── 9e7d941236a5e77523b746a304baf6cab737bbc8.webm
│   └── index.html
├── playwright.config.ts
├── prettier.config.js
├── tests
│   ├── example.spec.ts
│   └── redmine.spec.ts
├── tsconfig.json
└── yarn.lock

7 directories, 15 files
```

```ts:redmine.spec.ts
/* eslint-disable testing-library/prefer-screen-queries */
import { test, expect } from '@playwright/test'
import * as dotenv from 'dotenv'
dotenv.config()

test('test', async ({ page }) => {
  const userName = process.env.REACT_APP_USER_NAME || ''
  const password = process.env.REACT_APP_PASSWORD || ''

  await page.goto('http://localhost:3010/')
  await page.getByRole('link', { name: 'ログイン' }).click()
  await page.getByLabel('ログインID').click()
  await page.getByLabel('ログインID').fill(userName)
  await page.getByLabel('ログインID').press('Tab')
  await page.getByLabel('パスワード\n    パスワードの再設定').fill(password)
  await page.getByRole('button', { name: 'ログイン' }).click()
  await page.locator('#top-menu').getByRole('link', { name: 'プロジェクト' }).click()
  await page.getByRole('link', { name: 'test' }).click()
  await page.locator('#main-menu').getByRole('link', { name: 'チケット' }).click()
  await page.getByRole('link', { name: '新しいチケット' }).click()
  await page.getByLabel('題名 *').fill('PlayWright test')
  await page.getByLabel('説明').click()
  await page.getByLabel('説明').fill('PlayWright test')
  await page.locator('input[name="commit"]').click()
  await page.waitForTimeout(3000) // 確認様に3秒待機
  const actual = await page.locator('h2').first().innerText() // ①データ取得
  console.log('actual:', actual)
  await expect(actual).toContain('テスト') // ②値比較
})

```

### ソースコード実装時のPoint
基本的に、`npx playwright codegen localhost:3010`を利用して記載していきます。
ただ、データ取得や値の比較は実装する必要があるため、記載していきます。
①データ取得
 ブラウザの開発者モードを開き、DOMツリーを解析。解析結果を当てはめます。
　![スクリーンショット 2022-12-12 19.08.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/1d99ebbf-9f25-7e1a-8df6-22e4b9b5b5b6.png)

②実行値と期待値の検証
　今回はチケットのタイトルが「テスト」であるかを検証項目としています。タイトルは「テスト #19」のように余計な値が含まれており、`#19`はチケット番号を含むため、可変の値となります。そのため、完全一致ではなく、部分一致するかを確認する検証としています。
![スクリーンショット 2022-12-12 19.10.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/c5f5214f-1163-c66e-8a19-9300c3e9b58c.png)

## Appendix

<details><summary>Playwrightの設定ファイル</summary><div>

```ts:playwright.config.ts
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

const config: PlaywrightTestConfig = {
  testDir: './tests',
  /* Maximum time one test can run for. */
  timeout: 30 * 1000,
  expect: {
    timeout: 5000,
  },
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    actionTimeout: 0,
    headless: false, // ブラウザを表示するかどうか
    /* Base URL to use in actions like `await page.goto('/')`. */
    launchOptions:{
      slowMo: 1000, // テスト実行時のスローモーション
    },
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        locale: 'ja-JP', // ロケールを日本語に設定
      },
    },
  ],

};

export default config;
```

覚えておきたい事は下記。
①ヘッドレスモードの有無。テスト実行時にブラウザ表示するか。デバッグ時には必須機能！！
②テスト実行時の速度設定。デフォルト設定の場合実行速度が速すぎるため、デバッグ時はスローモーション設定必須。
③ローケールの設定
```
  use: {
    actionTimeout: 0,
    headless: false, // ブラウザを表示するかどうか
    /* Base URL to use in actions like `await page.goto('/')`. */
    launchOptions:{
      slowMo: 1000, // テスト実行時のスローモーション
    },
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        locale: 'ja-JP', // ロケールを日本語に設定
      },
    },
```

</div></details>

### Playwrightで覚えておきたい関数
①何秒か待つ
　　Debug時や処理を待ちたい時に利用する関数
```ts:何秒か待つ
  await page.waitForTimeout(3000) 
```
②動きを止める
Debug時に開発者ツールでセレクターや値を確認する。
```ts:動きを止める
  await page.pause（）
```


### test実行時に覚えておきたいオプション
①ブラウザ表示有りで実行
　playwright.config.tsでも定義できますが、開発時はブラウザあり、リグレッションテスト時はブラウザ無し等の要望に答えるため、実行時パラメータで制御可能です！！　
```shell:ブラウザありの実行
　　~/develop/react/playwright_test$ npx playwright test redmine.spec.ts --headed
```

②デバッグモードでのテスト実行
　ブレークポイントを利用したテストが可能となります。

```shell:デバッグモードでの実行
~/develop/react/playwright_test$ npx playwright test redmine.spec.ts --debug
```
![スクリーンショット 2022-12-12 20.13.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/c08c80fc-ba04-2de6-0178-91e2d533eccd.png)
実行部分がStepByStepで確認でき、赤いポインターが出てどこのセレクターを指しているのかわかりやすいです！！

③show-traceの活用
え、こんなこともできるの、PlayWrightって感じだった機能です。テスト実行時に動作毎にスクショを撮り、後でどんな動きをしたのかを確認できる機能です。事前にコードを仕込む必要があるので、それも記載しておきます。
```shell:デバッグモードでの実行
~/develop/react/playwright_test$ npx playwright show-trace ./trace.zip 
```

```ts:コード抜粋
test('test', async ({ page, context }) => {
  await context.tracing.start({ screenshots: true, snapshots: true }) ←最初に記載する。
  const userName = process.env.REACT_APP_USER_NAME || ''
  const password = process.env.REACT_APP_PASSWORD || ''

  await page.goto('http://localhost:3010/')
~~~~~~~~~~~~~~~~~~~~~割愛~~~~~~~~~~~~~~~~~~~~~~~~~~~
  await expect(actual).toContain('テスト')

  await context.tracing.stop({ path: 'trace.zip' })　←最後に記載する。
})

```
![スクリーンショット 2022-12-12 20.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/f8513ee6-c31d-54b7-80a5-ebe74be36769.png)
上記画像の様に、各スクリーンショットに対し、実行時間やSelector、値等のさまざまな情報が含まれており、DebugやTrobuleシューティングですごい役立ちそうな機能です！！しかも、Before、Action、Afterのスクショがならんでいるので、ここで何したっていうのが一目瞭然なんですよ！！
SIerで働いてた時、こんな風にスクショ取ってExcelにペタペタ貼ってたので、これは感動しました😭

![Before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/bf39232f-318e-b8c6-1a0e-090b8bac16b6.png)
![Action.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/70683986-b47d-09d0-5314-da8438ee900c.png)
![After.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/23947172-ca09-1de3-6d01-b0719712825d.png)


## 最後に
Playwrightの学習は、Youtubeを中心にやりました。公式サイトや海外Youtuberの方が懇切丁寧に解説してくれているため、効果的に利用すると学習しやすいですよ！！！ちなみに僕は下記で学習しております。

https://www.youtube.com/@Playwrightdev

https://www.youtube.com/@letcode

https://www.youtube.com/@RaghavPal