test.describe & Hooks (nhóm test và hàm setup/teardown)
automationplaywright
test.describe là block gom các test cùng feature thành một suite trên report, còn hooks (beforeAll/beforeEach/afterEach/afterAll) là các hàm chạy code setup và teardown tự động quanh test. Bộ đôi này giải quyết bài toán lặp code setup và tổ chức test suite — nhưng cần hiểu rõ scope file vs worker để không bị lệch state khi chạy parallel.
Khái niệm & Bản chất
- test.describe = block nhóm các test cùng chủ đề (feature, màn hình, flow). Trên HTML report chúng hiển thị như một suite, có thể lồng nhau nhiều tầng.
- Hooks = hàm chạy trước/sau test tự động:
beforeEachchạy trước TỪNG test,beforeAllchạy 1 lần cho cả nhóm,afterEach/afterAlltương ứng cho teardown. - Test isolation vs worker scope: mỗi test luôn có
page/contextmới tinh (isolation mặc định), nhưng worker process được tái sử dụng — nênbeforeAllchạy 1 lần per worker per describe, không phải 1 lần tuyệt đối cho cả suite. - Khi config
fullyParallel: true, các test trong cùng file có thể bị rải sang nhiều worker →beforeAllcủa describe đó sẽ chạy lại ở mỗi worker. Đừng giả định beforeAll chỉ chạy đúng 1 lần.
Cách hoạt động & API chính
test.beforeEach/test.afterEach: đặt trong describe thì chỉ áp dụng cho test trong nhóm đó; đặt ngoài describe ở level file thì áp dụng cho toàn bộ test trong file.test.beforeAll/test.afterAll: chạy 1 lần trước/sau tất cả test trong scope (file hoặc describe).- Hook nhận fixtures giống hệt test — nhưng
beforeAll/afterAllkhông dùng đượcpagevàcontext(đó là test-scoped fixture); phải tựbrowser.newPage()từ browser fixture hoặc dùngrequest. - Thứ tự chạy:
beforeAll→ (beforeEach→ test →afterEach) × N →afterAll. - Describe lồng nhau: hook của describe ngoài chạy TRƯỚC hook describe trong (before) và SAU (after) — tạo được setup nhiều lớp.
test.describe.only/test.describe.skip/test.describe.fixmeáp dụng cho cả nhóm;test.describe.configure({ mode: 'serial' })đổi mode chạy.
Ví dụ & Cú pháp thực tế
- Ví dụ cơ bản — nhóm login với đủ 4 loại hook:
``
ts import { test, expect } from '@playwright/test'; test.describe('Login feature', () => { test.beforeAll(async () => { // chạy 1 lần trước cả nhóm — ví dụ seed tài khoản test qua API }); test.beforeEach(async ({ page }) => { // chạy trước TỪNG test trong nhóm await page.goto('/login'); }); test.afterEach(async ({ page }, testInfo) => { // chụp screenshot khi test fail để debug if (testInfo.status !== 'passed') { await page.screenshot({ path:fail-${testInfo.testId}.png}); } }); test.afterAll(async () => { // dọn dữ liệu seed nếu cần }); test('đăng nhập thành công', async ({ page }) => { await page.getByLabel('Email').fill('qa@hoctest.com'); await page.getByLabel('Mật khẩu').fill('secret123'); await page.getByRole('button', { name: 'Đăng nhập' }).click(); await expect(page).toHaveURL(/dashboard/); }); test('báo lỗi khi sai mật khẩu', async ({ page }) => { // ... }); });`` - beforeAll cần page → phải tự tạo từ
browserfixture (worker-scoped): ``ts test.beforeAll(async ({ browser }) => { const page = await browser.newPage(); await page.goto('/admin'); // warm-up / seed state... await page.close(); });`` - Hook level file áp dụng cho mọi describe bên dưới:
``
ts // ngoài mọi describe — chạy trước từng test của cả file test.beforeEach(async ({ page }) => { await page.addInitScript(() => localStorage.setItem('lang', 'vi')); });``
Best practices & Khi nào dùng
- beforeEach cho setup cần sạch mỗi test:
gototrang đầu, set localStorage, reset qua API — vì context mới mỗi test nên setup phải lặp lại là bình thường. - beforeAll chỉ cho việc đắt đỏ và share được: seed DB, tạo tài khoản test dùng chung — và nhớ dọn trong
afterAll. Đừng dùng beforeAll để lưu state mà các test đọc/ghi lẫn nhau (đó là dependency ngầm). - Setup phức tạp lặp nhiều file → dùng fixture thay hook: custom fixture tự teardown theo scope, khai báo dependency rõ ràng, type-safe — mạnh hơn hook khi setup có nhiều bước/tài nguyên.
- Auth → ưu tiên storageState + setup project thay vì login UI trong beforeEach mỗi test (nhanh và ổn định hơn nhiều).
- Giữ describe tối đa 1–2 tầng lồng — hook nhiều lớp khó trace thứ tự chạy.
Gotchas, So sánh & Lưu ý
beforeAllkhông nhậnpage/context— lỗi phổ biến nhất của người mới: viếttest.beforeAll(async ({ page }) => ...)sẽ báo fixture không tồn tại. Dùngbrowserhoặcrequest.- Với
fullyParallel: true,beforeAllchạy lại ở mỗi worker chứa test của nhóm → seed data trùng nhau có thể xung đột; dùngworkerIndexhoặc khoá riêng per worker. afterAllkhông đảm bảo chạy nếu worker bị kill giữa chừng (timeout cứng, CI kill) — đừng đặt cleanup sống-còn (xoá user thật, huỷ đơn) vào afterAll; tốt hơn là data tự hủy (TTL, tên unique).- So với Jest/Mocha: tương tự
describe/beforeEach... nhưng khác biệt lớn là model worker + isolation của Playwright — state share qua biến module chỉ sống trong 1 worker. - Hook chạy được cả trong
test.step? Không — step chỉ dùng trong test/fixture body, đừng nhầm hook với step. test.describe.parallel/test.describe.serialghi đè mode parallel của nhóm — đọc thêm bài serial-mode.
