E2Eテストは「書くのが面倒、実行が遅い、そしてすぐ壊れる」という負のイメージが先行しがちです。特にCI/CDパイプラインで頻繁に失敗するFlaky Testは、開発者の貴重な時間を奪い、リリースサイクルを遅らせる元凶となります。
この記事では、E2Eテストフレームワークの二大巨頭であるPlaywrightとCypressの最新機能を徹底比較し、それぞれの特性を理解した上で、安定性と速度を両立するテスト構築のノウハウを具体的なコード例とともに解説します。これにより、あなたのプロジェクトに最適なE2Eテストフレームワークを選定し、Flaky Testに悩まされない堅牢なテスト環境を構築するための道筋が見えてくるでしょう。
E2Eテストの現状と課題:なぜ安定化が難しいのか
E2Eテストが不安定になる主な原因は、テスト対象のシステムが複雑であることと、テスト実行環境の変動にあります。ブラウザのレンダリングタイミング、非同期APIの応答速度、ネットワークの遅延、テストデータの一貫性の欠如など、様々な要因が絡み合い、ローカルでは成功するテストがCI環境で失敗する「Flaky Test」を生み出します。
このセクションでは、PlaywrightとCypressがそれぞれどのようなアプローチでこれらの課題に対処しているのか、その基本的なアーキテクチャと特徴を比較します。
Playwrightの概要と特徴
PlaywrightはMicrosoftが開発を主導するE2Eテストフレームワークで、その最大の特徴は主要な3つのブラウザエンジン(Chromium, Firefox, WebKit)を単一のAPIでサポートしている点です。これにより、クロスブラウザテストの記述と実行が非常に効率的になります。
Playwrightはブラウザの外部で動作するアーキテクチャを採用しており、WebDriverプロトコルを使用せずに、ブラウザと直接通信することで高速かつ安定した操作を実現しています。
Playwrightの主な特徴(バージョン 1.43.0 時点):
- クロスブラウザ対応: Chromium (Chrome, Edge), Firefox, WebKit (Safari)をサポート。
- 並列実行: 標準で並列実行をサポートしており、テストスイート全体の実行時間を大幅に短縮可能。
-
自動待機: DOM要素の表示、操作可能、アニメーション完了などを自動で待機するため、手動での
waitForTimeoutなどが不要となり、安定したテスト実行に貢献します。 - 強力なデバッグツール: Trace Viewer、UI Mode、Codegen(コード自動生成)など、開発効率とデバッグ体験を向上させるツールが豊富です。
Cypressの概要と特徴
Cypressはオープンソースで開発されているE2Eテストフレームワークで、ブラウザの内部でテストを実行するという独自のアーキテクチャを持っています。これにより、JavaScriptで記述されたテストコードがブラウザのJavaScriptエンジンと同じコンテキストで動作するため、DOMへのアクセスやネットワークリクエストのスタブ化などが非常に容易です。
Cypressは特に開発者体験を重視しており、GUIテストランナーによるリアルタイムなテスト実行監視や、充実したデバッグ機能が魅力です。
Cypressの主な特徴(バージョン 13.7.3 時点):
- ブラウザ内実行アーキテクチャ: JavaScriptでクライアントサイドのAPIを直接利用でき、開発者ツールのように動作します。
- GUIテストランナー: テストの実行状況をリアルタイムで視覚的に確認でき、デバッグが容易です。
- 自動待機: Playwrightと同様に、要素の存在や可視性を自動で待機します。
- 開発者体験: 導入が容易で、すぐにテストを書き始められる点が評価されています。
- 複数タブ/ウィンドウ操作の制約: ブラウザ内部で実行されるアーキテクチャの都合上、複数タブや別ウィンドウでのテストはサポートされていません。
PlaywrightとCypressのセットアップと基本操作を比較
E2Eテストを始めるには、まずフレームワークのセットアップと基本的なテストコードの記述が必要です。このセクションでは、それぞれのフレームワークの導入方法と、簡単なGoogle検索テストの例を通して、APIの違いと特徴を比較します。
Playwrightのセットアップと簡単なテストコード
Playwrightのセットアップは非常にシンプルで、専用のCLIツールが環境構築を強力にサポートします。
1. インストール
以下のコマンドを実行すると、Playwrightのプロジェクトが初期化され、必要なブラウザバイナリ、サンプルコード、CI設定ファイルなどが自動で生成されます。
npm init playwright@latest
2. テストコードの例 (TypeScript)
PlaywrightはTypeScriptをネイティブサポートしており、型安全なテストコードを記述できます。以下のコードは、Googleにアクセスし、「Playwright Cypress 比較」で検索して、結果ページのタイトルを確認するテストです。
import { test, expect } from '@playwright/test';
test('Google検索テスト', async ({ page }) => {
// 指定されたURLにアクセス
await page.goto('https://www.google.com');
// Web-firstセレクタの例: ユーザーが認識するラベルで要素を特定
// '検索' というラベルを持つ要素にテキストを入力
await page.getByLabel('検索', { exact: true }).fill('Playwright Cypress 比較');
// Web-firstセレクタの例: ユーザーが認識するボタン名で要素を特定
// 'Google 検索' というラベルを持つボタンをクリック
await page.getByLabel('Google 検索', { exact: true }).click();
// ページタイトルに指定された文字列が含まれることを検証
await expect(page).toHaveTitle(/Playwright Cypress 比較/);
});
ポイント:
getByLabelのようなWeb-firstセレクタは、ユーザーがUIを見る視点に基づいて要素を特定するため、HTML構造の変更に強く、壊れにくいテストコードを書く上で非常に有効です。
Cypressのセットアップと簡単なテストコード
Cypressのインストールも簡単で、WebDriverのバージョン管理などを気にする必要がありません。
1. インストール
以下のコマンドでCypressを開発依存性としてインストールします。
npm install cypress --save-dev
インストール後、npx cypress openを実行すると、CypressのGUIテストランナーが起動し、サンプルテストファイルが自動生成されます。
2. テストコードの例 (JavaScript)
CypressのテストコードはJavaScriptで記述し、jQueryライクなAPIが特徴です。
describe('Google検索テスト', () => {
it('検索フォームに値を入力して検索結果を確認する', () => {
// 指定されたURLにアクセス
cy.visit('https://www.google.com');
// CSSセレクタの例: name属性が"q"のtextarea要素にテキストを入力
cy.get('textarea[name="q"]').type('Playwright Cypress 比較');
// CSSセレクタの例: name属性が"btnK"のinput要素をクリック
// (Googleの検索ボタンのname属性は変更される可能性があるため注意)
cy.get('input[name="btnK"]').click();
// ページタイトルに指定された文字列が含まれることを検証
cy.title().should('include', 'Playwright Cypress 比較');
});
});
ポイント:
Cypressはcy.get()で要素を取得し、チェーンメソッドで操作や検証を行います。セレクタは主にCSSセレクタを使用しますが、安定性を高めるためにカスタムデータ属性を利用することが推奨されます。
E2Eテストの安定化と高速化:Flakiness解消テクニック
E2Eテストの最大の課題の一つは、実行結果が不安定になる「Flaky Test」です。このセクションでは、PlaywrightとCypressそれぞれのアプローチと共通のベストプラクティスを通じて、テストの安定化と高速化を実現するための具体的なテクニックを解説します。
1. タイムアウトエラーの回避策
タイムアウトエラーは、要素の表示遅延や非同期処理の完了待ちなどで頻繁に発生します。
Playwrightでの回避策
Playwrightは強力な自動待機機能を持っていますが、それでもタイムアウトが発生する場合は、設定を見直す必要があります。
-
設定の調整:
playwright.config.tsのtimeout設定(デフォルト30秒)を、テストの最長実行時間に合わせて調整します。ただし、無闇に長くしすぎるとテストの高速性が失われるため、ボトルネックとなっている箇所を特定することが重要です。// playwright.config.ts import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ timeout: 60 * 1000, // テスト全体のタイムアウトを60秒に設定 expect: { timeout: 10 * 1000, // expectアサーションのタイムアウトを10秒に設定 }, // ... その他の設定 }); -
固定秒数の待機は最小限に:
page.waitForTimeout(5000)のような固定秒数の待機は、テスト実行時間を無駄に延長し、Flaky Testの原因にもなります。Playwrightの自動待機機能を信頼し、必要最小限に留めましょう。
Cypressでの回避策
Cypressも自動待機機能を持ちますが、APIリクエストの完了待ちなど、特定のシナリオでは明示的な待機が必要になる場合があります。
-
DOMの状態に基づく待機:
cy.get('.element').should('be.visible')のように、DOM要素の状態が変化するまで待機するアサーションを積極的に使用します。 -
APIリクエストの待機:
cy.intercept()とcy.wait()を組み合わせて、特定のAPIリクエストが完了するまで待機することができます。これは、非同期処理の結果がUIに反映されるのを待つ場合に非常に有効です。// APIリクエストをインターセプトして待機する例 cy.intercept('GET', '/api/data').as('getData'); cy.visit('/dashboard'); cy.wait('@getData'); // /api/dataへのGETリクエストが完了するまで待機 cy.get('.data-table').should('be.visible');
2. 要素が見つからない/操作できないエラーの回避策
セレクタの不安定さがE2Eテストが壊れる最大の原因の一つです。
Playwrightでの回避策:Web-firstセレクタの活用
Playwrightは、ユーザーがUIを見る視点に近い形で要素を特定できる「Web-firstセレクタ」を強力に推奨しています。
-
getByRole: ARIAロールに基づいて要素を特定します。アクセシビリティにも配慮したテストコードになります。await page.getByRole('button', { name: '送信' }).click(); -
getByLabel:label要素のテキストやaria-label属性に基づいて要素を特定します。await page.getByLabel('ユーザー名').fill('testuser'); -
getByText: 表示されているテキストに基づいて要素を特定します。await page.getByText('詳細を見る').click();
これらのセレクタは、CSSクラスやIDの変更に影響されにくく、より堅牢なテストコードを実現します。
Cypressでの回避策:カスタムデータ属性の使用
Cypressでは、テスト専用のカスタムデータ属性をHTML要素に付与し、それをセレクタとして使用することがベストプラクティスとされています。
-
data-cy、data-test属性:<button data-cy="submit-button">送信</button>これにより、開発者が意図しないCSSやIDの変更によるテストの破損を防ぎ、テストコードの可読性も向上します。cy.get('[data-cy="submit-button"]').click();
3. CI環境での不安定なテスト (Flaky Test) の解消
CI環境でのFlaky Testは、開発者の生産性を著しく低下させます。
PlaywrightでのFlaky Test解消と並列実行
PlaywrightはCI環境での安定性に優れており、特に強力なデバッグツールと並列実行機能が特徴です。
-
並列実行の最適化:
playwright.config.tsのworkers設定で、CI環境のCPUコア数に合わせて並列実行数を調整します。これにより、テストスイート全体の実行時間を短縮できます。// playwright.config.ts export default defineConfig({ workers: process.env.CI ? 2 : undefined, // CI環境では2並列で実行、ローカルではデフォルト(CPUコアの半分) // ... }); -
Trace Viewerの活用: CIでテストが失敗した場合、PlaywrightのTrace Viewerは実行時の詳細なスナップショット、アクションログ、ネットワークログなどをGUIで再現し、問題の特定を劇的に効率化します。
npx playwright show-trace trace.zip - テスト間の依存関係を排除: 各テストが独立して実行できるように設計することで、並列実行時の競合状態を防ぎ、Flaky Testを減少させます。テストごとにクリーンな状態を保つためのテストデータ管理が重要です。
CypressでのFlaky Test解消
Cypressもリトライ設定やCypress Cloudとの連携により、CI環境での安定化を図ることができます。
-
リトライ設定:
cypress.config.jsのretriesオプションを利用して、テストが失敗した場合に自動で再実行するように設定できます。一時的なネットワーク遅延などによる失敗を防ぐのに有効です。// cypress.config.js const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { setupNodeEvents(on, config) { // implement node event listeners here }, retries: { runMode: 2, // CI/CD環境での失敗時に2回リトライ openMode: 0, // ローカル開発モードではリトライしない }, }, }); - Cypress Cloudの活用: 有料サービスであるCypress Cloudは、並列実行、テスト結果の集中管理、Flaky Testの自動検出、デバッグ情報の収集など、CI環境でのE2Eテスト運用を強力にサポートします。
設計上のトレードオフとベストプラクティス
PlaywrightとCypress、どちらを選ぶにしても、効果的なE2Eテストを構築するためには、設計上の考慮事項とベストプラクティスを理解しておく必要があります。
1. テスト対象の選定
E2Eテストは実行コストとメンテナンスコストが高いため、すべての機能を網羅するのではなく、ビジネス上最も重要なユーザーフローやクリティカルな機能に絞って自動化することが賢明です。UIの変更頻度が高い箇所や、単体テスト・結合テストで十分にカバーできる箇所は、E2Eテストの対象から外すことを検討しましょう。
- 戦略的な自動化: 「止まると売上や顧客対応に直結する主要動線」に焦点を当て、それ以外は下位のテスト(単体テスト、結合テスト)でカバーする。
2. セレクタの選定戦略
テストコードが壊れやすい最大の原因は、不安定なセレクタです。
-
Playwright:
getByRole、getByLabel、getByTextといったWeb-firstセレクタを優先的に使用し、ユーザー視点での堅牢なセレクタ戦略を採用します。 -
Cypress:
data-cy、data-testなどのカスタムデータ属性をHTML要素に付与し、それをセレクタとして使用することで、UIの変更に強いテストを記述します。
3. 待機処理の最適化
固定秒数の待機は、テストの実行時間を不必要に長くし、Flaky Testの原因にもなります。
-
自動待機を最大限に活用: PlaywrightもCypressも、要素の表示や操作可能状態を自動で待機する機能を備えています。これを信頼し、明示的な
waitForTimeoutやcy.wait()は極力避けましょう。 -
条件付き待機: 特定のAPIリクエストの完了やDOM要素の状態変化を待つ必要がある場合は、
cy.intercept()とcy.wait()(Cypress)、page.waitForResponse()やpage.waitForSelector()(Playwright)のような条件付き待機を活用します。
4. テストデータの管理
E2Eテストの安定性には、テストデータの管理が不可欠です。
- 独立性の確保: 各テストが独立して実行できるよう、テストごとにクリーンなテストデータを用意し、テスト間の依存関係をなくします。
- データ生成の自動化: テストデータファクトリーやAPIを通じて、テストデータを効率的に生成・初期化する仕組みを構築します。テスト開始時にデータを作成し、テスト終了後にクリーンアップするパターン(Setup/Teardown)を導入しましょう。
5. CI/CDとの統合と効率的な運用
E2Eテストは、CI/CDパイプラインに組み込むことでその真価を発揮します。
- PlaywrightのCI連携: PlaywrightはGitHub ActionsなどのCIサービスと容易に連携でき、最小限の設定で自動実行が可能です。テスト失敗時のトレースや動画の成果物保存設定は、原因調査の時間を大幅に短縮します。
- CypressのCI連携: CypressもCI連携は可能ですが、並列実行にはCypress Cloudの利用が推奨されます。
- 実行戦略の最適化: プルリクエスト時には主要なテストのみを実行し、全件テストはマージ後や夜間スケジュール実行に回すなど、CI/CDパイプラインのワークフローを最適化することで、開発サイクルへの影響を最小限に抑えます。
まとめ:Playwright vs Cypress、プロジェクトに最適なE2Eテストフレームワークの選択
PlaywrightとCypressは、どちらも強力なE2Eテストフレームワークですが、それぞれ異なる設計思想と強みを持っています。
-
Playwright:
- クロスブラウザ対応(Chromium, Firefox, WebKit)を重視し、高い安定性と並列実行性能を求めるプロジェクトに最適です。
-
getByRoleのようなWeb-firstセレクタにより、UI変更に強いテストコードが書けます。 - Trace ViewerやUI Modeなど、CI環境でのデバッグを強力にサポートするツールが充実しています。
- JavaScript/TypeScript以外の言語サポートも強みです。
-
Cypress:
- 開発者体験とブラウザ内実行によるデバッグの容易さを重視するプロジェクトに最適です。
- GUIテストランナーによるリアルタイムなフィードバックは、特にテストコード作成時の生産性を高めます。
- 複数タブ/ウィンドウ操作を必要としないWebアプリケーションであれば、選択肢として非常に有力です。
- CI環境での並列実行や高度な分析にはCypress Cloudとの連携が前提となります。
結局のところ、プロジェクトの要件(クロスブラウザ対応の範囲、テスト実行速度の要件、チームの技術スタック、予算など)と、開発者が重視する点(デバッグのしやすさ、CIでの安定性、学習コスト)を総合的に判断して選択することが重要です。
どちらのフレームワークを選択するにしても、本記事で解説したセレクタの選定戦略、待機処理の最適化、テストデータ管理、CI/CDとの統合といったベストプラクティスを適用することで、E2Eテストの安定化と高速化を実現し、Flaky Testに悩まされない堅牢なテスト環境を構築できるでしょう。
さらに深く掘り下げるには、各フレームワークの公式ドキュメントを参照し、より高度な機能や設定オプションについて学習することをお勧めします。