Mocking และ Intercepting Network Requests
จบบทนี้แล้วคุณจะ...
- ใช้
page.route()เพื่อ intercept และ mock network requests - จำลอง API response สำหรับ error states, slow response, empty data
- ทดสอบ UI error handling โดยไม่ต้องพึ่ง backend จริง
- เข้าใจว่าเมื่อไหร่ควร mock เมื่อไหร่ควรใช้ API จริง
1 · ทำไมต้อง Mock?
ในโลกจริง backend อาจ:
- ยังสร้างไม่เสร็จ
- ไม่เสถียร (ล่มบ้าง ช้าบ้าง)
- ยากที่จะจำลอง edge case (error 500, timeout, empty data)
Mock ช่วยให้เราควบคุม response ได้ 100% — test จึงเสถียรและเร็ว
2 · page.route() — อาวุธหลัก
page.route() ดักจับ request ที่ browser ส่งออกไป แล้วให้เรา "ตอบเอง" แทน server จริง:
import { test, expect } from '@playwright/test';
test('shows user list from mocked API', async ({ page }) => {
// ดักจับ request ไปยัง /api/users
await page.route('/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'สมชาย ใจดี' },
{ id: 2, name: 'สมหญิง เก่งมาก' }
])
});
});
await page.goto('/users');
await expect(page.locator('.user-item')).toHaveCount(2);
await expect(page.locator('.user-item').first()).toContainText('สมชาย ใจดี');
});
Pattern ที่ใช้:
/api/users— glob pattern จะ match URL ใดก็ได้ที่ลงท้ายด้วย/api/usersroute.fulfill()— ตอบ response เองโดยไม่ส่งไปถึง server จริง
3 · Mock Error States — ทดสอบ UI เมื่อ API พัง
test('shows error message when API returns 500', async ({ page }) => {
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Internal Server Error' })
});
});
await page.goto('/users');
await expect(page.locator('.error-banner')).toBeVisible();
await expect(page.locator('.error-banner')).toContainText('เกิดข้อผิดพลาด');
});
นี่คือ test ที่ยากมากถ้าต้องทำกับ backend จริง — แต่ mock ทำได้ใน 3 บรรทัด
4 · จำลอง Slow Response
test('shows loading spinner during slow API', async ({ page }) => {
await page.route('**/api/users', async (route) => {
// หน่วงเวลา 3 วินาที
await new Promise(resolve => setTimeout(resolve, 3000));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'สมชาย' }])
});
});
await page.goto('/users');
// ระหว่างรอ ต้องเห็น loading
await expect(page.locator('.loading-spinner')).toBeVisible();
// หลังโหลดเสร็จ loading หายไป
await expect(page.locator('.loading-spinner')).toBeHidden({ timeout: 5000 });
await expect(page.locator('.user-item')).toHaveCount(1);
});
5 · Empty Data — ทดสอบ empty state
test('shows empty state when no data', async ({ page }) => {
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([])
});
});
await page.goto('/users');
await expect(page.locator('.empty-state')).toBeVisible();
await expect(page.locator('.empty-state')).toContainText('ไม่พบข้อมูล');
});
6 · Modify Request — เปลี่ยน request ก่อนส่ง
test('add auth header to all API requests', async ({ page }) => {
await page.route('/api/', async (route) => {
const headers = {
...route.request().headers(),
'Authorization': 'Bearer fake-token-for-testing'
};
await route.continue({ headers });
});
await page.goto('/dashboard');
// page จะส่ง request พร้อม auth header ที่เราเพิ่มให้
});
route.continue() vs route.fulfill():
continue()— ส่ง request ต่อไปยัง server จริง (แต่แก้ไข request ได้)fulfill()— ตอบเองโดยไม่ส่งไปถึง server
7 · เมื่อไหร่ควร Mock vs ใช้ API จริง?
| สถานการณ์ | แนะนำ |
|---|---|
| ทดสอบ UI error handling | Mock |
| ทดสอบ loading state | Mock |
| Backend ยังไม่เสร็จ | Mock |
| ทดสอบ edge case ที่สร้างจริงยาก | Mock |
| ทดสอบ end-to-end flow ทั้งระบบ | API จริง |
| ทดสอบ integration ระหว่าง frontend-backend | API จริง |
หลักการ: Mock เพื่อ isolate สิ่งที่ต้องการทดสอบ — ไม่ใช่เพื่อหลีกเลี่ยง backend
แบบฝึกหัด: สร้าง Mock สำหรับ Scenarios ต่างๆ
สถานการณ์: คุณมีหน้า Product List ที่เรียก GET /api/products และแสดงผลดังนี้:
- สินค้ามี → แสดง list
- สินค้าไม่มี → แสดง "ยังไม่มีสินค้า"
- API error → แสดง "โหลดไม่สำเร็จ กรุณาลองใหม่"
- โหลดช้า → แสดง skeleton loading
โจทย์: เขียน 4 tests ครอบคลุมทั้ง 4 scenarios
ดูคำตอบแนะนำ
import { test, expect } from '@playwright/test';
test.describe('Product List - Network Scenarios', () => {
test('displays product list when API returns data', async ({ page }) => {
await page.route('/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
data: [
{ id: 1, name: 'กาแฟดริป', price: 120 },
{ id: 2, name: 'ชาเขียว', price: 80 }
]
})
});
});
await page.goto('/products');
await expect(page.locator('.product-card')).toHaveCount(2);
await expect(page.locator('.product-card').first()).toContainText('กาแฟดริป');
await expect(page.locator('.product-card').first()).toContainText('120');
});
test('displays empty state when no products', async ({ page }) => {
await page.route('/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: [] })
});
});
await page.goto('/products');
await expect(page.locator('.empty-state')).toBeVisible();
await expect(page.locator('.empty-state')).toContainText('ยังไม่มีสินค้า');
});
test('displays error message on API failure', async ({ page }) => {
await page.route('/api/products', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Server Error' })
});
});
await page.goto('/products');
await expect(page.locator('.error-message')).toBeVisible();
await expect(page.locator('.error-message')).toContainText('โหลดไม่สำเร็จ');
await expect(page.locator('.retry-button')).toBeVisible();
});
test('displays skeleton loading during slow response', async ({ page }) => {
await page.route('/api/products', async (route) => {
await new Promise(resolve => setTimeout(resolve, 3000));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ data: [{ id: 1, name: 'สินค้า', price: 100 }] })
});
});
await page.goto('/products');
// ระหว่างรอ ต้องเห็น skeleton
await expect(page.locator('.skeleton-loading')).toBeVisible();
// หลังโหลดเสร็จ skeleton หายไป
await expect(page.locator('.skeleton-loading')).toBeHidden({ timeout: 5000 });
await expect(page.locator('.product-card')).toHaveCount(1);
});
});
สิ่งที่ต้องจำ
page.route()คือเครื่องมือหลักสำหรับ mock — ดักจับ request แล้วตอบเองroute.fulfill()ตอบเอง,route.continue()ส่งต่อ (อาจแก้ไข)- Mock ช่วยทดสอบ edge cases ที่สร้างจริงยาก — error, slow, empty
- อย่า mock ทุกอย่าง — ยังต้องมี test ที่ใช้ API จริงเพื่อตรวจ integration
- Pattern:
/api/ใช้ glob matching — ยืดหยุ่นในการ match URL