テストに対する考え方
ユーザーが目にする動作をテストする
テストはエンドユーザーが実際に見る・操作する部分だけを対象にし、内部の実装詳細(関数名や CSS クラスなど)には依存しないことが重要。これにより、ユーザー視点での動作を確実に検証できる。
テストはできるだけ独立・分離させる
各テストは他のテストから完全に独立して動作させ、ローカルストレージや Cookie、データなども分けて管理することで再現性と安定性を高める。共通の前準備や後片付けは beforeEach
や afterEach
でまとめつつ、シンプルなテストでは多少の重複も許容し、読みやすさを優先してよい。
自分で制御できるものだけをテストする
外部サイトや第三者のサーバーは制御できず、内容の変化やネットワーク障害でテストが壊れやすくなるため、テストは避けるべき。Playwright の Network API を使って外部レスポンスをモックすることで、安定したテスト環境を作れる。
データベースを使ったテスト
データベース連携のテストでは、使用データが常に意図した状態に保たれることが必須。ステージング環境など本番と分離された環境でテストを実施し、テスト前にデータを初期化して他の影響を受けないようにすることで、再現性を確保する。ビジュアルリグレッションテストを行う場合は、OS やブラウザのバージョンを揃え、環境差異による見た目の変化を防ぐことも重要になる。
ロケーターを使ったテストの書き方
ロケーターで要素を見つける
E2E テストを書く際、まず必要になるのは「どの要素を操作するか」を明確に指定すること。Playwright では、組み込みのロケーター機能を使って、画面上の要素を正確に特定できる。ロケーターは単なるセレクターではなく、要素が「表示されているか」「操作可能か」などの状態も自動で確認してくれる。たとえば、ボタンをクリックする際には、それが表示されていて無効化されていないかどうかも自動でチェックしてくれるため、安定性の高いテストが書ける。
// 👍 ユーザー視点での指定
page.getByRole('button', { name: 'submit' });
こうしたユーザーに見える属性(ロールやラベル、テキスト)を使った指定方法が推奨されている。
ロケーターのチェーンとフィルタリング
ロケーターは チェーンでの絞り込み や フィルター処理 を使って、柔軟に目的の要素を探し出せる。たとえば、複数のリストアイテムの中から「Product 2」というテキストを含むものを探し、その中の「Add to cart」ボタンを押す、といった操作も簡潔に書ける。
await page
.getByRole('listitem')
.filter({ hasText: 'Product 2' })
.getByRole('button', { name: 'Add to cart' })
.click();
このように書くことで、意図が明確で、読みやすく保守しやすいテストになる。
XPath や CSS セレクターを避ける理由
クラス名や DOM 構造に依存したセレクターは、見た目の変更によってすぐに壊れてしまうリスクがある。たとえば以下のようなコードは、スタイルの変更などで .buttonIcon
や .episode-actions-later
のクラス名が変わるだけで動かなくなる。
// 👎
page.locator('button.buttonIcon.episode-actions-later');
一方で、ロールやテキストでの指定は、DOM構造の変化に強く、より堅牢でメンテナンスしやすい。
// 👍 安定した指定
page.getByRole('button', { name: 'submit' });
ロケーターを生成する方法
Playwright には、テストコードやロケーターを自動で生成してくれるテストジェネレーターがある。ページを解析し、ロール、テキスト、test-id といったユーザー視点の優先度の高い属性から最適なロケーターを選択してくれる。複数の要素にマッチする場合は、より特定しやすいようにロケーターを自動で改善してくれるため、壊れにくいテストコードができる。
codegen コマンドの使い方
codegen
コマンドを使い対象のURLを指定すると、ブラウザと Playwright Inspector が立ち上がる。
npx playwright codegen playwright.dev
- 自動で録画が開始されるので、まず「Record」ボタンで録画を停止する
- 「Pick Locator」ボタンが使えるようになるのでクリック
- ブラウザ画面上でマウスを動かすと、カーソル下の要素のロケーター候補が表示される
- クリックすると、そのロケーターがInspectorに追加される
- ロケーターをコピーしてテストコードに貼り付けたり、Inspector上で調整しながらブラウザで動作確認ができる
VSCode 拡張機能の活用
Playwright の VSCode 拡張機能も、ロケーターの生成やテストの録画・実行・デバッグをサポートし、より快適な開発体験を提供する。
デバッグの設定
ローカルでのデバッグには、VSCode 拡張機能をインストールし、VSCode上でライブデバッグすることが推奨される。テストファイルのデバッグしたい行の横で右クリックし、デバッグ実行を選ぶとブラウザが起動し、設定したブレークポイントで停止する。
また、--debug
フラグを付けてテストを実行すると Playwright Inspector が起動する。
npx playwright test --debug
Web First Assertions を使う
Web First Assertions は、期待する条件が満たされるまで自動で待機し、再試行してくれるアサーションのこと。例えば、ボタンをクリックして表示されるメッセージが少し遅れて出てくる場合でも、toBeVisible()
のような Web First Assertion はメッセージが表示されるまで待ってくれるので、テストの安定性が高まる。
// 👍
await expect(page.getByText('welcome')).toBeVisible();
// 👎
expect(await page.getByText('welcome').isVisible()).toBe(true);
Manual Assertions は使わない
expect(await ...)
のように手動で取得した値を使ってアサートする書き方は避けるべき。たとえば isVisible()
を使うと、現在の状態を即時に返すだけで表示されるまで待ってくれないため、非同期UIに弱く、テストが不安定になる。
すべての主要ブラウザでテストする
Playwright は、プラットフォームを問わず、主要なすべてのブラウザ(Chromium、Firefox、WebKit)で簡単にテストができる。複数ブラウザでのテストを行うことで、ユーザー全体に対してアプリが正しく動作することを保証できる。テスト対象のブラウザは、playwright.config.ts
にある projects
配列で指定できる。
以下のようにそれぞれのブラウザを登録しておくことで、1つのテストコードを3つのブラウザで並列実行できる。
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
webkit は、Apple が開発したブラウザエンジンのこと。代表的には Safari がこのエンジンを使っている。Playwright では、webkit プロジェクトを指定することで Safari相当の環境で E2E テストができるようになっている。
Playwright のバージョンは常に最新にする
Playwright を最新バージョンに保つことで、最新のブラウザ環境に対応したテストが可能になり、将来的なバグや不具合を早期に発見できる。ブラウザ側が先にアップデートされてしまうと、古い Playwright では互換性の問題が出ることもあるため、常に最新版にしておくのが安心。
npm install -D @playwright/test@latest
npx playwright --version
CIでテストを実行する
Playwright のテストは、できるだけ頻繁に実行するのが理想。特に、コミットやプルリクエストごとに自動で実行されるようにすることで、不具合を早期に検出できる。GitHub Actions には Playwright 専用のワークフロー雛形があり、特別な設定なしですぐに CI でのテストが可能。もちろん、他のCI環境(GitLab CI、CircleCIなど)でも使える。
テストにもLintをかける
エラーを早期発見するために、TypeScript と ESLint を使ってテストに Lint をかけることを推奨する。@typescript-eslint/no-floating-promises
ルールを使うことで、Playwright API への非同期呼び出しの前に await が抜けていないかを検出できる。
並列実行とシャーディングを使う
Playwright はデフォルトでテストを並列実行する。1つのファイル内のテストは順番に、同じワーカープロセス内で実行される。もし1つのファイルに多くの独立したテストがある場合、それらを並列に実行できる。
import { test } from '@playwright/test';
test.describe.configure({ mode: 'parallel' });
test('runs in parallel 1', async ({ page }) => { /* ... */ });
test('runs in parallel 2', async ({ page }) => { /* ... */ });
テストスイートを シャーディング(分割)して、複数のマシンで実行することもできる。
npx playwright test --shard=1/3
ソフトアサーションを使う
テストが失敗すると、Playwright はどの部分が失敗したかを示すエラーメッセージを出力する。これは VS Code やターミナル、HTMLレポート、Trace Viewer で確認できる。しかし、ソフトアサーションを使うこともできる。ソフトアサーションは、失敗してもすぐにテスト実行を中断せず、テストの終了時に失敗したアサーションの一覧を表示する。
// 失敗してもテストが止まらないチェックをいくつか行う
await expect.soft(page.getByTestId('status')).toHaveText('Success');
// テストを続行してさらにチェックを行う
await page.getByRole('link', { name: 'next page' }).click();
参考