Danh sách bài viết

Bài 100: Multiple globalSetup [v1.49]

Từ v1.49, trường globalSetup trong playwright.config.ts chấp nhận array thay vì chỉ một file duy nhất. Mỗi phần tử trong array là một file setup riêng, chạy tuần tự theo thứ tự khai báo. Tương tự, globalTeardown cũng nhận array và chạy theo thứ tự ngược lại (LIFO). Bài này tập trung vào cú pháp mảng, execution order, behavior teardown, pattern kết hợp với setup project, 4 pitfall và quiz 5 câu.

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ẽ:

  • Khai báo globalSetup dưới dạng array nhiều file trong playwright.config.ts (v1.49+).
  • Hiểu thứ tự thực thi: array setup chạy tuần tự từ phần tử đầu đến cuối.
  • Hiểu globalTeardown array chạy theo thứ tự ngược lại (LIFO).
  • Tách logic khởi tạo thành các file độc lập theo concern: DB migration, seed data, mock server.
  • Kết hợp globalSetup array cho infra với setup project cho auth.
  • Phân biệt rõ globalSetup array (pure Node, invisible report) với setup project dependencies (browser, visible report).
  • Tránh 4 pitfall phổ biến khi dùng array setup.
2

Trước v1.49 — Giới Hạn Single File

Trước v1.49, globalSetup chỉ nhận một giá trị string — đường dẫn đến một file duy nhất:

// playwright.config.ts — trước v1.49
export default defineConfig({
  globalSetup: require.resolve('./global-setup.ts'),
  globalTeardown: require.resolve('./global-teardown.ts'),
});

Khi cần nhiều bước khởi tạo (migrate DB, seed data, start mock server), toàn bộ logic phải nằm trong một file duy nhất:

// global-setup.ts — tất cả logic trong 1 file
export default async function globalSetup() {
  await runMigrations();   // bước 1
  await seedTestData();    // bước 2
  await startMockServer(); // bước 3
}

Cách này có hai vấn đề thực tế:

  • Không tách concern — file phình to khi số bước tăng. Migration logic, seed logic, và mock server logic nằm chung một chỗ.
  • Không tái sử dụng — một project khác cần chạy chỉ bước migration không có cách nào import từng phần mà không kéo cả file.

Bài này không đề cập thêm về cú pháp single-file vì đã được bài 97 trong nhóm này trình bày đầy đủ.

3

Cú Pháp Array [v1.49+]

Từ v1.49 (release tháng 11/2024), globalSetupglobalTeardown đều chấp nhận array:

// playwright.config.ts (v1.49+)
import { defineConfig } from '@playwright/test';

export default defineConfig({
  globalSetup: [
    require.resolve('./setup/db-migrate'),
    require.resolve('./setup/seed-data'),
    require.resolve('./setup/start-mock'),
  ],
  globalTeardown: [
    require.resolve('./teardown/stop-mock'),
    require.resolve('./teardown/drop-db'),
  ],
});

Một số điểm cú pháp cần lưu ý:

  • require.resolve() — trả về đường dẫn tuyệt đối đến file. Cách viết thay thế là dùng string path trực tiếp ('./setup/db-migrate'), nhưng require.resolve() được khuyến nghị vì báo lỗi ngay tại compile time nếu file không tồn tại.
  • Backward compatibility — single string vẫn hoạt động bình thường trên v1.49+. Không cần migrate nếu chỉ có một setup file.
  • TypeScript type — từ v1.49, kiểu của globalSetup trong FullConfigstring | string[].

Nếu dự án dùng Playwright < v1.49 mà truyền array, test runner sẽ báo lỗi type validation tại config parse — không chạy được. Kiểm tra version trước khi migrate:

npx playwright --version
4

Execution Order — Tuần Tự Theo Mảng

Mảng setup chạy tuần tự, không song song. Phần tử index 0 hoàn thành trước khi index 1 bắt đầu:

Thứ tự thực thi:

