playwright について
ブラウザーのエンドツーエンドテストを作成する開発者向けのPlaywrightスキルです。テスト設定や操作、アサーションに加え、ビジュアル回帰やCIの実践方法も扱います。
主な機能
- 01ブラウザーテストの設定
- 02ロケーター、操作、アサーション
- 03Page Objectパターン
- 04認証状態の再利用
- 05ビジュアル回帰とCIのガイド
活用シーン
- 01ブラウザーでのユーザーフローのテスト作成
- 02ページ要素と操作の確認
- 03テスト間でのログイン状態の再利用
- 04スクリーンショットによる見た目の変更確認
MCP 対応
包括的なPlaywrightドキュメントの参照先として、`mcp__documentation__fetch_docs`を案内しています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill playwrightインストール後、Claude Code で/playwrightを入力して読み込まれたか確認します。
同梱ファイルquick-ref/locators.mdquick-ref/page-objects.md
詳細
Playwright コア知識
深い知識:包括的なドキュメントを参照するには、technology:
playwrightを指定してmcp__documentation__fetch_docsを使用してください。
このスキルを使用しない場合
- 単体テスト - 独立した関数/モジュールのテストには
vitestまたはjestを使用してください - コンポーネントテスト - React/Vue コンポーネントのテストには
testing-libraryを使用してください - API のみのテスト - フレームワーク固有の HTTP クライアントまたは REST クライアントを使用してください
- パフォーマンステスト - k6 や Lighthouse などの専用ツールを使用してください
- モバイルアプリのテスト - ネイティブモバイルアプリには Appium または Detox を使用してください
基本的なテスト
import { test, expect } from '@playwright/test';
test('user can login', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', '[email protected]');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Welcome');
});
ロケーター
// By role (preferred)
page.getByRole('button', { name: 'Submit' });
page.getByRole('link', { name: 'Home' });
page.getByLabel('Email');
page.getByPlaceholder('Enter email');
page.getByText('Welcome');
// By test id
page.getByTestId('submit-button');
// CSS/XPath (fallback)
page.locator('.submit-btn');
page.locator('#user-form');
アサーション
// Visibility
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
// Text
await expect(locator).toHaveText('Hello');
await expect(locator).toContainText('Hello');
// Attributes
await expect(locator).toHaveAttribute('href', '/home');
await expect(locator).toHaveClass(/active/);
// Input
await expect(locator).toHaveValue('[email protected]');
// Page
await expect(page).toHaveURL(/dashboard/);
await expect(page).toHaveTitle('Dashboard');
アクション
await page.click('button');
await page.fill('input', 'text');
await page.check('input[type="checkbox"]');
await page.selectOption('select', 'option1');
await page.hover('.menu-item');
await page.keyboard.press('Enter');
ページオブジェクト
class LoginPage {
constructor(private page: Page) {}
async login(email: string, password: string) {
await this.page.fill('[name="email"]', email);
await this.page.fill('[name="password"]', password);
await this.page.click('button[type="submit"]');
}
}
設定
// playwright.config.ts
export default defineConfig({
testDir: './e2e',
use: { baseURL: 'http://localhost:3000' },
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }
]
});
本番環境への対応
テスト設定
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['junit', { outputFile: 'test-results/junit.xml' }],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
],
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
});
認証状態
// Save auth state for reuse
// auth.setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', process.env.TEST_USER_EMAIL!);
await page.fill('[name="password"]', process.env.TEST_USER_PASSWORD!);
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
// Save storage state
await page.context().storageState({ path: '.auth/user.json' });
});
// Use in tests
import { test } from '@playwright/test';
test.use({ storageState: '.auth/user.json' });
test('authenticated test', async ({ page }) => {
await page.goto('/dashboard');
// Already logged in
});
API テスト
test('API test', async ({ request }) => {
const response = await request.post('/api/users', {
data: { name: 'John', email: '[email protected]' },
headers: { Authorization: `Bearer ${token}` },
});
expect(response.ok()).toBeTruthy();
const user = await response.json();
expect(user.name).toBe('John');
});
// Mock API responses
test('with mocked API', async ({ page }) => {
await page.route('/api/users', async route => {
await route.fulfill({
status: 200,
body: JSON.stringify([{ id: 1, name: 'Mock User' }]),
});
});
await page.goto('/users');
await expect(page.getByText('Mock User')).toBeVisible();
});
ビジュアルリグレッション
test('visual regression', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixels: 100,
threshold: 0.2,
});
});
// Update snapshots: npx playwright test --update-snapshots
CI 設定
# GitHub Actions
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
env:
BASE_URL: ${{ secrets.STAGING_URL }}
- name: Upload report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
監視メトリクス
| メトリクス | 目標 |
|---|---|
| E2E テスト成功率 | > 99% |
| テスト実行時間 | < 10min |
| 不安定なテストの割合 | < 1% |
| ビジュアル差分による失敗 | すべて確認 |
チェックリスト
- 複数ブラウザーでのテストを設定済み
- モバイルビューポートのテスト
- 認証状態を再利用
- 失敗時に再試行(CI のみ)
- 失敗時のスクリーンショット
- トレース収集を有効化
- 分離のための API モック
- ビジュアルリグレッションテスト
- CI/CD との統合
- テストの並列化
- ページオブジェクトパターンを使用
アンチパターン
| アンチパターン | 問題点 | 解決策 |
|---|---|---|
| CSS セレクターを使ったテスト | 脆く、スタイル変更で壊れます | getByRole、getByLabel、getByTestId を使用してください |
| 要素を待たない | テストが不安定になります | 自動待機ロケーターを使用し、waitForTimeout は避けてください |
| 固定時間の待機(page.waitForTimeout) | 遅く、信頼性に欠けます | page.waitForSelector または自動待機を使用してください |
| ページオブジェクトモデルがない | コードが重複し、保守が困難になります | 共通のアクションをページオブジェクトに抽出してください |
| 1 つのテストで多くをテストしすぎる | 失敗のデバッグが困難になります | 1 テストにつきユーザーフローを 1 つにしてください |
| 認証状態を再利用しない | テストごとのログインに時間がかかります | storageState を保存し、テスト間で再利用してください |
| 不安定なテストを放置する | 誤った信頼につながります | 不安定なテストを修正し、再試行は必要最小限にしてください |
簡単なトラブルシューティング
| 問題 | 考えられる原因 | 解決策 |
|---|---|---|
| "Timeout waiting for selector" | 要素が描画されていない、またはセレクターが間違っています | セレクターを確認し、要素が存在することを確認してください |
| 不安定なテスト(ランダムに成功/失敗する) | 競合状態、ネットワークの遅延 | 自動待機を使用し、waitForTimeout は避けてください |
| "Element is not visible" | 要素が非表示、またはビューポートの外にあります | 要素をスクロールして表示し、CSS の display を確認してください |
| "Execution context destroyed" | アクション中にナビゲーションが発生しました | ナビゲーションの完了を待ってください |
| スクリーンショットが一致しない | フォントの描画、アニメーション | アニメーションを無効にし、ビューポートを固定してください |
| テストがいつまでも停止する | await が不足しています | すべての非同期呼び出しが await されていることを確認してください |
リファレンスドキュメント
コメント
Markdown 対応