PlaywrightでE2Eテスト自動化を導入したQAエンジニアが実際に経験した「失敗談」を7選で解説します。auto-waiting過信・言語選択ミス・CI環境差異・チーム導入の抵抗など、Playwright固有の躓きポイントと対策を実務経験をもとに紹介します。
「Playwrightに乗り換えれば解決する」——その思い込みが、次の失敗の始まりでした。ツールを変えても、使い方を間違えれば結果は同じです。
📌 この記事はこんな方におすすめ
- PlaywrightでのE2Eテスト導入を検討しているQAエンジニア
- SeleniumからPlaywrightへの移行を考えているエンジニア
- Playwright導入後に「思ってたのと違う」と感じている方
- チームへのPlaywright導入を推進しようとしているリード・マネージャー
✅ この記事を読むと得られること
- Playwright固有の失敗パターン7つと具体的な原因がわかる
- 「実装レベルのはまりポイント」ではなく「導入・運用レベルの判断ミス」がわかる
- 各失敗に対する具体的な回避策・改善策がわかる
👤 この記事を書いた人
QAエンジニア・テスト自動化エンジニアとして15年以上の実務経験を持つ Yoshi が執筆。Selenium・Playwright・pytest を実務で使い、Playwright導入時に「こうすればよかった」と感じた失敗を、包み隠さず共有します。
📖 関連記事との使い分け
- テスト自動化でよくある失敗5選:ツール非依存の「自動化戦略・設計」レベルの失敗 → 「何を自動化すべきか」で失敗した方はこちら
- Playwright × pytest ベストプラクティス:コードレベルの実装はまりポイント → 「書き方がわからない」方はこちら
- この記事:「Playwrightを選んで・導入して・チームで使う」という体験レベルの失敗談
📌 結論(3つのポイント)
- Playwright導入の失敗の多くは「ツールの問題」ではなく「導入計画・言語選択・チーム設計」の問題
- auto-waiting・page.route()・CI環境はPlaywright固有の落とし穴があり、事前に知っていれば回避できる
- Playwright自体は優れたツール。失敗を知った上で使えば、長期的に大きな価値を発揮する
「SeleniumでFlaky地獄に陥ったからPlaywrightに乗り換えよう」——2年前の自分の判断は半分正解で、半分間違いでした。
Playwright自体は確かに優れたツールです。でも「乗り換えれば万事解決」という思い込みで進めたことで、Playwright固有の新しい失敗を量産しました。この記事では、その体験談を7つにまとめて包み隠さず共有します。
Playwright導入で失敗した話7選とは?早見表
| # | 失敗の内容 | 根本原因 |
|---|---|---|
| ① | 「乗り換えれば万事解決」という過信 | ツール選定の思想レベルのミス |
| ② | auto-waitingを過信してタイムアウトまみれになった | Playwright固有機能の誤解 |
| ③ | TypeScriptで始めてチームが誰も保守できなくなった | チームスキルを無視した言語選択 |
| ④ | page.route()でモックしたつもりが実APIを叩いていた | ネットワークモックの仕組みへの誤解 |
| ⑤ | CI環境でブラウザが起動しない問題を放置した | ローカル依存の開発習慣 |
| ⑥ | バージョンアップで一気に30テストが落ちた | 依存管理・アップデート戦略の欠如 |
| ⑦ | 「なぜPlaywrightか」をチームに説明できなかった | ツール選定の根拠が自分の中にしかなかった |
① 「Playwrightに乗り換えれば万事解決」という過信とは?
何が起きたか
SeleniumでFlakyテストが増えて限界を感じたとき、「Playwrightはauto-waitingがあるからFlakyが解消する」「モダンなAPIで保守しやすい」という評判を信じて、既存の200件のSeleniumテストをすべてPlaywrightに書き直すプロジェクトを立ち上げました。結果としてFlakyは減りましたが、ゼロにはなりませんでした。そして書き直しに3ヶ月かけた工数は、テスト設計の改善には使われませんでした。
Playwrightで改善するFlakyと改善しないFlakyの違い
| Flakyの原因 | Playwrightで改善? | 理由 |
|---|---|---|
| Seleniumのimplicit wait不足 | ✅ 改善する | auto-waitingで自動的に待機 |
| ChromeDriverバージョン不一致 | ✅ 改善する | バイナリをライブラリと一緒に管理(ただしinstall実行・CIキャッシュの設定は別途必要) |
| アニメーション中の操作タイミング | △ 部分的に改善 | stableチェックはあるが完全ではない |
| 外部APIのレスポンスが不安定 | ❌ 改善しない | 外部依存はツールで解決できない |
| テスト間のDB・状態の競合 | ❌ 改善しない | テスト設計の問題はツールで解決できない |
| CI環境のCPU・メモリ性能差 | △ 部分的に対応可 | timeout値の調整で緩和できるが根本解決ではない |
| ネットワークの揺らぎ・遅延 | ❌ 改善しない | モック化で回避するのが正解 |
| 並列実行時のテスト間競合 | ❌ 改善しない | テスト設計(独立性の確保)で解決する |
② auto-waitingを過信してタイムアウトまみれになったとは?
何が起きたか
「Playwrightはauto-waitingがあるから明示的な待機処理は不要」——この理解で書き始めたテストは、しばらくすると謎のタイムアウトエラーを量産し始めました。
expect() を併用した明示的な待機が必要です。# ❌ auto-waitingに頼りすぎた書き方
# locator.click()はactionability checkを行うが、
# 非同期状態変化や独自UIコンポーネントでは明示的な待機が必要になるケースがある
page.locator("#submit-btn").click()
# ↑ 非同期でdisabled→enabled に変わるボタンは、
# 変化のタイミングによってタイムアウトになることがある
# ✅ 現代的なLocator API + expect()で意図を明示する
from playwright.sync_api import expect
expect(page.locator("#submit-btn")).to_be_enabled()
page.locator("#submit-btn").click()- 非同期でdisabled→enabled に変わるボタン:タイミングによってはdisabledのままクリックを試みてタイムアウト。
expect(locator).to_be_enabled()で先に状態を確認する - APIレスポンス後に表示されるコンテンツ:DOMに存在しない間は待ってくれるが、APIが遅い場合は全体タイムアウトになることがある
- アニメーション中の要素:stableチェックはあるが、CSSアニメーションの実装によっては想定どおりに動かないケースがある
- Shadow DOMの内部要素:セレクタの書き方によってはattachedと正しく判定されない
expect(locator).to_be_enabled() や expect(locator).to_be_visible() を組み合わせると「何を待っているか」が明示されてFlakyが減ります。なお page.wait_for_selector() は完全非推奨ではなく、hidden/detach状態の待機など一部の用途では今でも実務利用されますが、新規コードはLocator API + expect() が公式推奨スタイルです。③ TypeScriptで始めてチームが誰も保守できなくなったとは?
何が起きたか
「PlaywrightはTypeScript/Node.js版のサンプル・ドキュメント量が最も豊富」「型があった方が保守しやすい」という理由でTypeScriptを選択しました。自分はJavaScriptの経験があったので書けましたが、チームの他のメンバーはPythonエンジニアばかり。半年後にはテストコードのレビュアーが自分一人になり、事実上の属人化が完成していました。
| 判断軸 | TypeScript | Python |
|---|---|---|
| 公式サンプル・ドキュメントの豊富さ | ✅ 最も情報量が多い | ✅ 十分なサポートあり |
| 型安全性・補完 | ✅ 強力 | △ 型ヒントで対応可 |
| Pythonチームが多い現場での保守性 | ❌ 属人化リスク高 | ✅ 全員が触れる |
| pytest との統合 | ❌ 別エコシステム | ✅ pytest で統合可 |
④ page.route()でモックしたつもりが実APIを叩いていたとは?
何が起きたか
page.route() でAPIをモックしたつもりが、なぜかテスト結果が実際の本番データに依存していました。調査してわかったのは、テスト対象のAPIがJavaScript(ブラウザ)経由ではなく、サーバーサイドレンダリング(SSR)で直接叩かれていたというケースです。
page.route() は Browser Context 内のHTTP通信 のみをinterceptします。一方、PlaywrightのAPIテスト用 request fixture は APIRequestContext という独立したHTTPクライアント として動作するため、通信レイヤが完全に別です。「Playwrightだからpage.route()で全部モックできる」という誤解がハマりの根本原因です。# page.route() はブラウザのHTTP通信のみをインターセプト
await page.route("**/api/users", lambda route: route.fulfill(
status=200,
body='{"users": []}'
))
# ↑ SSRやNext.jsのgetServerSideProps内でのfetchは傍受できない
# ↑ Playwright API Testing(request fixture)のHTTP通信も傍受できない- SSR/SSG:Next.js・Nuxtなどのサーバーサイドでのfetch(Node.js側の通信のため)。この場合はupstream APIのモックやNode側でのモック対応が必要
- Service Worker:PWAやMSW(Mock Service Worker)導入環境でSWが通信を横取りしているケース。fetchがSW経由になっているとpage.route()が届かない
- WebSocket:WebSocket通信は別途
page.on("websocket")で対応 - Playwright API Testing(request fixture):Node.js側のHTTPクライアント通信
route.fulfill() のレスポンスが実際に返ってきているかをネットワークタブで確認します。SSR環境やService Worker環境では、msw・Nock・WireMock などのサーバー側またはSW側のモックと組み合わせるアプローチが有効です。「page.route()で全部モックできる」という前提で設計すると、環境によってハマります。⑤ CI環境でブラウザが起動しない問題を放置したとは?
何が起きたか
ローカルでは完璧に動くのに、GitHub Actionsに乗せると「ブラウザが起動しない」「依存ライブラリが足りない」エラーが続出しました。原因はわかっているのに後回しにして、「ローカルで動かして手動でCIに結果を報告する」という本末転倒な運用を2週間続けてしまいました。
CI環境でよく発生するPlaywright起動エラー
| エラーの種類 | 原因 | 対策 |
|---|---|---|
BrowserType.launch: Executable doesn't exist | ブラウザバイナリが未インストール | npx playwright install --with-deps |
error while loading shared libraries | Linux依存ライブラリ不足 | --with-deps オプションで解決 |
| タイムアウトがローカルより頻発 | CIマシンのCPU・メモリがローカルより低い | timeout を2〜3倍に設定 |
| ヘッドレスモードで挙動が違う | viewport差・GPU非対応・hover非動作・animation timing差異・Intersection Observer挙動差など | --headed で挙動確認・テスト設計を見直す |
⑥ バージョンアップで一気に30テストが落ちたとは?
何が起きたか
「pip install playwright --upgrade でバージョンを上げたら、翌日のCIで30件のテストが一気に落ちました。原因はバージョン間でのAPIの変更と、ブラウザバイナリのバージョン不一致です。
# ❌ バージョン固定なし(アップデートのたびに壊れるリスク)
playwright
# ✅ バージョンを固定(requirements.txt)
playwright==1.44.0
# ブラウザバイナリも必ず再インストール
# pip install playwright==1.44.0 の後に必ず実行
# playwright install chromiumrequirements.txt のバージョン固定は必須です。Renovate・Dependabot でバージョンアップのPRを自動作成する仕組みを入れると、アップデートの管理が楽になります。⑦ 「なぜPlaywrightか」をチームに説明できなかったとは?
何が起きたか
「Playwrightの方がいいから」と自分一人で決めてチームに展開しようとしたとき、先輩エンジニアから「なぜSeleniumじゃダメなの?」と聞かれ、うまく答えられませんでした。「なんとなくモダンだから」「Twitterで評判がいいから」——これでは技術的な意思決定として弱すぎます。結果として「とりあえず今のSeleniumで続けよう」となり、移行プロジェクトが半年間宙に浮きました。
チームを説得できる「なぜPlaywrightか」の根拠
| 比較軸 | Selenium | Playwright |
|---|---|---|
| auto-waiting | 手動でtime.sleep/waitが必要 | 要素がDOM+visibleになるまで自動待機 |
| ChromeDriver管理 | Chromeバージョンと手動で合わせる必要 | ブラウザバイナリを自動管理 |
| APIテストとの統合 | 別ライブラリ(requests等)が必要 | request fixtureでE2E+APIを統合 |
| ネットワークモック | 別途モックサーバーが必要 | page.route()でブラウザ通信をモック |
| HTML Report / Trace | 別途Allure等のセットアップが必要 | 組み込みHTMLレポート・Trace Viewer |
FAQ
Q. SeleniumからPlaywrightに移行する価値はありますか?
あります。ただし「移行さえすれば解決する」という過信は禁物です。ChromeDriver管理の撤廃・auto-waiting・HTMLレポートの組み込みなど、運用コストを下げる要素が多いのは事実です。「今のSeleniumで何が困っているか」を整理してから判断すると、移行の効果を最大化できます。
Q. Playwright導入はどこから始めるのが正解ですか?
「新規テストを1〜2件だけPlaywrightで書いて、CIに乗せるところまでやりきる」のが最初のステップとしてベストです。既存テストの全移行から始めると工数と学習コストで詰まります。小さく動かして成功体験を作ってから、徐々に範囲を広げていくのが失敗しにくいアプローチです。
Q. Python版とTypeScript版、どちらを選ぶべきですか?
チームの主要言語に合わせるのが最善です。PythonエンジニアのチームならPython版(playwright-pytest)が保守しやすく、既存のpytestスタックとも統合できます。TypeScriptは機能的に最もサポートが手厚いですが、「誰も保守できない」テストコードは最悪の状態です。チーム全員が読み書きできる言語を選んでください。
Q. auto-waitingがあるのになぜFlakyテストが発生するのですか?
auto-waitingは「要素がDOMに存在してvisibleになるまで」待つ機能です。「操作可能(enabled)になるまで」「APIレスポンスが返るまで」「アニメーションが完了するまで」は別途明示的な待機処理が必要です。Flakyが多い場合は、auto-waitingの対象外のケースが混入していないか確認してください。
Q. Playwrightのバージョンはどう管理すればいいですか?
requirements.txt でバージョンを固定し、アップデートは専用ブランチで全テストの通過を確認してからmainにマージするのが基本です。Playwrightはライブラリとブラウザバイナリのバージョンが連動しているため、ライブラリをアップデートしたら必ず playwright install でブラウザも更新してください。
📖 関連記事(この記事と合わせて読むと理解が深まります)
【Playwright実装】
【自動化戦略・判断基準】
- テスト自動化でよくある失敗5選|戦略・設計レベルの失敗と改善策
- 自動化しない方がいいテストケース7選|Flakyになりやすいテストの見分け方
- 自動化すべきテストとすべきでないテストの見分け方
- テスト自動化のROI(費用対効果)の考え方
【ロードマップ】
Playwright自体は本当に優れたツールです。この記事で紹介した失敗は、すべて「Playwrightの問題」ではなく「使い方・導入計画・チーム設計」の問題でした。失敗のパターンを知った上で始めれば、同じ轍を踏まずに済みます。小さく始めて、CIに乗せて、チームで使える状態を作る——この順番で進めることが、Playwright導入を成功させる最短ルートです。
📋 この記事のまとめ
- 「Playwrightに移行すれば解決する」は半分正解。テスト設計の問題はツールを変えても解決しない
- auto-waitingは「visible」まで待つ機能。「enabled」「APIレスポンス待ち」は明示的な待機が別途必要
- 言語選択はチームの多数が保守できるものを優先。「公式推奨」より「チームが使える」が重要
- page.route()はブラウザの通信のみモック。SSR・WebSocket・request fixtureには効かない
- CI対応は導入初日にやりきる。「後で」が最大の時間泥棒になる
- バージョンアップはライブラリとブラウザバイナリをセットで管理。固定バージョンで運用する
- チームへの導入は「現状課題→解決策→移行コスト試算」の3点セットで説得力を持たせる
