プログラミング · Playwright · テスト
Playwright構文のタイピング練習
Playwright Test のテストの書き方70個を、初級・上級の2つのリストで打って覚えます。page.goto、getByRole、toBeVisible などを、TypeScript のコードの形で練習できます。
2リスト・70語
無料・ログインなしで練習できます。押すとこのパックをフォローし、すぐに練習が始まります。
収録している言葉
Playwright構文(初級)
| 打つ言葉 | 意味・補足 |
|---|---|
| // Playwright Test / TypeScript | 対象環境:例の前提(@playwright/testを使用。Webアプリ・画面文言・APIパス等はサンプル。相対URLはbaseURL設定が必要) |
| import { test, expect } from '@playwright/test'; | import:テストAPIをimport(Jestのexpectとは別) |
| test.only('調査中', async ({ page }) => {}); | test.only:実行対象を限定(コミット前に解除。CIではforbidOnlyを推奨) |
| test.use({ viewport: { width: 1280, height: 720 } }); | test.use:グループ等のfixture設定(テスト本体の外で設定) |
| await page.goto('/login'); | page.goto:ページへ移動(相対URLはbaseURLが必要) |
| const email = page.getByLabel('メール', { exact: true }); | getByLabel:ラベルで入力欄を探す |
| const search = page.getByPlaceholder('検索'); | getByPlaceholder:プレースホルダーで探す |
| const message = page.getByText('保存しました', { exact: true }); | getByText:テキストで探す(複数一致に注意) |
| const card = page.getByTestId('user-card'); | getByTestId:テストIDで探す(既定はdata-testid) |
| const logo = page.getByAltText('ロゴ'); | getByAltText:画像等の代替テキスト |
| const form = page.locator('form#login'); | locator:CSS等で探す(壊れやすいDOM構造依存を避ける) |
| const second = page.getByRole('listitem').nth(1); | nth / first / last:位置で選ぶ(0始まり。曖昧さを位置で隠さず意味で絞る) |
| await page.getByRole('button', { name: '保存' }).click(); | click:クリック(Locatorは操作可能性を自動待機) |
| await page.getByLabel('メール').fill('pomo@example.com'); | fill:入力値を置き換える |
| await page.getByLabel('言語').selectOption('ja'); | selectOption:selectの選択肢を選ぶ(HTML select向け。独自ドロップダウンとは別) |
| await page.getByRole('button', { name: 'メニュー' }).hover(); | hover:マウスを乗せる |
| await expect(page.getByTestId('loading')).toBeHidden(); | toBeHidden:非表示または未接続を検証 |
| await expect(page.getByTestId('status')).toHaveText('保存済み'); | toHaveText:テキスト一致 |
| await expect(page.getByTestId('message')).toContainText('完了'); | toContainText:テキストを含む |
| await expect(page.getByRole('listitem')).toHaveCount(3); | toHaveCount:一致する要素数(即時取得したcountへの汎用expectより安定) |
| await expect(page.getByTestId('error')).not.toBeVisible(); | not:否定 |
| await expect(page).toHaveScreenshot('home.png'); | toHaveScreenshot:画像比較(初回基準画像と更新差分をレビュー。OS・フォント等の環境をそろえる) |
| await expect(page.getByTestId('loaded')).toBeVisible(); | 待機の基本:状態を待つ(固定のwaitForTimeoutやnetworkidleを万能な完了条件にしない) |
| await page.route('**/api/users', route => route.abort()); | route.abort:通信を失敗させる(異常系の検証) |
| await page.route('**/api/users', route => route.continue()); | route.continue:通信を続行(登録したroute内) |
| test.use({ storageState: 'playwright/.auth/user.json' }); | test.use({ storageState }):認証状態を再利用(事前に生成。セッション期限・サーバー側状態に依存) |
| npx playwright install | playwright install:ブラウザーを導入(ターミナル。CI等では--with-depsを検討) |
| npx playwright test | playwright test:テスト実行(ターミナル) |
| npx playwright test --ui | --ui / --headed / --debug:UI・画面表示・デバッグ(ターミナル) |
| npx playwright test --headed | --ui / --headed / --debug:UI・画面表示・デバッグ(ターミナル) |
| npx playwright test --debug | --ui / --headed / --debug:UI・画面表示・デバッグ(ターミナル) |
| npx playwright test --project=chromium | --project / -g:プロジェクト・テスト名で絞る(プロジェクト名は設定と一致) |
| npx playwright test -g 'ログイン' | --project / -g:プロジェクト・テスト名で絞る(プロジェクト名は設定と一致) |
| npx playwright show-report | show-report / codegen:レポート・操作からコード生成(生成コードもLocator・アサーション・機密情報をレビュー) |
| npx playwright codegen http://localhost:3000 | show-report / codegen:レポート・操作からコード生成(生成コードもLocator・アサーション・機密情報をレビュー) |
Playwright構文(上級)
| 打つ言葉 | 意味・補足 |
|---|---|
| test('トップページ', async ({ page }) => { await page.goto('/'); await expect(page.getByRole('heading', { name: 'ホーム' })).toBeVisible(); }); | test:ブラウザーテストを定義(pageはテストごとに準備されるfixture) |
| test.describe('ログイン', () => { test('フォーム表示', async ({ page }) => { await page.goto('/login'); await expect(page.getByLabel('メール')).toBeVisible(); }); }); | test.describe:グループ化 |
| test.beforeEach(async ({ page }) => { await page.goto('/'); }); | test.beforeEach:各テスト前に実行 |
| test.afterEach(async ({ page }, testInfo) => { if (testInfo.status !== testInfo.expectedStatus) { await testInfo.attach('failure', { body: await page.screenshot(), contentType: 'image/png' }); } }); | test.afterEach:各テスト後に実行(成果物に認証情報・個人情報が含まれないか確認) |
| await test.step('フォーム入力', async () => { await page.getByLabel('メール').fill('pomo@example.com'); }); | test.step:処理をレポート上で区切る(test内の例) |
| test.skip('未対応', async ({ page }) => {}); test.fixme('修正待ち', async ({ page }) => {}); | test.skip / test.fixme:スキップ・修正待ち(放置しない) |
| const save = page.getByRole('button', { name: '保存', exact: true }); | getByRole:役割とアクセシブル名で探す(利用者から見える契約を優先) |
| const row = page.getByRole('row').filter({ hasText: 'Pomo' }); await row.getByRole('button', { name: '編集' }).click(); | Locator連結 / filter:候補を絞る(filterで一意になるようにする) |
| const card = page.getByTestId('card').filter({ has: page.getByRole('heading', { name: 'Pomo', exact: true }) }); | filter({ has }):子要素で絞る(hasのLocatorは外側を基準に評価) |
| await page.frameLocator('#payment').getByLabel('カード番号').fill('test-value'); | frameLocator:iframe内を操作(対象iframeを準備。実決済用の例ではない) |
| await page.getByRole('textbox', { name: '検索' }).press('Enter'); | press:キー入力 |
| await page.getByRole('textbox', { name: '検索' }).pressSequentially('hello'); | pressSequentially:キーを順に入力(キーイベントが必要な場合。通常はfillを優先) |
| await page.getByRole('checkbox', { name: '同意する' }).check(); | check / uncheck:チェック状態を設定 |
| await page.getByLabel('添付').setInputFiles('tests/fixtures/sample.txt'); | setInputFiles:ファイルを選択(ファイルが必要。相対パスは実行ディレクトリ基準) |
| await page.getByTestId('item').dragTo(page.getByTestId('drop-zone')); | dragTo:ドラッグ(アプリのドラッグ実装に依存) |
| page.once('dialog', dialog => dialog.accept()); await page.getByRole('button', { name: '削除' }).click(); | dialog:ダイアログへ応答(リスナーは操作前に登録。実際に削除する画面ではテストデータを使用) |
| const popupPromise = page.waitForEvent('popup'); await page.getByRole('link', { name: '詳細' }).click(); const popup = await popupPromise; await expect(popup).toHaveURL(/detail/); | popup:新規ページを取得(待機を操作前に登録) |
| const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: 'CSV出力' }).click(); const download = await downloadPromise; await download.saveAs(testInfo.outputPath(download.suggestedFilename())); | download:ダウンロードを取得(testコールバックの第2引数等でtestInfoを受け取る) |
| await expect(page.getByText('完了', { exact: true })).toBeVisible(); | toBeVisible:表示されるまで検証(自動リトライするのでawait) |
| await expect(page.getByLabel('メール')).toHaveValue('pomo@example.com'); | toHaveValue:入力値 |
| await expect(page.getByRole('button', { name: '送信' })).toBeEnabled(); | toBeEnabled / toBeDisabled:有効・無効 |
| await expect(page.getByRole('checkbox', { name: '同意する' })).toBeChecked(); | toBeChecked:チェック状態 |
| await expect(page.getByRole('link', { name: 'ヘルプ' })).toHaveAttribute('href', '/help'); | toHaveAttribute:属性値 |
| await expect(page).toHaveURL(/dashboard/); await expect(page).toHaveTitle(/マイページ/); | toHaveURL / toHaveTitle:URL・ページタイトル |
| await expect.soft(page.getByTestId('status')).toHaveText('完了'); | expect.soft:失敗後も続ける検証(テスト自体は失敗扱いになる) |
| await expect.poll(async () => { const response = await request.get('/api/status'); const body = await response.json(); return body.state; }).toBe('ready'); | expect.poll:値を再取得して検証(request fixtureとAPIが必要) |
| await page.route('**/api/users', route => route.fulfill({ json: [{ id: 1, name: 'Pomo' }] })); await page.goto('/users'); | page.route / fulfill:通信をモック(対象リクエストより前に登録) |
| const responsePromise = page.waitForResponse(r => r.url().endsWith('/api/users') && r.request().method() === 'POST'); await page.getByRole('button', { name: '保存' }).click(); const response = await responsePromise; expect(response.ok()).toBeTruthy(); | waitForResponse:特定レスポンスを待つ(待機を操作前に登録。画面結果も別途検証) |
| const response = await request.post('/api/users', { data: { name: 'Pomo' } }); await expect(response).toBeOK(); | request.get / post:APIを直接呼ぶ(request fixtureを受け取る。テストデータを後片付け) |
| const response = await request.delete('/api/users/1'); await expect(response).toBeOK(); | request.delete:APIでテストデータを削除(専用テスト環境と対象IDを使用) |
| await page.context().storageState({ path: 'playwright/.auth/user.json' }); | storageState:認証状態を保存(機密情報を含む。gitへコミットしない) |
| import { test as base } from '@playwright/test'; const test = base.extend<{ appName: string }>({ appName: async ({}, use) => { await use('Sample'); } }); | test.extend:独自fixtureを作る(TypeScript例。準備・後片付けをuseの前後に配置) |
| import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: './tests', forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, use: { baseURL: 'http://localhost:3000', trace: 'retain-on-failure', screenshot: 'only-on-failure' } }); | defineConfig:テスト実行設定(playwright.config.ts。再試行で不安定なテストを放置しない) |
| webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI } | webServer:アプリ起動を管理(defineConfig内の断片) |
| import { devices } from '@playwright/test'; /* defineConfig内: */ /* projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }] */ | projects / devices:複数ブラウザー等で実行(ブラウザーのインストールが必要) |