[0] db-migrate   → chạy + await xong
[1] seed-data    → chạy + await xong  (DB đã migrate)
[2] start-mock   → chạy + await xong  (data đã có sẵn)

→ Toàn bộ test suite bắt đầu

Thứ tự này quan trọng về mặt phụ thuộc logic: seed-data cần DB đã migrate xong mới có thể insert rows. Nếu đổi vị trí hai phần tử, seed sẽ fail vì tables chưa tồn tại.

Quy tắc đặt thứ tự:

  • Infra cơ bản nhất lên đầu mảng — những thứ khác depend vào nó.
  • Mock server thường đứng sau DB vì mock có thể cần đọc seed data để config response.
  • Nếu hai setup hoàn toàn độc lập (không depend vào nhau), thứ tự giữa chúng không ảnh hưởng correctness — chỉ ảnh hưởng thời gian chờ tổng cộng (vẫn chạy tuần tự).
5

Cấu Trúc Mỗi Setup File

Mỗi file trong mảng vẫn phải export default một async function — cùng contract với single-file setup:

// setup/db-migrate.ts
import { runMigrations } from '../db/migrator';

export default async function() {
  console.log('[setup] Running DB migrations...');
  await runMigrations();
  console.log('[setup] Migrations complete');
}
// setup/seed-data.ts
import { seedTestData } from '../db/seeder';

export default async function() {
  // Tại thời điểm này, db-migrate đã xong
  // Tables tồn tại, có thể insert rows an toàn
  await seedTestData();
}
// setup/start-mock.ts
import { startMockServer } from '../mocks/server';

export default async function() {
  const server = await startMockServer({ port: 9001 });
  // Lưu port vào env để tests có thể dùng
  process.env.MOCK_SERVER_PORT = String(server.port);
}

Mỗi file chịu trách nhiệm một concern duy nhất. Điều này cũng có nghĩa mỗi file có thể được test độc lập và import ở nơi khác mà không kéo theo logic của file khác.

Về việc truyền data giữa các setup: các file trong mảng không có cơ chế truyền giá trị trực tiếp cho nhau (không giống pipe). Cách share thông tin duy nhất là thông qua:

  • process.env — set trong setup trước, đọc trong setup sau.
  • File trên disk — ghi JSON trong setup trước, đọc trong setup sau.
// setup/db-migrate.ts — set env sau khi migrate
export default async function() {
  const { connectionString } = await runMigrations();
  process.env.TEST_DB_URL = connectionString;
}

// setup/seed-data.ts — đọc env từ setup trước
export default async function() {
  const dbUrl = process.env.TEST_DB_URL; // set bởi db-migrate
  await seedTestData({ connectionString: dbUrl });
}
6

globalTeardown Array và LIFO Order

globalTeardown cũng nhận array. Thứ tự chạy của teardown array là theo thứ tự khai báo trong mảng — không tự động đảo ngược so với setup array. Playwright không tự LIFO teardown array.

Do đó, nếu bạn muốn teardown chạy theo thứ tự ngược với setup (pattern cleanup thông thường), bạn phải tự khai báo teardown array theo thứ tự ngược:

export default defineConfig({
  // Setup order: db → seed → mock
  globalSetup: [
    require.resolve('./setup/db-migrate'),   // index 0
    require.resolve('./setup/seed-data'),    // index 1
    require.resolve('./setup/start-mock'),   // index 2
  ],

  // Teardown khai báo ngược: mock trước, db sau
  // (tự khai báo reverse — Playwright không tự đảo)
  globalTeardown: [
    require.resolve('./teardown/stop-mock'),  // index 0 — đảo lại của setup[2]
    require.resolve('./teardown/drop-db'),    // index 1 — đảo lại của setup[0]
  ],
});

Lý do teardown mock trước DB: mock server đang giữ connection đến DB (để đọc data khi serving responses). Nếu drop DB trước khi stop mock, mock server sẽ crash với connection error khi nhận request cuối. Stop mock trước → đóng tất cả connection → drop DB an toàn.

