Danh sách bài viết

Bài 99: Setup Project Vs Legacy globalSetup — Khi Nào Dùng Cái Nào

Bài này tập trung vào quyết định: chọn setup project hay giữ legacy globalSetup. Bảng so sánh 9 tiêu chí, phân tích khi nào từng approach phù hợp, hybrid pattern kết hợp cả hai, migration step-by-step, 4 pitfall và quiz 5 câu. Nội dung cú pháp chi tiết của từng approach đã được trình bày ở bài 56 và bài 97.

28/05/2026
0 lượt xem
1

Mục Tiêu Bài Học

Sau khi hoàn thành bài này, bạn sẽ:

  • Đọc được bảng so sánh 9 tiêu chí và biết tiêu chí nào quan trọng với dự án mình.
  • Quyết định đúng approach cho từng loại setup requirement.
  • Kết hợp globalSetup và setup project trong hybrid pattern mà không conflict.
  • Migrate từ globalSetup sang setup project theo 4 bước với code before/after.
  • Tránh 4 pitfall thường gặp khi chọn sai approach hoặc migrate nửa vời.
2

Bảng So Sánh 9 Tiêu Chí

Playwright docs (v1.31+) chính thức recommend setup project thay cho globalSetup khi setup cần browser. Bảng dưới trình bày đầy đủ sự khác biệt:

Tiêu chí globalSetup (legacy) Setup project
Hiện trong report Không — chạy ngoài reporter Có — là project thực sự
Retry Không Có — theo retries config
Trace / video Không Có — đầy đủ trace, screenshot, video
UI Mode debug Không Có — hiện trong UI Mode như test thường
Fixture access Không — phải chromium.launch() thủ công Có — dùng page, context, request
Per-browser setup 1 lần chung cho toàn bộ suite Có thể per project nếu cần
Parallelism Serial, 1 lần duy nhất Nhiều setup project chạy song song
Assertion Manual throw / console.error expect() built-in, auto fail + retry
Dependency graph Không — chạy trước mọi thứ cứng dependencies array, linh hoạt per project

Nhìn vào bảng, setup project chiếm ưu thế ở mọi tiêu chí liên quan đến observability và developer experience. globalSetup vẫn hữu ích trong một số trường hợp cụ thể được phân tích ở phần 4.

3

Khi Nào Dùng Setup Project

Playwright recommend setup project cho các tình huống sau:

Auth — login và lưu storageState

Đây là use case chính. Setup project dùng page fixture trực tiếp, có assertion, trace nếu fail:

// auth.setup.ts
import { test as setup, expect } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.TEST_USER!);
  await page.getByLabel('Password').fill(process.env.TEST_PASS!);
  await page.getByRole('button', { name: 'Login' }).click();
  await expect(page).toHaveURL('/dashboard');
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

Khi login fail, report hiện rõ setup test nào fail, kèm screenshot và trace — không phải main tests fail bí ẩn.

Cần report visibility

Mọi setup test đều xuất hiện trong HTML report. Trên CI, khi nhìn vào report có thể phân biệt ngay: "setup fail" hay "test logic fail". Với globalSetup, chỉ có stdout log — không có artifact đính kèm.

Cần debug qua UI Mode hoặc Trace

UI Mode (npx playwright test --ui) hiện setup project như một project thường — có thể click vào từng test để xem trace, video. globalSetup không xuất hiện trong UI Mode.

Multi-role auth

Nhiều setup project chạy song song, mỗi role một file setup riêng. globalSetup buộc làm thủ công trong 1 file, khó maintain khi số role tăng.

Modern codebase hoặc dự án mới

Với dự án bắt đầu từ v1.31+, không có lý do dùng globalSetup cho browser-based setup. Setup project là approach mặc định.

4

Khi Nào Vẫn Dùng globalSetup

globalSetup vẫn phù hợp trong các tình huống cụ thể:

Env validation trước mọi thứ

Kiểm tra các service có up không, biến môi trường có đủ không — logic này không cần browser và phải chạy TRƯỚC khi bất kỳ project nào khởi động:

// infra-check.ts — globalSetup
import { FullConfig } from '@playwright/test';
import fetch from 'node-fetch';

