Danh sách bài viết

Bài 34: afterAll Cleanup — Pattern Đáng Tin Cậy

test.afterAll() chạy 1 lần sau tất cả test trong scope — kể cả khi test fail, kể cả khi beforeAll fail giữa chừng. Bài này phân tích behavior chính xác, cách viết cleanup idempotent, LIFO order khi khai báo nhiều afterAll, pattern combine với beforeAll cho DB pool và mock server, timeout handling, và 4 pitfall hay bị bỏ qua.

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

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

Sau bài này bạn sẽ:

  • Giải thích chính xác khi nào afterAll chạy và khi nào nó không chạy.
  • Viết cleanup idempotent — an toàn khi beforeAll fail giữa chừng và state là partial.
  • Hiểu LIFO order khi khai báo nhiều afterAll trong cùng scope.
  • Áp dụng đúng pattern combine beforeAll + afterAll cho DB pool và mock server.
  • Biết timeout mặc định của hook và cách override.
  • Phân biệt afterAll với globalTeardown.
  • Nhận diện 4 pitfall phổ biến và cách tránh.

Bài này không lặp lại nội dung bài 400 Series 1 (cú pháp cơ bản, worker scope). Focus vào các behavior nâng cao, defensive coding, và cleanup pattern thực tế.

2

Cú Pháp Và Vị Trí Khai Báo

import { test, expect } from '@playwright/test';

test.afterAll(async () => {
  await cleanupSeededData();
});

Hook nhận callback async, trả về Promise<void>. Playwright đợi promise resolve trước khi kết thúc scope.

Có thể khai báo ở hai vị trí:

  • Top-level: chạy sau tất cả test trong file.
  • Trong describe: chỉ chạy sau tất cả test trong describe đó.
// Top-level — áp dụng toàn file
test.afterAll(async () => {
  await dropTestSchema();  // cleanup cho mọi test trong file
});

// Trong describe — chỉ áp dụng cho group
test.describe('Checkout flow', () => {
  test.afterAll(async () => {
    await deleteOrdersByPrefix('TEST-');
  });

  test('creates order', async ({ page }) => { /* ... */ });
  test('cancels order', async ({ page }) => { /* ... */ });
});

// Test này không bị ảnh hưởng bởi afterAll trong describe
test('homepage loads', async ({ page }) => { /* ... */ });
3

Behavior Chi Tiết

Bốn điểm behavior quan trọng cần nắm rõ:

1. Chạy sau test cuối của scope

Không phải sau mỗi test mà sau khi test cuối cùng trong file hoặc describe hoàn thành.

2. Chạy kể cả khi test fail

Dù 1 hay nhiều test trong scope throw error, afterAll vẫn được gọi. Đây là lý do afterAll phù hợp cho cleanup — không phụ thuộc vào kết quả test.

test.afterAll(async () => {
  // Chạy ngay cả khi test 1, test 2, test 3 đều fail
  await cleanupSeededData();
});

test('test 1', async ({ page }) => {
  throw new Error('intentional fail');
});

test('test 2', async ({ page }) => { /* ... */ });

3. Chạy kể cả khi beforeAll fail

Đây là điểm dễ bị bỏ qua. Nếu beforeAll thực hiện 3 bước và fail ở bước 2, state lúc này là partial — bước 1 đã xong, bước 3 chưa chạy. afterAll vẫn được gọi sau đó. Cleanup code phải xử lý được trạng thái partial này.

let pool: Pool;
let schemaCreated = false;

test.beforeAll(async () => {
  pool = await createPool();           // bước 1: OK
  await createSchema(pool);            // bước 2: fail tại đây
  await seedData(pool);                // bước 3: không chạy
  schemaCreated = true;
});

test.afterAll(async () => {
  // afterAll vẫn chạy dù beforeAll fail ở bước 2
  // pool đã tạo nhưng schema chưa có, seedData chưa chạy
  // Cần handle partial state
  if (schemaCreated) {
    await dropSchema(pool).catch(err => console.warn('dropSchema:', err));
  }
  await pool?.end();  // optional chain phòng pool chưa được assign
});

4. Worker scope

afterAll là worker-scope hook — chạy 1 lần per worker. Với fullyParallel: true (mặc định), mỗi file chạy trên 1 worker riêng nên afterAll chạy đúng 1 lần per file.

4

Fixture Khả Dụng

afterAll chạy ở worker scope — giới hạn fixture giống beforeAll:

