アサーション(検証)
アサーション(検証)
アサーション(assertion)は、テストにおける「検証」の部分です。期待する結果と実際の結果を比較し、テストの合否を判定します。このチャプターでは、アサーションの基本から、様々な検証方法、良いアサーションの書き方まで学びます。
アサーションとは?
基本概念
アサーションは、「期待する状態」と「実際の状態」が一致しているかを確認するための仕組みです。
// 基本的なアサーションの形
await expect(実際の値).toBe(期待する値);
アサーションが失敗すると、テストは失敗し、詳細なエラーメッセージが表示されます。
アサーションの役割
| 役割 | 説明 |
|---|---|
| 検証 | 期待通りの動作をしているか確認 |
| ドキュメント | コードの期待動作を明示 |
| 早期発見 | バグを早い段階で検出 |
| 信頼性 | テストの信頼性を確保 |
Playwright の expect 文
Playwright では、expect 関数を使用してアサーションを記述します。
基本的な使い方
import { test, expect } from '@playwright/test';
test('基本的なアサーション', async ({ page }) => {
await page.goto('https://example.com');
// ページタイトルの検証
await expect(page).toHaveTitle('Example Domain');
// URLの検証
await expect(page).toHaveURL('https://example.com/');
// 要素の表示検証
await expect(page.locator('h1')).toBeVisible();
// テキスト内容の検証
await expect(page.locator('h1')).toHaveText('Example Domain');
});
[!INFO] 自動待機機能
Playwright の
expectは、アサーションが成功するまで自動的に待機します(デフォルト5秒)。これにより、非同期処理の完了を待つコードを書く必要が減ります。
様々な検証方法
1. 要素の表示・非表示
test('要素の表示状態を検証', async ({ page }) => {
await page.goto('https://example.com/login');
// 要素が表示されている
await expect(page.locator('#login-form')).toBeVisible();
// 要素が非表示
await expect(page.locator('#error-message')).not.toBeVisible();
// 要素が存在しない
await expect(page.locator('#deleted-item')).toHaveCount(0);
// 要素が有効
await expect(page.locator('#submit-button')).toBeEnabled();
// 要素が無効
await expect(page.locator('#submit-button')).toBeDisabled();
});
2. テキストの検証
test('テキスト内容を検証', async ({ page }) => {
await page.goto('https://example.com/products');
// 完全一致
await expect(page.locator('h1')).toHaveText('商品一覧');
// 部分一致
await expect(page.locator('.description')).toContainText('ノートPC');
// 正規表現
await expect(page.locator('.price')).toHaveText(/¥[0-9,]+/);
// 複数要素のテキスト配列
await expect(page.locator('.product-name')).toHaveText([
'ノートPC',
'マウス',
'キーボード'
]);
});
3. 属性の検証
test('要素の属性を検証', async ({ page }) => {
await page.goto('https://example.com/form');
// 属性の値
await expect(page.locator('#email')).toHaveAttribute('type', 'email');
// 属性の存在
await expect(page.locator('#required-field')).toHaveAttribute('required');
// クラスの存在
await expect(page.locator('#active-tab')).toHaveClass(/active/);
// 複数のクラス
await expect(page.locator('#button')).toHaveClass('btn btn-primary btn-large');
});
4. 入力値の検証
test('フォーム入力値を検証', async ({ page }) => {
await page.goto('https://example.com/form');
// 入力フィールドの値
await page.fill('#name', '山田太郎');
await expect(page.locator('#name')).toHaveValue('山田太郎');
// チェックボックス
await page.check('#agree');
await expect(page.locator('#agree')).toBeChecked();
// ラジオボタン
await page.check('#option-a');
await expect(page.locator('#option-a')).toBeChecked();
await expect(page.locator('#option-b')).not.toBeChecked();
// セレクトボックス
await page.selectOption('#country', 'japan');
await expect(page.locator('#country')).toHaveValue('japan');
});
5. 要素数の検証
test('要素の数を検証', async ({ page }) => {
await page.goto('https://example.com/products');
// 要素数が指定した数
await expect(page.locator('.product-card')).toHaveCount(10);
// 要素が存在する(1つ以上)
await expect(page.locator('.product-card')).not.toHaveCount(0);
// 特定の条件に一致する要素数
const saleItems = page.locator('.product-card:has(.sale-badge)');
await expect(saleItems).toHaveCount(3);
});
6. URL とタイトルの検証
test('ページのURLとタイトルを検証', async ({ page }) => {
await page.goto('https://example.com');
// URLの完全一致
await expect(page).toHaveURL('https://example.com/');
// URLの部分一致(正規表現)
await expect(page).toHaveURL(/.*example.com/);
// タイトルの完全一致
await expect(page).toHaveTitle('Example Domain');
// タイトルの部分一致
await expect(page).toHaveTitle(/Example/);
// ページ遷移後のURL検証
await page.click('#login-button');
await expect(page).toHaveURL(/.*dashboard/);
});
7. CSS とスタイルの検証
test('CSSとスタイルを検証', async ({ page }) => {
await page.goto('https://example.com');
// CSSプロパティの値
await expect(page.locator('.header')).toHaveCSS('background-color', 'rgb(0, 0, 255)');
// 要素の表示/非表示(visibility)
await expect(page.locator('.hidden-element')).toHaveCSS('display', 'none');
// フォントサイズ
await expect(page.locator('h1')).toHaveCSS('font-size', '32px');
});
8. スクリーンショット比較
test('ビジュアルリグレッションテスト', async ({ page }) => {
await page.goto('https://example.com');
// 要素のスクリーンショット比較
await expect(page.locator('.hero-section')).toHaveScreenshot('hero.png');
// ページ全体のスクリーンショット比較
await expect(page).toHaveScreenshot('full-page.png');
// 許容誤差を指定
await expect(page.locator('.dynamic-chart')).toHaveScreenshot('chart.png', {
maxDiffPixels: 100
});
});
カスタムアサーション
API レスポンスの検証
test('APIレスポンスを検証', async ({ request }) => {
const response = await request.get('https://api.example.com/products');
// ステータスコード
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
// レスポンスヘッダー
const headers = response.headers();
expect(headers['content-type']).toContain('application/json');
// レスポンスボディ
const body = await response.json();
expect(body.products).toHaveLength(10);
expect(body.products[0]).toHaveProperty('id');
expect(body.products[0].name).toBe('ノートPC');
expect(body.products[0].price).toBeGreaterThan(0);
});
JavaScript の値の検証
test('JavaScript値を検証', async ({ page }) => {
await page.goto('https://example.com');
// ページ内のJavaScript変数を取得して検証
const cartCount = await page.evaluate(() => {
return window.localStorage.getItem('cartCount');
});
expect(cartCount).toBe('3');
// 配列の検証
const items = ['りんご', 'バナナ', 'オレンジ'];
expect(items).toHaveLength(3);
expect(items).toContain('バナナ');
// オブジェクトの検証
const user = { name: '山田太郎', age: 30 };
expect(user).toHaveProperty('name');
expect(user.age).toBeGreaterThanOrEqual(18);
});
良いアサーションの書き方
1. 明確で具体的な検証
// ❌ 悪い例:漠然とした検証
test('ログイン', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button');
await expect(page.locator('div')).toBeVisible(); // 何を検証しているのか不明
});
// ✅ 良い例:具体的な検証
test('ログイン成功時にダッシュボードが表示される', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password123');
await page.click('button[type="submit"]');
// 具体的に何を検証しているか明確
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('h1')).toContainText('ダッシュボード');
await expect(page.locator('#user-name')).toContainText('user@example.com');
});
2. 適切な待機
// ❌ 悪い例:固定の待機時間
test('検索結果が表示される', async ({ page }) => {
await page.goto('https://example.com');
await page.fill('#search', 'ノートPC');
await page.click('#search-button');
await page.waitForTimeout(3000); // 固定待機は不安定
await expect(page.locator('.results')).toBeVisible();
});
// ✅ 良い例:条件待機
test('検索結果が表示される', async ({ page }) => {
await page.goto('https://example.com');
await page.fill('#search', 'ノートPC');
await page.click('#search-button');
// 自動待機(expect が自動的に待つ)
await expect(page.locator('.results')).toBeVisible();
});
3. 複数の観点から検証
test('フォーム送信成功を多角的に検証', async ({ page }) => {
await page.goto('https://example.com/contact');
await page.fill('#name', '山田太郎');
await page.fill('#email', 'yamada@example.com');
await page.fill('#message', 'お問い合わせ内容');
await page.click('#submit');
// 複数の観点で検証(より確実)
await expect(page.locator('.success-message')).toBeVisible();
await expect(page.locator('.success-message')).toContainText('送信完了');
await expect(page).toHaveURL(/.*success/);
await expect(page.locator('#name')).toHaveValue(''); // フォームがリセットされている
});
4. エラーメッセージを含める
test('商品価格が正しいことを確認', async ({ page }) => {
await page.goto('https://example.com/products/123');
const price = await page.locator('.product-price').textContent();
// カスタムエラーメッセージ
expect(price, '商品価格が期待値と異なります').toBe('¥100,000');
});
5. ソフトアサーション
テストを途中で止めず、すべての検証を実行したい場合:
import { test, expect } from '@playwright/test';
test('複数項目を検証(ソフトアサーション)', async ({ page }) => {
await page.goto('https://example.com/products/123');
// すべての検証を実行(途中で失敗しても続行)
await expect.soft(page.locator('.product-name')).toContainText('ノートPC');
await expect.soft(page.locator('.product-price')).toContainText('¥100,000');
await expect.soft(page.locator('.product-stock')).toContainText('在庫あり');
await expect.soft(page.locator('.product-rating')).toContainText('★4.5');
// 最後にすべてのソフトアサーションの結果を確認
});
アサーションのパターン集
パターン1: 存在確認
test('要素の存在確認パターン', async ({ page }) => {
await page.goto('https://example.com');
// 要素が存在する
await expect(page.locator('#header')).toBeVisible();
// 要素が存在しない
await expect(page.locator('#deleted-banner')).not.toBeVisible();
// 条件付き要素の存在
const errorMessage = page.locator('.error');
const count = await errorMessage.count();
if (count > 0) {
await expect(errorMessage).toBeVisible();
}
});
パターン2: リスト検証
test('リスト要素の検証パターン', async ({ page }) => {
await page.goto('https://example.com/products');
// リストの要素数
await expect(page.locator('.product-card')).toHaveCount(10);
// すべての要素に特定のクラスがある
const products = page.locator('.product-card');
const count = await products.count();
for (let i = 0; i < count; i++) {
await expect(products.nth(i)).toHaveClass(/product-card/);
}
// 特定の要素が含まれる
await expect(page.locator('.product-name')).toContainText(['ノートPC', 'マウス']);
});
パターン3: 状態変化の検証
test('状態変化の検証パターン', async ({ page }) => {
await page.goto('https://example.com/cart');
// 初期状態
await expect(page.locator('#cart-count')).toHaveText('0');
await expect(page.locator('.empty-cart-message')).toBeVisible();
// アクション実行
await page.click('#add-item-1');
// 変化後の状態
await expect(page.locator('#cart-count')).toHaveText('1');
await expect(page.locator('.empty-cart-message')).not.toBeVisible();
await expect(page.locator('.cart-item')).toHaveCount(1);
});
[!TIP] アサーションの選択基準
- 厳密な検証が必要:
toHaveText(),toBe()を使用- 柔軟な検証:
toContainText(), 正規表現を使用- 複数の可能性:
toHaveText([...])で配列を使用- 動的な値: 正規表現や
toMatch()を使用
よくある間違いと対処法
間違い1: 過度な待機時間
// ❌ 避けるべき
await page.waitForTimeout(5000);
await expect(page.locator('.result')).toBeVisible();
// ✅ 推奨
await expect(page.locator('.result')).toBeVisible({ timeout: 5000 });
間違い2: 不安定なセレクタ
// ❌ 避けるべき(構造変更に弱い)
await expect(page.locator('div > div > span:nth-child(3)')).toBeVisible();
// ✅ 推奨(意味のあるセレクタ)
await expect(page.locator('[data-testid="user-name"]')).toBeVisible();
間違い3: 検証なしのテスト
// ❌ 悪い例:検証がない
test('ログイン', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button');
// 検証がない!
});
// ✅ 良い例:適切な検証がある
test('ログイン成功', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
// 適切な検証
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('#welcome-message')).toBeVisible();
});
まとめ
- アサーションは、期待する結果と実際の結果を比較する検証機構
- Playwright の
expectは自動待機機能を持ち、非同期処理に強い - 様々な検証方法がある:表示、テキスト、属性、値、数、URL、CSS など
- 良いアサーションは明確、具体的、適切な待機を持つ
- 複数の観点から検証することで、テストの信頼性が向上
- ソフトアサーションを使うと、すべての検証を実行できる
- 意味のあるセレクタと適切な待機戦略が重要
次のチャプターでは、テスト設計の基本について学びます。