Cypress の使い方完全ガイド — モダンWebのためのE2Eテストツール
一言でいうと
Cypressは、ブラウザ上で動作するE2E(エンドツーエンド)テスト・コンポーネントテストのためのオールインワンテストフレームワークです。セットアップの容易さ、リアルタイムリロード、タイムトラベルデバッグなど、開発者体験を最優先に設計されています。
どんな時に使う?
- Webアプリケーションの E2E テスト — ユーザーがブラウザで行う操作(ログイン、フォーム送信、ページ遷移など)を自動化し、アプリ全体の動作を検証したいとき
- コンポーネント単体テスト — React、Vue、Angular などのコンポーネントを実際のブラウザ環境でマウントしてテストしたいとき
- API モック・スタブを使った統合テスト — バックエンドのレスポンスをインターセプトし、フロントエンドの振る舞いを様々な条件下で検証したいとき
インストール
# npm
npm install --save-dev cypress
# yarn
yarn add --dev cypress
# pnpm
pnpm add --save-dev cypress
インストール後、初回起動でプロジェクトの雛形が自動生成されます。
npx cypress open
基本的な使い方
プロジェクト構成
初回起動後、以下のようなディレクトリ構成が生成されます。
cypress/
├── e2e/ # E2Eテストファイル
├── fixtures/ # テストデータ(JSONなど)
├── support/
│ ├── commands.ts # カスタムコマンド定義
│ └── e2e.ts # E2Eテスト共通設定
└── tsconfig.json
cypress.config.ts # Cypress設定ファイル
設定ファイル(cypress.config.ts)
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.ts',
supportFile: 'cypress/support/e2e.ts',
viewportWidth: 1280,
viewportHeight: 720,
setupNodeEvents(on, config) {
// Node.jsイベントリスナーをここに登録
},
},
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
},
});
最初のE2Eテスト
// cypress/e2e/login.cy.ts
describe('ログイン機能', () => {
beforeEach(() => {
cy.visit('/login');
});
it('正しい認証情報でログインできる', () => {
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();
cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome-message"]').should(
'contain',
'ようこそ'
);
});
it('誤ったパスワードでエラーが表示される', () => {
cy.get('[data-testid="email-input"]').type('user@example.com');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();
cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', '認証に失敗しました');
});
});
テストの実行
# GUIモード(インタラクティブ)
npx cypress open
# CLIモード(CI向け・ヘッドレス)
npx cypress run
# 特定のスペックファイルのみ実行
npx cypress run --spec "cypress/e2e/login.cy.ts"
# 特定のブラウザで実行
npx cypress run --browser chrome
よく使うAPI
1. cy.visit() — ページ遷移
// 基本
cy.visit('/about');
// baseUrlを使わず絶対URLで指定
cy.visit('https://example.com/about');
// オプション付き
cy.visit('/login', {
timeout: 10000,
headers: {
'Accept-Language': 'ja',
},
});
2. cy.get() / cy.contains() — 要素の取得
// CSSセレクタで取得
cy.get('.submit-button').click();
// data-testid属性で取得(推奨パターン)
cy.get('[data-testid="user-name"]').should('have.text', '田中太郎');
// テキスト内容で要素を検索
cy.contains('送信する').click();
// スコープを絞って検索
cy.get('[data-testid="user-list"]').contains('田中太郎').click();
// find で子要素を検索
cy.get('[data-testid="nav"]').find('a').should('have.length', 5);
3. cy.intercept() — ネットワークリクエストのインターセプト
// GETリクエストをモック
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [
{ id: 1, name: '田中太郎' },
{ id: 2, name: '鈴木花子' },
],
}).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
cy.get('[data-testid="user-item"]').should('have.length', 2);
// エラーレスポンスをシミュレート
cy.intercept('POST', '/api/orders', {
statusCode: 500,
body: { message: 'Internal Server Error' },
}).as('createOrder');
// リクエストの内容を検証
cy.intercept('POST', '/api/users', (req) => {
expect(req.body).to.have.property('email', 'new@example.com');
req.reply({ statusCode: 201, body: { id: 3, ...req.body } });
}).as('createUser');
4. cy.should() / cy.and() — アサーション
// 要素の状態を検証
cy.get('[data-testid="modal"]').should('be.visible');
cy.get('[data-testid="submit-btn"]').should('be.disabled');
cy.get('[data-testid="item-list"]').should('have.length', 3);
// チェーンで複数のアサーション
cy.get('[data-testid="status-badge"]')
.should('be.visible')
.and('have.class', 'active')
.and('contain', '有効');
// コールバック形式(複雑な検証)
cy.get('[data-testid="price"]').should(($el) => {
const price = parseInt($el.text().replace(/[^0-9]/g, ''), 10);
expect(price).to.be.greaterThan(0);
expect(price).to.be.lessThan(100000);
});
// URL・Cookieの検証
cy.url().should('include', '/dashboard');
cy.getCookie('session_id').should('exist');
5. cy.fixture() / カスタムコマンド — テストデータとコマンドの再利用
// cypress/fixtures/user.json
{
"email": "test@example.com",
"password": "securePassword123",
"name": "テストユーザー"
}
// fixtureの利用
cy.fixture('user').then((user) => {
cy.get('[data-testid="email-input"]').type(user.email);
cy.get('[data-testid="password-input"]').type(user.password);
});
// fixtureをinterceptと組み合わせる
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as(
'getProducts'
);
// cypress/support/commands.ts — カスタムコマンド定義
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>;
}
}
}
Cypress.Commands.add('login', (email: string, password: string) => {
// UIを経由せずAPIで直接ログイン(テスト高速化)
cy.request('POST', '/api/auth/login', { email, password }).then(
(response) => {
window.localStorage.setItem('token', response.body.token);
}
);
});
// テストでの利用
describe('ダッシュボード', () => {
beforeEach(() => {
cy.login('admin@example.com', 'admin123');
cy.visit('/dashboard');
});
it('ユーザー情報が表示される', () => {
cy.get('[data-testid="user-profile"]').should('be.visible');
});
});
類似パッケージとの比較
| 特徴 | Cypress | Playwright | Selenium |
|---|---|---|---|
| 対応ブラウザ | Chrome, Firefox, Edge, Electron | Chromium, Firefox, WebKit | ほぼ全ブラウザ |
| 言語 | JavaScript / TypeScript | JS/TS, Python, Java, C# 等 | 多言語対応 |
| 実行速度 | ◎ 高速 | ◎ 高速 | △ やや遅い |
| セットアップ容易さ | ◎ 非常に簡単 | ○ 簡単 | △ 設定が多い |
| デバッグ体験 | ◎ タイムトラベル・スナップショット | ○ トレースビューア | △ 限定的 |
| コンポーネントテスト | ○ 対応 | ○ 対応 | × 非対応 |
| マルチタブ操作 | × 非対応 | ◎ 対応 | ○ 対応 |
| iframe操作 | △ 制限あり | ◎ 対応 | ○ 対応 |
| 並列実行 | 有料(Cypress Cloud) | ◎ 標準対応 | ○ Grid利用 |
| ライセンス | MIT | Apache 2.0 | Apache 2.0 |
選定の目安:
- Cypress — フロントエンド開発者が手軽にE2Eテストを始めたい場合。DXが最も優れている
- Playwright — マルチブラウザ・マルチタブ・複雑なシナリオが必要な場合。CIでの並列実行も標準で可能
- Selenium — レガシーシステムや特殊なブラウザ環境が必要な場合
注意点・Tips
1. cy.wait() に固定時間を使わない
// ❌ アンチパターン — フレーキーテストの原因
cy.wait(3000);
cy.get('[data-testid="result"]').should('exist');
// ✅ ネットワークリクエストの完了を待つ
cy.intercept('GET', '/api/data').as('getData');
cy.wait('@getData');
cy.get('[data-testid="result"]').should('exist');
// ✅ should() は自動リトライされるので、要素の出現を待てる
cy.get('[data-testid="result"]', { timeout: 10000 }).should('be.visible');
2. テストの独立性を保つ
// ❌ テスト間で状態を共有する
let userId: string;
it('ユーザーを作成する', () => {
// userId に代入...
});
it('ユーザーを削除する', () => {
// 上のテストに依存している
});
// ✅ 各テストで必要な状態をセットアップする
beforeEach(() => {
cy.request('POST', '/api/test/reset'); // テストDBリセット
cy.login('admin@example.com', 'admin123');
});
3. セレクタは data-testid を使う
CSSクラスやタグ構造に依存するセレクタは、UIリファクタリングで壊れやすくなります。
// ❌ 壊れやすい
cy.get('.btn.btn-primary.mt-4').click();
cy.get('div > form > div:nth-child(2) > input').type('hello');
// ✅ 安定する
cy.get('[data-testid="submit-button"]').click();
cy.get('[data-testid="search-input"]').type('hello');
4. 環境変数の活用
// cypress.config.ts
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
env: {
apiUrl: 'http://localhost:8080',
testUserEmail: 'test@example.com',
},
},
});
// テスト内で参照
cy.request(`${Cypress.env('apiUrl')}/api/health`);
// CLI から上書き
// npx cypress run --env apiUrl=https://staging.example.com
5. CI環境での実行
# GitHub Actions の例
name: E2E Tests
on: [push]
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: npm start &
- run: npx cypress run
Tips: 公式の `cypress