Fixture Khả dụng Lý do
browser Worker-scope built-in
playwright Worker-scope built-in
Custom worker-scope fixture Cùng vòng đời
page Không Test-scope — không tồn tại ngoài test
context Không Test-scope — tương tự page
Custom test-scope fixture Không Test-scope — không tồn tại ngoài test

Nếu cleanup cần navigate (ví dụ gọi admin endpoint qua browser), tạo page thủ công từ browser rồi close:

test.afterAll(async ({ browser }) => {
  const page = await browser.newPage();
  await page.goto('/admin/cleanup');
  await page.getByRole('button', { name: 'Reset Test Data' }).click();
  await page.close();
});

Trong thực tế, cleanup nên dùng direct API call (DB client, HTTP request) thay vì browser navigation để giảm flakiness và không phụ thuộc UI.

5

Idempotent Cleanup

Cleanup idempotent là cleanup an toàn khi gọi nhiều lần hoặc khi resource chưa được tạo đầy đủ. Vì afterAll chạy kể cả khi beforeAll fail, cleanup code không thể assume rằng mọi thứ đã sẵn sàng.

Pattern cơ bản — try/catch

test.afterAll(async () => {
  try {
    await dropTestSchema();
  } catch (err) {
    // Schema có thể chưa được tạo (beforeAll fail trước khi tạo schema)
    console.warn('Schema already dropped or never created:', err);
    // Không re-throw — cleanup error không nên làm fail test
  }
});

Pattern với flag trạng thái

Khi cleanup phức tạp và phụ thuộc vào trạng thái setup:

let userId: number | null = null;
let orderIds: number[] = [];

test.beforeAll(async () => {
  userId = await db.createTestUser({ email: '[email protected]' });
  orderIds = await db.createTestOrders(userId, 3);
});

test.afterAll(async () => {
  // Cleanup theo thứ tự ngược với setup (FK constraint)
  if (orderIds.length > 0) {
    await db.deleteOrders(orderIds).catch(err =>
      console.warn('deleteOrders failed:', err)
    );
  }
  if (userId !== null) {
    await db.deleteUser(userId).catch(err =>
      console.warn('deleteUser failed:', err)
    );
  }
});

SQL idempotent

Khi cleanup là SQL query, dùng IF EXISTS hoặc DELETE ... WHERE thay vì assume resource tồn tại:

test.afterAll(async () => {
  // Idempotent — không throw nếu schema không tồn tại
  await db.query('DROP SCHEMA IF EXISTS test_schema CASCADE');
  await db.query("DELETE FROM users WHERE email LIKE 'test_%'");
});
6

Multiple afterAll — LIFO Order

Khi khai báo nhiều afterAll trong cùng scope, Playwright thực thi theo thứ tự LIFO (Last In, First Out) — hook khai báo sau chạy trước.

test.afterAll(async () => {
  console.log('afterAll #1 — khai báo đầu tiên');
});

test.afterAll(async () => {
  console.log('afterAll #2 — khai báo thứ hai');
});

test.afterAll(async () => {
  console.log('afterAll #3 — khai báo cuối cùng');
});

// Output:
// afterAll #3 — khai báo cuối cùng
// afterAll #2 — khai báo thứ hai
// afterAll #1 — khai báo đầu tiên

LIFO order match với nguyên tắc cleanup stack: resource tạo sau thường phụ thuộc resource tạo trước, nên cần cleanup trước. Ví dụ: beforeAll #1 tạo DB pool, beforeAll #2 tạo schema dùng pool đó. Cleanup hợp lý: xoá schema trước, rồi đóng pool sau.

let pool: Pool;
let schemaName: string;

test.beforeAll(async () => {   // #1 — setup pool
  pool = await createPool();
});

test.beforeAll(async () => {   // #2 — tạo schema (dùng pool)
  schemaName = `test_${Date.now()}`;
  await createSchema(pool, schemaName);
});

// LIFO: afterAll #2 chạy trước afterAll #1
test.afterAll(async () => {   // #1 — đóng pool (chạy sau)
  await pool?.end();
});

test.afterAll(async () => {   // #2 — xoá schema (chạy trước, dùng pool còn mở)
  await dropSchema(pool, schemaName).catch(err =>
    console.warn('dropSchema:', err)
  );
});

Nếu quên thứ tự LIFO và viết cleanup theo thứ tự FIFO, pool.end() có thể chạy trước khi schema bị drop — cleanup thứ hai sẽ fail vì pool đã đóng.

