Wiki

← Tất cả bài wiki

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 request trong @playwright/test là 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.ts dưới use: { baseURL } hoặc khi request.newContext({ baseURL }) — gọi request.get('/api/users') gọn.
  • Auth: extraHTTPHeaders: { Authorization: 'Bearer ...' } khi tạo context, hoặc httpCredentials cho 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ào request.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.baseURL trong 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; fixture request thì Playwright tự lo.

Gotchas, So sánh & Lưu ý

  • request fixture ≠ browser context: cookie của page và request khô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.get trả 404 không throw — phải tự expect(res.status()), đừng giả định ok.
  • data vs form vs multipart: data gửi JSON (content-type application/json), form gửi urlencoded, multipart cho 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.