APIRequestContext & API Testing (Test API trực tiếp, không cần mở trang)
automationplaywright
Playwright có sẵn HTTP client (APIRequestContext) để gọi REST API trực tiếp từ test — không cần browser, không cần thư viện ngoài như axios. Giá trị lớn: test API thuần nhanh gọn, và đặc biệt là pattern hybrid — dùng API để setup/teardown data, dùng UI để verify — giúp test vừa nhanh vừa ổn định.
Khái niệm & Bản chất
- APIRequestContext là HTTP client tích hợp của Playwright: gọi được GET/POST/PUT/DELETE..., tự quản lý cookie jar, hỗ trợ
baseURL, auth headers, và storageState. - Fixture
requesttrong@playwright/testlà một APIRequestContext sẵn sàng dùng — tách biệt với browser context (không share cookie với page). - request.newContext() tạo context riêng với config riêng — VD: context có token admin vs context token user để test phân quyền.
- Giá trị cốt lõi — hybrid UI+API: dữ liệu chuẩn bị bằng API (nhanh, đúng state) → verify trên UI (đúng thứ user thấy). Test nhanh gấp nhiều lần so với thao tác UI để seed data.
- APIResponse trả về có
status(),ok(),json(),text(),headers()— đủ cho mọi assert API.
Cách hoạt động & API chính
- Gọi request:
request.get(url, options),request.post(url, { data, headers }),request.put/patch/delete/fetch(method tuỳ ý). - Options thường dùng:
data(object → JSON body, hoặc string/Buffer),params(query string object),headers,form(urlencoded),multipart(upload file),timeout,failOnStatusCode(mặc định false — tự assert status). - baseURL: config trong
playwright.config.tsdướiuse: { baseURL }hoặc khirequest.newContext({ baseURL })— gọirequest.get('/api/users')gọn. - Auth:
extraHTTPHeaders: { Authorization: 'Bearer ...' }khi tạo context, hoặchttpCredentialscho Basic Auth; storageState tái dùng được luôn cho API context. - Chia sẻ cookie với browser: lấy
context.storageState()của browser context → truyền vàorequest.newContext({ storageState })để API context 'đăng nhập sẵn'.
Ví dụ & Cú pháp thực tế
- ``
ts import { test, expect, request } from '@playwright/test'; test('test API thuần — không cần browser', async ({ request }) => { const res = await request.post('/api/auth/login', { data: { email: 'qa@hoctest.com', password: '123456' }, }); expect(res.ok()).toBeTruthy(); expect(res.status()).toBe(200); const body = await res.json(); expect(body.token).toBeTruthy(); // Dùng token cho request sau const profile = await request.get('/api/me', { headers: { Authorization:Bearer ${body.token}` }, }); expect((await profile.json()).email).toBe('qa@hoctest.com'); }); - ``
ts // Pattern hybrid: setup bằng API, verify bằng UI test('hiển thị đơn hàng vừa tạo', async ({ page, request }) => { // 1. Seed data bằng API — nhanh, không qua UI const created = await request.post('/api/orders', { data: { productId: 5, qty: 2 }, }); const order = await created.json(); // 2. Mở UI verify await page.goto(/orders/${order.id}); await expect(page.getByText(#${order.id})).toBeVisible(); await expect(page.locator('.qty')).toHaveText('2'); // 3. Teardown bằng API await request.delete(/api/orders/${order.id}`); }); - ``
ts // Context riêng với auth — test 2 role song song test('admin xoá được, user thường không', async ({ playwright }) => { const adminCtx = await playwright.request.newContext({ baseURL: 'https://api.hoctest.com', extraHTTPHeaders: { Authorization:Bearer ${ADMIN_TOKEN}}, }); const userCtx = await playwright.request.newContext({ baseURL: 'https://api.hoctest.com', extraHTTPHeaders: { Authorization:Bearer ${USER_TOKEN}` }, }); expect((await adminCtx.delete('/api/items/1')).status()).toBe(204); expect((await userCtx.delete('/api/items/2')).status()).toBe(403); await adminCtx.dispose(); await userCtx.dispose(); }); - ```ts // Upload file + query params await request.post('/api/import', { params: { dryRun: 'true' }, multipart: { file: { name: 'users.csv', mimeType: 'text/csv', buffer: csvBuffer }, }, });
- ```ts // Browser context → API context: reuse phiên đăng nhập const state = await context.storageState(); const api = await playwright.request.newContext({ storageState: state }); // api giờ đã có cookie session như browser await api.get('/api/me');
Best practices & Khi nào dùng
- Nguyên tắc vàng: API cho setup/teardown/assert state, UI cho verify thứ user tương tác. Đừng dùng UI để tạo data mà API làm được trong 200ms.
- Assert cả status lẫn body:
expect(res.ok())chỉ check 2xx — với API cần check schema:expect(body).toMatchObject({ id: expect.any(Number) }). - Config baseURL theo env: đặt
use.baseURLtrong config hoặc env var — đổi env chỉ sửa 1 chỗ. - Auth 1 lần trong setup: login bằng API trong
globalSetup/setup project, lưu token/storageState — mọi test API reuse, không login lại. - dispose context tự tạo:
await ctx.dispose()giải phóng kết nối; fixturerequestthì Playwright tự lo.
Gotchas, So sánh & Lưu ý
- request fixture ≠ browser context: cookie của
pagevàrequestkhông share tự động — phải qua storageState (xem ví dụ trên). - So với axios/supertest: APIRequestContext đủ mạnh cho test (cookie jar, storageState, tracing tích hợp trong HTML report); axios quen thuộc hơn nhưng phải tự lo cookie/auth. Dùng request của Playwright cho đồng bộ.
- failOnStatusCode mặc định false:
request.gettrả 404 không throw — phải tựexpect(res.status()), đừng giả định ok. - data vs form vs multipart:
datagửi JSON (content-type application/json),formgửi urlencoded,multipartcho file. BE nhận sai format → 400 khó hiểu. - Request API context không hiện trong network tab của page — debug bằng
console.log(await res.text())hoặc bật trace. - CORS không áp dụng: request context gọi thẳng từ Node, không qua browser nên không bị CORS — nghĩa là API test có thể pass dù thực tế browser gọi bị chặn CORS. Nhớ test CORS case riêng nếu cần.
