Playwrightで秒数指定wait処理を避けてスマートにする

## はじめに

### 自己紹介

こんにちは。

ミツモアのQAエンジニアの山本です。

1人目のQAエンジニアとしてチームを立ち上げ、現在はミツモア, MeetsOneの2つのプロダクトのQA全体のリーダーとして日々プロダクトに向き合いつつ、楽しく働いています。

ミツモアは「テクノロジーでシンプルに」をバリューとしており、継続的インテグレーションとリグレッションテストの効率化の観点からE2E自動テストを重視しています。

QAチームでは全員が手動テストも自動E2Eテストも行っており、今年は安定性向上や自動テスト実行スピードアップ, 安定性向上などを狙ってAutifyからPlaywrightへ移行しました。

今回はPlaywrightで秒数指定wait処理のハードコードを避けてスマートにする方法をご紹介します。

### よくあるwait処理の悪い例

```jsx
await page.locator('input[name="email"]').fill(email);
await page.locator('input[name="password"]').fill(password);
await page.getByRole("button", { name: "ログイン" }).click();
await page.waitForTimeout(1000);

await page.getByRole('button', { name: elemName })).click()
```

E2Eテストを書いていれば、 ページや要素が読み込まれない問題に直面し、解決法を考えたことがあるはずです。近道として、このように明示的に秒数を指定して待機処理を挟むこともできます。
一方で、E2Eテストのパフォーマンスを下げ、失敗しやすいflakyなテストとすることからアンチパターンと捉えられます。

### なぜアンチパターンなのか

自動テストツールにとって`waitForTimeout(1000)`は文字通り「1秒待って」という指示に過ぎません。つまり、この指示は以下を考慮に入れないのです。

- ログイン後の画面に遷移済であるか
- 遷移先のページが読み込まれているか
- クリックする要素が描画済で、可視であるか

クリックする要素が1秒未満で描画されていてればタイムロスが発生してパフォーマンスが下がることになります。積み重なるとCIでの自動テストの時間が長くなり開発者体験を下げることになります。また、1秒経過時に遷移先の画面が読み込み中の場合でも次の処理に移り、その時点で要素が描画されていなければテストは失敗してしてしまいます。

### waitステップをどうスマートにすべきなのか

Playwrightが提供している解決方法として明示的なwaitと暗黙的なwait(auto-wait)の2つがあります。明示的なwaitの例は以下のようなものです。秒数指定ではなく特定の何かが終わるまで待ってくれるので、タイムロスやflakyなテストになりづらくなります。

```jsx
//必要なロード状態になるまで待機する
page.waitForLoadState()

//特定のURLをターゲットし、指定されたURLに遷移するまで待つ
page.waitForURL(URL)

//特定のセレクタを持つ要素が描画されるまで待つ
page.waitForSelector(select)
```

明示的なwaitに対して、よりスマートなのは`auto-wait`です。以下に列挙する`click()`や`hover()`のようなAPIにはPlayWrightがauto-waitを提供しています。 これらのアクションをする前に要素に対してアクション可能性をチェックし、関連する全てのチェックが通過するまで自動的に待機してくれます。
チェック対象の具体例は以下のポイントです。

- 要素の可視であるか
- 要素が有効になっているか
- 編集可能になっているか

ただし、全てのAPIが一様に同じ項目をチェックしてくれるわけではありません。例えば、`fill()`と`type()` はどちらも文字を入力してくれるAPIです。しかし、`fill()`がロケーターの可視性、有効性、編集可能性を確認するauto-wait機能を持つ一方、`type()`にはその機能がありません。詳しくは以下の表を参照してください。

| Action | ロケーターがvisibleになるまで待つ | ロケーターがEnabledになるまで待つ | ロケーターがEditableになるまで待つ |
| --- | --- | --- | --- |
| check | Yes | Yes | No |
| click | Yes | Yes | No |
| fill | Yes | Yes | Yes |
| type | No | No | No |

Timeout内にこれらのチェックが全て通過しない場合にはTimeoutErrorが発生します。

これらは自動的な待機ステップであり、アクションであり、実質的なアサーションであるとも言えます。1行で複数の役割を果たしてくれるのでコードが綺麗に保てます。

以下のようなアサーションでも明示的にauto-waitと同じことをしてくれます。
しかし、上記テーブルのアクションはアサーションを兼ねてくれるので必ずしも必要というわけではありません。

```jsx
locator.isChecked()
locator.isDisabled()
locator.isEditable()
locator.isEnabled()
locator.isHidden()
locator.isVisible()
```

今回はPlayWrightで秒数指定のwait処理を避けてスマートにする方法をご紹介しました。
UIの変更などでE2Eテストが失敗するのは避けて通れない道ですが、可能な限り失敗しにくいコードにすることで保守にかける工数を他のプロジェクトや生産性の向上に使えると考えています。
弊社のQAチームでは開発との連携を強めることでUI変更時の修正に加わってもらっており、全員参加でE2Eテストを構築していきます。

### 参考

https://playwright.dev/docs/actionability

### 終わりに

少し話が変わりますが、私は自動テストが大好きです。
単純にバグを見つけて弾く「品質管理 = Quality Control」も好きですが、大切なプロダクトが機能していると継続的に証明し続けることこそ「品質保証 = Quality Assurance」の重要な役割の一つだと考えています。
PlayWright移行後のE2Eテストのコーディングのタスクの増加は、それまで手動テストの多かったQAチームメンバーの好奇心と秘めたるエンジニア魂を刺激し、チーム全体の士気向上にも繋がっています。
PlayWrightを導入したときにベストプラクティスを指南してくださった私の師匠、シニアエンジニアのGuillaume (ギヨーム)さんに感謝を添えたいと思います。

ミツモアでは様々な職種のエンジニアを積極的に採用しています。

QAチームでは、品質を高めるために理想から逆算し、あらゆる施策を一緒に打てる人を募集しています。「手動テストだけしか経験がないし技術的なことはわからない」「 自動テストだけしか興味がない」ではなく「品質のためなら何でもやる」という強い想いとチャレンジを楽しめる好奇心をもったそこのあなた、面談で楽しく話しましょう！

- 採用ページ: https://meetsmore.com/company/recruit
- 募集要項: https://herp.careers/v1/meetsmore/3cQ_H41s6tZu