cypress、nightwatch、puppeteer、testcafeはいずれもJavaScript/TypeScriptエコシステムで広く使われるE2E(End-to-End)テストおよびブラウザ自動化ツールです。これらのパッケージは、Webアプリケーションのユーザーインターフェースを実際のブラウザ環境でテストし、回帰バグの検出や品質保証を支援します。cypressとtestcafeはテストフレームワークとして統合された体験を提供し、nightwatchはWebDriverプロトコルを活用した伝統的なアプローチを採用しています。一方、puppeteerはChromiumをプログラムから制御するための低レベルライブラリであり、テスト以外の自動化タスクにも利用されます。
フロントエンド開発において、信頼性の高いE2E(End-to-End)テストは欠かせません。cypress、nightwatch、puppeteer、testcafeはいずれも人気のある選択肢ですが、アーキテクチャや使い勝手、対応範囲に大きな違いがあります。この記事では、実際の開発現場で直面する課題を軸に、各ツールの技術的特徴をコード例とともに詳しく見ていきます。
cypress は独自の非同期処理モデルを採用し、テストコードを同期的に記述できます。内部でPromiseチェーンを隠蔽しており、初心者にも書きやすいのが特徴です。
// cypress
it('ログインしてダッシュボードを表示', () => {
cy.visit('/login');
cy.get('#email').type('user@example.com');
cy.get('#password').type('secret');
cy.get('form').submit();
cy.url().should('include', '/dashboard');
});
nightwatch はWebDriverプロトコルに基づき、命令的なスタイルで操作を記述します。非同期処理にはPromiseまたはコールバックを使用します。
// nightwatch
module.exports = {
'ログインしてダッシュボードを表示': function (browser) {
browser
.url('/login')
.setValue('#email', 'user@example.com')
.setValue('#password', 'secret')
.submitForm('form')
.assert.urlContains('/dashboard')
.end();
}
};
puppeteer はChrome DevTools Protocol(CDP)を直接操作し、完全にプログラム可能な制御を提供します。Promiseベースで、細かいブラウザ操作が可能です。
// puppeteer
const puppeteer = require('puppeteer');
test('ログインしてダッシュボードを表示', async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://localhost:3000/login');
await page.type('#email', 'user@example.com');
await page.type('#password', 'secret');
await page.click('form button[type="submit"]');
expect(page.url()).toContain('/dashboard');
await browser.close();
});
testcafe は独自の非同期ラッパーを使い、テストコードを同期的に記述できます。WebDriverに依存せず、HTTPプロキシ経由でブラウザを操作します。
// testcafe
import { Selector } from 'testcafe';
fixture`ログインテスト`.page`/login`;
test('ログインしてダッシュボードを表示', async t => {
await t
.typeText('#email', 'user@example.com')
.typeText('#password', 'secret')
.click('form button[type="submit"]')
.expect(getLocation().pathname).contains('/dashboard');
});
cypress は当初Electron専用でしたが、現在はChrome、Edge、Firefoxをサポートしています。ただし、Safariやモバイルブラウザは非対応です。また、同一ドメイン外へのナビゲーション(例:OAuthフロー)には制限があります。
nightwatch はWebDriver準拠のため、Selenium Grid経由でほぼすべてのブラウザ(Chrome、Firefox、Safari、Edgeなど)をテストできます。クラウドテストサービス(BrowserStack、Sauce Labs)との連携も容易です。
puppeteer はChromium(またはChrome)専用です。他のブラウザをテストしたい場合は、Playwright(Puppeteerの後継プロジェクト)を検討する必要があります。
testcafe はWebDriverを使わず、独自のメカニズムでブラウザを操作するため、Chrome、Firefox、Safari、Edgeをネイティブにサポートしています。追加のドライバ設定不要で、複数ブラウザでの並列実行が可能です。
cypress はテストコードとアプリケーションが同一オリジンで実行されるため、CORSやCookieの扱いに注意が必要です。ただし、cy.intercept()でネットワークリクエストを簡単にモックできます。
// cypress: ネットワークリクエストのモック
cy.intercept('POST', '/api/login', { fixture: 'auth-success.json' }).as('login');
cy.get('form').submit();
cy.wait('@login');
nightwatch は標準のWebDriver APIを通じて動作するため、ネットワークモックには外部ツール(例:WireMock)やブラウザ拡張が必要です。直接的なリクエストインターセプト機能はありません。
puppeteer はpage.setRequestInterception(true)を有効にすることで、リクエストやレスポンスを細かく制御できます。
// puppeteer: リクエストのインターセプト
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (interceptedRequest.url().includes('/api/login')) {
interceptedRequest.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ token: 'fake-token' })
});
} else {
interceptedRequest.continue();
}
});
testcafe はRequestMockクラスを使って、HTTPリクエストを簡単にモック・スタブできます。
// testcafe: リクエストモック
import { RequestMock } from 'testcafe';
const mock = RequestMock()
.onRequestTo('http://localhost:3000/api/login')
.respond({ token: 'fake-token' }, 200, { 'content-type': 'application/json' });
test.requestHooks(mock)('ログインAPIをモック', async t => {
// ...
});
cypress はタイムトラベリングデバッガを備え、テスト実行中のDOMスナップショットを再生できます。また、テストランナーUIが直感的で、失敗時のスクリーンショットや動画記録も標準でサポートされています。
nightwatch はCLI中心のツールで、GUIデバッガは提供していません。ただし、Seleniumのログやスクリーンショット機能を活用できます。
puppeteer はヘッドフルモードで実行すれば、実際のブラウザウィンドウを操作しながらデバッグ可能です。DevToolsとの連携も容易です。
testcafe はライブモード(--liveオプション)でテストを再実行せずに修正を反映でき、デバッグに便利です。また、失敗時に自動でスクリーンショットを保存します。
cypress はcypress.config.jsで設定を管理し、プラグインシステムでカスタマイズ可能です。ただし、カスタムレポーターの統合には少し工夫が必要です。
nightwatch はnightwatch.conf.jsで柔軟な設定が可能で、MochaやJasmineなどのテストランナーと統合できます。WebDriverの設定も詳細に制御できます。
puppeteer は単なるライブラリであり、テストフレームワークではありません。JestやMochaなど既存のテストランナーと組み合わせて使う必要があります。
testcafe は設定ファイル不要で即座にテストを書けますが、testcafe.config.jsで詳細設定も可能です。独自のテストランナーを内蔵しており、外部依存が少ないのが利点です。
| 特徴 | cypress | nightwatch | puppeteer | testcafe |
|---|---|---|---|---|
| 学習コスト | 低(初心者向け) | 中 | 高(プログラミング知識必要) | 低 |
| ブラウザサポート | Chrome/Firefox/Edge | 全ブラウザ(WebDriver経由) | Chromium/Chromeのみ | 全主要ブラウザ(ネイティブ) |
| ネットワークモック | 組み込み(cy.intercept) | 外部ツール必要 | プログラム制御可能 | 組み込み(RequestMock) |
| デバッグ体験 | 優れている(タイムトラベル) | 標準的 | 実ブラウザで直接操作 | ライブモードあり |
| CI/CD統合 | 容易 | 標準的 | 容易(ただしChromium限定) | 容易 |
cypressnightwatchpuppeteertestcafeどのツールも成熟しており、メンテナンスも継続されています。プロジェクトの要件(対象ブラウザ、チームスキル、インフラ)に最も合うものを選ぶことが成功の鍵です。
puppeteerは、E2EテストだけでなくPDF生成やスクリーンショット取得など、ブラウザ自動化全般が必要な場合に適しています。ChromiumまたはChromeのみで十分であれば、細かい制御が可能な低レベルAPIが魅力です。ただし、テストフレームワークではないため、JestやMochaなどと組み合わせる必要があります。マルチブラウザテストが必要なら、代わりにPlaywrightを検討すべきです。
cypressは、チームにテスト初心者が多く、迅速に信頼性の高いE2Eテストを構築したい場合に最適です。ChromeやFirefoxなど主要ブラウザでのテストが中心で、ネットワークモックやタイムトラベリングデバッガといった開発体験に優れた機能が必要なプロジェクトに向いています。ただし、Safariやモバイルブラウザのテスト、あるいは複雑なクロスドメイン認証フローがある場合は制限に注意が必要です。
testcafeは、WebDriverに依存せず、追加設定なしで複数ブラウザでのテストをすぐに始めたい場合に最適です。SafariやEdgeを含む全主要ブラウザをネイティブにサポートし、ネットワークモックやライブデバッグといった開発者フレンドリーな機能も充実しています。CI/CD環境での安定性も高く、中規模以上のプロジェクトでバランスの取れた選択肢となります。
nightwatchは、既にSeleniumインフラを運用しており、Safariを含む幅広いブラウザでのテストが必須な環境で選ぶべきです。WebDriver標準に準拠しているため、BrowserStackやSauce Labsといったクラウドテストサービスとの連携も容易です。ただし、ネットワークモックには外部ツールが必要で、現代的な開発体験を求めるチームにはやや古めかしく感じられるかもしれません。
Puppeteer is a JavaScript library which provides a high-level API to control Chrome or Firefox over the DevTools Protocol or WebDriver BiDi. Puppeteer runs in the headless (no visible UI) by default
npm i puppeteer # Downloads compatible Chrome during installation.
npm i puppeteer-core # Alternatively, install as a library, without downloading Chrome.
Install chrome-devtools-mcp,
a Puppeteer-based MCP server for browser automation and debugging.
import puppeteer from 'puppeteer';
// Or import puppeteer from 'puppeteer-core';
// Launch the browser and open a new blank page.
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Navigate the page to a URL.
await page.goto('https://developer.chrome.com/');
// Set screen size.
await page.setViewport({width: 1080, height: 1024});
// Open the search menu using the keyboard.
await page.keyboard.press('/');
// Type into search box using accessible input name.
await page.locator('::-p-aria(Search)').fill('automate beyond recorder');
// Wait and click on first result.
await page.locator('.devsite-result-item-link').click();
// Locate the full title with a unique string.
const textSelector = await page
.locator('::-p-text(Customize and automate)')
.waitHandle();
const fullTitle = await textSelector?.evaluate(el => el.textContent);
// Print the full title.
console.log('The title of this blog post is "%s".', fullTitle);
await browser.close();