やはり俺のPlaywrightはまちがっている。
過去のプロジェクトから流用したPlaywrightのコードは「動いて」いました。しかし、偶然ドキュメントを読み直したとき、「動く」と「正しく動く」の間には設計思想の違いがあることに気づきました。page.evaluate()ではなくlocatorを使うべき理由、waitForNavigation()が非推奨である理由。公式ドキュメントには、単なるAPIリファレンスではなく「なぜこの方法が推奨されるのか」が書かれています。時間がない現実の中で、それでもドキュメントを読む価値とは何かを考えます。
目次
生成AIと共同での開発が主軸となる昨今の開発現場でも、基本的な開発手法に変化はありません。生成AIが作成したコードに型エラーが出た場合、開発者も生成AIもドキュメントを参照し、正しい使い方を確認します。読んだだけでも分からない時は、具体的なエラー内容とドキュメントの内容を生成AIに伝え、修正案を提案してもらいます。AIが介入したことによりこのサイクルは高速化しましたが、根本的な流れは変わっていません。
動いたら、次のタスクに移ります。常に良いコードを作成するべきだとは思いますが、現実的には「動けば良い」という判断をして「// ToDo: 後で直す」としてしまうことも多いでしょう。こうしないためにも「ドキュメントをちゃんと読め」と言われることもあります。
しかし、開発までのサイクルや、納期がありますし、他のタスクもあります。もし過去のプロジェクトから流用したコードを今回の開発で利用しているのであれば、動いているならそれで良いと考えてしまうのも無理はありません。
現時点で動いているものを新たに理解し実践するための時間を捻出するのは難しいでしょう。わざわざ公式ドキュメントを隅々まで読んで、理解して、実践する時間なんてなかなか取れません。
これはエンジニアとしての怠慢ではなく、プロジェクトの現実です。開発にだけ没頭している時間は少ないこともあるため、その選択が間違っているとは思いません。これを読んでいるあなたも、同じように考えているはずです。
しかし、偶然にも公式ドキュメントを最初から読み直す機会を得ました。Claude Codeにベストプラクティスを読み込ませるためのAgents Skills1を作成する必要があったからです。
Agents SkillsはMarkdownとして定義する必要があり「どうせなら、ちゃんと読んでおくか」と思い、現在使用しているPlaywrightの公式ドキュメントを一通り読むことにしました。
ドキュメントを読んで思ったことは、「やはり俺のPlaywrightはまちがっている。」 です。
動けばよい?
私は過去プロジェクトのコードを流用していました。Playwrightを使う際、動いていたテストコードをコピーして、今回のプロジェクト用に少し修正して使用します。CSSセレクタを変更したり、URLを変えたりするだけで済むため、時間の節約になります。
動いているコードがあるなら、それを使う。この選択には合理的な理由があります。時間は限られており、すでに動作が確認されているコードを再利用することで、新たなバグを生むリスクも減らせます。
たとえば、私はボタンをクリックする処理を page.evaluate() で実装していました。
await page.evaluate(() => {
document.querySelector('button.submit').click();
});事実、このコードは「動いて」いました。ボタンはクリックされて次画面への遷移、ラジオボタンの選択など、一連の操作は問題なく実行されています。
しかし、1箇所変更するだけで動作が向上することに気付きました。
await page.locator('button.submit').click();locatorの設計思想
page.evaluate() と page.locator().click() の違いは何でしょうか?どちらもボタンを押下するコードです。公式ドキュメントを詳しく読むと、まったく異なる設計思想が反映されていることが分かります。
page.evaluate() は、ブラウザコンテキスト2内でJavaScriptコードを実行するためのメソッドです。指定された関数は、ページ上で直接実行され、DOM要素にアクセスして操作を行います。ところが、この方法では要素が存在しない場合や、まだインタラクティブでない場合にエラーが発生する可能性があります。そのため、要素がクリック可能な状態であることを確認するための追加のロジックが必要になります。
Playwrightの公式ドキュメントでは、DOM操作を直接行う方法について、actionability checks3を行わないため不安定なテストにつながる可能性があると説明されています。ベストプラクティスでは、locator の使用が強く推奨されています。
Evaluating JavaScript | Playwright
Introduction
一方、page.locator().click() は、PlaywrightのLocator APIを使用しています。Locatorは、要素の位置を特定し、その要素がインタラクティブであることを自動で確認します。これには、要素が表示されているか、クリック可能な状態であるかなどのチェックが含まれます。これにより、要素がまだロードされていない場合や、他の要素によって覆われている場合でも、エラーを回避できます。
Locators | Playwright
Introduction
この設計思想の違いにより、page.locator().click() を使用することで、動作が安定します。要素がクリック可能になるまで自動的に待機するため、タイミングに起因するエラーを減少させ、コードはより堅牢になります。
あらためて整理すると、公式ドキュメントでは、このしくみをActionability Checks3と呼んでいます。locatorは、クリック前に以下のチェックを自動的に行います。
- 要素がDOMに存在すること
- locatorが1つの要素だけにマッチすること
- 要素が表示されていること(Visible)
- 要素が安定していること(Stable) - アニメーションが完了しているか
- 要素がイベントを受け取れること(Receives Events) - 他の要素に覆われていないか
- 要素が有効であること(Enabled)
- 要素がビューポート内にスクロールされること
つまり、locatorは「要素を特定するだけのしくみ」ではなく、要素が操作可能になるまで待つしくみを備えています。これらのチェックがすべてパスしない限り、指定されたタイムアウト時間内で自動的にリトライを繰り返します。
ネットワークが不安定な環境では、この違いが現れます。evaluate ベースの実装では、要素の読み込みが遅れるとエラーになってしまいます。しかし locator なら、自動で待機してくれるため、テストの信頼性と保守性が大きく向上します。
Auto-waiting | Playwright
Introduction
具体例
ここでは実際に私が過去に使用していたアンチパターンと、公式ドキュメントで推奨されているパターンをいくつか紹介します。
ナビゲーションの待機
waitForNavigation() は複数の処理が同時に実行された場合に、実行順序によって結果が変わってしまう「競合状態(レースコンディション)」が発生する可能性があるため、非推奨APIです。waitForURL() や waitForSelector() を使うことで、より明示的に「何を待つか」を表現できます。
// NG
const navigationPromise = page.waitForNavigation();
await page.click('button');
await navigationPromise;
// OK
await page.click('button');
await page.waitForURL('**/next-page');
// または特定の要素を待機
await page.waitForSelector('#next-page-element');要素の操作
ボタン操作やテキスト入力など、要素を操作する際には、evaluate() を使うのではなく、locator を使うことが推奨されています。
// NG
await page.evaluate(() => {
document.querySelector('button.submit').click();
});
// OK
await page.locator('button.submit').click();公式が推奨している locator を使用する場合の優先度は以下の通りです。
// 1. Role(役割)ベース (アクセシビリティに最適)
// スクリーンリーダーや支援技術でも認識できるため、最も堅牢
await page.getByRole('button', { name: 'Sign in' });
// 2. Label(ラベル)ベース (フォームに最適)
// ユーザーが見ている表示と一致するため、可読性が高い
await page.getByLabel('Email address');
// 3. Text(テキスト)ベース
// シンプルで直感的だが、表示テキストの変更に弱い
await page.getByText('Submit');
// 4. Test ID (テスト用ID)
// 表示に依存しないが、HTMLに専用属性が必要
await page.getByTestId('submit-button');
// 5. CSS/XPath (最終手段)
// 柔軟だが、HTML構造の変更に脆弱
await page.locator('.btn-primary');公式ドキュメントを「隅々まで見る」価値
「動いているコードがあるなら、わざわざドキュメントを読み直す必要はない」。この判断は妥当です。しかし、偶然ドキュメントを読み直したことで、「動く」と「正しく動く」の間には大きな差があることに気付きました。
たしかに page.evaluate() で実装したコードは動いていました。ところが、それは「たまたま成功していた」に過ぎません。要素の読み込みが遅れる環境や、ネットワークが不安定な状況では、エラーが発生していたでしょう。
一方、locator を使った実装は、こうした不確実性を自動で吸収してくれます。要素がクリック可能になるまで待機し、安定性を確保します。コードの見た目はほとんど変わりませんが、内部の設計思想はまったく異なります。
公式ドキュメントには「なぜこの方法が推奨されるのか」という背景が書かれています。単なるAPIリファレンスではなく、設計思想やベストプラクティスが凝縮されています。ドキュメントを読むことは、ライブラリの作者が考えた「正しい使い方」を学ぶことです。
過去のコードを流用することは、開発スピードを維持するためには合理的な選択ですが、それは「過去の自分が正しく理解していた」という前提に立っています。ところが、ライブラリは進化し、ベストプラクティスも変化します。公式ドキュメントを読み直すことは、その変化に追いつくための最も確実な方法です。
もちろん、すべてのライブラリのドキュメントを隅々まで読む時間はありません。しかし、日常的に使っているツールや、プロジェクトの基盤となるライブラリについては、一度は腰を据えて読む価値があります。その投資は、将来的なトラブルを未然に防ぎ、より安定したコードを書くための基盤となります。
「動けばよい」という判断は間違っていません。しかし「正しく動く」ことを知った今、少し時間をかけてでも、公式ドキュメントを上から下まで読む価値があると私は考えます。
まとめ
- 過去のコードを流用することは効率的で合理的な判断だが、「動く」と「正しく動く」の間には設計思想の違いがある
page.evaluate()は動作するが、要素の操作可能性を確認しないため不安定なテストにつながる可能性があるlocatorは要素がクリック可能になるまで自動で待機し、Actionability Checks により安定性を向上させるwaitForNavigation()は競合状態が発生する可能性があるため非推奨。waitForURL()やwaitForSelector()を使うべき- 公式ドキュメントには「なぜこの方法が推奨されるのか」という設計思想が書かれており、単なるAPIリファレンス以上の価値がある
- 日常的に使うツールについては、一度は腰を据えてドキュメントを読む価値がある
参考
- Agents Skills - Claude Code
- Actionability | Playwright
- Best Practices | Playwright
- Evaluating JavaScript | Playwright
- Locator | Playwright - evaluate
- Locators | Playwright
- Page | Playwright - waitForNavigation
- Page | Playwright - waitForLoadState
- やはり俺の青春ラブコメはまちがっている。 (ガガガ文庫)
Footnotes
理解度チェック
問題1: Playwrightで要素をクリックする際、`page.evaluate()`を使った実装が正常に動作しています。この実装について正しい説明はどれですか?
動いているので問題ない。過去のプロジェクトで実績があるコードなら信頼できる
問題がある。要素が存在するか事前にチェックする処理を追加すべき
問題がある。page.locator()を使うことで、要素が操作可能になるまで自動的に待機できる
問題がある。page.waitForSelector()で要素の読み込みを待ってからevaluate()を実行すべき
問題2: PlaywrightのLocatorが要素をクリックする前に自動的に行うチェックはどれですか?
要素がDOMに存在することのみ
要素が表示されていること(Visible)と、要素が有効であること(Enabled)のみ
要素がDOMに存在すること、表示されていること、安定していること、イベントを受け取れること、有効であること、ビューポート内にスクロールされることなど複数のチェック
要素のCSSセレクタが正しいことのみ
...