Playwright入門:TypeScriptでE2Eテストを書く
Playwrightとは
PlaywrightはMicrosoftが開発するE2E(End-to-End)テストフレームワークです。実際のブラウザを操作してユーザーの操作を自動化し、アプリ全体の動作を確認できます。
- Chromium・Firefox・WebKit(Safari)の3ブラウザに対応
- TypeScriptファーストで型補完が効く
- ロケーターAPIが直感的で保守しやすい
- UIモードで視覚的にテストを確認できる
セットアップ
新規プロジェクトに追加する
npm init playwright@latest
対話形式でセットアップされます。tests/ ディレクトリが作成され、playwright.config.ts が生成されます。
既存プロジェクトに追加する
npm install -D @playwright/test npx playwright install # ブラウザのインストール
基本的なテストの書き方
// tests/example.spec.ts import { test, expect } from '@playwright/test' test('ページタイトルを確認する', async ({ page }) => { await page.goto('https://example.com') await expect(page).toHaveTitle(/Example Domain/) }) test('リンクをクリックしてページ遷移する', async ({ page }) => { await page.goto('https://example.com') await page.getByRole('link', { name: 'More information' }).click() await expect(page).toHaveURL(/iana.org/) })
test() の第2引数の { page } はPlaywrightが提供するフィクスチャで、ブラウザのページを表します。
ロケーター(要素の取得)
Playwrightはユーザー視点で要素を取得するロケーターAPIを推奨しています。
// ロールで取得(最推奨) page.getByRole('button', { name: '送信' }) page.getByRole('link', { name: 'トップへ' }) page.getByRole('textbox', { name: 'メールアドレス' }) // ラベルで取得 page.getByLabel('パスワード') // プレースホルダーで取得 page.getByPlaceholder('example@email.com') // テキストで取得 page.getByText('ようこそ') // テストIDで取得(data-testid属性) page.getByTestId('submit-button') // CSSセレクター(最終手段) page.locator('.submit-btn') page.locator('#email')
| ロケーター | 使いどころ |
|---|---|
getByRole |
ボタン・リンク・入力欄など意味のある要素 |
getByLabel |
フォームの入力欄(label と紐づいているもの) |
getByText |
特定のテキストを含む要素 |
getByTestId |
テスト専用属性で確実に特定したい場合 |
locator |
CSSセレクターで直接指定する場合 |
ページ操作
test('フォームの入力と送信', async ({ page }) => { await page.goto('/login') // テキスト入力 await page.getByLabel('メールアドレス').fill('user@example.com') await page.getByLabel('パスワード').fill('password123') // クリック await page.getByRole('button', { name: 'ログイン' }).click() // ドロップダウンの選択 await page.getByLabel('言語').selectOption('ja') // チェックボックス await page.getByLabel('利用規約に同意する').check() // キーボード操作 await page.getByRole('textbox').press('Enter') })
アサーション
// ページ全体 await expect(page).toHaveTitle('ダッシュボード') await expect(page).toHaveURL('/dashboard') // 要素の状態 await expect(page.getByText('ようこそ')).toBeVisible() await expect(page.getByRole('button', { name: '削除' })).toBeEnabled() await expect(page.getByRole('checkbox')).toBeChecked() await expect(page.getByRole('button', { name: '送信' })).toBeDisabled() // テキスト・値 await expect(page.getByRole('heading')).toHaveText('マイページ') await expect(page.getByLabel('名前')).toHaveValue('田中太郎') // 件数 await expect(page.getByRole('listitem')).toHaveCount(5)
実践例:ログインフローのテスト
// tests/login.spec.ts import { test, expect } from '@playwright/test' test.describe('ログイン', () => { test('正しい認証情報でログインできる', async ({ page }) => { await page.goto('/login') await page.getByLabel('メールアドレス').fill('user@example.com') await page.getByLabel('パスワード').fill('password123') await page.getByRole('button', { name: 'ログイン' }).click() await expect(page).toHaveURL('/dashboard') await expect(page.getByText('ようこそ、田中さん')).toBeVisible() }) test('誤ったパスワードでエラーが表示される', async ({ page }) => { await page.goto('/login') await page.getByLabel('メールアドレス').fill('user@example.com') await page.getByLabel('パスワード').fill('wrong-password') await page.getByRole('button', { name: 'ログイン' }).click() await expect(page.getByText('メールアドレスまたはパスワードが正しくありません')).toBeVisible() await expect(page).toHaveURL('/login') }) })
playwright.config.ts の基本設定
// playwright.config.ts import { defineConfig, devices } from '@playwright/test' export default defineConfig({ testDir: './tests', use: { baseURL: 'http://localhost:3000', // page.goto('/login') のベースURL screenshot: 'only-on-failure', // 失敗時にスクリーンショットを保存 video: 'retain-on-failure', // 失敗時に動画を保存 }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, ], })
baseURL を設定すると page.goto('/login') のように相対パスで書けます。
テストの実行
# 全テストを実行 npx playwright test # 特定のファイルだけ実行 npx playwright test tests/login.spec.ts # ブラウザを表示して実行(デバッグ時) npx playwright test --headed # UIモードで実行(視覚的に確認できる) npx playwright test --ui # レポートを表示 npx playwright show-report
まとめ
| 操作 | メソッド |
|---|---|
| ページ遷移 | page.goto(url) |
| テキスト入力 | locator.fill(text) |
| クリック | locator.click() |
| 選択 | locator.selectOption(value) |
| チェック | locator.check() |
| アサーション | 確認内容 |
|---|---|
toHaveTitle() |
ページタイトル |
toHaveURL() |
URL |
toBeVisible() |
表示されているか |
toHaveText() |
テキスト内容 |
toBeEnabled() / toBeDisabled() |
有効・無効状態 |
toHaveCount() |
要素の件数 |
getByRole/getByLabelなど意味のあるロケーターを使うと、実装の変更に強いテストになるbaseURLを設定すると相対パスで書けてテスト環境の切り替えが楽になる--uiモードを使うとテストの実行過程を視覚的に確認・デバッグできる
コンポーネント単位の動作確認には「Storybook入門:React TypeScriptでコンポーネントのStoryを書く」も参照してください。