7

Pattern Combine beforeAll + afterAll

Pattern phổ biến nhất: biến module-level lưu resource tạo trong beforeAll, cleanup trong afterAll. Optional chain ?. là cần thiết để phòng trường hợp beforeAll fail trước khi assign biến.

DB connection pool

import { Pool } from 'pg';
import { test, expect } from '@playwright/test';

let pool: Pool;

test.beforeAll(async () => {
  pool = new Pool({
    connectionString: process.env.DATABASE_URL,
    max: 5,
  });
  // Kiểm tra connection trước khi test chạy
  const client = await pool.connect();
  await client.query('SELECT 1');
  client.release();
});

test.afterAll(async () => {
  // Optional chain: pool chưa assign nếu beforeAll fail ngay dòng đầu
  await pool?.end();
});

test('query users', async () => {
  const client = await pool.connect();
  const result = await client.query('SELECT count(*) FROM users');
  client.release();
  expect(Number(result.rows[0].count)).toBeGreaterThan(0);
});

Seeded data với cleanup

let productIds: number[] = [];

test.beforeAll(async () => {
  productIds = await db.insertProducts([
    { name: 'Widget A', price: 9.99, sku: 'W-001' },
    { name: 'Widget B', price: 14.99, sku: 'W-002' },
    { name: 'Widget C', price: 19.99, sku: 'W-003' },
  ]);
});

test.afterAll(async () => {
  if (productIds.length === 0) return;  // beforeAll fail trước khi insert
  await db.deleteProductsByIds(productIds).catch(err =>
    console.warn('[afterAll] deleteProducts failed:', err)
  );
});
8

Pattern Với Mock Server

Spin up mock server trong beforeAll, đóng trong afterAll. server.close() nhận callback — cần wrap trong Promise để await đúng cách:

import * as http from 'http';
import { test } from '@playwright/test';

let server: http.Server;

test.beforeAll(async () => {
  server = http.createServer((req, res) => {
    if (req.url === '/api/health') {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ status: 'ok' }));
      return;
    }
    res.writeHead(404);
    res.end();
  });

  await new Promise<void>(resolve => server.listen(0, resolve));
  // Port 0 → OS tự chọn port trống, tránh conflict
});

test.afterAll(async () => {
  await new Promise<void>((resolve, reject) => {
    server?.close((err) => (err ? reject(err) : resolve()));
  });
});

test('health endpoint returns 200', async ({ request }) => {
  const address = server.address() as { port: number };
  const res = await request.get(`http://localhost:${address.port}/api/health`);
  expect(res.ok()).toBe(true);
});

Dùng port 0 để tránh conflict port khi chạy nhiều file song song — OS tự gán port khả dụng. Lấy port thực tế qua server.address() sau khi listen.

Kill spawned process

Khi test cần spawn process ngoài (Docker container, external mock):

import { spawn, ChildProcess } from 'child_process';

let mockProcess: ChildProcess;

test.beforeAll(async () => {
  mockProcess = spawn('node', ['test/mock-server.js'], {
    env: { ...process.env, PORT: '3099' },
  });
  // Đợi server ready — đơn giản nhất là đợi stdout log
  await new Promise<void>((resolve, reject) => {
    mockProcess.stdout?.on('data', (data: Buffer) => {
      if (data.toString().includes('listening')) resolve();
    });
    mockProcess.on('error', reject);
    setTimeout(() => reject(new Error('mock server timeout')), 10_000);
  });
});

test.afterAll(async () => {
  mockProcess?.kill('SIGTERM');
  // Đợi process exit để tránh orphan process
  await new Promise<void>(resolve => {
    mockProcess?.on('exit', () => resolve());
    setTimeout(resolve, 3000);  // fallback nếu process không exit
  });
});
9

Nested Describe — Thứ Tự Thực Thi

Với nested describe, afterAll inner chạy trước outer — ngược với beforeAll:

test.beforeAll(async () => {
  console.log('outer beforeAll');
});

test.afterAll(async () => {
  console.log('outer afterAll');
});

test.describe('inner', () => {
  test.beforeAll(async () => {
    console.log('inner beforeAll');
  });

  test.afterAll(async () => {
    console.log('inner afterAll');
  });

  test('inner case 1', async ({ page }) => {
    console.log('inner test 1');
  });

  test('inner case 2', async ({ page }) => {
    console.log('inner test 2');
  });
});

