「E2Eテスト、導入したはいいけど、テストが不安定でメンテが大変…」「結局どのツールを選べばいいの?」
多くのエンジニアが直面するこの悩みを解決するため、本記事では主要なE2EテストフレームワークであるPlaywrightとCypressを徹底比較します。パフォーマンス、機能、開発体験、AI活用への展望まで多角的に分析し、あなたのプロジェクトに最適なE2Eテストツールを選定するための具体的な判断基準と導入ポイントを解説します。
PlaywrightとCypress、なぜ今この2つが注目されるのか?
このセクションでは、E2Eテスト市場におけるPlaywrightとCypressの立ち位置と、両者がなぜ現代のWeb開発において不可欠なツールとなっているのかを解説します。
Webアプリケーションの複雑化に伴い、ユーザー体験の品質保証はこれまで以上に重要になっています。特に、エンドユーザーの視点でアプリケーション全体を検証するE2E(End-to-End)テストは、リリース前の最終防衛線としてその価値を高めています。数あるE2Eテストツールの中でも、近年特に注目を集めているのがMicrosoftが開発を主導するPlaywrightと、開発者体験に強みを持つCypressです。
両者ともにTypeScript/JavaScriptベースで記述でき、モダンなWeb開発環境との親和性が高いことから、多くの開発現場で採用が進んでいます。しかし、そのアーキテクチャや思想には大きな違いがあり、プロジェクトの要件やチームの特性によって最適な選択は異なります。
PlaywrightとCypressのアーキテクチャと基本機能の比較
このセクションでは、PlaywrightとCypressの根本的な違いであるアーキテクチャに焦点を当て、それらがもたらす機能面での差異を比較します。
Playwrightの「アウトオブプロセス」アーキテクチャ
Playwrightは、テストコードがブラウザの外部で動作する「アウトオブプロセス」アーキテクチャを採用しています。これにより、Playwrightはブラウザと直接通信し、高速かつ安定した操作を実現します。
-
特徴:
- 広範なブラウザサポート: Chromium(Chrome, Edge)、Firefox、WebKit(Safariのエンジン)といった主要なブラウザエンジンをネイティブでサポートし、一貫したAPIでクロスブラウザテストを容易にします。
- 並列実行: 標準でテストの並列実行をサポートしており、テストスイート全体の実行時間を大幅に短縮できます。CI/CD環境での効率的な実行に貢献します。
- 複数オリジン・タブの操作: 異なるオリジンへのナビゲーションや、複数タブ・ウィンドウの操作もネイティブにサポートしており、複雑なユーザーシナリオのテストが可能です。
-
APIテスト: UIテストだけでなく、
APIRequestContextを使用してREST APIの基本的な操作を行うことができ、UIテストとAPIテストを組み合わせた堅牢なE2Eテストが可能です。
Cypressの「インプロセス」アーキテクチャ
Cypressは、テストコードがブラウザの内部、つまりアプリケーションと同じ実行コンテキストで動作する「インプロセス」アーキテクチャを採用しています。
-
特徴:
- 開発者体験: DOM、ネットワーク層、アプリケーションの状態に直接アクセスできるため、リアルタイムデバッグやタイムトラベルデバッグといった優れた開発者体験を提供します。
- 自動待機: 要素の表示、DOMへのアタッチ、アニメーション完了などを自動で待機する機能が強力です。
-
APIテスト:
cy.request()を使用してHTTPリクエストを送信し、APIの機能、パフォーマンス、セキュリティを検証できます。 - ブラウザサポート: 主にChromiumベースのブラウザをサポートしており、FirefoxとEdgeへの限定的なサポートがあります。WebKit (Safari) には対応していません。(Cypress v13.x 時点)
- 並列実行: 並列実行には有料のCypress Cloudサービスまたは複雑な回避策が必要です。(Cypress v13.x 時点)
PlaywrightとCypressの導入と実装例
このセクションでは、それぞれのフレームワークのセットアップ方法と基本的なテストコードの書き方を具体例とともに示します。
Playwrightのセットアップと簡単なテストコード
Playwrightの導入は非常に簡単です。以下のコマンドでプロジェクトを初期化し、必要なパッケージをインストールできます。
# プロジェクトの初期化とPlaywrightのインストール
npm init playwright@latest
example.spec.ts というファイルを作成し、以下のテストコードを記述します。
// example.spec.ts
import { test, expect } from '@playwright/test';
test('トップページにアクセスし、タイトルを確認する', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
test('Get startedリンクをクリックし、URLを確認する', async ({ page }) => {
await page.goto('https://playwright.dev/');
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page).toHaveURL(/.*intro/);
});
Cypressのセットアップと簡単なテストコード
Cypressもnpmで簡単にインストールできます。
# Cypressのインストール
npm install cypress --save-dev
cypress/e2e/example.cy.js というファイルを作成し、以下のテストコードを記述します。
// cypress/e2e/example.cy.js
describe('トップページテスト', () => {
it('トップページにアクセスし、タイトルを確認する', () => {
cy.visit('https://example.cypress.io');
cy.title().should('eq', 'Cypress.io: Kitchen Sink');
});
it('Actionsページに移動し、URLを確認する', () => {
cy.visit('https://example.cypress.io');
cy.contains('Actions').click();
cy.url().should('include', '/commands/actions');
});
});
APIテストの活用
UIテストの安定性を高めるためには、APIテストを併用することが効果的です。例えば、テストデータの準備をAPIで行い、UIテストではそのデータが表示されているかを確認するといった使い方ができます。
PlaywrightのAPIテスト例
Playwrightでは request フィクスチャを使用してAPIリクエストを送信できます。
// api.spec.ts
import { test, expect } from '@playwright/test';
test('APIでユーザーリストを取得する', async ({ request }) => {
const response = await request.get('https://api.example.com/users');
expect(response.status()).toBe(200);
const users = await response.json();
expect(users).toBeInstanceOf(Array);
expect(users.length).toBeGreaterThan(0);
});
CypressのAPIテスト例
Cypressでは cy.request() を使用します。cypress.config.js で baseUrl を設定すると、相対パスでリクエスト可能です。
// cypress.config.js
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:1234', // テスト対象のアプリケーションのベースURLを設定
},
})
// cypress/e2e/api.cy.js
describe('APIテスト', () => {
it('GETリクエストでユーザーリストを取得する', () => {
cy.request('GET', '/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array').and.have.length.greaterThan(0);
});
});
});
開発体験とデバッグ機能の比較
このセクションでは、開発者がテストを作成・デバッグする際の体験に焦点を当て、それぞれのツールの強みと弱みを比較します。
Playwrightの開発体験とデバッグ
Playwrightは強力なツール群を提供し、効率的なテスト開発をサポートします。
-
コード自動生成(レコード機能):
npx playwright codegen <URL>コマンドでブラウザ操作を記録し、テストコードを自動生成できます。これにより、テストの初期作成時間を大幅に短縮できます。 -
デバッグ機能:
-
npx playwright test --debug: テスト実行中にブラウザを一時停止し、ステップ実行やDOMの状態確認が可能です。VS Code拡張機能との連携も強力です。 -
トレースビューア:
npx playwright test --trace onでテスト実行時のスクリーンショット、DOMスナップショット、アクションログ、ネットワークリクエストなどを記録し、後から専用のUIで再生・分析できます。これにより、テスト失敗時の原因特定が非常に容易になります。
-
- 学習曲線: Cypressと比較すると、非同期処理の理解や、Cypressとは異なるデバッグフローに慣れるまで時間がかかる場合があります。しかし、一度慣れてしまえば非常に強力なツールとして機能します。
Cypressの開発体験とデバッグ
Cypressは「開発者のために作られたE2Eテストツール」というだけあり、直感的でパワフルなデバッグ体験を提供します。
- リアルタイムデバッグ: テストランナーのUIを通じて、テストの実行状況、DOMの状態、コンソールログ、ネットワークリクエストなどをリアルタイムで確認できます。テストコードの変更が即座にブラウザに反映されるため、高速なフィードバックループが得られます。
- タイムトラベルデバッグ: 各コマンドが実行された時点のDOMの状態を遡って確認できます。これにより、「テストの途中で何が起きたか」を視覚的に把握しやすくなります。
-
自動待機: 要素の表示やDOMへのアタッチを自動で待機するため、手動での
cy.wait()などの記述を減らし、テストコードの可読性と安定性を高めます。 -
セレクタの独立性:
data-*属性など、CSSやJavaScriptの変更に影響されにくいセレクタの使用を推奨しており、テストのメンテナンス性を向上させます。
よくあるハマりどころと回避策
このセクションでは、PlaywrightとCypressを導入・運用する際によく遭遇する問題と、その具体的な解決策を解説します。
Playwrightのハマりどころ
-
Host system is missing dependencies to run browsers.エラー (WebKitのみ)- 原因: WebKitのテスト実行に必要なシステム依存関係がインストールされていない。
-
回避策: 以下のコマンドで依存関係をインストールします。
npx playwright install-deps
-
Path is not available when connecting remotelyエラー (リモートブラウザでのファイルダウンロード)-
原因: リモートブラウザでPlaywrightテストを実行し、ファイルのダウンロード機能をテストする際に、ダウンロードファイルインスタンスの
path()関数が使用できない。 -
回避策:
saveAs()関数を使用して、クライアント側でファイルを保存します。const downloadPromise = page.waitForEvent('download'); await page.getByText('Download file').click(); // ダウンロードをトリガーする要素をクリック const download = await downloadPromise; await download.saveAs('./path/to/save/downloaded-file.zip');
-
原因: リモートブラウザでPlaywrightテストを実行し、ファイルのダウンロード機能をテストする際に、ダウンロードファイルインスタンスの
-
会社のPCでPlaywrightが実行できない (ブラウザ拡張機能)
- 原因: 会社のPCにセキュリティ関連のブラウザ拡張機能がインストールされており、Playwrightの実行を妨げている。
-
回避策:
launchオプションにignoreDefaultArgs: ['--disable-extensions']を追記して、デフォルトの拡張機能を無効にします。// playwright.config.js import { defineConfig } from '@playwright/test'; export default defineConfig({ use: { launchOptions: { // デフォルトの引数を無視し、拡張機能を無効にする ignoreDefaultArgs: ['--disable-extensions'], }, }, });
Cypressのハマりどころ
-
cy.visit()が異なるオリジンドメインへの遷移で失敗する- 原因: Cypress v11.0.0以前では、単一のテスト内で複数のオリジンドメインに遷移することが制限されていた。
- 回避策: Cypress v12.0.0以降ではこの制限が解消されています。古いバージョンを使用している場合はバージョンアップを検討してください。
-
要素が他の要素に隠れてクリックできない
- 原因: Cypressが要素をクリックしようとした際に、別の要素(ヘッダーなど)が重なっていてクリックできない。
-
回避策:
should('be.visible')で要素が表示されていることを確認後、click()メソッドにforce: trueオプションを付けて強制的にクリックします。ただし、これは根本原因の特定と解決が理想です。cy.get('[data-cy="target-element"]').should('be.visible'); cy.get('[data-cy="target-element"]').click({ force: true });
-
画面遷移時のエラー(要素の取得失敗など)
- 原因: 画面遷移が完了する前に次の操作を実行しようとしてしまう。
-
回避策: Cypressは自動待機機能を持っているため、要素が表示されるまで待つなどのスマートな待機方法を検討します。
cy.wait()を使用した固定時間待機は最後の手段とすべきです。// 推奨される方法: 要素が表示されるまで待つ cy.get('h1').should('contain', '新しいページタイトル'); // 最終手段: 固定時間待機 cy.wait(2000); // 2秒待機
PlaywrightとCypressの選定基準とプロジェクトへの適用
このセクションでは、PlaywrightとCypressの設計上のトレードオフとベストプラクティスを比較し、プロジェクトの特性に応じた最適なE2Eテストツール選定の指針を提示します。
Playwrightがフィットするプロジェクト
- クロスブラウザテストが必須: Chromium, Firefox, WebKit (Safari) 全てで一貫したテストが必要な場合。Playwrightはネイティブサポートにより、信頼性の高いクロスブラウザテストを実現します。
- 高速なテスト実行と並列実行が重要: 大規模なテストスイートをCI/CDで高速に実行したい場合。Playwrightは標準で並列実行をサポートしており、CI/CD環境での実行効率が非常に高いです。
- 複雑なユーザーシナリオ(複数タブ/ウィンドウ、別オリジン)のテスト: ログイン後に別ドメインのサービスに遷移する、複数タブを開いて連携操作を行うなど、複雑なシナリオをテストする場合。Playwrightはこれらの操作をネイティブにサポートします。
- UIテストとAPIテストを密に連携させたい: テストデータ準備や状態確認のためにAPIテストを積極的に活用し、E2Eテスト全体の安定性と効率を高めたい場合。PlaywrightはAPIテスト機能も充実しています。
- 堅牢なテストコードを重視: DOMの変更に強く、Flaky Test(不安定なテスト)の発生を抑えたい場合。Playwrightのロケータや自動待機機能は堅牢なテストコード作成に貢献します。
Playwrightのベストプラクティス
-
ロケータの利用:
getByRole,getByText,getByLabelなど、Playwrightに組み込まれているロケータを使用し、DOM構造に依存しない堅牢なテストを作成します。 -
テストジェネレータの活用:
npx playwright codegen <URL>で初期のテストコード生成を効率化します。 - CI/CDでの頻繁な実行: GitHub ActionsなどのCI/CDでテストを自動実行し、早期に不具合を検知します。
-
ウェブファーストのアサーション: Playwrightの自動待機機能を信頼し、
page.waitForTimeout()のような固定的な待機は極力避けます。 - APIテストの併用: UI操作のみでは原因特定が難しいケースに備え、APIテストを組み合わせて安定性を高めます。
Cypressがフィットするプロジェクト
- 優れた開発者体験を最優先: テストコードの記述、デバッグ、メンテナンスにおいて、直感的で高速なフィードバックループを重視する場合。Cypressのタイムトラベルデバッグやリアルタイムデバッグは非常に強力です。
- フロントエンド開発チームとの連携が密: フロントエンド開発者が自身のコードと同時にE2Eテストを記述・デバッグする文化がある場合。Cypressはブラウザ内で動作するため、開発者にとって親しみやすい環境を提供します。
- 小〜中規模のプロジェクト: テストスイートの規模がそれほど大きくなく、並列実行のコストが課題にならない場合。
- Chromiumベースのブラウザでのテストが主: WebKit(Safari)でのテストが必須ではなく、主にChrome/Edge/Firefoxでの動作確認で十分な場合。
- モダンなSPA(Single Page Application)のテスト: SPAの内部状態に直接アクセスできるCypressのアーキテクチャは、SPAのテストにおいて特に有効な場合があります。
Cypressのベストプラクティス
- 開発者体験の活用: リアルタイムデバッグやインタラクティブなテストランナーを最大限に活用し、テスト開発の効率を高めます。
-
セレクタの独立性:
data-*属性など、UIの変更に強いセレクタを使用し、テストのメンテナンスコストを削減します。 -
テストの分離: 各テストは独立しており、前のテストの状態に依存しないように
beforeEachやafterEachを活用してクリーンな状態を保ちます。 - カスタムコマンドの活用: 繰り返し利用する操作をカスタムコマンドとして定義し、テストコードの可読性と再利用性を高めます。
- APIテストの活用: UIテストの前にAPIリクエストでテストデータを準備するなど、APIテストを効果的に利用してUIテストの安定性を向上させます。
まとめ:あなたのE2Eテスト選定を成功させるために
本記事では、PlaywrightとCypressという主要なE2Eテストフレームワークを、そのアーキテクチャ、機能、開発体験、そして導入時のハマりどころから比較しました。
- Playwrightは、高速な並列実行、広範なクロスブラウザサポート、複雑なシナリオへの対応力を求める大規模プロジェクトや、堅牢なCI/CDパイプラインを構築したい場合に強力な選択肢となります。
- Cypressは、優れた開発者体験、直感的なデバッグ機能、素早いフィードバックループを重視するチームや、主にChromiumベースのブラウザでのテストで十分な場合に、高い生産性を提供します。
最終的なE2Eテスト選定は、プロジェクトの具体的な要件、チームのスキルセット、そしてテスト対象アプリケーションの特性によって異なります。両者の強みと弱みを理解し、あなたのプロジェクトにとって最適なツールを見つける一助となれば幸いです。
さらに深く理解するためには、それぞれの公式ドキュメントを参照し、実際に手を動かして試してみることを強く推奨します。