export default async function globalSetup(config: FullConfig) {
  const base = config.projects[0].use.baseURL;
  const res = await fetch(`${base}/health`);
  if (!res.ok) {
    throw new Error(`App not ready: ${res.status}. Check deployment before running tests.`);
  }
  if (!process.env.TEST_USER || !process.env.TEST_PASS) {
    throw new Error('TEST_USER and TEST_PASS env vars are required');
  }
}

Khi throw, toàn bộ run dừng ngay với thông báo rõ ràng — không project nào chạy lãng phí tài nguyên.

One-time infra không cần report

Start Docker container, chạy DB migration, seed data tĩnh — đây là pure Node, không cần browser, không cần report visibility. globalSetup đơn giản hơn, không cần config project phụ.

Legacy không muốn refactor

Nếu codebase đã có globalSetup hoạt động ổn định và không có nhu cầu debug setup qua trace, không cần migrate vội. globalSetup không bị deprecated — vẫn hoạt động trong v1.50+.

Setup không dùng browser (pure Node)

Tạo test data qua API REST, populate cache Redis, generate fixture files — những việc này chạy nhanh hơn và đơn giản hơn trong Node function. Setup project mang thêm overhead khởi động browser không cần thiết.

5

Hybrid Approach

Hai approach không loại trừ nhau. Pattern phổ biến trong production suite là dùng cả hai với phân công rõ ràng:

  • globalSetup — infra: DB migrate, Docker start, env check. Pure Node, không browser, không cần report.
  • Setup project — auth: login UI, save storageState. Cần browser, cần trace khi fail.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // globalSetup chạy TRƯỚC tất cả projects
  // Dùng cho infra: DB migration, Docker, env validation
  globalSetup: require.resolve('./infra-setup'),

  projects: [
    // Setup project chạy auth — cần browser + report
    {
      name: 'setup',
      testMatch: /.*\.setup\.ts/,
    },

    // Main projects depend vào auth setup
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
    {
      name: 'firefox',
      use: {
        ...devices['Desktop Firefox'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
});

Thứ tự thực thi trong hybrid pattern:

  1. globalSetup chạy — DB migrate, check services health.
  2. Setup project chạy — login UI, ghi playwright/.auth/user.json.
  3. Main projects chạy song song — đọc auth file, chạy test.
  4. globalTeardown chạy (nếu có) — cleanup infra.

Điểm cần chú ý: globalSetup không thể depend vào setup project — nó luôn chạy trước. Nếu infra setup cần auth token (ví dụ: seed data qua authenticated API), phải tự xử lý auth trong globalSetup bằng fetch hoặc SDK, không dùng lại auth file từ setup project.

6

Migration globalSetup → Setup Project

Migration từ globalSetup auth sang setup project theo 4 bước:

Step 1 — Tạo file auth.setup.ts với test() syntax

Tạo file tests/auth.setup.ts. Import test as setup từ @playwright/test:

// tests/auth.setup.ts (file mới)
import { test as setup, expect } from '@playwright/test';

Step 2 — Move logic từ global-setup.ts sang setup file

Code before/after:

// BEFORE: global-setup.ts (legacy)
import { chromium, FullConfig } from '@playwright/test';

async function globalSetup(config: FullConfig) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('/login');
  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Login' }).click();
  await page.waitForURL('/dashboard');
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
  await browser.close();  // Bắt buộc — phải close thủ công
}

export default globalSetup;
// AFTER: tests/auth.setup.ts (setup project)
import { test as setup, expect } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  // page fixture tự manage bởi Playwright — không cần launch/close
  await page.goto('/login');
  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Login' }).click();
  // Assertion built-in — fail rõ ràng thay vì continue với state sai
  await expect(page).toHaveURL('/dashboard');
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

Điểm khác biệt chính sau migration:

  • Không cần chromium.launch() / browser.close()page fixture tự cleanup.
  • Thêm await expect(page).toHaveURL('/dashboard') — assertion thay vì silent fail.
  • Code ngắn hơn và dùng đúng Playwright idiom.

Step 3 — Config setup project và dependencies

// playwright.config.ts — thêm setup project + dependencies
export default defineConfig({
  projects: [
    {
      name: 'setup',
      testMatch: /.*\.setup\.ts/,
    },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],  // Thêm dòng này
    },
  ],
});

Step 4 — Bỏ globalSetup config