test('outer case', async ({ page }) => {
  console.log('outer test');
});

Thứ tự output:

outer beforeAll
  inner beforeAll
    inner test 1
    inner test 2
  inner afterAll
  outer test
outer afterAll

Inner afterAll chạy ngay sau khi test cuối của describe đó hoàn thành — không phải sau outer test. Outer afterAll chạy sau tất cả mọi thứ trong file.

10

Hook Fail Trong afterAll

Khi afterAll throw error, behavior khác với beforeAll:

  • beforeAll throw → test trong scope bị skip, báo là "did not run".
  • afterAll throw → không fail test đã chạy, chỉ log error và tiếp tục.

Điều này có nghĩa là cleanup failure không làm thay đổi kết quả test. Nhưng nó mask vấn đề cleanup, dẫn đến state leak qua các run sau.

Best practice: log error rõ ràng nhưng không re-throw từ cleanup. Nếu cleanup quan trọng và cần alert, log ra stderr hoặc dùng monitoring riêng:

test.afterAll(async () => {
  try {
    await dropTestSchema(schemaName);
    await pool?.end();
  } catch (err) {
    // Log đủ thông tin để debug
    console.error('[afterAll] cleanup failed:', {
      schema: schemaName,
      error: err instanceof Error ? err.message : err,
    });
    // Không re-throw — tránh mask test failure original
    // Nếu cần alert, ghi vào file log riêng hoặc push metric
  }
});

Nếu có nhiều bước cleanup, dùng Promise.allSettled hoặc xử lý từng bước riêng để một bước fail không chặn các bước còn lại:

test.afterAll(async () => {
  const results = await Promise.allSettled([
    dropTestSchema(schemaName),
    deleteTestFiles(tmpDir),
    redis?.quit(),
  ]);

  results.forEach((result, i) => {
    if (result.status === 'rejected') {
      console.error(`[afterAll] cleanup step ${i} failed:`, result.reason);
    }
  });
});
11

Timeout

Mặc định, afterAll dùng chung timeout với test — thường 30 giây. Cleanup chậm (đóng pool nhiều connection, drop schema lớn) có thể vượt timeout này, dẫn đến partial cleanup.

Khi timeout xảy ra trong afterAll:

Error: afterAll hook timeout of 30000ms exceeded.

Override timeout bằng test.setTimeout() bên trong hook:

test.afterAll(async () => {
  test.setTimeout(60_000);  // 60 giây cho cleanup

  await dropTestSchema(schemaName);     // có thể mất 5-15s
  await pool?.end();                    // đợi connection drain
  await clearMessageQueue(queueName);   // flush queue
});

test.setTimeout() gọi trong afterAll chỉ áp dụng cho hook đó. Timeout của các test trong file không bị ảnh hưởng.

Nếu cleanup thường xuyên cần hơn 30 giây, nên xem lại thiết kế: có thể tách cleanup nặng ra globalTeardown, hoặc dùng database transaction rollback thay vì manual delete để cleanup nhanh hơn.

12

Order Với Worker Fixture Cleanup

Khi dùng cả worker-scope fixture và afterAll trong cùng worker, thứ tự cleanup là:

test N (test cuối)
  → test.afterEach (nếu có)
test.afterAll (cuối cùng trong scope)
worker fixture cleanup (teardown sau use())

Worker fixture cleanup chạy sau afterAll cuối cùng. Điều này quan trọng khi afterAll cần dùng resource từ worker fixture:

// fixtures.ts
export const test = base.extend<{}, { dbPool: Pool }>({
  dbPool: [
    async ({}, use) => {
      const pool = new Pool({ connectionString: process.env.DATABASE_URL });
      await use(pool);
      await pool.end();  // chạy SAU afterAll — OK
    },
    { scope: 'worker' },
  ],
});

// spec.ts
import { test } from './fixtures';

test.afterAll(async ({ dbPool }) => {
  // dbPool vẫn còn mở ở đây — worker fixture cleanup chưa chạy
  await dbPool.query("DELETE FROM test_data WHERE session = 'test-run'");
});

Nếu afterAll cần worker fixture, inject trực tiếp qua destructuring parameter — Playwright hỗ trợ điều này.

13

So Sánh Với globalTeardown

