Wiki

← Tất cả bài wiki

Project Dependencies & Global Setup/Teardown (Chạy setup một lần trước toàn bộ test)

automationplaywright

Cơ chế của Playwright cho phép khai báo một project "setup" chạy trước các project test chính (ví dụ login một lần rồi lưu storageState), hoặc dùng globalSetup/globalTeardown để chuẩn bị và dọn dẹp tài nguyên toàn cục. Nó giải quyết bài toán mọi test đều phải login lặp lại → suite chậm và dễ flaky, đồng thời chuẩn hóa bước chuẩn bị môi trường trước khi test chạy.

Khái niệm & Bản chất

  • Project dependencies: trong playwright.config.ts, một project test có thể khai báo dependencies: ['setup'] — nghĩa là nó chỉ bắt đầu chạy sau khi project tên setup hoàn thành thành công. Đây là cách chuẩn để "login một lần, dùng lại cho mọi test".
  • globalSetup / globalTeardown: một file script chạy đúng 1 lần trước (và sau) TOÀN BỘ test run, ở ngoài worker process. Thường dùng để seed database, khởi động service phụ, hoặc login qua API lấy token.
  • storageState là mảnh ghép quan trọng: sau khi login, lưu cookie/localStorage ra file .json, rồi các project test nạp lại qua use: { storageState } → mỗi test mở ra là đã ở trạng thái đăng nhập, không cần đi qua form login.
  • Khác biệt cốt lõi: project dependencies chạy TRONG test runner (là test thật, có fixture, có retry, gắn với project/browser cụ thể), còn globalSetup chạy Ở NGOÀI — một process duy nhất, không có fixture page tự động, không gắn project nào.
  • Teardown project: project cũng có thể khai báo teardown: 'cleanup' — project cleanup sẽ chạy sau khi project phụ thuộc nó kết thúc, dùng để xoá auth file hoặc dọn dữ liệu test.

Cách hoạt động & API chính

  • Trong config, projects là mảng — mỗi phần tử có name, testMatch (lọc file), dependencies, teardown, và use riêng. Project setup thường match pattern .*\.setup\.ts.
  • context.storageState({ path: 'auth.json' }) ghi session hiện tại ra file; use: { storageState: 'auth.json' } trong project khác nạp lại → mọi page/context tạo ra đều kèm session.
  • globalSetup: require.resolve('./global-setup') — function nhận FullConfig làm tham số; nếu return một async function thì function đó được gọi như globalTeardown sau khi mọi test xong.
  • Có thể bỏ qua dependencies bằng CLI --no-deps, hoặc chạy riêng một project bằng --project=setup.
  • Trong file setup, dùng setup() thay test() — import { test as setup } — để code tự nói rõ đây là bước chuẩn bị, không phải test case.

Ví dụ & Cú pháp thực tế

  • Cấu hình projects với dependency và teardown: ``ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ projects: [ // project setup: chạy trước, thực hiện login { name: 'setup', testMatch: /.*\.setup\.ts/ }, { name: 'chromium', use: { storageState: 'playwright/.auth/user.json' }, // nạp session đã lưu dependencies: ['setup'], // chỉ chạy sau khi 'setup' xong teardown: 'cleanup', // chạy 'cleanup' sau khi xong }, // project teardown: dọn dẹp sau cùng { name: 'cleanup', testMatch: /.*\.teardown\.ts/ }, ], }); ``
  • File auth setup — login qua UI rồi lưu storageState: ``ts // tests/auth.setup.ts import { test as setup, expect } from '@playwright/test'; const authFile = 'playwright/.auth/user.json'; setup('đăng nhập và lưu session', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('qa@hoctest.com'); await page.getByLabel('Mật khẩu').fill('secret'); await page.getByRole('button', { name: 'Đăng nhập' }).click(); // assert chắc chắn đã login thành công — fail ngay tại đây thay vì // để cả suite fail hàng loạt await expect(page).toHaveURL(/dashboard/); await page.context().storageState({ path: authFile }); }); ``
  • global-setup với teardown trả về: ``ts // global-setup.ts — chạy 1 lần trước tất cả worker import { chromium, type FullConfig } from '@playwright/test'; async function globalSetup(config: FullConfig) { const baseURL = config.projects[0].use.baseURL; const browser = await chromium.launch(); const page = await browser.newPage({ baseURL }); // ví dụ: warm-up server hoặc seed dữ liệu test qua API await page.request.post('/api/test-seed'); await browser.close(); // function trả về sẽ được gọi SAU khi toàn bộ test kết thúc return async () => { console.log('dọn dẹp dữ liệu test...'); }; } export default globalSetup; ``
  • File teardown project xoá auth file: ``ts // tests/global.teardown.ts import { test as teardown } from '@playwright/test'; import { rmSync } from 'fs'; teardown('xoá file auth sau run', async () => { rmSync('playwright/.auth/user.json', { force: true }); }); ``

Best practices & Khi nào dùng

  • Auth qua UI → dùng project dependencies; việc không cần browser theo project (seed DB, reset queue, tạo user qua API) → dùng globalSetup cho gọn.
  • Luôn assert sau login trong file setup — nếu login hỏng thì fail-fast ngay ở project setup, không phí thời gian chạy cả suite rồi fail hàng loạt với lỗi khó hiểu.
  • Thêm playwright/.auth/ vào .gitignore — file storageState chứa cookie session thật, tuyệt đối không commit.
  • Tách riêng storageState cho từng role (admin.json, user.json) bằng nhiều file setup hoặc test.use({ storageState }) trong describe.
  • Vẫn giữ nguyên tắc test độc lập: login một lần qua storageState là OK vì mỗi test vẫn tự tạo context mới từ state đó — không share browser/page.

Gotchas, So sánh & Lưu ý

  • globalSetup chạy đúng 1 process duy nhất và KHÔNG chia theo shard — khi CI chạy --shard=1/4, MỖI shard đều chạy globalSetup riêng. Nếu seed chung một DB thì cẩn thận việc 4 shard cùng ghi đè.
  • Nếu file storageState chưa tồn tại mà project đã use nó → test fail ngay. Trên CI phải đảm bảo project setup luôn chạy (đừng filter --project bỏ sót nó).
  • dependencies chỉ đảm bảo thứ tự khi chạy trong cùng một lệnh npx playwright test; dùng --no-deps để bỏ qua khi debug nhanh.
  • So với beforeEach login trong mỗi file: dependencies nhanh hơn nhiều (login 1 lần thay vì N lần), và storageState ổn định hơn việc replay UI login.
  • Cookie/token hết hạn giữa run (suite rất dài) có thể làm test cuối fail lạ — cân nhắc refresh storageState hoặc kiểm tra TTL của session app.
  • Biến môi trường baseURL phải trỏ đúng môi trường trong config, vì cả setup lẫn test đều đọc từ đó.