// TRƯỚC
export default defineConfig({
  globalSetup: require.resolve('./global-setup'),  // Xóa dòng này
  projects: [...],
});

// SAU
export default defineConfig({
  // globalSetup không còn ở đây
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    { name: 'chromium', ..., dependencies: ['setup'] },
  ],
});

Sau khi xóa globalSetup config, file global-setup.ts có thể xóa hoặc giữ lại nếu muốn so sánh. Không có impact đến test execution vì config không reference nó nữa.

7

Decision Tree

Quy trình quyết định khi gặp một setup requirement mới:

  1. Setup có cần mở browser không?
    • Có → dùng setup project.
    • Không → tiếp tục câu 2.
  2. Cần thấy kết quả setup trong report hoặc debug qua UI Mode?
    • Có → dùng setup project (dù không cần browser, có thể dùng request fixture).
    • Không → tiếp tục câu 3.
  3. Setup là pure Node (DB, Docker, env check)?
    • Có → globalSetup phù hợp.
    • Không → tiếp tục câu 4.
  4. Dự án có nhiều role cần auth riêng?
    • Có → dùng setup project (nhiều setup project song song, mỗi role một file).
    • Không → cả hai đều OK, ưu tiên setup project cho modern codebase.

Tóm gọn: nếu setup liên quan đến browser hoặc cần observability, chọn setup project. Nếu setup là pure Node infra và không cần trace, globalSetup đơn giản hơn và không cần config thêm.

8

Limitation Từng Approach

globalSetup

  • Invisible trong report — fail chỉ hiện trong stdout. Trên CI với log dài, message setup fail dễ bị trôi qua.
  • Không retry — nếu service chưa ready và health check fail trong vài giây đầu, phải tự implement retry loop thủ công.
  • Không có trace/screenshot — khi browser-based setup fail, không có artifact để debug. Chỉ có console.log.
  • Browser lifecycle thủ công — phải tự gọi browser.close(). Quên close → process leak, CI hang.
  • Không nhận tính năng mới — Playwright docs note rằng globalSetup không nhận update tính năng từ v1.31+.

Setup project

  • Config phức tạp hơn — cần thêm một project entry và dependencies array trên mỗi main project. globalSetup chỉ cần một dòng config.
  • Chạy lại mỗi invocation — không cache auth file giữa các run. Mỗi npx playwright test đều chạy lại setup (trừ khi dùng --no-deps).
  • Không thể chạy trước infra check — setup project không thể chạy trước globalSetup. Nếu cần kiểm tra service health trước auth, phải kết hợp hybrid.
  • State chỉ qua file system — không share JavaScript heap với main projects. Mọi data transfer phải đi qua file trên disk.
9

4 Pitfall

Pitfall 1 — Dùng globalSetup cho auth, mất report visibility

Setup login bằng globalSetup: code chạy được, nhưng khi login fail trên CI, report chỉ hiện main tests fail với lỗi redirect về trang login. Không có trace, không có screenshot, không biết login fail ở bước nào. Developer mất thời gian debug main tests trong khi root cause là globalSetup silently fail hoặc save state trước khi login hoàn thành.

Cách tránh: auth luôn dùng setup project. Khi login fail, report hiện ngay setup test nào fail, kèm trace và screenshot đầy đủ.

Pitfall 2 — Migrate nửa vời, cả hai cùng làm auth

Developer tạo auth.setup.ts mới nhưng quên xóa globalSetup config. Cả hai chạy: globalSetup login và ghi user.json, sau đó setup project cũng login và ghi đè user.json. Bình thường vẫn pass — cho đến khi globalSetup dùng credential cũ (hard-code) còn setup project dùng env var, hai state khác nhau. Test fail không nhất quán tùy thứ tự ghi file.

Cách tránh: sau khi setup project hoạt động đúng, xóa ngay globalSetup config và file cũ.

Pitfall 3 — Setup project quên dependencies, race condition

// Config thiếu dependencies — race condition
{
  name: 'chromium',
  use: {
    storageState: 'playwright/.auth/user.json',
    // Thiếu: dependencies: ['setup']
  },
}