Đặc điểm afterAll globalTeardown
Chạy khi nào Sau test cuối của scope (file/describe) 1 lần sau toàn bộ run, tất cả file
Số lần chạy 1 lần per worker per scope Đúng 1 lần duy nhất
Truy cập fixture Có (worker-scope fixtures) Không — chạy trong process riêng
Truy cập test result Không trực tiếp Có qua fullResult param
Phù hợp cho Cleanup resource per file/describe Cleanup toàn cục (truncate DB, xoá asset chung)

Rule of thumb: nếu cleanup thuộc về file/group test cụ thể, dùng afterAll. Nếu cleanup thuộc về cả run (tạo bởi globalSetup), dùng globalTeardown.

14

Limitation

  • Cleanup không guaranteed khi process bị kill: nếu worker bị SIGKILL (OOM, system crash), afterAll không chạy. External resource (DB schema, file tạm, port) sẽ không được dọn dẹp. Cần cleanup strategy ngoài Playwright: script chạy trước run để dọn orphan resource từ run trước.
  • State leak qua run: nếu afterAll fail hoặc bị skip (process kill), data test tồn tại sang run tiếp theo. Test sau có thể fail do unique constraint hoặc nhận data cũ.
  • Phụ thuộc beforeAll status: cleanup phải handle partial state khi beforeAll fail — xem bài 3. Không có cách built-in để biết chính xác beforeAll fail ở bước nào ngoài việc dùng flag.
  • Không access test result: afterAll không biết test nào pass hay fail (khác với afterEachtestInfo.status). Nếu cần conditional cleanup dựa trên kết quả test, dùng afterEach hoặc custom reporter.
15

Common Pitfalls

Pitfall 1: afterAll throw — mask test failure original

Khi afterAll throw, Playwright log error nhưng không fail test. Nếu test đã fail và cleanup cũng fail, output chỉ thấy test failure — cleanup error có thể bị bỏ qua trong CI. Cần đọc kỹ full log để không miss.

// SAI — throw từ cleanup làm log bị noise
test.afterAll(async () => {
  await dropSchema(pool, schemaName);  // throw nếu schema không tồn tại
});

// ĐÚNG — bắt và log
test.afterAll(async () => {
  await dropSchema(pool, schemaName).catch(err =>
    console.error('[afterAll] dropSchema failed:', err.message)
  );
});

Pitfall 2: Null reference khi beforeAll fail

// SAI — pool chưa được assign nếu createPool() throw
let pool: Pool;

test.beforeAll(async () => {
  pool = await createPool();  // fail ở đây
  await migrateSchema(pool);
});

test.afterAll(async () => {
  await pool.end();  // TypeError: Cannot read properties of undefined
});

// ĐÚNG — optional chain
test.afterAll(async () => {
  await pool?.end();  // an toàn kể cả khi pool undefined
});

Pitfall 3: Quên LIFO khi multiple afterAll phụ thuộc nhau

// SAI — pool.end() chạy TRƯỚC dropSchema (LIFO order)
// dropSchema cần pool còn mở nhưng pool đã end rồi
test.afterAll(async () => {
  await pool?.end();          // khai báo trước → chạy SAU (LIFO)
});

test.afterAll(async () => {
  await dropSchema(pool, schemaName);  // khai báo sau → chạy TRƯỚC
  // pool đã end ở đây → error
});

// ĐÚNG — đảo thứ tự khai báo
test.afterAll(async () => {
  await dropSchema(pool, schemaName);  // khai báo trước → chạy SAU
  // pool vẫn mở khi chạy dòng này → OK
});

test.afterAll(async () => {
  await pool?.end();          // khai báo sau → chạy TRƯỚC (LIFO)
});

Pitfall 4: Cleanup chậm vượt timeout — partial state

// SAI — cleanup drop schema lớn có thể mất >30s
test.afterAll(async () => {
  await db.query('DROP SCHEMA test_schema CASCADE');
  // Nếu schema có nhiều table + data → timeout 30s bị vượt
  // Error: afterAll hook timeout of 30000ms exceeded
  // Schema bị drop dở, state partial → run sau có thể nhận orphan table
});

// ĐÚNG — set timeout đủ lớn hoặc tách cleanup nặng
test.afterAll(async () => {
  test.setTimeout(120_000);  // 2 phút cho cleanup nặng
  await db.query('DROP SCHEMA test_schema CASCADE');
});
16

