【初心者】Playwrightテスト自動化の進め方

## はじめに

[Playwright]はMicrosoftが開発したオープンソースのE2Eテストフレームワークで、主要なブラウザ（Chromium、Firefox、WebKit）をサポートし、クロスブラウザテストを簡単に実現できるツールです。

https://playwright.dev/

導入が簡単で、画面操作からそのままコード生成をできるので、テスト自動化の初心者にも扱いやすく人気があります。

しかし全くの初心者がいざ自動化しよう！とはじめてみても「なにからやればいいん？」となったので、「わたしはこう進めた！」をまとめておこうと思います。

## テストコードを作成する前に

### テストシナリオを作成する

大前提として当然ですが、E2Eテストのシナリオは準備しておきましょう。
自動化されたテストの実行結果を見ただけではテストケースの妥当性が確認できないので、このテストケースはなにをテストしている、というのが作成者以外にもわかるようにメモ書きでもいいので残しておきましょう。

### 自動化するシナリオを決定する

テストコードの作成は当然工数がかかります。
自動化する目的のひとつは、何度も繰り返し実施されるテストを自動で実行することで、人の作業量を削減することです。
そのため1年に1回実施すればいい程度のテストは自動化するメリットがあまりありません。
アジャイルで開発していてUIが頻繁に更新されるような機能も、テストコードの保守に工数がかかってしまうので開発中の自動化は避けた方がいいでしょう（その機能と関連する別機能のテストを自動化して影響を確認する、などは自動化するメリットがあると思います）。

ChatGPTさんに自動化する判断の指標について聞いてみました。

> 1. テストの頻度と規模
> 同じテストを頻繁に実行する場合やテスト対象が大規模な場合は、自動化による効率化が有効。
> 2. 手動テストのコスト
> 手動でのテストが時間やリソースを多く消費する場合は、自動化のコストパフォーマンスが高い。
> 3. テストの安定性
> 環境や実行タイミングに依存しない安定したテストなら、自動化が適している。
> 4. テストカバレッジの必要性
> ユーザーに影響を与える重要な機能や複雑なシナリオでは、自動化で漏れを防止できる。
> 5. プロジェクトの寿命
> 長期間メンテナンスが必要なプロジェクトでは、自動化によりテスト作業の効率が向上。
> 6. 初期コストに対する許容度
> 自動化には初期コスト（ツールの選定・導入、スクリプトの作成）がかかるため、それを許容できるか。
> 7. チームのスキルセット
> 自動化スクリプトを作成・維持できるスキルがチーム内にあるか。
> 
> これらの指標を基に、コスト対効果やプロジェクトの状況を考慮して判断するのが適切です。

上記を踏まえた上で自動化するシナリオの範囲を決定します。

### ロケーターになにを指定するかルールを決める

E2Eテストにおけるロケーターとは、Webページやアプリケーション内の特定の要素を一意に識別し操作するための方法です。
「送信ボタンをクリックする」という操作をコードで表現するには、「送信ボタン」がWebページ内のどこにあるのか特定する必要があるので、その特定にロケーターを使用します。
Playwrightはロケーターの指定が柔軟で、id、class、XPathでの指定や、画面上の表示文字で指定することもできます。
`送信`というテキストを持つボタン、`はじめに`というテキストの要素、などの指定です。

```ts
page.getByRole("button", { name: "送信" })
page.getByText("はじめに")
```

また要素の親子関係でも指定することができるので、「`form-item`クラスを持つ`div`タグの子要素から`ユーザーID`ラベルを持つ要素を探し、その要素からテキストボックスを特定」のような書き方もできます。

```html
<div class="form-item">
    <label for="" class="c-form-label"><span>ユーザーID</span></label>
    <div>
      <input type="text"/>
    </div>
</div>
<div class="form-item">
    <label for="" class="c-form-label"><span>ユーザー名</span></label>
    <div>
      <input type="text"/>
    </div>
</div>
```

```ts
// ユーザーIDの方を取得
page.locator("div.form-item")
    .filter({ has: page.locator('label:has-text("ユーザーID")') })
    .getByRole("textbox")
```

https://playwright.dev/docs/locators

https://note.shiftinc.jp/n/nfad26e53a2ee

ロケーターの指定方法は複数ありますが、そのシステムにおいてどの方法で指定するかはルールとして決めておいた方が良いと考えます。
ロケーターの指定方法によって、システム側コード変更時のテストコードの更新要否が変わってくるからです。