Teardown array cũng chạy tuần tự theo thứ tự khai báo, không song song.

7

Use Cases — Tách Concern Theo File

Modular setup theo tầng infra

Pattern phổ biến nhất: mỗi tầng infra là một file riêng.

globalSetup: [
  require.resolve('./setup/01-start-postgres'),   // khởi docker container
  require.resolve('./setup/02-run-migrations'),   // tạo schema
  require.resolve('./setup/03-seed-fixtures'),    // insert test data
  require.resolve('./setup/04-start-redis'),      // cache layer
  require.resolve('./setup/05-warm-cache'),       // pre-populate cache
],

Số prefix trong tên file (01-, 02-) giúp thứ tự rõ ràng khi nhìn vào thư mục, dù thứ tự thực sự được xác định bởi vị trí trong mảng config.

Reusable setup cross-project

Monorepo có nhiều Playwright project (frontend tests, API tests, e2e tests) có thể chia sẻ cùng một setup file cho infra chung:

// packages/test-infra/setup/db-migrate.ts
// (shared package)

// apps/frontend/playwright.config.ts
globalSetup: [
  require.resolve('@company/test-infra/setup/db-migrate'),
  require.resolve('./setup/frontend-fixtures'),  // local only
],

// apps/api/playwright.config.ts
globalSetup: [
  require.resolve('@company/test-infra/setup/db-migrate'),
  require.resolve('./setup/api-fixtures'),       // local only
],

Mỗi project reuse shared DB migration setup, thêm fixture riêng của mình vào cuối mảng.

Environment check trước khi setup

File đầu mảng thường dùng để validate điều kiện tiên quyết — fail sớm nếu env chưa sẵn sàng:

// setup/env-check.ts
export default async function() {
  const required = ['DATABASE_URL', 'REDIS_URL', 'API_KEY'];
  const missing = required.filter(k => !process.env[k]);
  if (missing.length > 0) {
    throw new Error(
      `Missing required env vars: ${missing.join(', ')}`
    );
  }
}
globalSetup: [
  require.resolve('./setup/env-check'),   // fail fast nếu thiếu env
  require.resolve('./setup/db-migrate'),
  require.resolve('./setup/seed-data'),
],

Nếu env-check throw, toàn bộ run abort ngay tại file đầu tiên — không lãng phí thời gian chạy migration rồi mới phát hiện thiếu biến môi trường.

8

Kết Hợp globalSetup Array + Setup Project

Hai cơ chế này phục vụ tầng khác nhau và hoạt động tốt khi dùng cùng nhau:

  • globalSetup array — khởi tạo infra (DB, mock server, external services). Pure Node.js, không cần browser, chạy trước tất cả.
  • Setup project — xử lý auth bằng browser (login UI, lưu storageState). Cần Playwright context, visible trong report.
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // Tầng 1: infra — chạy trước tất cả, pure Node
  globalSetup: [
    require.resolve('./setup/env-check'),
    require.resolve('./setup/db-migrate'),
    require.resolve('./setup/seed-data'),
  ],

  globalTeardown: [
    require.resolve('./teardown/drop-db'),
  ],

  projects: [
    // Tầng 2: auth — dùng browser, visible trong report
    {
      name: 'auth-setup',
      testMatch: /.*\.setup\.ts/,
    },

    // Tầng 3: main tests — depend vào auth
    {
      name: 'chromium',
      dependencies: ['auth-setup'],
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
    },
    {
      name: 'firefox',
      dependencies: ['auth-setup'],
      use: {
        ...devices['Desktop Firefox'],
        storageState: 'playwright/.auth/user.json',
      },
    },
  ],
});

Thứ tự thực thi khi chạy npx playwright test:

  1. globalSetup[0]: env-check — validate env vars.
  2. globalSetup[1]: db-migrate — tạo schema.
  3. globalSetup[2]: seed-data — insert fixtures.
  4. Project auth-setup: chạy *.setup.ts, login UI, save storageState.
  5. Projects chromiumfirefox: chạy main tests với auth đã sẵn sàng.
  6. globalTeardown[0]: drop-db — dọn dẹp sau toàn bộ suite.