Tổng Kết

  • afterAll chạy 1 lần sau test cuối trong scope — kể cả khi test fail và kể cả khi beforeAll fail.
  • Chỉ worker-scope fixture (browser, playwright, custom worker fixture) khả dụng. pagecontext không dùng được.
  • Cleanup phải idempotent: dùng optional chain ?., flag trạng thái, IF EXISTS trong SQL, try/catch per step.
  • Nhiều afterAll trong cùng scope chạy LIFO — khai báo sau chạy trước. Thiết kế thứ tự khai báo cho đúng khi cleanup có dependency.
  • afterAll throw không fail test đã chạy — chỉ log error. Cleanup failure có thể mask hoặc bị miss trong CI.
  • Timeout mặc định = test timeout (thường 30s). Override bằng test.setTimeout(N) bên trong hook khi cleanup chậm.
  • Worker fixture cleanup chạy SAU afterAll cuối cùng — có thể inject worker fixture vào afterAll nếu cần.
  • afterAll khác globalTeardown: scope per file/describe vs toàn run, có fixture vs không có fixture.
  • Cleanup không guaranteed khi process bị kill — cần external cleanup strategy cho resource persistent.
17

Quiz

Câu 1

File có 4 test, test số 2 fail với expect() không match. afterAll có được gọi không? Kết quả test thay đổi như thế nào?

Đáp án

Có — afterAll luôn chạy sau test cuối của scope, bất kể test nào fail. Test số 2 vẫn báo FAIL trong kết quả; afterAll chạy sau test số 4 hoàn thành. Kết quả tổng: 3 PASSED, 1 FAILED.

Câu 2

Đoạn code dưới đây có vấn đề gì? Sửa thế nào?

let server: http.Server;

test.beforeAll(async () => {
  server = http.createServer(handler);
  server.listen(3099);  // listen không await — có thể chưa sẵn sàng
});

test.afterAll(async () => {
  server.close();  // close không await
});
Đáp án

Hai vấn đề:

  1. server.listen(3099) không được await — test có thể chạy trước khi server thực sự ready. Sửa: wrap trong Promise và resolve trong callback.
  2. server.close() không được await — afterAll kết thúc trước khi server thực sự đóng. Sửa: wrap trong Promise với callback.
test.beforeAll(async () => {
  server = http.createServer(handler);
  await new Promise<void>(resolve => server.listen(3099, resolve));
});

test.afterAll(async () => {
  await new Promise<void>((resolve, reject) => {
    server?.close(err => err ? reject(err) : resolve());
  });
});

Câu 3

File khai báo 3 afterAll theo thứ tự A, B, C. Thứ tự chạy thực tế là gì?

Đáp án

C → B → A. Playwright thực thi nhiều afterAll trong cùng scope theo LIFO (Last In, First Out). Hook khai báo cuối cùng (C) chạy đầu tiên.

Câu 4

beforeAll tạo DB schema rồi fail khi seed data. Biến schemaName đã được assign trước khi fail. Viết afterAll cleanup đúng cách.

Đáp án
let schemaName: string | null = null;
let pool: Pool | null = null;

test.beforeAll(async () => {
  pool = new Pool({ connectionString: process.env.DATABASE_URL });
  schemaName = `test_${Date.now()}`;
  await pool.query(`CREATE SCHEMA ${schemaName}`);
  await seedData(pool, schemaName);  // fail ở đây
});

test.afterAll(async () => {
  // Schema đã tạo → cần drop
  if (schemaName !== null && pool !== null) {
    await pool.query(`DROP SCHEMA IF EXISTS ${schemaName} CASCADE`)
      .catch(err => console.error('[afterAll] dropSchema:', err.message));
  }
  await pool?.end().catch(err =>
    console.error('[afterAll] pool.end:', err.message)
  );
});

Câu 5

Khi nào nên dùng afterAll thay vì worker-scope fixture teardown (code sau await use())?

Đáp án

Dùng afterAll khi:

  • Cleanup logic đặc thù cho một file/describe cụ thể, không reuse qua nhiều file.
  • Cleanup dùng biến module-level tạo trong beforeAll (không qua fixture injection).
  • Muốn cleanup chạy trong scope nhỏ hơn — một describe thay vì toàn worker.

Dùng worker fixture teardown khi:

  • Cleanup gắn với resource được inject vào nhiều file trong cùng worker.
  • Muốn type-safe injection và reuse pattern qua nhiều test file.
18

Bài Tiếp Theo

Bài 35 chuyển sang beforeEachafterEach — hooks chạy per test, có quyền truy cập testInfo để đọc kết quả test và metadata.

Bài 35: beforeEach & afterEach — Per-Test Hooks