例えばCSSのclassで指定した場合、レイアウト調整などでその要素につけるclassが変更された場合に、テストコードも更新する必要があります。
classは通常複数の要素に付与されていることが多いでしょうから、要素を一意に識別するために合わせて「`form-item`クラスを持つ要素の子要素の`child-item`クラスを持つ要素」や「`form-item`クラスを持つ要素の1つ目」のように複数要素を組み合わせて指定することになります。
この場合、入れ子関係が変わったり要素の表示箇所を変えたときにテストコードを更新する必要が出てきます。

画面表示のテキストで指定した場合は、フォームのラベル名やボタン名のテキストを更新した際にテストコードの更新が必要です。

ロケーターの指定方法によって「画面側にどのような変更が加えられたときにテストコードも更新する必要があるか」が変わってくるので、システムとして統一されていない場合その共通認識が持てず、更新の度に毎回「テストコードも更新しないといけないかな？」と考えたり、テストコードの変更漏れに繋がります。
また、共通認識があるとテスト実行に失敗した際「システムコードの変更がテストコードに反映されていないから失敗したのか」「システムコードに不備があるから失敗したのか」の切り分けもしやすくなります。

ではどの方法で指定するのが良いか考えていきます。
以下の記事が参考になりました。

https://blog.autify.jp/article/best-practices-for-element-locating-in-e2e-testing

テストコードはなるべく変更に強く、保守性が高いものであるべきです。
上記でも述べた通り、class指定の場合はレイアウトや要素の親子関係の調整によって将来的に更新される可能性が高いです。
頻繁にテストコードの更新が発生するとその分工数もかかりますし、結局面倒くさくなって更新しない→使わなくなる、なんてことになる可能性もあります。

参考記事を読んでなるほどと思ったのが、「E2Eテストは現実のユーザー操作を反映したものであるべき」との文です。
要はシステム更新によってユーザー操作が変わるならテストも更新するべきだし、ユーザー操作は変わらずに内部的なコードが変わった場合は更新しない方がいいよね、の考えのもとロケーターを決めます。

一番良いのは`page.getByRole()`を使用した、要素の役割(Role)で指定する方法のようです。

公式ドキュメントより抜粋

```html
<h3>Sign up</h3>
<label>
  <input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
```
```ts
await expect(page.getByRole('heading', { name: 'Sign up' })).toBeVisible();
await page.getByRole('checkbox', { name: 'Subscribe' }).check();
await page.getByRole('button', { name: /submit/i }).click();
```

`button`,`checkbox`等の要素の役割とその要素の表示テキストで指定します。
アイコンボタンのようなテキストを持たないボタンなどは`aria-label='送信'`のように`aria-label`属性をタグに付与することで`name`に指定することができます。
同じ役割、名前の要素が複数ある場合なども、`aria-label`の値を変えておけば一意に要素が特定できます。

`div`や`span`などRoleを持たない要素については`page.getByText("はじめに")`のように表示テキストで特定します。

たしかに要素の役割が変わればユーザー操作も変わりますし、表示テキストが変わればユーザーがその要素を特定する情報が変わるので、テストコードも一緒に更新するべきですね。
この場合ルールとして「画面の表示項目や表示テキストが変更された場合にテストコードも更新する」としておけば、レイアウトの微調整や画面に影響しないバックエンドの更新時にテストコードの更新を気にする必要がなくなります。

表示テキストに左右されずもっと厳密に要素を特定したい！といった場合は、`data-testid`属性をタグに付与して`page.getByTestId()`で要素を特定することもできます。

公式ドキュメントより抜粋

```html
<button data-testid="directions">Itinéraire</button>
```
```ts
await page.getByTestId('directions').click();
```

開発初期からE2Eテストの自動化が決まっている場合や、自動化に向けて大幅なリファクタリングを予定している場合、この方法でtestidを付与していくことで、テストコードを変更に強くできます。
testidの採番ルールもあわせて決めておきましょう。

classや親子関係での指定は変更に弱くなるので推奨はされていないようですが、既存のシステムコードに手を加えたくない、でも要素の特定がclassや「この要素の子要素の何番目」のような指定しかできない、といった場合は、それをチーム全体で共有しておけばありだと思います（可能であればシステム側コードに`area-label`なり`data-testid`をつけるなりの対応をした方が後々を考えると良い）。

あくまで「画面側にどのような変更が加えられたときにテストコードも更新する必要があるか」を共通認識として持っておけばいいと思います。

## テストコードの作成

さてロケーターの指定方法で長々書いてしまいましたが（一番大事だと思う）、ここからはテストコードの作成について記載していきます。
Playwrightの使い方はネット上にいろんな記事がありますが、ここでは「私はこの流れで作った」を残しておければと思います。

### Playwrightのインストール

