学ぶ / チートシート
Playwright チートシート
毎日使う Playwright Test の API を 1 枚にまとめました。ロケーターは Playwright が推奨する順に並べています。ロール、ラベル、テキストをテスト ID より優先し、CSS や XPath は最後の手段です。ここに載せたアサーションはすべて Web ファーストで、合格するかタイムアウトするまで自動で再試行するため、手動の待機は不要です。
Playwright Test 1.5x、TypeScript で確認済み
実行とデバッグ
| やりたいこと | コード |
|---|---|
| 新規プロジェクト | npm init playwright@latest |
| すべてのテストを実行 | npx playwright test |
| 1 ファイルの特定の行 | npx playwright test tests/login.spec.ts:12 |
| タイトルやタグで絞り込む | npx playwright test --grep @smoke |
| 1 つのブラウザプロジェクト | npx playwright test --project=chromium |
| ブラウザを表示する | npx playwright test --headed |
| Inspector でステップ実行 | npx playwright test --debug |
| UI モード(ウォッチ、タイムトラベル) | npx playwright test --ui |
| 前回失敗したテストだけ | npx playwright test --last-failed |
| フレーキーテストを探す | npx playwright test my.spec.ts --repeat-each=20 |
| CI マシン間でシャーディング | npx playwright test --shard=1/4 |
| HTML レポートを開く | npx playwright show-report |
| トレースを開く | npx playwright show-trace trace.zip |
| スクリプトを記録する | npx playwright codegen https://example.com |
ロケーター(推奨順)
| やりたいこと | コード |
|---|---|
| ロールとアクセシブルネーム | page.getByRole('button', { name: 'Sign in' }) |
| ラベルでフォーム項目を指定 | page.getByLabel('Email') |
| プレースホルダーで指定 | page.getByPlaceholder('Search') |
| テキストで指定(完全一致) | page.getByText('Welcome back', { exact: true }) |
| テスト ID で指定(data-testid) | page.getByTestId('checkout-submit') |
| CSS | page.locator('#orders tbody tr') |
| XPath | page.locator('xpath=//table[@id="orders"]//tr') |
| テキストで絞り込む | page.getByRole('row').filter({ hasText: 'Mei Lin' }) |
| 子要素で絞り込む | page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Buy' }) }) |
| 一致した要素の中をさらに検索 | row.getByRole('button', { name: 'Cancel' }) |
| 複数の中から 1 つを選ぶ | .first() .last() .nth(2) |
| iframe の中 | page.frameLocator('#payment').getByLabel('Card number') |
ロケーターは遅延評価です。操作やアサーションを行うまで要素は検索されないので、一度定義して使い回せます。
操作
| やりたいこと | コード |
|---|---|
| ページ遷移(baseURL からの相対パス) | await page.goto('/login') |
| クリック | await locator.click() |
| 入力欄の値を置き換える | await locator.fill('mei@example.com') |
| 1 キーずつ入力する | await locator.pressSequentially('abc', { delay: 50 }) |
| キーを押す | await locator.press('Enter') |
| チェックボックス・ラジオボタン | await locator.check() / await locator.uncheck() |
| 選択肢を選ぶ | await page.getByLabel('Country').selectOption('UK') |
| ファイルをアップロード | await locator.setInputFiles('fixtures/invoice.pdf') |
| ホバー | await locator.hover() |
| ドラッグ&ドロップ | await source.dragTo(target) |
| 次のダイアログを承認する | page.once('dialog', (d) => d.accept()) |
Web ファーストアサーション
| やりたいこと | コード |
|---|---|
| 表示 / 非表示 | await expect(locator).toBeVisible() / .toBeHidden() |
| テキストの完全一致 | await expect(locator).toHaveText('Order placed') |
| テキストを含む | await expect(locator).toContainText('placed') |
| 入力値 | await expect(input).toHaveValue('42') |
| 一致する要素の数 | await expect(page.getByRole('row')).toHaveCount(5) |
| 有効 / 無効 / チェック済み | toBeEnabled() toBeDisabled() toBeChecked() |
| 属性 | await expect(menu).toHaveAttribute('aria-expanded', 'true') |
| URL とタイトル | await expect(page).toHaveURL(/dashboard/) / .toHaveTitle('Orders') |
| 否定 | await expect(spinner).not.toBeVisible() |
| 失敗しても続行する | await expect.soft(total).toHaveText('£40.00') |
| 任意の非同期チェックを再試行 | await expect.poll(async () => (await api.get(url)).status()).toBe(200) |
| ビジュアル比較 | await expect(page).toHaveScreenshot('home.png') |
ネットワークと API
| やりたいこと | コード |
|---|---|
| レスポンスをモックする | await page.route('**/api/orders', (r) => r.fulfill({ json: [] })) |
| リクエストを失敗させる | await page.route('**/api/orders', (r) => r.abort()) |
| 実際のレスポンスを書き換える | await page.route('**/api/me', async (r) => { const res = await r.fetch(); const json = await res.json(); await r.fulfill({ response: res, json: { ...json, plan: 'pro' } }); }) |
| レスポンスを待つ | const res = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Save' }).click();
expect((await res).ok()).toBeTruthy(); |
| API テスト | test('list users', async ({ request }) => {
const res = await request.get('/api/users');
await expect(res).toBeOK();
expect(await res.json()).toHaveLength(3);
}); |
構造、フィクスチャ、フック
| やりたいこと | コード |
|---|---|
| テストをグループ化 | test.describe('checkout', () => { /* tests */ }) |
| 各テストの前に実行 | test.beforeEach(async ({ page }) => { await page.goto('/'); }) |
| テストにタグを付ける | test('pays by card', { tag: '@smoke' }, async ({ page }) => { /* ... */ }) |
| 条件付きでスキップ | test.skip(browserName === 'webkit', 'Not supported on WebKit') |
| ファイル内で並列実行 | test.describe.configure({ mode: 'parallel' }) |
| カスタムフィクスチャ | export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
}); |
| ログイン済みの状態を再利用 | test.use({ storageState: 'playwright/.auth/user.json' }) |
| レポートにステップ名を付ける | await test.step('add to cart', async () => { /* ... */ }) |
playwright.config.ts
| 設定 | 例 |
|---|---|
| page.goto のベース URL | use: { baseURL: 'http://localhost:3000' } |
| 失敗時の証跡 | use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' } |
| CI でのみリトライ | retries: process.env.CI ? 2 : 0 |
| 並列実行 | fullyParallel: true, workers: process.env.CI ? 2 : undefined |
| test.only がコミットされたら失敗させる | forbidOnly: !!process.env.CI |
| 先にアプリを起動する | webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI } |
| ブラウザ | projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] |
| デフォルトのタイムアウト | timeout: 30_000, expect: { timeout: 5_000 } |