Wiki

← Tất cả bài wiki

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: beforeEach chạy trước TỪNG test, beforeAll chạy 1 lần cho cả nhóm, afterEach/afterAll tương ứng cho teardown.
  • Test isolation vs worker scope: mỗi test luôn có page/context mới tinh (isolation mặc định), nhưng worker process được tái sử dụng — nên beforeAll chạ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 → beforeAll củ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/afterAll không dùng được page và context (đó là test-scoped fixture); phải tự browser.newPage() từ browser fixture hoặc dùng request.
  • 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ừ browser fixture (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: goto trang đầ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 ý

  • beforeAll không nhận page/context — lỗi phổ biến nhất của người mới: viết test.beforeAll(async ({ page }) => ...) sẽ báo fixture không tồn tại. Dùng browser hoặc request.
  • Với fullyParallel: true, beforeAll chạy lại ở mỗi worker chứa test của nhóm → seed data trùng nhau có thể xung đột; dùng workerIndex hoặc khoá riêng per worker.
  • afterAll khô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.serial ghi đè mode parallel của nhóm — đọc thêm bài serial-mode.