Playwright ネットワーク傍受と Mock
この章では、Playwright を使用してネットワークリクエストを傍受・変更・模擬し、バックエンド API に依存しない独立したテストを実現する方法を紹介します。
ネットワーク傍受とは
ネットワーク傍受により、ブラウザが HTTP リクエストを発行する際に介入できます。
次のことができます:リクエストのブロック、レスポンスの変更、モックデータの返却により、テストを外部 API やネットワーク状態に依存させません。
page.route() によるリクエスト傍受
page.route(url, handler)指定した URL パターンに一致するすべてのネットワークリクエストを傍受するために使用します。
基本の傍受
例
test('すべての画像リクエストを傍受', async ({ page }) => {
// すべての PNG および JPEG 画像を傍受
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
// ページに移動(画像は読み込まれない)
await page.goto('https://www.example.com/');
// 画像をダウンロードしないため、ページの読み込みが速くなる可能性があります
});
// すべての PNG および JPEG 画像を傍受
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
// ページに移動(画像は読み込まれない)
await page.goto('https://www.example.com/');
// 画像をダウンロードしないため、ページの読み込みが速くなる可能性があります
});
URL マッチングパターン次の形式をサポートしています:
| パターン | 例 | 説明 |
|---|---|---|
| 完全な URL | 'https://api.example.com/users' | 完全一致 |
| Glob ワイルドカード | '**/api/**' | **任意のパスに一致 |
| 正規表現 | /\/api\/v\d\/users/ | 柔軟なマッチング |
| 関数 | url => url.includes('/api/') | プログラミングロジックによるマッチング |
route.abort() によるリクエストのブロック
例
test('サードパーティ製アナリティクススクリプトをブロック', async ({ page }) => {
// Google Analytics などの分析スクリプトをブロック
await page.route('**/*analytics*', route => route.abort());
// CSS ファイルをブロック
await page.route('**/*.css', route => route.abort());
await page.goto('https://www.example.com/');
// ページはスタイルなしで読み込まれますが、機能テストには影響しません
});
// Google Analytics などの分析スクリプトをブロック
await page.route('**/*analytics*', route => route.abort());
// CSS ファイルをブロック
await page.route('**/*.css', route => route.abort());
await page.goto('https://www.example.com/');
// ページはスタイルなしで読み込まれますが、機能テストには影響しません
});
route.fulfill() によるレスポンスの模擬
route.fulfill()は最も強力な Mock メソッドであり、任意のカスタムレスポンスを返すことができます。
JSON データの Mock
例
test('ユーザー一覧 API の Mock', async ({ page }) => {
// ユーザー一覧 API を傍受し、モックデータを返す
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
json: [
{ id: 1, name: 'サンプル_user', email: '[email protected]' },
{ id: 2, name: 'test_user', email: '[email protected]' },
],
});
});
await page.goto('https://www.example.com/users');
// ページには実際の API データではなく Mock データが表示されます
});
// ユーザー一覧 API を傍受し、モックデータを返す
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
json: [
{ id: 1, name: 'サンプル_user', email: '[email protected]' },
{ id: 2, name: 'test_user', email: '[email protected]' },
],
});
});
await page.goto('https://www.example.com/users');
// ページには実際の API データではなく Mock データが表示されます
});
エラーレスポンスの Mock
例
test('API エラーハンドリングのテスト', async ({ page }) => {
// 500 サーバーエラーを模擬
await page.route('**/api/data', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
json: { error: 'サーバー内部エラー' },
});
});
await page.goto('https://www.example.com/dashboard');
// エラーメッセージが正しく表示されるか検証
await expect(page.getByText('サーバー内部エラー')).toBeVisible();
});
// 500 サーバーエラーを模擬
await page.route('**/api/data', async route => {
await route.fulfill({
status: 500,
contentType: 'application/json',
json: { error: 'サーバー内部エラー' },
});
});
await page.goto('https://www.example.com/dashboard');
// エラーメッセージが正しく表示されるか検証
await expect(page.getByText('サーバー内部エラー')).toBeVisible();
});
route.continue() で変更後に続行
モックデータを直接返すのではなく、実際のリクエストまたはレスポンスを変更してからサーバーへの送信を続けます。
例
test('リクエストヘッダーの変更', async ({ page }) => {
await page.route('**/api/**', async (route, request) => {
// リクエストヘッダーを変更し、カスタムヘッダーを追加
const headers = {
...request.headers(),
'X-Custom-Header': 'サンプル-TEST',
};
await route.continue({ headers });
});
await page.goto('https://www.example.com/');
});
test('POST データの変更', async ({ page }) => {
await page.route('**/api/login', async (route, request) => {
// POST リクエストの body を上書き
const postData = JSON.parse(request.postData() || '{}');
postData.mode = 'test'; // テストマーカーを追加
await route.continue({
postData: JSON.stringify(postData),
});
});
});
await page.route('**/api/**', async (route, request) => {
// リクエストヘッダーを変更し、カスタムヘッダーを追加
const headers = {
...request.headers(),
'X-Custom-Header': 'サンプル-TEST',
};
await route.continue({ headers });
});
await page.goto('https://www.example.com/');
});
test('POST データの変更', async ({ page }) => {
await page.route('**/api/login', async (route, request) => {
// POST リクエストの body を上書き
const postData = JSON.parse(request.postData() || '{}');
postData.mode = 'test'; // テストマーカーを追加
await route.continue({
postData: JSON.stringify(postData),
});
});
});
route.fetch() による実際のリクエスト
Mock ハンドラー関数内では、route.fetch()実際にリクエストを送信して実際のレスポンスを取得し、それを変更できます。
例
test('実際のレスポンスの変更', async ({ page }) => {
await page.route('**/api/products', async route => {
// まず実際のレスポンスを取得
const response = await route.fetch();
const body = await response.json();
// レスポンスデータを変更
body.items[0].name = 'サンプル 変更後の商品名';
// 変更後のデータを返す
await route.fulfill({
status: response.status(),
contentType: 'application/json',
json: body,
});
});
});
await page.route('**/api/products', async route => {
// まず実際のレスポンスを取得
const response = await route.fetch();
const body = await response.json();
// レスポンスデータを変更
body.items[0].name = 'サンプル 変更後の商品名';
// 変更後のデータを返す
await route.fulfill({
status: response.status(),
contentType: 'application/json',
json: body,
});
});
});
Context レベルの傍受 vs Page レベルの傍受
| 方式 | 適用範囲 | 適用シーン |
|---|---|---|
page.route() | 現在のページのみ | 単一ページのネットワーク Mock |
context.route() | この Context 配下のすべてのページ | 複数のページで共有される Mock ルール |
例
test.beforeEach(async ({ context }) => {
// Context 全体で傍受し、すべてのページで有効
await context.route('**/*.css', route => route.abort());
});
test('テスト 1', async ({ page }) => { /* CSS なし */ });
test('テスト 2', async ({ page }) => { /* CSS なし */ });
// Context 全体で傍受し、すべてのページで有効
await context.route('**/*.css', route => route.abort());
});
test('テスト 1', async ({ page }) => { /* CSS なし */ });
test('テスト 2', async ({ page }) => { /* CSS なし */ });
HAR ファイルの記録と再生
HAR(HTTP Archive)ファイルは、ページのすべてのネットワークリクエストを記録し、それを再生して模擬することができます。
例
test('HAR ファイルを使用した Mock', async ({ page }) => {
// HAR ファイルからネットワーク記録を読み込んで再生
await page.routeFromHAR('tests/hars/api-responses.har');
await page.goto('https://www.example.com/');
// 一致するすべてのリクエストは HAR ファイルからレスポンスを取得します
});
// HAR ファイルからネットワーク記録を読み込んで再生
await page.routeFromHAR('tests/hars/api-responses.har');
await page.goto('https://www.example.com/');
// 一致するすべてのリクエストは HAR ファイルからレスポンスを取得します
});
# 录制 HAR 文件 npx playwright open --save-har=tests/hars/api-responses.har https://www.example.com/
その他の拡張HAR ファイルは、大量または複雑な API レスポンスの Mock に適しています。一度記録しておけば、以降のテストはすべて HAR から再生されるため、実際のサーバーに依存しません。