Lưu ý thứ tự quan trọng: globalSetup luôn chạy trước bất kỳ project nào, kể cả auth-setup. DB phải sẵn sàng trước khi login UI có thể hoạt động (login cần gọi vào DB để xác thực).

9

Khác Biệt Với Setup Project Dependencies

Hai cơ chế có những điểm khác biệt cần nắm rõ để chọn đúng:

Tiêu chí globalSetup array Setup project dependencies
Runtime Pure Node.js Playwright browser context
Hiển thị trong report Không Có — là project thực sự
Retry khi fail Không Có — theo retries config
Trace / Screenshot Không Có đầy đủ
Chạy song song Không (array = tuần tự) Có — các setup project không depend vào nhau chạy song song
Dùng Playwright API Không — phải import và launch thủ công Có — fixture page, context sẵn sàng
Dùng cho Infra (DB, server, env) Auth (login UI, storageState)

Quy tắc thực tế: nếu setup cần mở browser → dùng setup project. Nếu setup là pure Node (gọi API, chạy CLI, connect DB) → dùng globalSetup array.

10

Limitation

  • v1.49+ only — array syntax không hoạt động trên phiên bản cũ hơn. Nếu team có nhiều dự án với version khác nhau, cần chú ý khi chia sẻ config template.
  • Invisible trong report — giống single-file globalSetup, array setup vẫn không xuất hiện trong HTML report hay trace. Khi một setup trong array fail, bạn chỉ thấy error message trên stdout/stderr, không có screenshot hay trace.
  • Không có retry per file — nếu seed-data fail do flaky DB connection, toàn bộ run abort. Không có cơ chế retry từng file riêng lẻ. Phải implement retry logic trong bản thân file setup nếu cần:
// setup/seed-data.ts — retry thủ công
import { retry } from '../utils/retry';

export default async function() {
  await retry(
    () => seedTestData(),
    { attempts: 3, delay: 1000 }
  );
}
  • Chạy tuần tự — không parallel — array luôn chạy tuần tự dù các setup hoàn toàn độc lập. Nếu cần khởi tạo song song, phải gộp vào cùng một file và dùng Promise.all bên trong.
// setup/parallel-infra.ts — parallel trong 1 file
export default async function() {
  // Redis và mock server độc lập với nhau → chạy song song
  await Promise.all([
    startRedis(),
    startMockServer(),
  ]);
}
11

4 Pitfall

Pitfall 1 — Thứ tự array sai dẫn đến dependency fail

// SAI — seed trước migrate
globalSetup: [
  require.resolve('./setup/seed-data'),   // chạy trước
  require.resolve('./setup/db-migrate'),  // chạy sau
],

seed-data cố insert vào tables chưa tồn tại → fail với lỗi relation "users" does not exist. Lỗi này không phải bug trong seed logic mà là thứ tự array sai. Sửa bằng cách đặt migration trước seed trong mảng.

Pitfall 2 — Dùng array trên version < v1.49

// playwright.config.ts trên Playwright v1.48
globalSetup: [
  require.resolve('./setup/db-migrate'),  // array syntax
  require.resolve('./setup/seed-data'),
],

Playwright v1.48 không nhận array ở đây — config parse fail với type error, toàn bộ run không khởi động. Thông báo lỗi không luôn rõ ràng là do version mismatch. Xác nhận version bằng npx playwright --version trước khi áp dụng cú pháp mới.

Pitfall 3 — Một setup trong array fail, teardown không chạy

Khi globalSetup[1] throw error, test runner abort ngay lập tức: không chạy globalSetup[2], không chạy globalTeardown. Nếu globalSetup[0] đã khởi động một service (ví dụ Docker container), service đó vẫn còn chạy sau khi test run kết thúc vì teardown bị bỏ qua.

Giải pháp: đưa cleanup logic vào bản thân setup file bằng try/finally:

