Network Events (request, response, requestfailed & waitForResponse)
automationplaywright
Playwright phát event cho mọi hoạt động mạng của page: request đi ra, response về, request fail. Kết hợp page.waitForResponse/waitForRequest theo pattern Promise.all (bắt đầu chờ TRƯỚC khi trigger action), QA có thể assert chính xác app gọi API nào, nhận status gì — thay vì chỉ nhìn UI đoán già đoán non.
Khái niệm & Bản chất
- Event-driven network: mỗi request/response trong page phát ra event mà test có thể lắng nghe qua
page.on(...). Đây là cơ chế quan sát (observe), khác vớipage.routelà can thiệp (intervene). - Các event chính:
'request'(request vừa bắn ra),'response'(response về),'requestfinished'(hoàn tất, kèm body),'requestfailed'(lỗi DNS/timeout/abort...). - waitForRequest / waitForResponse: biến event thành Promise — 'chờ đến khi request khớp điều kiện xuất hiện'. Đây là cách sync action UI với API call chuẩn của Playwright.
- Bản chất quan trọng: event network chạy song song với test code — nếu bạn chờ SAU khi action đã xảy ra request, bạn miss nó. Phải đăng ký chờ TRƯỚC (pattern Promise.all).
Cách hoạt động & API chính
- page.on('request', handler): handler nhận
Requestobject —.url(),.method(),.postData(),.headers(),.resourceType()(document/xhr/fetch/image...),.frame(). - page.on('response', handler): handler nhận
Response—.status(),.ok(),.url(),.json()(async),.request()(ngược về request). - requestfailed:
request.failure()trả{ errorText }— lý do fail:net::ERR_ABORTED,net::ERR_CONNECTION_REFUSED, timeout... - waitForResponse(urlOrPredicate, options?): trả
Responsekhi có response khớp glob/regex/function.waitForRequesttương tự cho request. - Pattern Promise.all:
const [response] = await Promise.all([page.waitForResponse('**/api/save'), page.click('button')])— chờ và trigger đồng thời, không miss event. - expect(response).toBeOK() — assertion sẵn của Playwright check status 200-299.
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; test('log toàn bộ request và response lỗi', async ({ page }) => { page.on('request', (req) => console.log('>>', req.method(), req.url()) ); page.on('response', (res) => { if (!res.ok()) console.log('<< FAIL', res.status(), res.url()); }); page.on('requestfailed', (req) => console.log('!! FAILED', req.url(), req.failure()?.errorText) ); await page.goto('https://app.hoctest.com'); });
- ```ts // PATTERN CHUẨN: chờ response TRƯỚC khi click — dùng Promise.all test('submit form và verify API response', async ({ page }) => { await page.goto('/register'); await page.fill('#email', 'qa@hoctest.com'); const [response] = await Promise.all([ // Chờ response khớp pattern — đăng ký chờ TRƯỚC page.waitForResponse( (res) => res.url().includes('/api/register') && res.status() === 201 ), page.getByRole('button', { name: 'Đăng ký' }).click(), // trigger SAU ]); const body = await response.json(); expect(body.userId).toBeTruthy(); });
- ```ts // waitForRequest: verify app gọi đúng API với đúng payload test('form gửi đúng payload', async ({ page }) => { const [request] = await Promise.all([ page.waitForRequest( (req) => req.url().includes('/api/search') && req.method() === 'POST' ), page.getByPlaceholder('Tìm kiếm').press('Enter'), ]); const payload = request.postDataJSON(); expect(payload).toMatchObject({ keyword: 'playwright' }); });
- ```ts // Chờ nhiều call API xong hết mới assert UI (tránh flaky) test('dashboard load đủ data', async ({ page }) => { const statsPromise = page.waitForResponse('/api/stats'); const listPromise = page.waitForResponse('/api/orders?*'); await page.goto('/dashboard'); await Promise.all([statsPromise, listPromise]); await expect(page.locator('.stat-card')).toHaveCount(4); });
Best practices & Khi nào dùng
- Khi nào dùng: verify app gọi đúng API/đúng payload (không chỉ nhìn UI), chờ data load xong trước khi assert (ổn định hơn waitForSelector một mình), bắt API lỗi ngầm mà UI không hiển thị rõ, debug flaky test.
- waitForResponse > chờ UI: UI có thể render xong nhưng API chưa trả — chờ response đảm bảo data đã về. Với SPA nên chờ cả hai.
- Dùng predicate function cho điều kiện phức tạp: URL động, check method, check status —
(res) => res.url().includes('/x') && res.request().method() === 'POST'. - Collect vào mảng để assert tổng hợp:
const errors = []; page.on('response', r => !r.ok() && errors.push(r))→ cuối testexpect(errors).toHaveLength(0)— bắt mọi API fail trong flow. - Bọc thành helper:
async function clickAndWait(locator, urlPattern)tái dùng pattern Promise.all khắp suite.
Gotchas, So sánh & Lưu ý
- Miss event vì chờ sau action:
await page.click(); await page.waitForResponse(...)— nếu response về nhanh hơn dòng thứ 2 chạy, test treo tới timeout. LUÔN Promise.all hoặc gán promise trước. - waitForResponse timeout mặc định theo testTimeout (30s) — request không bao giờ xảy ra → timeout chung chung. Debug bằng
page.on('request')log để xem app có thật sự gọi không. - Resource type khác nhau: filter theo
request.resourceType() === 'fetch' | 'xhr'để bỏ qua document/image — API call hiện nay đa số làfetch. - So với page.route: on() chỉ quan sát, route có thể sửa/huỷ. Muốn vừa chặn vừa assert → dùng route với counter/flag trong handler, hoặc dùng cả hai (route trước, on vẫn nhận event).
- response.json() chỉ đọc được 1 lần với body stream — cần nhiều lần thì gán biến:
const body = await res.json(). - Event attach ở page level:
page.ongắn với page đó suốt đời — test dài accumulate handler; nhớpage.removeListenerhoặc page mới cho mỗi test (Playwright mặc định tạo page mới per test nên thường không cần lo).
