Playwright E2E Testing Skill
References (Official)
Basic Policy of This Skill
- Basic policy: Test user-visible behavior. Do not depend on implementation details (class names/ids/DOM structure).
- Locator:
getByRole > getByLabel > getByText > getByTestId > CSS selectors. Prioritize role-based above all.
- Assertions: Use Web First assertions (with auto-retry). Avoid manual
isVisible() checks.
- Test isolation: Each test is completely independent. Eliminate shared state and test order dependencies.
- Performance: Global installation recommended. On CI, use Linux + only necessary browsers + sharding.
Philosophy (Decision Rules)
- Test from the user's perspective: Write tests the way an end user operates. Do not depend on internal APIs or DOM structure.
- Prioritize role-based locators:
getByRole('button', { name: '送信' }) is resistant to refactoring. CSS selectors break when the DOM changes.
- Trust auto-waiting: Playwright automatically waits for element visibility and enabled state. Explicit
sleep や waitForTimeout is unnecessary.
- Keep tests independent: Each test runs in its own browser context (cookies/storage). Common setup in beforeEach.
- Mock external dependencies: Intercept third-party APIs with the Network API. Do not test links to external sites.
- Use Trace Viewer for debugging: Trace has more information than screenshots or videos. On CI, enable with
on: 'first-retry' .
Priority Order for Locator Selection
// 1. ロールベース(最優先・推奨)
page.getByRole('button', { name: '送信' })
page.getByRole('textbox', { name: 'メールアドレス' })
page.getByRole('heading', { name: 'ログイン' })
// 2. ラベル/プレースホルダー
page.getByLabel('メールアドレス')
page.getByPlaceholder('example@mail.com')
// 3. テキスト
page.getByText('ログイン')
// 4. テストID(他の方法で特定できない場合)
page.getByTestId('submit-button')
// 5. CSSセレクタ(最終手段)
page.locator('#submit-btn')
How to Write Assertions
// 推奨: Web Firstアサーション(自動リトライ付き)
await expect(page.getByText('ようこそ')).toBeVisible()
await expect(page.getByRole('alert')).toHaveText('保存しました')
await expect(page).toHaveURL('/dashboard')
// 非推奨: 手動チェック(リトライなし)
expect(await page.getByText('ようこそ').isVisible()).toBe(true)
Test Structure Patterns
import { test, expect } from '@playwright/test'
test.describe('ログインフロー', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login')
})
test('正常系_有効な認証情報でダッシュボードに遷移', async ({ page }) => {
await page.getByLabel('メールアドレス').fill('user@example.com')
await page.getByLabel('パスワード').fill('password123')
await page.getByRole('button', { name: 'ログイン' }).click()
await expect(page).toHaveURL('/dashboard')
})
test('異常系_無効なパスワードでエラー表示', async ({ page }) => {
await page.getByLabel('メールアドレス').fill('user@example.com')
await page.getByLabel('パスワード').fill('wrong')
await page.getByRole('button', { name: 'ログイン' }).click()
await expect(page.getByRole('alert')).toHaveText('認証に失敗しました')
})
})
CI Optimization
// playwright.config.ts のCI向け設定
export default defineConfig({
// CIでは全リトライ1回
retries: process.env.CI ? 1 : 0,
// CIでは並列ワーカーを制限
workers: process.env.CI ? 2 : undefined,
// Traceは最初のリトライ時のみ
use: {
trace: 'on-first-retry',
},
})
# CIで必要なブラウザだけインストール
npx playwright install --with-deps chromium
# シャーディングで並列化
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
Output Format (Always in This Order)
- Recommended policy (1-3 lines)
- Rationale (test stability / maintainability / user perspective)
- Design proposal (flow under test / locator strategy / mock strategy / CI configuration)
- Checklist (confirm before implementation)
- Pitfalls (to avoid)
- Next actions (order of small experiments)
Checklist
- Locators are role-based (
getByRole) is given top priority
- Assertions use Web First (
await expect(...).toBeVisible() , etc.)
- Each test is independent (no shared state or order dependency)
-
waitForTimeout / sleep is not used
- External APIs/services are mocked with the Network API
- Trace Viewer is enabled on CI (
on-first-retry)
- Test names express "which flow_under what conditions_what happens"
Common pitfalls
- CSS selectors (
.btn-primary) and refactoring breaks everything
page.waitForTimeout(3000) trying to absorb CI environment speed differences (the root cause of flakiness)
- Sharing login state between tests, causing failures only on CI due to order dependency
- Writing a large number of E2E tests makes CI execution time tens of minutes, slowing down the development cycle
- Screenshot comparisons produce false positives due to font rendering differences (threshold settings are required)
- The Playwright version is old, and tests break due to behavior changes in the latest browsers