// setup/start-mock.ts
let server: MockServer | undefined;

export default async function() {
  server = await startMockServer();
  // Không dùng teardown file, tự cleanup khi process exit
  process.on('exit', () => server?.stop());
}

Pitfall 4 — Assume teardown tự đảo ngược thứ tự setup

Playwright không tự đảo thứ tự teardown. Nếu bạn khai báo:

globalTeardown: [
  require.resolve('./teardown/drop-db'),   // index 0
  require.resolve('./teardown/stop-mock'), // index 1
],

drop-db chạy trước stop-mock. Mock server vẫn đang giữ connection đến DB đã bị drop → crash hoặc error log. Phải tự khai báo teardown theo thứ tự cleanup đúng (mock stop trước, DB drop sau) — không assume LIFO tự động.

12

Quiz

Câu 1. Config sau có vấn đề gì không?

// Playwright v1.48
export default defineConfig({
  globalSetup: [
    require.resolve('./setup/db-migrate'),
    require.resolve('./setup/seed-data'),
  ],
});
Đáp án

Có vấn đề. Cú pháp array cho globalSetup chỉ được hỗ trợ từ v1.49+. Trên v1.48, config parse sẽ fail với type error — toàn bộ run không khởi động được. Phải nâng Playwright lên v1.49+ hoặc gộp logic vào một file duy nhất.

Câu 2. Với config dưới đây, seed-data sẽ chạy khi nào?

globalSetup: [
  require.resolve('./setup/env-check'),
  require.resolve('./setup/db-migrate'),
  require.resolve('./setup/seed-data'),
],
Đáp án

seed-data chạy sau khi env-checkdb-migrate đều hoàn thành. Mảng chạy tuần tự: index 0 xong → index 1 xong → index 2 bắt đầu. Nếu env-check hoặc db-migrate throw error, seed-data không bao giờ chạy.

Câu 3. Tại sao bạn chọn globalSetup array thay vì setup project để khởi tạo DB?

Đáp án

Khởi tạo DB là pure Node.js operation (gọi migration script, chạy SQL) — không cần browser. globalSetup phù hợp vì nhẹ hơn (không launch browser), chạy trước mọi project. Setup project phù hợp khi cần Playwright API và browser context (ví dụ login UI). Dùng sai cơ chế sẽ làm setup phức tạp hơn cần thiết.

Câu 4. Nếu globalSetup[1] fail, globalTeardown có chạy không?

Đáp án

Không. Khi bất kỳ setup trong array throw uncaught error, test runner abort ngay — không chạy setup tiếp theo trong array và không chạy globalTeardown. Resources đã khởi động bởi globalSetup[0] có thể bị leak. Cần implement cleanup trong bản thân setup file (process exit handler hoặc try/finally) nếu resource cần được đóng trong mọi tình huống.

Câu 5. Bạn cần hai setup chạy song song vì chúng hoàn toàn độc lập nhau và tốn nhiều thời gian. Cách nào để làm điều này với globalSetup?

Đáp án

globalSetup array luôn chạy tuần tự — không có cách nào để hai phần tử trong array chạy song song. Giải pháp: gộp cả hai vào một file setup và dùng Promise.all bên trong file đó:

// setup/parallel-two-tasks.ts
export default async function() {
  await Promise.all([
    setupTaskA(),  // chạy song song
    setupTaskB(),  // với TaskA
  ]);
}

Sau đó array config chỉ cần một phần tử: require.resolve('./setup/parallel-two-tasks').

13

Kết Chương A — Bài Tiếp Theo

Đây là bài cuối Chương A. 100 bài đã đi qua toàn bộ nền tảng nâng cao của Playwright Test Framework: Fixtures, Hooks, Annotations, Projects, Parallel/Sharding, Retries, Timeouts, Parameterize, và Global Setup/Teardown. Chương B mở tiếp với chủ đề Authentication nâng cao — multi-role, SSO, 2FA, và các pattern quản lý session phức tạp.

Bài 101: Multi-Role Auth Pattern