入力欄を空にする、要素数を確認する、別タブを操作する。
Playwrightの書き方を、やりたいことから引けるようにまとめました。基本操作は表で、数行の手順が必要なものは短いコード例で載せています。
やりたいことから探す
| やりたいこと | 参照先 |
|---|---|
| 名前・ラベル・属性から要素を探す | 1. 要素を探す |
| クリック・入力・選択する | 2. 要素を操作する |
| 表示・値・件数を確かめる | 3. 結果を検証する |
| 画面更新・通信を待つ | 4. 必要な状態を待つ |
| 別タブ・iframe・ダイアログを扱う | 5. ページを操作する |
| ファイルや画像を扱う | 6. ファイルとスクリーンショット |
| APIを呼ぶ・レスポンスを差し替える | 7. APIとモック |
| ログイン済み状態を使う | 8. 認証状態を再利用する |
| テストを実行・絞り込み・調査する | 9. 実行とデバッグ |
| 設定やテストの前後処理を確認する | 10. 設定とテスト構成 |
コードの前提
対象はPlaywright Test(@playwright/test)+TypeScriptです。
以下の基本形で使います。短いコード例はそれぞれ独立した例です。
import { test, expect } from '@playwright/test';
test('ホーム画面を表示する', async ({ page }) => {
await page.goto('/');
await expect(
page.getByRole('heading', { name: 'ホーム', exact: true }),
).toBeVisible();
});
1. 要素を探す
名前・ラベル・属性で指定する
| 手がかり | 書き方 |
|---|---|
| ボタンのロールと名前 | page.getByRole('button', { name: '保存', exact: true }) |
| 入力欄のラベル | page.getByLabel('メールアドレス', { exact: true }) |
| 表示テキスト | page.getByText('保存しました', { exact: true }) |
| プレースホルダー | page.getByPlaceholder('キーワードを入力') |
| 画像の代替テキスト | page.getByAltText('プロフィール画像') |
| title属性 | page.getByTitle('詳細を表示') |
| テスト用の識別子 | page.getByTestId('submit-button') |
| ID | page.locator('#email') |
| class | page.locator('.menu-item') |
| 任意の属性 | page.locator('[name="email"]') |
| XPath | page.locator('//input[@name="email"]') |
複数の候補から絞る
| やりたいこと | 書き方 |
|---|---|
| 文字を含む要素に絞る | locator.filter({ hasText: '公開中' }) |
| 文字を含まない要素に絞る | locator.filter({ hasNotText: '非公開' }) |
| 特定の子孫要素を持つものに絞る | locator.filter({ has: page.getByText('商品A', { exact: true }) }) |
| 最初の要素 | locator.first() |
| 最後の要素 | locator.last() |
| n+1番目の要素(0始まりのため右の例では3番目の要素) | locator.nth(2) |
同名ボタンは、親要素から範囲を限定できます。
const dialog = page.getByRole('dialog', { name: '設定' });
await dialog.getByRole('button', { name: '保存', exact: true }).click();
2. 要素を操作する
クリック・マウス操作
| やりたいこと | 書き方 |
|---|---|
| クリック | await locator.click(); |
| ダブルクリック | await locator.dblclick(); |
| 右クリック | await locator.click({ button: 'right' }); |
| ホバー | await locator.hover(); |
| フォーカスを当てる | await locator.focus(); |
| フォーカスを外す | await locator.blur(); |
| 要素が見える位置までスクロール | await locator.scrollIntoViewIfNeeded(); |
| 別の要素へドラッグ | await source.dragTo(target); |
sourceとtargetは、移動元・移動先のLocatorです。
文字入力・キーボード
| やりたいこと | 書き方 |
|---|---|
| 文字を入力・置換 | await locator.fill('テスト'); |
| 入力欄を空にする | await locator.clear(); |
| Enterを押す | await locator.press('Enter'); |
| Tabを押す | await locator.press('Tab'); |
| 入力欄の文字を全選択 | await locator.press('ControlOrMeta+A'); |
| 1文字ずつキー入力する | await locator.pressSequentially('hello'); |
チェックボックス・セレクトボックス
| やりたいこと | 書き方 |
|---|---|
| チェックを入れる | await locator.check(); |
| チェックを外す | await locator.uncheck(); |
| チェック状態をbooleanで指定 | await locator.setChecked(true); |
| 選択肢のvalueで選ぶ | await locator.selectOption({ value: 'tokyo' }); |
| 選択肢の表示名で選ぶ | await locator.selectOption({ label: '東京都' }); |
| 複数選択する | await locator.selectOption(['red', 'blue']); |
3. 結果を検証する
以下は、自動再試行するアサーションです。awaitを付けます。
| 確認したいこと | 書き方 |
|---|---|
| 表示されている | await expect(locator).toBeVisible(); |
| 非表示、または存在しない | await expect(locator).toBeHidden(); |
| 一致する要素がない | await expect(locator).toHaveCount(0); |
| 有効になっている | await expect(locator).toBeEnabled(); |
| 無効になっている | await expect(locator).toBeDisabled(); |
| チェックされている | await expect(locator).toBeChecked(); |
| チェックされていない | await expect(locator).not.toBeChecked(); |
| テキスト全体が一致する | await expect(locator).toHaveText('保存しました'); |
| テキストを含む | await expect(locator).toContainText('保存'); |
| 入力値が一致する | await expect(locator).toHaveValue('山田太郎'); |
| 要素が3件ある | await expect(locator).toHaveCount(3); |
| 複数要素のテキストと順序が一致する | await expect(locator).toHaveText(['東京', '大阪']); |
| 属性の値が一致する | await expect(locator).toHaveAttribute('aria-expanded', 'true'); |
| ページタイトルが一致する | await expect(page).toHaveTitle('ホーム'); |
| URLが条件に一致する | await expect(page).toHaveURL(/\/complete$/); |
検証ではなく、値を取得する
| 取得したいもの | 書き方 |
|---|---|
| innerText | const text = await locator.innerText(); |
| textContent | const text = await locator.textContent(); |
| 入力値 | const value = await locator.inputValue(); |
| 属性値 | const href = await locator.getAttribute('href'); |
| 要素数 | const count = await locator.count(); |
| 複数要素のテキスト | const texts = await locator.allTextContents(); |
| 一致するLocatorの配列 | const items = await locator.all(); |
| 現在のURL | const url = page.url(); |
| ページタイトル | const title = await page.title(); |
失敗しても、残りの検証を続ける
await expect.soft(page.getByLabel('氏名')).toHaveValue('山田太郎');
await expect.soft(page.getByLabel('メール')).toHaveValue('qa@example.com');
expect.soft()は失敗を記録しつつ後続の処理を続けます。失敗を無視するわけではなく、テスト全体は失敗になります。
参照:Assertions/LocatorAssertions/Locator API
4. 必要な状態を待つ
click()などは操作に必要な条件を自動で待ちます。
| 待ちたい状態 | 書き方 |
|---|---|
| 要素が表示される | await expect(locator).toBeVisible(); |
| 文言が変わる | await expect(locator).toHaveText('完了'); |
| 指定URLへ移動する | await page.waitForURL('**/complete'); |
| DOMに追加される | await locator.waitFor({ state: 'attached' }); |
| DOMから削除される | await locator.waitFor({ state: 'detached' }); |
特定のAPIレスポンスを待つ
// 通信を発生させる操作より先に登録する。
const responsePromise = page.waitForResponse((response) =>
new URL(response.url()).pathname === '/api/search'
&& response.request().method() === 'GET',
);
await page.getByRole('button', { name: '検索', exact: true }).click();
const response = await responsePromise;
expect(response.status()).toBe(200);
ステータスは待機条件に含めず、受信後に検証しています。500が返った場合も、待機タイムアウトではなくステータスの不一致として確認するためです。
同じAPIが複数回呼ばれる画面では、クエリや送信内容でも対象を絞ります。レスポンスを受け取った後の画面反映は、別途アサーションで確認します。
タイムアウトを個別に調整する
| 調整する対象 | 書き方 |
|---|---|
| 特定の操作 | await locator.click({ timeout: 10_000 }); |
| 特定の検証 | await expect(locator).toBeVisible({ timeout: 10_000 }); |
| テスト1件の上限 | test.setTimeout(60_000); |
単位はミリ秒です。
参照:Auto-waiting/Page API/Timeouts
5. ページを操作する
| やりたいこと | 書き方 |
|---|---|
| URLを開く | await page.goto('https://example.com/'); |
| 再読み込み | await page.reload(); |
| 前のページへ戻る | await page.goBack(); |
| 次のページへ進む | await page.goForward(); |
| ページを閉じる | await page.close(); |
クリックで開く別タブ
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'ヘルプ', exact: true }).click();
const popup = await popupPromise;
await expect(popup).toHaveTitle('ヘルプ');
iframe内の要素
await page
.frameLocator('#preview-frame')
.getByRole('button', { name: '印刷', exact: true })
.click();
JavaScriptの確認ダイアログ
page.once('dialog', async (dialog) => {
await dialog.accept();
});
await page.getByRole('button', { name: '削除', exact: true }).click();
6. ファイルとスクリーンショット
アップロードするファイルを選択する
ここでのlocatorは<input type="file">を指定します。
| やりたいこと | 書き方 |
|---|---|
| 1ファイルを選択 | await locator.setInputFiles('fixtures/sample.pdf'); |
| 複数ファイルを選択 | await locator.setInputFiles(['fixtures/a.csv', 'fixtures/b.csv']); |
| 選択を解除 | await locator.setInputFiles([]); |
ダウンロードしたファイルを保存する
// 冒頭のtest・expectのimportを使用する。
test('CSVをダウンロードする', async ({ page }, testInfo) => {
await page.goto('/downloads');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'CSV出力', exact: true }).click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toMatch(/\.csv$/);
await download.saveAs(testInfo.outputPath('report.csv'));
});
testInfo.outputPath()で、そのテスト用の保存先を使えます。
スクリーンショット・画像比較
| やりたいこと | 書き方 |
|---|---|
| 表示領域を保存 | await page.screenshot({ path: testInfo.outputPath('page.png') }); |
| ページ全体を保存 | await page.screenshot({ path: testInfo.outputPath('full.png'), fullPage: true }); |
| 特定の要素だけ保存 | await locator.screenshot({ path: testInfo.outputPath('element.png') }); |
| 基準画像と比較 | await expect(page).toHaveScreenshot('home.png'); |
参照:Actions/Downloads/TestInfo/Visual comparisons
7. APIとモック
APIを直接呼ぶ
requestフィクスチャを受け取ります。以下は、レスポンスが{ items: [...] }という形式の場合の例です。
test('一覧APIが応答する', async ({ request }) => {
const response = await request.get('/api/items');
await expect(response).toBeOK();
const body = await response.json();
expect(body).toMatchObject({ items: expect.any(Array) });
});
| やりたいこと | 書き方 |
|---|---|
| GETで取得 | await request.get('/api/items'); |
| クエリパラメータを渡す | await request.get('/api/items', { params: { q: 'test' } }); |
| JSONをPOST | await request.post('/api/items', { data: { name: 'sample' } }); |
| JSONをPATCH | await request.patch('/api/items/123', { data: { name: 'updated' } }); |
| DELETEで削除 | await request.delete('/api/items/123'); |
APIの応答を差し替える
対象の通信が発生する前に登録します。この例はGET /api/itemsだけを差し替えます。
await page.route(
(url) => url.pathname === '/api/items',
async (route) => {
if (route.request().method() !== 'GET') {
await route.continue();
return;
}
await route.fulfill({
status: 200,
json: { items: [] },
});
},
);
await page.goto('/items');
ハンドラー内の処理を変えると、次の状態も再現できます。
| 再現したい状態 | ハンドラー内の書き方 |
|---|---|
| HTTP 500 | await route.fulfill({ status: 500, json: { message: 'Internal error' } }); |
| 通信自体の失敗 | await route.abort('failed'); |
| 差し替えず実通信を続ける | await route.continue(); |
HTTPエラーと通信失敗は別の状態です。 モックしたAPIについては、実バックエンドとの結合を確認していない点も区別します。
参照:API testing/APIRequestContext/APIResponseAssertions/Mock APIs/Route
8. 認証状態を再利用する
ログイン完了後に保存する
playwright/.auth/を作成し、ログインが完了したことを確認してから保存します。
await page.context().storageState({
path: 'playwright/.auth/user.json',
});
保存済みファイルを読み込む
test.use()はテスト関数の外側に置きます。
import { test, expect } from '@playwright/test';
test.use({ storageState: 'playwright/.auth/user.json' });
test('アカウント画面を開く', async ({ page }) => {
await page.goto('/account');
await expect(page.getByRole('heading', { name: 'アカウント' }))
.toBeVisible();
});
保存用の処理を先に完了させ、ファイルが存在する状態で実行します。実行順を自動化する構成は、公式のsetupプロジェクトによる認証例を参照してください。
認証状態ファイルは、非公開リポジトリにもコミットしません。 Cookieなどの機密情報を含むため、CIの共有成果物にも混ぜないようにします。
# .gitignore
playwright/.auth/
参照:Authentication/BrowserContext
9. 実行とデバッグ
準備
# 新規プロジェクトを初期化する。
npm init playwright@latest
# 必要なブラウザをインストールする。
npx playwright install
# 使用中のバージョンを確認する。
npx playwright --version
実行・絞り込み
| やりたいこと | コマンド |
|---|---|
| 全テストを実行 | npx playwright test |
| ファイルを指定 | npx playwright test tests/example.spec.ts |
| 行番号を指定 | npx playwright test tests/example.spec.ts:10 |
| テスト名で絞る | npx playwright test -g "検索" |
| プロジェクトを指定 | npx playwright test --project=chromium |
| ブラウザを表示 | npx playwright test --headed |
| UIモードで実行 | npx playwright test --ui |
| Inspectorでデバッグ | npx playwright test --debug |
| 前回失敗したテストだけ実行 | npx playwright test --last-failed |
| 10回繰り返す | npx playwright test --repeat-each=10 |
| ワーカー数を1にする | npx playwright test --workers=1 |
| 実行対象を一覧表示 | npx playwright test --list |
| 操作からコードを生成 | npx playwright codegen http://localhost:3000 |
-gは正規表現、--projectは設定済みのプロジェクト名です。
レポート・トレース
# HTMLレポートを生成して実行する。
npx playwright test --reporter=html
# 作成済みのHTMLレポートを開く。
npx playwright show-report
# トレースを記録して実行する。
npx playwright test --trace on
# 保存されたトレースを開く。
npx playwright show-trace test-results/対象ディレクトリ/trace.zip
エラー別の確認ポイント
| 症状 | まず確認すること |
|---|---|
strict mode violation |
複数の要素に一致していないか。範囲や名前で絞れるか |
| クリックでタイムアウト | 要素の有無、disabled、オーバーレイ、アニメーション |
| アサーションでタイムアウト | 期待値、APIエラー、画面更新のタイミング |
| レスポンス待機でタイムアウト | 待機を登録した順序、URL、HTTPメソッド |
| モックが効かない | 登録前に通信していないか。URL条件やService Workerの影響 |
| 単独では通り、並列だと落ちる | 共有アカウント・共有データの干渉、同時負荷 |
| CIでだけ落ちる | ブラウザ、環境変数、データ、実行資源の違い |
切り分けの入り口として、Trace ViewerのDOM・通信・ログと照らし合わせます。
npx playwright test tests/example.spec.ts \
--repeat-each=10 --retries=0 --workers=1
これは単独実行での確認です。並列時だけ失敗する場合は、実際の並列条件とも比較します。
参照:Installation/Command line/Trace viewer/Network
10. 設定とテスト構成
playwright.config.ts
以下は設定例であり、デフォルト値の一覧ではありません。
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
expect: { timeout: 5_000 },
forbidOnly: Boolean(process.env.CI),
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
baseURLを設定すると、page.goto('/login')のように書けます。この設定だけではアプリは起動しないため、テスト対象は別途起動しておきます。
| 設定 | 用途 |
|---|---|
timeout |
テスト1件の時間上限 |
expect.timeout |
アサーションの待機時間 |
forbidOnly |
test.only()の残存をエラーにする |
use.trace |
トレースの記録方法 |
use.screenshot |
スクリーンショットの記録方法 |
トレースは、何回目の実行を残したいかで選びます。
| 設定値 | 記録対象 |
|---|---|
retain-on-failure |
各実行を記録し、失敗した実行の分を残す |
on-first-retry |
最初の再試行を記録する。初回失敗そのものではない |
再試行がなければ、on-first-retryでは記録されません。トレースやスクリーンショットの画面・通信情報にも注意し、閲覧範囲と保持期間を決めておきます。
テストをまとめる・前後の処理を入れる
| やりたいこと | 書き方 |
|---|---|
| テストをグループ化 | test.describe('検索', () => { /* テストを定義 */ }); |
| 各テストの前に処理 | test.beforeEach(async ({ page }) => { /* 準備 */ }); |
| 各テストの後に処理 | test.afterEach(async () => { /* 後片付け */ }); |
| 実行手順に名前を付ける | await test.step('検索する', async () => { /* 操作 */ }); |
describeやフックはテスト関数の外側、test.step()はテスト関数の中で使います。