Jest で E2E テストを導入する:基本と発展テクニック

現代の Web 開発において、品質保証はもはや選択肢ではありません。ユーザーが期待するのは、完璧に動作するアプリケーションです。しかし、手動テストだけでは限界があります。そこで登場するのが、Jest を使った E2E テストです。
E2E テストは、ユーザーの視点からアプリケーション全体をテストする手法です。ログインからログアウトまで、実際のユーザーが行う操作を自動化することで、本番環境での問題を事前に発見できます。
この記事では、Jest を使った E2E テストの導入から発展的なテクニックまで、実践的な内容をお届けします。初心者の方でも安心して始められるよう、段階的に解説していきます。
Jest と E2E テストの基本概念
Jest とは何か
Jest は、Facebook が開発した JavaScript のテストフレームワークです。シンプルさと強力さを兼ね備え、多くの開発者に愛されています。
Jest の特徴は以下の通りです:
- ゼロコンフィグ: 設定なしで即座にテストを開始できる
- スナップショットテスト: UI の変更を効率的に検出
- モック機能: 依存関係を簡単にモック化
- 並列実行: テストの実行速度を大幅に向上
- 豊富なマッチャー: 直感的なアサーション
javascript// Jest の基本的なテスト例
describe('計算機能のテスト', () => {
test('1 + 1 は 2 になる', () => {
expect(1 + 1).toBe(2);
});
test('配列の長さを確認', () => {
const fruits = ['apple', 'banana', 'orange'];
expect(fruits).toHaveLength(3);
});
});
E2E テストの定義と特徴
E2E テスト(End-to-End Testing)は、アプリケーション全体をユーザーの視点でテストする手法です。単体テストや統合テストとは異なり、実際のブラウザ環境でテストを実行します。
E2E テストの特徴:
- ユーザー視点: 実際のユーザー操作をシミュレート
- 全体テスト: フロントエンドからバックエンドまで包括的にテスト
- ブラウザ環境: 実際のブラウザでテストを実行
- 信頼性: 本番環境に近い状態でテスト
javascript// E2E テストの基本的な構造
describe('ログインフローのテスト', () => {
test('正常なログインができる', async () => {
// ブラウザを開く
await page.goto('http://localhost:3000/login');
// ユーザー名を入力
await page.fill('#username', 'testuser');
// パスワードを入力
await page.fill('#password', 'password123');
// ログインボタンをクリック
await page.click('#login-button');
// ダッシュボードに遷移することを確認
await expect(page).toHaveURL(
'http://localhost:3000/dashboard'
);
});
});
他のテスト手法との違い
テストには複数のレベルがあります。それぞれの役割と違いを理解することで、適切なテスト戦略を立てられます。
テスト種別 | 範囲 | 実行環境 | 実行速度 | 信頼性 |
---|---|---|---|---|
単体テスト | 関数・クラス | Node.js | 高速 | 中 |
統合テスト | モジュール間 | Node.js | 中速 | 中高 |
E2E テスト | アプリケーション全体 | ブラウザ | 低速 | 高 |
javascript// 単体テストの例
test('ユーザー名のバリデーション', () => {
expect(validateUsername('john')).toBe(true);
expect(validateUsername('')).toBe(false);
});
// 統合テストの例
test('ユーザー作成API', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' });
expect(response.status).toBe(201);
});
// E2E テストの例
test('ユーザー登録フロー', async () => {
await page.goto('/register');
await page.fill('#name', 'John');
await page.fill('#email', 'john@example.com');
await page.click('#submit');
await expect(
page.locator('.success-message')
).toBeVisible();
});
Jest での E2E テスト環境構築
必要なパッケージのインストール
Jest で E2E テストを実行するには、いくつかのパッケージが必要です。Yarn を使って効率的にインストールしましょう。
bash# 基本的な Jest パッケージ
yarn add --dev jest @types/jest
# E2E テスト用の Playwright
yarn add --dev @playwright/test
# 型定義
yarn add --dev @types/node
Playwright は、Microsoft が開発した E2E テストライブラリです。Jest との相性が良く、高速で安定したテストを実行できます。
bash# Playwright のブラウザをインストール
npx playwright install
# 特定のブラウザのみインストール
npx playwright install chromium
npx playwright install firefox
npx playwright install webkit
設定ファイルの作成
Jest の設定ファイルを作成して、E2E テストに最適化された環境を構築します。
javascript// jest.config.js
module.exports = {
// テスト環境の設定
testEnvironment: 'node',
// テストファイルのパターン
testMatch: [
'**/__tests__/**/*.test.js',
'**/e2e/**/*.test.js',
],
// タイムアウト設定(E2E テストは時間がかかるため)
testTimeout: 30000,
// 並列実行の設定
maxWorkers: 1, // E2E テストは並列実行を避ける
// レポート出力
verbose: true,
// カバレッジ設定
collectCoverage: false, // E2E テストでは不要
// セットアップファイル
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
javascript// jest.setup.js
// グローバルなセットアップ
beforeAll(async () => {
// テストデータベースの初期化
console.log('テスト環境のセットアップを開始します');
});
afterAll(async () => {
// クリーンアップ処理
console.log('テスト環境のクリーンアップを実行します');
});
// 各テストの前後処理
beforeEach(async () => {
// テストデータの準備
});
afterEach(async () => {
// テストデータのクリーンアップ
});
基本的なディレクトリ構造
プロジェクトの規模に応じて、適切なディレクトリ構造を作成します。
arduinoproject-root/
├── src/
│ ├── components/
│ ├── pages/
│ └── utils/
├── tests/
│ ├── unit/
│ ├── integration/
│ └── e2e/
│ ├── fixtures/
│ ├── pages/
│ └── utils/
├── jest.config.js
├── jest.setup.js
└── package.json
javascript// tests/e2e/utils/test-utils.js
// テスト用のユーティリティ関数
export const createTestUser = async () => {
return {
username: `testuser_${Date.now()}`,
email: `test_${Date.now()}@example.com`,
password: 'testpassword123',
};
};
export const waitForElement = async (
page,
selector,
timeout = 5000
) => {
await page.waitForSelector(selector, { timeout });
};
export const takeScreenshot = async (page, name) => {
await page.screenshot({
path: `./screenshots/${name}_${Date.now()}.png`,
});
};
基本的な E2E テストの書き方
テストファイルの構造
E2E テストファイルは、明確な構造を持つことで保守性を高められます。
javascript// tests/e2e/login.test.js
const { test, expect } = require('@playwright/test');
// テストグループの定義
describe('ログイン機能の E2E テスト', () => {
let page;
// 各テストの前処理
beforeEach(async ({ browser }) => {
page = await browser.newPage();
await page.goto('http://localhost:3000');
});
// 各テストの後処理
afterEach(async () => {
await page.close();
});
// 正常系のテスト
test('正常なログインができる', async () => {
// テストの実装
});
// 異常系のテスト
test('無効な認証情報でログインできない', async () => {
// テストの実装
});
});
基本的なアサーション
Jest と Playwright を組み合わせた、強力なアサーション機能を活用します。
javascript// 要素の存在確認
test('ログインフォームが表示される', async ({ page }) => {
await page.goto('/login');
// 要素が存在することを確認
await expect(page.locator('#login-form')).toBeVisible();
// 要素が存在しないことを確認
await expect(
page.locator('.error-message')
).not.toBeVisible();
// 要素の数を確認
const buttons = page.locator('button');
await expect(buttons).toHaveCount(2);
});
javascript// テキスト内容の確認
test('エラーメッセージが正しく表示される', async ({
page,
}) => {
await page.goto('/login');
// 無効な認証情報でログイン
await page.fill('#username', 'invalid');
await page.fill('#password', 'wrong');
await page.click('#login-button');
// エラーメッセージの確認
await expect(
page.locator('.error-message')
).toContainText(
'ユーザー名またはパスワードが正しくありません'
);
// 部分一致での確認
await expect(page.locator('.error-message')).toHaveText(
/ユーザー名またはパスワード/
);
});
javascript// URL とナビゲーションの確認
test('ログイン後にダッシュボードに遷移する', async ({
page,
}) => {
await page.goto('/login');
// 正常なログイン
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('#login-button');
// URL の確認
await expect(page).toHaveURL(
'http://localhost:3000/dashboard'
);
// リダイレクトの確認
await expect(page).toHaveURL(/.*dashboard/);
});
ページオブジェクトパターンの導入
ページオブジェクトパターンを使うことで、テストコードの保守性と可読性を大幅に向上できます。
javascript// tests/e2e/pages/LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
// セレクターの定義
this.selectors = {
usernameInput: '#username',
passwordInput: '#password',
loginButton: '#login-button',
errorMessage: '.error-message',
successMessage: '.success-message',
};
}
// ページに移動
async goto() {
await this.page.goto('http://localhost:3000/login');
}
// ログイン実行
async login(username, password) {
await this.page.fill(
this.selectors.usernameInput,
username
);
await this.page.fill(
this.selectors.passwordInput,
password
);
await this.page.click(this.selectors.loginButton);
}
// エラーメッセージの取得
async getErrorMessage() {
return await this.page.textContent(
this.selectors.errorMessage
);
}
// 成功メッセージの確認
async isSuccessMessageVisible() {
return await this.page.isVisible(
this.selectors.successMessage
);
}
}
module.exports = LoginPage;
javascript// tests/e2e/login-with-pom.test.js
const LoginPage = require('./pages/LoginPage');
describe('ページオブジェクトを使ったログインテスト', () => {
let loginPage;
beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.goto();
});
test('正常なログインができる', async ({ page }) => {
await loginPage.login('testuser', 'password123');
// ダッシュボードに遷移することを確認
await expect(page).toHaveURL(/.*dashboard/);
});
test('無効な認証情報でエラーが表示される', async ({
page,
}) => {
await loginPage.login('invalid', 'wrong');
const errorMessage = await loginPage.getErrorMessage();
expect(errorMessage).toContain(
'ユーザー名またはパスワードが正しくありません'
);
});
});
発展的な E2E テストテクニック
カスタムマッチャーの作成
Jest のカスタムマッチャーを作成することで、より直感的で読みやすいテストを書けます。
javascript// tests/e2e/matchers/custom-matchers.js
expect.extend({
// 要素が表示されるまで待機するマッチャー
async toBeVisibleWithTimeout(received, timeout = 5000) {
const pass = await received.isVisible({ timeout });
if (pass) {
return {
message: () =>
`要素が ${timeout}ms 以内に表示されました`,
pass: true,
};
} else {
return {
message: () =>
`要素が ${timeout}ms 以内に表示されませんでした`,
pass: false,
};
}
},
// 要素のテキストが期待値と一致するマッチャー
async toHaveTextContent(received, expectedText) {
const actualText = await received.textContent();
const pass = actualText.includes(expectedText);
if (pass) {
return {
message: () =>
`要素のテキスト "${actualText}" に "${expectedText}" が含まれています`,
pass: true,
};
} else {
return {
message: () =>
`要素のテキスト "${actualText}" に "${expectedText}" が含まれていません`,
pass: false,
};
}
},
});
javascript// jest.setup.js に追加
require('./tests/e2e/matchers/custom-matchers');
// カスタムマッチャーの使用例
test('カスタムマッチャーを使ったテスト', async ({
page,
}) => {
await page.goto('/dashboard');
const welcomeMessage = page.locator('.welcome-message');
// カスタムマッチャーを使用
await expect(welcomeMessage).toBeVisibleWithTimeout(3000);
await expect(welcomeMessage).toHaveTextContent(
'ようこそ'
);
});
モックとスタブの活用
E2E テストでも、外部 API やデータベースをモックすることで、テストの安定性を向上できます。
javascript// tests/e2e/mocks/api-mocks.js
// API レスポンスのモック
const mockApiResponses = {
login: {
success: {
status: 200,
body: {
token: 'mock-jwt-token',
user: {
id: 1,
username: 'testuser',
email: 'test@example.com',
},
},
},
failure: {
status: 401,
body: {
error: 'Invalid credentials',
},
},
},
};
// API のモック設定
const setupApiMocks = (page) => {
// ログインAPI のモック
page.route('**/api/login', async (route) => {
const postData = route.request().postDataJSON();
if (
postData.username === 'testuser' &&
postData.password === 'password123'
) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(
mockApiResponses.login.success.body
),
});
} else {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify(
mockApiResponses.login.failure.body
),
});
}
});
};
module.exports = { setupApiMocks };
javascript// モックを使ったテスト例
const { setupApiMocks } = require('./mocks/api-mocks');
test('モックAPI を使ったログインテスト', async ({
page,
}) => {
// API モックを設定
setupApiMocks(page);
await page.goto('/login');
// 正常なログイン
await page.fill('#username', 'testuser');
await page.fill('#password', 'password123');
await page.click('#login-button');
// モックレスポンスに基づいてテスト
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('.user-info')).toContainText(
'testuser'
);
});
並列実行とパフォーマンス最適化
大規模なテストスイートでは、並列実行とパフォーマンス最適化が重要です。
javascript// jest.config.js の並列実行設定
module.exports = {
// ワーカー数の設定
maxWorkers: process.env.CI ? 2 : '50%',
// テストの並列実行設定
testRunner: 'jest-circus/runner',
// タイムアウト設定
testTimeout: 30000,
// テストの実行順序
testSequencer: './test-sequencer.js',
};
javascript// test-sequencer.js
// テストの実行順序を制御
const TestSequencer =
require('@jest/test-sequencer').default;
class CustomSequencer extends TestSequencer {
sort(tests) {
// 高速なテストを先に実行
return tests.sort((testA, testB) => {
const isFastA = testA.path.includes('fast');
const isFastB = testB.path.includes('fast');
if (isFastA && !isFastB) return -1;
if (!isFastA && isFastB) return 1;
return 0;
});
}
}
module.exports = CustomSequencer;
javascript// パフォーマンス最適化の例
describe('最適化されたテストスイート', () => {
// 共有のセットアップ
beforeAll(async ({ browser }) => {
// ブラウザコンテキストを共有
context = await browser.newContext({
viewport: { width: 1280, height: 720 },
});
});
// テスト間でページを再利用
beforeEach(async () => {
page = await context.newPage();
});
afterEach(async () => {
await page.close();
});
afterAll(async () => {
await context.close();
});
test('高速なテスト', async () => {
// 軽量なテスト
});
});
実際のプロジェクトでの活用例
ログインフロー
実際のプロジェクトでよく使われるログインフローのテスト例です。
javascript// tests/e2e/flows/login-flow.test.js
const LoginPage = require('../pages/LoginPage');
const DashboardPage = require('../pages/DashboardPage');
describe('ログインフローの統合テスト', () => {
let loginPage, dashboardPage;
beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
});
test('新規ユーザーのログインフロー', async ({ page }) => {
// 1. ログインページにアクセス
await loginPage.goto();
// 2. 新規登録リンクをクリック
await page.click('#register-link');
await expect(page).toHaveURL('/register');
// 3. ユーザー情報を入力
await page.fill('#username', 'newuser');
await page.fill('#email', 'newuser@example.com');
await page.fill('#password', 'newpassword123');
await page.fill('#confirm-password', 'newpassword123');
// 4. 登録ボタンをクリック
await page.click('#register-button');
// 5. 確認メッセージを確認
await expect(
page.locator('.success-message')
).toContainText('アカウントが正常に作成されました');
// 6. 自動ログインされることを確認
await expect(page).toHaveURL('/dashboard');
await expect(
dashboardPage.getWelcomeMessage()
).toContainText('newuser');
});
test('既存ユーザーのログインフロー', async ({ page }) => {
// 1. ログインページにアクセス
await loginPage.goto();
// 2. 認証情報を入力
await loginPage.login('existinguser', 'password123');
// 3. ダッシュボードに遷移することを確認
await expect(page).toHaveURL('/dashboard');
// 4. ユーザー情報が正しく表示されることを確認
await expect(dashboardPage.getUserInfo()).toContainText(
'existinguser'
);
// 5. ログアウト機能をテスト
await dashboardPage.logout();
await expect(page).toHaveURL('/login');
});
test('エラーハンドリングのテスト', async ({ page }) => {
await loginPage.goto();
// 無効な認証情報でログイン
await loginPage.login('invalid', 'wrong');
// エラーメッセージの確認
const errorMessage = await loginPage.getErrorMessage();
expect(errorMessage).toContain(
'ユーザー名またはパスワードが正しくありません'
);
// ログインフォームがクリアされないことを確認
await expect(page.locator('#username')).toHaveValue(
'invalid'
);
await expect(page.locator('#password')).toHaveValue('');
});
});
商品検索機能
EC サイトなどで重要な商品検索機能のテスト例です。
javascript// tests/e2e/flows/product-search.test.js
const SearchPage = require('../pages/SearchPage');
const ProductPage = require('../pages/ProductPage');
describe('商品検索機能のテスト', () => {
let searchPage, productPage;
beforeEach(async ({ page }) => {
searchPage = new SearchPage(page);
productPage = new ProductPage(page);
await page.goto('/search');
});
test('キーワード検索の基本機能', async ({ page }) => {
// 1. 検索キーワードを入力
await searchPage.search('ノートパソコン');
// 2. 検索結果が表示されることを確認
await expect(
page.locator('.search-results')
).toBeVisible();
// 3. 検索結果の件数を確認
const resultCount = await page
.locator('.product-item')
.count();
expect(resultCount).toBeGreaterThan(0);
// 4. 検索結果にキーワードが含まれていることを確認
const firstProduct = page
.locator('.product-item')
.first();
await expect(
firstProduct.locator('.product-name')
).toContainText('ノートパソコン');
});
test('フィルター機能のテスト', async ({ page }) => {
// 1. 価格フィルターを設定
await searchPage.setPriceFilter(50000, 100000);
// 2. ブランドフィルターを設定
await searchPage.selectBrand('Apple');
// 3. フィルター適用ボタンをクリック
await page.click('#apply-filters');
// 4. フィルター条件が適用されることを確認
await expect(
page.locator('.active-filters')
).toContainText('¥50,000 - ¥100,000');
await expect(
page.locator('.active-filters')
).toContainText('Apple');
// 5. 検索結果がフィルター条件に合致することを確認
const products = page.locator('.product-item');
const productCount = await products.count();
for (let i = 0; i < Math.min(productCount, 3); i++) {
const product = products.nth(i);
const price = await product
.locator('.price')
.textContent();
const priceValue = parseInt(
price.replace(/[^\d]/g, '')
);
expect(priceValue).toBeGreaterThanOrEqual(50000);
expect(priceValue).toBeLessThanOrEqual(100000);
}
});
test('ソート機能のテスト', async ({ page }) => {
// 1. 価格の安い順でソート
await searchPage.sortBy('price-asc');
// 2. 最初の商品の価格を取得
const firstPrice = await page
.locator('.product-item')
.first()
.locator('.price')
.textContent();
const firstPriceValue = parseInt(
firstPrice.replace(/[^\d]/g, '')
);
// 3. 価格の高い順でソート
await searchPage.sortBy('price-desc');
// 4. 最後の商品の価格を取得
const lastPrice = await page
.locator('.product-item')
.last()
.locator('.price')
.textContent();
const lastPriceValue = parseInt(
lastPrice.replace(/[^\d]/g, '')
);
// 5. ソートが正しく機能することを確認
expect(firstPriceValue).toBeLessThan(lastPriceValue);
});
});
決済プロセス
EC サイトの決済プロセスのテスト例です。
javascript// tests/e2e/flows/checkout-flow.test.js
const CartPage = require('../pages/CartPage');
const CheckoutPage = require('../pages/CheckoutPage');
const PaymentPage = require('../pages/PaymentPage');
describe('決済プロセスのテスト', () => {
let cartPage, checkoutPage, paymentPage;
beforeEach(async ({ page }) => {
cartPage = new CartPage(page);
checkoutPage = new CheckoutPage(page);
paymentPage = new PaymentPage(page);
});
test('正常な決済フロー', async ({ page }) => {
// 1. カートに商品を追加
await page.goto('/products/laptop');
await page.click('#add-to-cart');
// 2. カートページに移動
await page.goto('/cart');
await expect(page.locator('.cart-item')).toHaveCount(1);
// 3. チェックアウトに進む
await page.click('#proceed-to-checkout');
await expect(page).toHaveURL('/checkout');
// 4. 配送情報を入力
await checkoutPage.fillShippingInfo({
firstName: '田中',
lastName: '太郎',
email: 'tanaka@example.com',
phone: '090-1234-5678',
address: '東京都渋谷区1-1-1',
postalCode: '150-0001',
});
// 5. 支払い方法を選択
await checkoutPage.selectPaymentMethod('credit-card');
// 6. 支払い情報を入力
await paymentPage.fillPaymentInfo({
cardNumber: '4111111111111111',
expiryMonth: '12',
expiryYear: '2025',
cvv: '123',
cardholderName: 'TANAKA TARO',
});
// 7. 注文を確定
await page.click('#place-order');
// 8. 注文完了ページに遷移することを確認
await expect(page).toHaveURL(/.*order-confirmation/);
await expect(
page.locator('.order-success')
).toBeVisible();
// 9. 注文番号が表示されることを確認
const orderNumber = await page
.locator('.order-number')
.textContent();
expect(orderNumber).toMatch(/^[A-Z0-9]{8}$/);
});
test('決済エラーのハンドリング', async ({ page }) => {
await page.goto('/checkout');
// 無効なカード情報で決済
await paymentPage.fillPaymentInfo({
cardNumber: '4000000000000002', // 決済拒否カード
expiryMonth: '12',
expiryYear: '2025',
cvv: '123',
cardholderName: 'TEST USER',
});
await page.click('#place-order');
// エラーメッセージが表示されることを確認
await expect(
page.locator('.payment-error')
).toBeVisible();
await expect(
page.locator('.payment-error')
).toContainText('決済が拒否されました');
// チェックアウトページに留まることを確認
await expect(page).toHaveURL('/checkout');
});
test('在庫切れ商品の処理', async ({ page }) => {
// 在庫切れ商品をカートに追加
await page.goto('/products/out-of-stock-item');
await page.click('#add-to-cart');
await page.goto('/cart');
// 在庫切れ警告が表示されることを確認
await expect(
page.locator('.out-of-stock-warning')
).toBeVisible();
// チェックアウトボタンが無効化されることを確認
await expect(
page.locator('#proceed-to-checkout')
).toBeDisabled();
});
});
デバッグとトラブルシューティング
よくある問題と解決策
E2E テストでよく遭遇する問題とその解決策を紹介します。
javascript// 問題1: 要素が見つからないエラー
// エラー: Error: Timeout 5000ms exceeded while waiting for selector "#login-button"
// 解決策: より堅牢なセレクターと待機処理
test('堅牢な要素待機の例', async ({ page }) => {
await page.goto('/login');
// 良い例: 複数のセレクターを試す
const loginButton = page.locator(
'#login-button, button[type="submit"], .login-btn'
);
await expect(loginButton).toBeVisible({ timeout: 10000 });
// 良い例: 要素の状態を確認してから操作
await loginButton.waitFor({ state: 'visible' });
await loginButton.click();
});
javascript// 問題2: フレイキーテスト(時々失敗するテスト)
// 原因: 非同期処理の待機不足
// 解決策: 適切な待機処理
test('安定した非同期処理のテスト', async ({ page }) => {
await page.goto('/dashboard');
// 良い例: ネットワークアイドルを待つ
await page.waitForLoadState('networkidle');
// 良い例: 特定の要素が読み込まれるまで待つ
await page.waitForSelector('.user-data', {
state: 'attached',
});
// 良い例: 条件が満たされるまで待つ
await page.waitForFunction(() => {
return (
document.querySelectorAll('.product-item').length > 0
);
});
// テストの実行
const productCount = await page
.locator('.product-item')
.count();
expect(productCount).toBeGreaterThan(0);
});
javascript// 問題3: タイムアウトエラー
// エラー: Error: Test timeout of 30000ms exceeded
// 解決策: タイムアウト設定の最適化
describe('タイムアウト対策のテスト', () => {
// 個別のテストでタイムアウトを設定
test('長時間の処理を含むテスト', async ({ page }) => {
// テスト固有のタイムアウト設定
test.setTimeout(60000);
await page.goto('/slow-loading-page');
// 明示的な待機処理
await page.waitForSelector('.content-loaded', {
timeout: 30000,
});
// テストの実行
await expect(page.locator('.content')).toBeVisible();
}, 60000); // タイムアウトを60秒に設定
});
javascript// 問題4: ブラウザの互換性問題
// 解決策: 複数ブラウザでのテスト実行
// playwright.config.js
module.exports = {
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
};
// ブラウザ固有の処理
test('ブラウザ固有の処理', async ({
page,
browserName,
}) => {
await page.goto('/test-page');
// ブラウザ固有の処理
if (browserName === 'firefox') {
// Firefox 固有の処理
await page.waitForTimeout(1000);
} else if (browserName === 'webkit') {
// Safari 固有の処理
await page.waitForTimeout(500);
}
// 共通のテスト
await expect(page.locator('.test-element')).toBeVisible();
});
ログとレポートの活用
効果的なデバッグのために、ログとレポートを活用します。
javascript// 詳細なログ出力の設定
// jest.config.js
module.exports = {
verbose: true,
reporters: [
'default',
[
'jest-html-reporters',
{
publicPath: './reports',
filename: 'e2e-test-report.html',
expand: true,
},
],
],
};
javascript// テスト内でのログ出力
test('詳細なログ付きテスト', async ({ page }) => {
console.log('テスト開始: ログインフローのテスト');
await page.goto('/login');
console.log('ログインページにアクセス完了');
// スクリーンショットを撮影
await page.screenshot({
path: './screenshots/login-page.png',
});
await page.fill('#username', 'testuser');
console.log('ユーザー名入力完了');
await page.fill('#password', 'password123');
console.log('パスワード入力完了');
await page.click('#login-button');
console.log('ログインボタンクリック完了');
// ネットワークリクエストの監視
page.on('request', (request) => {
console.log(
`リクエスト: ${request.method()} ${request.url()}`
);
});
page.on('response', (response) => {
console.log(
`レスポンス: ${response.status()} ${response.url()}`
);
});
await expect(page).toHaveURL('/dashboard');
console.log('ダッシュボードへの遷移確認完了');
// 最終スクリーンショット
await page.screenshot({
path: './screenshots/dashboard.png',
});
console.log('テスト完了: ログインフローのテスト');
});
javascript// カスタムレポートの作成
// tests/e2e/utils/test-reporter.js
class CustomTestReporter {
constructor() {
this.results = [];
}
onTestStart(test) {
console.log(`🧪 テスト開始: ${test.name}`);
this.currentTest = {
name: test.name,
startTime: Date.now(),
status: 'running',
};
}
onTestEnd(test, result) {
const duration =
Date.now() - this.currentTest.startTime;
this.currentTest.duration = duration;
this.currentTest.status = result.status;
if (result.status === 'passed') {
console.log(
`✅ テスト成功: ${test.name} (${duration}ms)`
);
} else {
console.log(
`❌ テスト失敗: ${test.name} (${duration}ms)`
);
console.log(` エラー: ${result.error.message}`);
}
this.results.push(this.currentTest);
}
onRunComplete() {
const summary = {
total: this.results.length,
passed: this.results.filter(
(r) => r.status === 'passed'
).length,
failed: this.results.filter(
(r) => r.status === 'failed'
).length,
totalDuration: this.results.reduce(
(sum, r) => sum + r.duration,
0
),
};
console.log('\n📊 テスト結果サマリー:');
console.log(` 総テスト数: ${summary.total}`);
console.log(` 成功: ${summary.passed}`);
console.log(` 失敗: ${summary.failed}`);
console.log(
` 総実行時間: ${summary.totalDuration}ms`
);
}
}
module.exports = CustomTestReporter;
まとめ
Jest を使った E2E テストの導入は、アプリケーションの品質向上に大きく貢献します。この記事で紹介した内容を実践することで、以下のメリットを得られます:
品質の向上
- ユーザー視点での包括的なテスト
- 本番環境での問題を事前に発見
- 回帰テストの自動化
開発効率の向上
- 手動テストの時間短縮
- デバッグ時間の削減
- 自信を持ったデプロイ
チーム開発の改善
- テスト結果の共有
- 品質基準の統一
- 継続的インテグレーション
E2E テストは最初は複雑に感じるかもしれませんが、段階的に導入することで、確実にスキルを向上させられます。まずは小さなテストから始めて、徐々に範囲を広げていくことをお勧めします。
テストコードは、アプリケーションコードと同じくらい重要です。読みやすく、保守しやすいテストを書くことで、長期的なプロジェクトの成功につながります。
最後に、E2E テストは完璧ではありませんが、適切に活用することで、ユーザーに信頼されるアプリケーションを構築できます。継続的な改善と学習を心がけ、より良いテストを書いていきましょう。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来