Network Interception với page.route (Chặn và điều khiển request mạng)
automationplaywright
page.route là cơ chế của Playwright cho phép chặn mọi HTTP request đi ra từ trang web, rồi quyết định xử lý nó: cho đi tiếp (route.continue), sửa header/payload, gọi request thật rồi sửa response (route.fetch), trả response giả (route.fulfill) hoặc huỷ luôn (route.abort). Đây là nền tảng cho mock API, test offline, test edge case lỗi mạng — thứ mà Selenium gần như không làm được.
Khái niệm & Bản chất
- Network interception = đứng giữa trang web và server, bắt mọi request trước khi nó rời khỏi browser, rồi quyết định số phận của nó. Playwright làm việc này ở tầng browser network stack nên thấy cả request lẫn response thật.
- page.route(urlPattern, handler) đăng ký một route theo pattern: glob (
**/api/users), RegExp, hoặc function nhận URL trả về boolean. - Route handler nhận đối tượng
route+request. Trong handler bạn PHẢI gọi đúng 1 trong các lệnh kết thúc route:continue,fulfill,abort— nếu không request sẽ treo. - Bản chất khác mock truyền thống: interception chặn request thật của app, nên UI vẫn chạy code thật (fetch/axios), chỉ khác là response đến từ bạn thay vì server. Test sát thực tế hơn hẳn việc stub hàm JS.
- Hai tầng route:
page.route()chỉ áp dụng cho 1 page;context.route()áp dụng cho mọi page trong browser context — dùng khi app mở nhiều tab.
Cách hoạt động & API chính
- route.continue(overrides?): cho request đi tiếp tới server, tuỳ chọn override
url,method,headers,postData. Dùng để inject auth token, đổi môi trường API, sửa payload. - route.fetch(overrides?): Playwright tự gọi request thật tới server và trả về
APIResponse— bạn đọc response rồi sửa rồi mớifulfill. Dùng khi muốn 'nửa thật nửa giả': data thật nhưng thay 1 vài field. - route.fulfill(options): trả response giả không cần server. Options:
status,headers,body/json,path(file trên disk),response(kết hợp route.fetch). - route.abort(errorCode?): huỷ request, giả lập lỗi mạng. Error code:
'failed','timedout','connectionrefused','internetdisconnected'... Dùng test UI khi mất mạng/API chết. - route.fallback(): nhảy sang handler kế tiếp nếu có nhiều route trùng pattern (handler đăng ký sau chạy trước).
- page.unroute(): gỡ route đã đăng ký. Nhớ dọn route trong test dài để tránh rò rỉ.
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; test('chặn request và sửa header', async ({ page }) => { // Chặn mọi request tới /api/, thêm token auth await page.route('/api/**', async (route) => { const headers = { ...route.request().headers(), 'x-custom-token': 'test-token-123', }; await route.continue({ headers }); }); await page.goto('https://app.hoctest.com'); // App vẫn gọi API thật, nhưng header đã có token });
- ```ts test('gọi thật rồi sửa response', async ({ page }) => { await page.route('**/api/products/1', async (route) => { // Gọi API thật lấy response const response = await route.fetch(); const body = await response.json(); // Sửa 1 field để test UI hiển thị giá sale body.price = 999; body.onSale = true; await route.fulfill({ response, json: body }); }); await page.goto('/products/1'); await expect(page.locator('.price')).toHaveText('999'); });
- ```ts test('giả lập mất mạng', async ({ page }) => { await page.goto('/dashboard'); // Sau khi trang load, chặn và abort mọi API await page.route('/api/', (route) => route.abort('internetdisconnected')); await page.getByRole('button', { name: 'Refresh' }).click(); await expect(page.getByText('Không có kết nối mạng')).toBeVisible(); });
- ```ts // Pattern nâng cao: route theo function và method await page.route( (url) => url.pathname === '/api/orders' , async (route) => { if (route.request().method() === 'DELETE') { await route.abort(); // Chặn riêng DELETE } else { await route.continue(); } } );
Best practices & Khi nào dùng
- Khi nào dùng: test edge case khó tái hiện trên backend thật (lỗi 500, timeout, data rỗng, data xấu), tách FE khỏi BE chưa xong, chạy test ổn định không phụ thuộc môi trường backend, cắt request tracking/ads cho test nhanh hơn.
- Đăng ký route TRƯỚC page.goto() — route chỉ bắt request phát sinh sau khi đăng ký. Request của lần load đầu tiên sẽ lọt nếu goto trước.
- Chặn càng hẹp càng tốt: pattern
**/api/userstốt hơn**/*— route rộng làm chậm test và dễ vô tình chặn nhầm request quan trọng (VD: request load JS bundle). - Với suite lớn: bọc setup mock vào fixture/hook
test.beforeEachđể không lặp code; dùngcontext.routenếu app dùng multi-tab. - Chặn resource nặng để tăng tốc:
route.abortcho**/*.{png,jpg,woff2}hoặc analytics khi test không cần ảnh/font.
Gotchas, So sánh & Lưu ý
- Quên gọi continue/fulfill/abort → request treo vĩnh viễn, test timeout không rõ lý do. Lỗi phổ biến nhất khi mới dùng route.
- Route không bắt request từ Service Worker và một số request low-level; HAR replay/route cũng không thấy WebSocket frame (WS dùng API riêng — xem bài websockets).
- So với Selenium: Selenium không có network interception tích hợp (phải dùng CDP riêng hoặc proxy kiểu BrowserMob). Playwright làm sẵn ở API level — đây là lý do lớn QA chuyển sang.
- So với cy.intercept (Cypress): tương đương về ý tưởng, nhưng Playwright route chạy ở Node nên linh hoạt hơn (đọc file, gọi API khác bằng route.fetch) và không bị giới hạn trong browser.
- CORS không phải vấn đề: vì interception xảy ra trước khi response đến browser check CORS — mock response không cần Access-Control header trừ khi bạn fulfill rồi frontend check lại.
- Nhiều route trùng pattern: handler đăng ký SAU chạy TRƯỚC (LIFO); dùng
route.fallback()để chuyển tiếp xuống handler cũ nếu không xử lý.
