E2Eテストをより簡単に？！〜Playwrightってなんですか？~

## はじめに
こんばんわ。今日はEC-CUBEアドベントカレンダーの20日目です。
アドベントカレンダーというものに初めて参加するため、何書いたらいいのか先輩方のを参考に悩んでました。
いろいろ考えてたら何やら、E2Eテストについての声が聞こえて来ました。
Playwrightというのがあるらしい。

## 気になった経緯
一番気になっている理由は、非同期処理に強いことです。
現在のE2Eテストはcodeceptionで書かれているのですが、不安定なことがあります。
ページの要素が見つからないという理由で落ちている事が多く、再実行したらクリアするという謎現象。。。
テストコードを見ていると、Sleep関数がいくつかあります。

上記から、非同期処理に強いPlaywrightが気になりました。
（ちなみにTypeScriptはよくわからないので、雰囲気で書きます。:v:）

## Playwrightについて
Microsoftさんが主体となって開発しているツールです。
一時期、GitHubのトレンドにもなりました。

機能について、ざっとまとめると以下になります。
- 対象ブラウザ
    - Chrome, Firefox, Safariなど
- デバイスのViewport や Geolocation、権限、タッチ操作など、モダンブラウザやモバイルデバイスで使われる機能のエミュレートができる
- テストを安定させるためにsetTimeout（Sleepなど）に依存しない自動化の推奨と、それを推進するためのAPIを提供している

https://github.com/microsoft/playwright

## 前提
デフォルトでEC-CUBEをインストールして、フロントの表示確認まで済み。
npmのインストール済み。
インストールしてない場合は`npm install npm`

環境は以下の通りです。
```
Mac: Sonoma14.1.1
PHP: 7.4.33
EC-CUBE: 4.2.3
npm: 10.2.3 (更新します。。）
```

## PlayWrigthのインストール
公式のREADMEを参照して進めます。
``` 
# Run from your project's root directory
npm init playwright@latest
```
いろいろ質問されますが、基本はEnterで良いと思います。
```
Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
✔ Do you want to use TypeScript or JavaScript? · TypeScript
✔ Where to put your end-to-end tests? · e2e
✔ Add a GitHub Actions workflow? (y/N) · N
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true
```
しばらく待って`Happy hacking! 🎭`が表示されれば、無事インストール完了となります。
インストールしたファイルツリーはこんな感じ。
設定したフォルダ（デフォルトだとe2e）以下のspecファイルが、自動で認識され実行されます。
```text:ファイルリスト
- ec-cube
    - tests-examples
        - demo-todo-app.spec.ts #todoアプリをテストするための例
    - e2e
        - example.spec.ts #簡単なブラウザテストの実行例
    - playwright.config.ts #設定ファイル
```
実際にPlaywrithを実行して、成功するのを念のため確認してみます。
```
> npx playwright test

Running 6 tests using 5 workers
  6 passed (9.4s)

To open last HTML report run:

  npx playwright show-report
```
ちゃんと成功してますね。
この時点でブラウザ別（Chrome, Firefox, Safari)でテストしてくれました。すごい。
レポートが見たい場合は `npx playwright show-report`コマンドを入力すると、ブラウザが立ち上がりレポートが表示されるとのこと。
実際見てみると、なかなか便利そうです↓
![スクリーンショット 2023-12-18 17.29.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131573/23409334-1f9d-f1a7-9c29-9df0c383b078.png)


## 簡単なテストケースを作成
次に簡単なケースでテストを作成してみましょう。
1. `https://127.0.0.1:8000/`にアクセス（EC-CUBEが立ち上がっているIPアドレス）
1. h1タグに「EC-CUBE SHOP」があることを確認
1. スクリーンショットを撮る

こんな感じで実装してみます。

```typescript:ec-cube.spec.ts
import { test, expect } from '@playwright/test';

test('has shop name', async ({ page }) => {

  // goto localhost
  await page.goto('https://127.0.0.1:8000/');

  // Get the h1 element and expect it
  const h1Text = await page.innerText("h1");
  expect(h1Text).toBe("EC-CUBE SHOP");

  // Get a Screenshot
  await page.screenshot({ path: 'example.png' });

});

```
ここで特徴的なのがexpect関数です。
非同期マッチャーを使用しているため、自動で表示を待つ仕様になっています。（デフォルトでは5000ms）
テストを走らせてみましょう。`npx playwright test`コマンドを実行します。
また特定のテストだけの実行もできます。

```
> npx playwright test ec-cube

Running 3 tests using 3 workers
  3 passed (5.2s)

To open last HTML report run:

  npx playwright show-report
```
正常に完了したようです。
以下のスクリーンショットは実際に撮影されたものです。
![example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131573/c7d3cd6a-b396-593a-c539-343f577c2a56.png)

ナビまでしか表示されてないですが、スクリーンショット自体は撮れていますね！
Playwrightでは特定の要素の表示を待つ事ができるので、ここにSleepを入れなくても取得できます。

## EC-CUBEのE2Eを書いてみる
試しにトップページのテスト（[EF01TopCest:topページ_初期表示](https://github.com/EC-CUBE/ec-cube/blob/cae3bf93fe9b688f3b8fe6e1840aa0697dc4db67/codeception/acceptance/EF01TopCest.php#L48-L91 "EF01TopCest:topページ_初期表示")
）を1部書いてみたいと思います。
テストの中身の処理は２つになります。
1. カテゴリ選択ボックス（キーワード検索用）、キーワード検索入力欄、虫眼鏡ボタンが表示チェック
1. カテゴリ名（カテゴリ検索用）が表示チェック
それぞれの行の説明はコードに記載します。
```typescript:ec-cube.spec.ts
test('default display for top', async ({ page }) => {

  // goto localhost
  await page.goto('https://127.0.0.1:8000/');

  // カテゴリ選択ボックス（キーワード検索用）、キーワード検索入力欄、虫眼鏡ボタンが表示されている
  /* combobox（SelectBox）に含まれる要素を取得して、該当のワードが入っているのかをみている。*/
  await expect(page.getByRole('combobox')).toContainText('全ての商品');
  await expect(page.getByRole('combobox')).toHaveValue('');
  /* キーワード検索入力欄の表示チェック。*/
  await expect(page.getByRole('searchbox', { name: 'キーワードを入力' })).toBeVisible();
  /* 虫眼鏡（検索ボタン）の表示チェック。*/
  await expect(page.getByRole('banner').getByRole('button')).toBeVisible(); // 本家テストの虫眼鏡ボタンは何処へ...?

  // カテゴリ名（カテゴリ検索用）が表示されている
  /* カテゴリの配列を指定して、ループで回して検査する */
  const categories: string[] = ['全ての商品','新入荷','ジェラート','彩のデザート','CUBE','アイスサンド','フルーツ']
  for (const category of categories) {
    await expect(page.getByRole('combobox')).toContainText(category);
  }
});
```

### 最後に
実際に書いて思ったのは、locaterの種類が多く難しく感じました。
命名規則は直感的で覚えやすいので、スグに慣れそうなのは良いと思いました。
locaterに関しては、もっと良い方法がありそうなので、引き続き見ていきたいと思います。

## 参考サイト

https://playwright.dev/

https://qiita.com/cc822jp/items/6f786a9ed104af1a382f

https://qiita.com/oh_rusty_nail/items/d955e3273994214a0afa

https://www.cresco.co.jp/blog/entry/14335.html