まずはplaywrightをインストールします。
フロントエンドの開発者であればNode.jsは入っているでしょうから割愛します。
パッケージマネージャーはお好みで。今回は`yarn`です。適宜読み替えてください。

https://playwright.dev/docs/intro#installing-playwright

```bash
yarn create playwright
```

TypeScriptで書くかJavaScriptで書くか聞かれるので、選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/feb88a26-2061-103b-17e5-ba2f8e538637.png)

続いてテストコードをどこに置くか聞かれるので、デフォルトの`tests`から変更しない場合はそのままEnterを、変更したい場合は入力してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/a859812d-b3c3-dfe3-359e-621c9655be77.png)

Github Actionsを設定するか聞かれます。yesにするといい感じのGithub Actionsのワークフローを作ってくれます。とりあえず不要なのでそのままEnterです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/fef47aa7-c239-597b-cb74-2a7b338e8b4f.png)

最後にPlaywright実行用のブラウザをインストールするか聞かれます。
デフォルトだとchromium(Chrome),firefox,webkit(Safari)がインストールされるので、いらないブラウザがある場合はここではNoにしておいて、設定ファイル変更後に`yarn playwright install`を実行すれば必要なブラウザだけインストールできます。
システム要件に合わせてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/24a95d19-61d6-66d1-b42e-2a9f2fbf1690.png)

インストールが終了したら以下のファイルが作られます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/e0817358-07af-8871-f257-0de214e8aa80.png)

サンプルテストを実行します。

```bash
yarn playwright test
```

以下のような出力がされれば実行成功です。インストールできています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/f0ed37cb-01c0-2bc5-ed5f-cdcd3559d405.png)

### コード作成

インストールできたのでテストコードを書いていきます。
テストコードは`tests`フォルダ（インストール時に別のフォルダ名にした場合はそれ）に`spec.ts`拡張子でファイルを作成すると、テスト実行時に自動でそのファイルのテストコードが実行されます。
デフォルトで作成される`example.spec.ts`は不要なので削除しておきます。

`tests`フォルダに任意の名前で`spec.ts`ファイルを作ってください。

テストコードの作成は[Test generator][test-generator]を使います。
以下のコマンドを実行すると、playwrightのブラウザが立ち上がります。

```bash
yarn playwright codegen <テスト対象WEBサイトのURL>
```

画面上部の録画ボタンが赤の状態で画面操作をすると、同じ画面操作を実行するコードがInspectorに生成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/926213ff-b470-dc7f-babf-c5932eb3b7c3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/62f7b99d-2bc8-0965-bba6-5e46f5e147d7.png)

画面操作が終わったらInspectorウインドウの左から5つ目のコピーボタンをクリックすると生成されたコードがコピーできるので、`tests/<任意の名前>.spec.ts`にコードを貼り付けます。

テストの実行は以下のコマンドを実行します。

```bash
yarn playwright test
```

正常終了したら実行結果がHTMLで吐き出されるので以下のコマンドで確認します。

```bash
yarn playwright show-report
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/49fdbaa8-491f-2641-eed7-3149e90d9a10.png)

テスト対象のブラウザごとに実行結果が確認できます。
どこかでエラーが発生した場合は強制的にこのページが開かれます。

自動で作成されるロケーターは`getByRole()`や`getByText()`などplaywright推奨の指定になっているので、チームで決めたロケーターの指定ルールに則っている場合は変更不要でそのまま使えます。
ロケーターの指定ルールをclass指定やid指定にする場合は適宜書き換えが必要になります。

テストの実行経過が見たい場合は`--ui`オプションをつけて実行します。

```bash
yarn playwright test --ui
```

こうするとテストの実行経過を確認することができます。
左側の`▶︎`ボタンから実行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/6c45f583-f4dd-c110-2477-ba2f670c96f4.png)

実行に失敗するとエラー箇所と原因が見られるので、該当箇所のコードを修正して左上のリロードボタンを押下→再度実行で修正後のコードが実行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1649149/36dfdc51-1cc7-0f3d-20b2-4f905bdf382b.png)

この流れを繰り返しながらテストコードを作りました。

1. Test Genaratorでシナリオ通りの画面操作をしてベースのコードを作成
2. コードを調整
3. `--ui`モードでテストを実行
4. エラーが出ればコードを修正
5. リロードして再実行

## おわりに

テスト自動化初心者がPlaywright導入にあたり何から進めるかをまとめました。
コードの書き方は公式ドキュメントやわかりやすくまとめられている記事がたくさんありますので、そちらを参考にしていただければと思います。

https://qiita.com/rokumura7/items/c82f205db5310c922bbd

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


[playwright]: https://playwright.dev/
[test-generator]: https://playwright.dev/docs/codegen