Test Fixtures ใน Playwright
จบบทนี้แล้วคุณจะ...
- เข้าใจว่า fixture คืออะไรและทำไมถึงสำคัญ
- รู้จัก built-in fixtures: page, context, browser, request
- สร้าง custom fixtures ของตัวเอง
- เข้าใจ fixture scope และ composition
1 · Fixture คืออะไร?
Fixture คือ "สิ่งที่จัดเตรียมให้ test ใช้" — อาจเป็น browser page, database connection, test data, หรือ object ใดๆ ที่ test ต้องการ
ใน Playwright คุณเคยใช้ fixture มาแล้วโดยไม่รู้ตัว:
test('example', async ({ page }) => {
// ^^^^ นี่คือ fixture!
await page.goto('/');
});
page ที่ได้มาใน test function คือ built-in fixture ที่ Playwright สร้างให้อัตโนมัติ
2 · Built-in Fixtures
| Fixture | คืออะไร | ใช้เมื่อ |
|---|---|---|
page |
Browser page (tab) ที่พร้อมใช้ | ทุก test (ใช้บ่อยที่สุด) |
context |
Browser context (isolated session) | ต้องการจัดการ cookies/storage |
browser |
Browser instance | ต้องการสร้าง context หลายตัว |
request |
API request context | ทดสอบ API โดยไม่ต้องเปิด browser |
// ใช้ page — ได้ page พร้อมใช้ทันที
test('use page', async ({ page }) => {
await page.goto('/');
});
// ใช้ context — จัดการ session เอง
test('use context', async ({ context }) => {
const page = await context.newPage();
await page.goto('/');
});
// ใช้ browser — สร้าง context หลายตัว
test('multi-user', async ({ browser }) => {
const adminContext = await browser.newContext({ storageState: 'admin.json' });
const userContext = await browser.newContext({ storageState: 'user.json' });
const adminPage = await adminContext.newPage();
const userPage = await userContext.newPage();
});
// ใช้ request — ทดสอบ API
test('api test', async ({ request }) => {
const response = await request.get('/api/users');
expect(response.ok()).toBeTruthy();
});
3 · สร้าง Custom Fixtures
เมื่อ test หลายตัวต้องการ setup เดียวกัน สร้าง custom fixture แทนการ copy-paste:
// fixtures.ts
import { test as base, expect } from '@playwright/test';
// กำหนด type ของ fixtures
type MyFixtures = {
todoPage: Page;
loggedInPage: Page;
};
export const test = base.extend<MyFixtures>({
// fixture ที่ login แล้วไปหน้า todo
todoPage: async ({ page }, use) => {
// Setup — ทำก่อน test
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('pass123');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.goto('/todos');
// ส่ง page ให้ test ใช้
await use(page);
// Teardown — ทำหลัง test (ถ้าต้องการ cleanup)
},
});
export { expect };
// tests/todo.spec.ts
import { test, expect } from '../fixtures';
test('should add todo', async ({ todoPage }) => {
// todoPage login แล้วอยู่หน้า /todos แล้ว!
await todoPage.getByLabel('New todo').fill('Buy groceries');
await todoPage.getByRole('button', { name: 'Add' }).click();
await expect(todoPage.getByText('Buy groceries')).toBeVisible();
});
4 · Fixture Scope
// Worker-scoped fixture — สร้างครั้งเดียว ใช้ร่วมกันทุก test ใน worker
export const test = base.extend<{}, { dbConnection: Connection }>({
dbConnection: [async ({}, use) => {
const conn = await createConnection();
await use(conn);
await conn.close();
}, { scope: 'worker' }],
});
| Scope | สร้างเมื่อ | ทำลายเมื่อ | ใช้กับ |
|---|---|---|---|
| test (default) | ก่อนแต่ละ test | หลังแต่ละ test | page, context |
| worker | ก่อน worker เริ่ม | หลัง worker จบ | db connection, server |
5 · Fixture vs beforeEach
| Fixture | beforeEach | |
|---|---|---|
| Reusable | ใช้ข้าม files ได้ | ใช้ได้แค่ใน file เดียว |
| Type-safe | TypeScript type ชัดเจน | ต้องใช้ตัวแปรภายนอก |
| Lazy | สร้างเฉพาะเมื่อ test ต้องการ | รันทุกครั้ง |
| Composable | รวม fixtures หลายตัวได้ | ต้อง nest setup |
กฎง่ายๆ: ถ้า setup ใช้ใน file เดียว → beforeEach ถ้าใช้หลาย files → fixture
แบบฝึกหัด
สร้าง custom fixture สำหรับ scenario นี้: test หลายตัวต้องการหน้า admin ที่ login ด้วย admin account แล้วไปที่ /admin/dashboard
ดูคำตอบแนะนำ
// fixtures.ts
import { test as base, Page } from '@playwright/test';
type AdminFixtures = {
adminPage: Page;
};
export const test = base.extend<AdminFixtures>({
adminPage: async ({ page }, use) => {
// Setup: login as admin
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('adminpass123');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('**/dashboard');
await page.goto('/admin/dashboard');
// ให้ test ใช้
await use(page);
},
});
export { expect } from '@playwright/test';
// tests/admin.spec.ts
import { test, expect } from '../fixtures';
test('should see admin settings', async ({ adminPage }) => {
await adminPage.getByRole('link', { name: 'Settings' }).click();
await expect(adminPage).toHaveURL(/.*settings/);
});
test('should see user list', async ({ adminPage }) => {
await adminPage.getByRole('link', { name: 'Users' }).click();
await expect(adminPage.getByRole('table')).toBeVisible();
});
สรุปสิ่งที่ต้องจำ
- Fixture คือสิ่งที่จัดเตรียมให้ test ใช้ — page, context, browser, custom objects
- Built-in fixtures: page (ใช้บ่อยที่สุด), context, browser, request
- สร้าง custom fixtures ด้วย
base.extend<>()— reusable, type-safe, composable - Fixture มี setup → use → teardown — cleanup อัตโนมัติ
- ใช้ fixture scope 'worker' สำหรับ resource ที่แพง (db connection)
- กฎ: ใช้ใน file เดียว → beforeEach, ใช้หลาย files → fixture