Playwright を使いこなすためのベストプラクティス

## はじめに

Playwright を使うことで比較的簡単に E2E テストを実装することができます。しかし、通常テストコードは実装したら終わりということではなく、継続的にメンテナンス（保守）が必要になります。その際に保守しやすいように実装するため、Playwright の公式ドキュメントに記載されているベストプラクティスの中で参考になりそうな部分を確認しておこうと思います。

https://playwright.dev/docs/best-practices

## テストの独立性を高める

可能な限りテスト間の依存が無いようにして、テストを分離すると良いというプラクティスです。各テストが独立していることで、

- １つのテストが失敗しても他のテストに影響しない
- テストの順序を考慮する必要がない
- テストをシンプルに保つことができる

あたりのメリットがあるかと思います。また、特定の処理（例えば特定の URL に遷移する処理）の繰り返し実装するのを避けるために [before and after hooks](https://playwright.dev/docs/api/class-test) を利用するのが良さそうですね。

https://playwright.dev/docs/best-practices#make-tests-as-isolated-as-possible

## 外部サイトのテストをしない

タイトルの通り、自分が管理しているサイトだけをテストしようということです。外部サイトはコントロールできないので、テストが不安定になるし、テストの時間がかかるおそれがあります。
実装の中で外部サイトにアクセスすることを回避できない場合は、[Network API](https://playwright.dev/docs/network#handle-requests) を利用して、リクエストをモックしましょう。

https://playwright.dev/docs/best-practices#avoid-testing-third-party-dependencies

## Locator を利用する

対象の要素を指定する際は、[Locator](https://playwright.dev/docs/locators) を利用しましょう。XPath や CSS セレクタで指定すると DOM の変更に追従することが難しくなるので、DOM の変更に強い Locator を使いましょう。

``` typescript
// 👎
page.locator('button.buttonIcon.episode-actions-later');

// 👍
page.getByRole('button', { name: 'submit' });
```

https://playwright.dev/docs/best-practices#avoid-testing-third-party-dependencies

## Test generator を使って Locator を生成する

Locator の生成は [Test generator](https://playwright.dev/docs/codegen) を使って生成しましょう。`Test generator` として [VS Code の extension](https://playwright.dev/docs/codegen#generate-tests-in-vs-code) や [Playwright Inspector](https://playwright.dev/docs/codegen#generate-tests-with-the-playwright-inspector) を使って生成することができます。今回は `Codegen` コマンドを利用して実際に生成してみます。

### Codegen を起動

以下のコマンドを実行して起動します。`demo.playwright.dev/todomvc` は接続する URL ですので、適宜読み替えてください。

```
% npx playwright codegen demo.playwright.dev/todomvc
```

### テストを生成

Codegen を起動すると、ブラウザと Playwright Inspector ウィンドウの２つが立ち上がります。

![codegen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/cbbfa2cc-5738-5036-304b-fb49cccad5d7.png)

立ち上がったブラウザで実際に操作（クリックやテキスト入力など）をすることで、操作に対応したコードが Playwright Inspector ウィンドウに生成されます。
例えば、テキストボックスをクリックして文字を入職した場合、以下のようなコードが生成されました。

```typescript
await page.getByPlaceholder('What needs to be done?').click();
await page.getByPlaceholder('What needs to be done?').fill('hogehoge');
```

また、Locator の生成だけに利用したい場合は、`Record` を停止して、`Pick Locator` を有効にしてから、ブラウザで対象の要素をクリックすることで Locator を生成することができます。

![pick-locator.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331769/6ac384ac-1a68-483f-4213-2c79a94f8411.png)

https://playwright.dev/docs/best-practices#generate-locators

## Web first assertions を使ってアサーションする

Web の仕組み上、要素の読み込み時間や表示されるまでのタイムラグがあります。これらをアサーションする際に、手動で実装してしまうとテストを実行した瞬間の状態（まだ要素が存在しないかもしれない。）でアサーションが実行され、不安定なテストになってしまいます。（何秒か待機するようなワークアラウンドが必要になる。）
このような問題を解決するためには、あらかじめ用意されている [Web first assertions](https://playwright.dev/docs/test-assertions)を利用しましょう。

```typescript
// 👎
expect(await page.getByText('welcome').isVisible()).toBe(true);

// 👍
await expect(page.getByText('welcome')).toBeVisible();
```

自動で待機、またはリトライしてくれるアサーション一覧は[こちら](https://playwright.dev/docs/test-assertions#auto-retrying-assertions)を確認してください。なお、デフォルトのタイムアウトは 5 秒となっています。変更したい場合は、[こちら](https://playwright.dev/docs/test-timeouts)を参照してください。

https://playwright.dev/docs/best-practices#generate-locators

## 最新の Playwright パッケージを利用する

基本的な事かもしれませんが、テストの実行は最新の Playwright を利用するようにしましょう。そうすることで、最新のブラウザでテストを実行することができます。また、最新の機能も利用することができるので、積極的に追従したいですね。[リリースノート](https://playwright.dev/docs/release-notes)を確認することで、最新バージョンと変更点を確認することができます。

https://playwright.dev/docs/best-practices#keep-your-playwright-dependency-up-to-date

## CI でテストを実行する

CI でテストを実行することで、テストの実行頻度をあげましょう。GitHub Actions での設定については、[公式ドキュメント](https://playwright.dev/docs/ci-intro)で解説されてますし、実際に実行してみた[ブログ](https://qiita.com/ore88ore/items/5c9f93b4158c30ccd1bd#github-actions-%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B) も執筆していますので、確認してみてください。テストの実行頻度を上げることで、より早く失敗に気づくことができそうですね。

https://playwright.dev/docs/best-practices#run-tests-on-ci

## Linter を設定する

ESLint の [@typescript-eslint/no-floating-promises](https://typescript-eslint.io/rules/no-floating-promises/) ルールを設定しましょう。これにより非同期実行時に `await` が漏れていてうまく動かないなどに素早く気づくことができそうです。より早く気づくために可能であれば、ローカル開発環境に組み込むのが良さそうですね。

https://playwright.dev/docs/best-practices#lint-your-tests

## 並列処理やシャーディングを設定する

テストの実行はデフォルトで並列実行されます。ただし、ひとつのファイル内のテストは同じワーカープロセス内で実行されます。各テストが独立している場合は、同じファイル内のテストも並列実行するように設定することができます。設定方法については [API のドキュメント](https://playwright.dev/docs/api/class-test#test-describe-configure)も参考にしてみてください。

```typescript
test.describe.configure({ mode: 'parallel' });
```

さらにテストの規模が大きくなった場合は、複数マシンで実行することもできます。詳細については、[Sharding](https://playwright.dev/docs/test-sharding) 機能をご確認ください。

https://playwright.dev/docs/best-practices#use-parallelism-and-sharding

## ソフトアサーションを利用する

通常、アサーションでテストが失敗した場合、その失敗したアサーションでテストが終了してしまいます。しかし、失敗したテストを修正した後に再実行すると、別の部分でまた失敗することがありますよね？ こういう場合、[ソフトアサーション](https://playwright.dev/docs/test-assertions#soft-assertions)を利用することで、すべてのアサーションを実行して、失敗したアサーションの一覧を確認できるようになります。これにより、修正すべき箇所をより明確に把握することができます。

```typescript
await expect.soft(page.getByTestId('status')).toHaveText('Success');
await expect.soft(page.getByTestId('eta')).toHaveText('1 day');
```

https://playwright.dev/docs/best-practices#use-parallelism-and-sharding