Before/After Hooks
จบบทนี้แล้วคุณจะ...
- ใช้ beforeEach, afterEach, beforeAll, afterAll ได้ถูกต้อง
- จัดกลุ่ม test ด้วย test.describe()
- ออกแบบ setup/teardown patterns ที่ดี
- refactor duplicated setup ให้ใช้ hooks
1 · Hooks คืออะไร?
Hooks คือ function ที่รันก่อนหรือหลัง test — ใช้สำหรับ setup (จัดเตรียม) และ teardown (ทำความสะอาด)
| Hook | รันเมื่อ | ใช้สำหรับ |
|---|---|---|
beforeEach |
ก่อนแต่ละ test | navigate, reset state |
afterEach |
หลังแต่ละ test | cleanup, take screenshot |
beforeAll |
ก่อน test ตัวแรกใน group | สร้าง test data, seed database |
afterAll |
หลัง test ตัวสุดท้ายใน group | ลบ test data, close connection |
2 · beforeEach / afterEach
import { test, expect } from '@playwright/test';
test.describe('Product Page', () => {
test.beforeEach(async ({ page }) => {
// รันก่อนทุก test — ไปที่หน้า products
await page.goto('/products');
});
test.afterEach(async ({ page }) => {
// รันหลังทุก test (ไม่ค่อยจำเป็น เพราะ page ถูกสร้างใหม่ทุก test)
console.log(`Test finished: ${test.info().title}`);
});
test('should display product list', async ({ page }) => {
// page อยู่ที่ /products แล้ว
await expect(page.getByRole('listitem')).toHaveCount(10);
});
test('should filter by category', async ({ page }) => {
// page อยู่ที่ /products แล้ว
await page.getByRole('combobox', { name: 'Category' }).selectOption('Electronics');
await expect(page.getByRole('listitem').first()).toContainText('Electronics');
});
});
3 · beforeAll / afterAll
import { test, expect } from '@playwright/test';
test.describe('Order Management', () => {
let orderId: string;
test.beforeAll(async ({ request }) => {
// สร้าง test data ผ่าน API ก่อนเริ่ม test ทั้งหมด
const response = await request.post('/api/orders', {
data: { product: 'Test Product', quantity: 1 }
});
const data = await response.json();
orderId = data.id;
});
test.afterAll(async ({ request }) => {
// ลบ test data หลัง test ทั้งหมดจบ
await request.delete(`/api/orders/${orderId}`);
});
test('should display order details', async ({ page }) => {
await page.goto(`/orders/${orderId}`);
await expect(page.getByText('Test Product')).toBeVisible();
});
test('should update order status', async ({ page }) => {
await page.goto(`/orders/${orderId}`);
await page.getByRole('button', { name: 'Ship' }).click();
await expect(page.getByText('Shipped')).toBeVisible();
});
});
สิ่งสำคัญ: beforeAll/afterAll ได้รับ request fixture (ไม่ใช่ page) เพราะรันนอก context ของแต่ละ test
4 · test.describe() — จัดกลุ่ม Test
test.describe('Shopping Cart', () => {
test.describe('when cart is empty', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/cart');
});
test('should show empty message', async ({ page }) => {
await expect(page.getByText('Your cart is empty')).toBeVisible();
});
test('should hide checkout button', async ({ page }) => {
await expect(page.getByRole('button', { name: 'Checkout' })).toBeHidden();
});
});
test.describe('when cart has items', () => {
test.beforeEach(async ({ page }) => {
// เพิ่มสินค้าก่อน
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
await page.goto('/cart');
});
test('should show item count', async ({ page }) => {
await expect(page.getByRole('listitem')).toHaveCount(1);
});
test('should enable checkout button', async ({ page }) => {
await expect(page.getByRole('button', { name: 'Checkout' })).toBeEnabled();
});
});
});
5 · Setup/Teardown Patterns
Pattern 1: API-based setup (แนะนำ)
test.beforeEach(async ({ request, page }) => {
// สร้าง data ผ่าน API (เร็วกว่าผ่าน UI)
await request.post('/api/reset-test-data');
await page.goto('/dashboard');
});
Pattern 2: Data cleanup after test
test.afterEach(async ({ request }) => {
await request.delete('/api/test-data/cleanup');
});
Pattern 3: Conditional setup
test.beforeEach(async ({ page }, testInfo) => {
// setup เฉพาะ test ที่ต้องการ login
if (testInfo.title.includes('[auth]')) {
await page.goto('/login');
// login...
}
});
6 · ลำดับการรัน Hooks
beforeAll (outer describe)
beforeAll (inner describe)
beforeEach (outer)
beforeEach (inner)
TEST
afterEach (inner)
afterEach (outer)
afterAll (inner describe)
afterAll (outer describe)
แบบฝึกหัด
Refactor test suite ด้านล่างที่มี duplicated setup ให้ใช้ hooks:
Before (duplicated setup)
test('should add item to cart', async ({ page }) => {
await page.goto('/products');
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
});
test('should show product details', async ({ page }) => {
await page.goto('/products');
await page.getByRole('link', { name: 'iPhone 15' }).click();
await expect(page.getByRole('heading', { name: 'iPhone 15' })).toBeVisible();
});
test('should filter products', async ({ page }) => {
await page.goto('/products');
await page.getByRole('combobox', { name: 'Category' }).selectOption('Phones');
await expect(page.getByRole('listitem').first()).toContainText('Phone');
});
ดูคำตอบแนะนำ
After (ใช้ hooks)
test.describe('Products Page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/products');
});
test('should add item to cart', async ({ page }) => {
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
});
test('should show product details', async ({ page }) => {
await page.getByRole('link', { name: 'iPhone 15' }).click();
await expect(page.getByRole('heading', { name: 'iPhone 15' })).toBeVisible();
});
test('should filter products', async ({ page }) => {
await page.getByRole('combobox', { name: 'Category' }).selectOption('Phones');
await expect(page.getByRole('listitem').first()).toContainText('Phone');
});
});
สิ่งที่เปลี่ยน:
- ย้าย
page.goto('/products')ที่ซ้ำกันทุก test ไปbeforeEach - ครอบทั้งหมดด้วย
test.describe()เพื่อจัดกลุ่ม - แต่ละ test สั้นลงและอ่านง่ายขึ้น
สรุปสิ่งที่ต้องจำ
- beforeEach ใช้บ่อยที่สุด — สำหรับ navigate, reset state ก่อนทุก test
- beforeAll/afterAll สำหรับ setup/teardown ที่ทำครั้งเดียว (seed data, cleanup)
- test.describe() จัดกลุ่ม test ที่เกี่ยวข้อง — hooks ใน describe รันเฉพาะ test ใน group นั้น
- ใช้ API-based setup (request) แทน UI-based setup เมื่อเป็นไปได้ — เร็วกว่ามาก
- อย่าลืม cleanup — test data ที่สร้างใน beforeAll ต้องลบใน afterAll
- เมื่อ setup ซ้ำกัน → ย้ายไป hooks เพื่อลด duplication