Test runner có thể khởi động main project song song với setup. Khi chromium worker đọc user.json trước khi setup project ghi xong, context load state cũ hoặc trống. Lỗi xuất hiện không nhất quán — đôi khi pass (setup vừa xong kịp), đôi khi fail (setup chậm hơn). Race condition khó reproduce hơn fail nhất quán rất nhiều.

Cách tránh: mọi main project dùng storageState phải có dependencies: ['setup'].

Pitfall 4 — globalSetup launch browser nhưng không close

// Sai — browser leak
async function globalSetup(config) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('/login');
  // ... login logic
  await page.context().storageState({ path: authFile });
  // Thiếu: await browser.close();
}

Process treo, CI job không exit — sau một thời gian bị kill bởi CI timeout. Trên local, thấy Chromium process zombie sau khi test xong. Với setup project, page fixture tự cleanup — không có rủi ro này.

Cách tránh nếu vẫn dùng globalSetup: wrap toàn bộ logic trong try/finally:

async function globalSetup(config) {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('/login');
    // ... login
    await page.context().storageState({ path: authFile });
  } finally {
    await browser.close();  // Luôn close dù có exception
  }
}
10

Quiz

Câu 1. Suite hiện tại dùng globalSetup cho auth. Trên CI login thỉnh thoảng fail do mạng chậm, nhưng không có cách nào biết fail ở bước nào vì report không hiện gì. Bạn nên migrate sang approach nào và tại sao?

Đáp án

Migrate sang setup project. Setup project chạy như một test thực sự — khi fail, report hiện đúng test nào fail, kèm trace và screenshot. Có thể mở trace viewer để xem từng step: goto('/login'), fill credentials, click button — thấy ngay bước nào timeout hay element không tìm thấy. Thêm retries config để tự retry khi mạng chậm.

Câu 2. Dự án cần: (1) kiểm tra DB sẵn sàng trước mọi thứ, (2) login UI và lưu auth. Bạn sẽ dùng approach nào cho từng phần?

Đáp án

Hybrid approach: globalSetup cho phần (1) — kiểm tra DB là pure Node, chạy trước hết, không cần browser, không cần report visibility. Setup project cho phần (2) — login cần browser, nên dùng setup project để có trace và assertion khi fail. Config globalSetup: require.resolve('./db-check')projects: [{ name: 'setup', testMatch: /.*\.setup\.ts/ }, ...].

Câu 3. Config sau hoạt động không? Nếu có vấn đề, chỉ ra cụ thể:

export default defineConfig({
  globalSetup: require.resolve('./global-setup'),
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { storageState: 'playwright/.auth/user.json' },
      dependencies: ['setup'],
    },
  ],
});
Đáp án

Config hoạt động được về mặt syntax, nhưng có khả năng conflict nếu cả globalSetup và setup project đều thực hiện auth vào cùng file user.json. globalSetup chạy trước, ghi file. Sau đó setup project chạy và ghi đè. Nếu hai file dùng credential khác nhau hoặc có logic khác nhau, state cuối cùng phụ thuộc vào thứ tự ghi — không nhất quán. Nếu globalSetup chỉ làm infra (không auth), config này là hybrid hợp lệ.

Câu 4. Sau khi migrate thành công, developer chạy npx playwright test --project=chromium --no-deps trong giờ dev. Auth file tồn tại từ sáng, nhưng các test fail với redirect về login. Nguyên nhân có thể là gì?

Đáp án

Session đã expire. Flag --no-deps bỏ qua setup project — main project dùng auth file cũ từ lần chạy trước. Nếu session timeout của app ngắn hơn khoảng cách giữa hai lần chạy, auth file không còn hợp lệ. Chạy lại setup để refresh: npx playwright test --project=setup, sau đó chạy lại main tests với --no-deps.

Câu 5. Khi nào globalSetup phù hợp hơn setup project dù setup có dùng fetch để gọi API (không dùng browser)?

Đáp án

Khi cần chạy TRƯỚC mọi project (infra validation, env check) và không cần report visibility. Setup project luôn chạy trong pipeline của test runner — sau khi globalSetup xong. Nếu muốn fail fast trước khi bất kỳ project nào khởi động (ví dụ: kiểm tra service health, validate env vars), chỉ globalSetup mới đảm bảo thứ tự đó. Setup project dùng request fixture cũng gọi được API, nhưng vẫn chạy sau globalSetup và cần config project entry.