CupCode.cc
Section 04 · Module 05 · Lesson 01 · ~15 นาที

Test Fixtures ใน Playwright

เวลาเรียน: 15 นาที ต้องรู้มาก่อน: Module 04: Auto-Waiting and Flaky Test Prevention รูปแบบ: อ่าน + แบบฝึกสั้น

จบบทนี้แล้วคุณจะ...

  • เข้าใจว่า 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