การทดสอบซอฟต์แวร์เป็นขั้นตอนสำคัญที่ไม่ควรมองข้ามในกระบวนการพัฒนา Web Application ในปี 2026 เครื่องมือที่ได้รับความนิยมมากที่สุดสำหรับการทำ End-to-End Testing คือ Playwright ซึ่งพัฒนาโดยทีม Microsoft ด้วยความสามารถที่ครอบคลุมทั้ง Cross-Browser Testing, Auto-waiting, Network Interception และ Trace Viewer ทำให้ Playwright กลายเป็นตัวเลือกอันดับหนึ่งของนักพัฒนาทั่วโลก
บทความนี้จะพาคุณเรียนรู้ Playwright ตั้งแต่พื้นฐานจนถึงระดับสูง ครอบคลุมการติดตั้ง การเขียน Test แรก Locators Assertions Page Object Model การทดสอบ Form และ Authentication จนถึงการใช้งานร่วมกับ CI/CD Pipeline
Playwright คืออะไร?
Playwright คือ Framework สำหรับทำ End-to-End (E2E) Testing ที่สร้างโดย Microsoft ออกแบบมาเพื่อทดสอบ Web Application ข้าม Browser ได้อย่างน่าเชื่อถือ โดยรองรับ Chromium (Chrome, Edge), Firefox และ WebKit (Safari) ด้วย API เดียวกัน Playwright เปิดตัวครั้งแรกในปี 2020 และพัฒนาอย่างรวดเร็วจนกลายเป็นคู่แข่งสำคัญของ Cypress และ Selenium
จุดเด่นหลักของ Playwright ที่ทำให้มันแตกต่าง ได้แก่ การรองรับหลาย Browser ในตัว ระบบ Auto-waiting ที่ฉลาดไม่ต้องเขียน sleep หรือ waitFor เอง Browser Contexts ที่สามารถจำลองสถานการณ์แยกกันได้เหมือนเปิด Incognito หลายหน้าต่างพร้อมกัน และ Trace Viewer ที่ช่วยวิเคราะห์ปัญหาได้อย่างละเอียดทุกขั้นตอน
Playwright vs Cypress vs Selenium เปรียบเทียบ
ก่อนจะเลือกเครื่องมือทดสอบ มาดูการเปรียบเทียบระหว่าง 3 เครื่องมือยอดนิยม:
| คุณสมบัติ | Playwright | Cypress | Selenium |
|---|---|---|---|
| Browser Support | Chromium, Firefox, WebKit | Chromium, Firefox, WebKit (จำกัด) | ทุก Browser |
| ภาษา | JS/TS, Python, Java, C# | JavaScript/TypeScript | หลายภาษา |
| Auto-waiting | มีในตัว | มีในตัว | ต้องเขียนเอง |
| Multi-tab/window | รองรับ | ไม่รองรับ | รองรับ |
| iframe | รองรับดี | จำกัด | รองรับ |
| Network Interception | ดีมาก | ดี | จำกัด |
| Parallel Testing | ในตัว | ต้องใช้ Dashboard | ต้องใช้ Grid |
| Trace Viewer | ยอดเยี่ยม | ไม่มี (ใช้ Video) | ไม่มี |
| ความเร็ว | เร็วมาก | เร็ว | ปานกลาง |
| Community | เติบโตเร็ว | ใหญ่ | ใหญ่ที่สุด |
สถาปัตยกรรมของ Playwright
Playwright ทำงานโดยเชื่อมต่อกับ Browser ผ่าน Protocol โดยตรง (Chrome DevTools Protocol สำหรับ Chromium) ไม่ใช่ผ่าน WebDriver เหมือน Selenium ทำให้เร็วกว่าและควบคุมได้ละเอียดกว่ามาก
Browser Contexts
Browser Context คือแนวคิดสำคัญของ Playwright เทียบได้กับ Incognito Window แต่ละ Context มี Cookies, Storage และ Session แยกกันอย่างสมบูรณ์ ทำให้สามารถทดสอบหลาย User พร้อมกันใน Test เดียวกันได้โดยไม่ชนกัน
// สร้าง 2 Context แยกกัน (เหมือน 2 User คนละคน)
const context1 = await browser.newContext();
const context2 = await browser.newContext();
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// User 1 login
await page1.goto('/login');
await page1.fill('#email', 'user1@test.com');
// User 2 login (ไม่กระทบ User 1)
await page2.goto('/login');
await page2.fill('#email', 'user2@test.com');
Pages
แต่ละ Context สามารถมีหลาย Page (Tab) ได้ ทำให้ทดสอบ Multi-tab Workflow ได้อย่างง่ายดาย เช่น การเปิด Link ใน Tab ใหม่ หรือ Popup Window
// จับ Page ใหม่ที่เปิดขึ้นมา
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.click('a[target="_blank"]')
]);
await newPage.waitForLoadState();
console.log(await newPage.title());
ติดตั้งและตั้งค่า Playwright
ติดตั้งด้วย npm
# สร้างโปรเจกต์ใหม่
npm init playwright@latest
# หรือเพิ่มเข้าโปรเจกต์ที่มีอยู่
npm install -D @playwright/test
npx playwright install # ดาวน์โหลด Browser binaries
# ตรวจสอบว่าติดตั้งสำเร็จ
npx playwright --version
โครงสร้างโปรเจกต์
my-project/
├── playwright.config.ts # การตั้งค่าหลัก
├── tests/
│ ├── example.spec.ts # Test files
│ └── helpers/
│ └── login.ts # Helper functions
├── pages/ # Page Object Models
│ ├── login.page.ts
│ └── dashboard.page.ts
├── test-results/ # ผลลัพธ์ Test
└── playwright-report/ # HTML Report
การตั้งค่า playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
เขียน Test แรกกับ Playwright
// tests/homepage.spec.ts
import { test, expect } from '@playwright/test';
test('homepage should display welcome message', async ({ page }) => {
await page.goto('/');
// ตรวจสอบ Title
await expect(page).toHaveTitle(/My App/);
// ตรวจสอบ Heading
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
// ตรวจสอบ Navigation links
await expect(page.getByRole('link', { name: 'About' })).toBeVisible();
await expect(page.getByRole('link', { name: 'Contact' })).toBeVisible();
});
test('should navigate to about page', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL(/.*about/);
await expect(page.getByRole('heading', { name: 'About Us' })).toBeVisible();
});
# รัน Test
npx playwright test # รันทุก Test
npx playwright test homepage.spec.ts # รันเฉพาะไฟล์
npx playwright test --headed # เปิด Browser ให้เห็น
npx playwright test --debug # Debug mode
npx playwright test --ui # UI mode (interactive)
Locators — หาองค์ประกอบบนหน้าเว็บ
Locators คือวิธีที่ Playwright ใช้ค้นหา Element บนหน้าเว็บ Playwright สนับสนุน Locator หลายแบบ โดยแนะนำให้ใช้ User-facing Locators เป็นอันดับแรกเพราะทนทานต่อการเปลี่ยนแปลง HTML มากกว่า
User-facing Locators (แนะนำ)
// getByRole — ค้นหาตาม ARIA Role (ดีที่สุด)
page.getByRole('button', { name: 'Submit' })
page.getByRole('link', { name: 'Learn More' })
page.getByRole('heading', { name: 'Welcome', level: 1 })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('checkbox', { name: 'Remember me' })
// getByText — ค้นหาตาม Text Content
page.getByText('Hello World')
page.getByText('Hello World', { exact: true }) // ตรงทั้งหมด
// getByLabel — ค้นหา Input ตาม Label
page.getByLabel('Email')
page.getByLabel('Password')
// getByPlaceholder — ค้นหาตาม Placeholder
page.getByPlaceholder('Search...')
// getByAltText — ค้นหา Image ตาม Alt Text
page.getByAltText('Company Logo')
// getByTestId — ค้นหาตาม data-testid (สำหรับ Element ที่หา Role ไม่ได้)
page.getByTestId('submit-button')
page.getByTestId('user-profile-card')
CSS และ XPath Locators (ใช้เมื่อจำเป็น)
// CSS Selector
page.locator('.btn-primary')
page.locator('#login-form')
page.locator('div.card >> text=Hello')
// XPath
page.locator('xpath=//button[@type="submit"]')
// Filter Locator
page.getByRole('listitem')
.filter({ hasText: 'Product A' })
.getByRole('button', { name: 'Buy' })
getByRole เป็นอันดับแรกเสมอ เพราะมันสะท้อนวิธีที่ User จริงมองเห็นหน้าเว็บ และทนทานต่อการเปลี่ยนแปลง CSS class หรือ HTML structure ถ้า getByRole ใช้ไม่ได้ ให้ใช้ getByTestId เป็นทางเลือกสำรอง
Assertions — ตรวจสอบผลลัพธ์
Playwright มี Assertion ที่ Auto-retry จนกว่าเงื่อนไขจะเป็นจริงหรือ Timeout ทำให้ Test เสถียรมากขึ้น ไม่ต้องเขียน wait เอง
// Page Assertions
await expect(page).toHaveTitle('My App');
await expect(page).toHaveURL('https://example.com/dashboard');
// Element Assertions
await expect(page.getByRole('button')).toBeVisible();
await expect(page.getByRole('button')).toBeEnabled();
await expect(page.getByRole('button')).toBeDisabled();
await expect(page.getByRole('button')).toHaveText('Submit');
await expect(page.getByRole('textbox')).toHaveValue('hello');
await expect(page.getByRole('textbox')).toBeEmpty();
await expect(page.getByRole('checkbox')).toBeChecked();
await expect(page.locator('.item')).toHaveCount(5);
await expect(page.locator('.card')).toHaveClass(/active/);
await expect(page.locator('.box')).toHaveCSS('color', 'rgb(0, 0, 0)');
// Negation — ตรวจสอบว่าไม่เป็น
await expect(page.getByRole('dialog')).not.toBeVisible();
await expect(page.getByRole('button')).not.toBeDisabled();
// Soft Assertions — ไม่หยุด Test ถ้า Fail
await expect.soft(page.getByRole('heading')).toHaveText('Title');
await expect.soft(page.getByRole('button')).toBeVisible();
Page Object Model (POM)
Page Object Model เป็น Design Pattern ที่ช่วยจัดระเบียบ Test Code ให้ดูแลรักษาง่าย โดยแยก Logic ของแต่ละหน้าออกมาเป็น Class หรือ Object
// pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign In' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test('should login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('admin@test.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});
test('should show error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('wrong@test.com', 'wrongpass');
await expect(loginPage.errorMessage).toBeVisible();
await expect(loginPage.errorMessage).toHaveText('Invalid credentials');
});
ทดสอบ Forms, Navigation และ Authentication
ทดสอบ Form
test('should submit contact form', async ({ page }) => {
await page.goto('/contact');
// กรอก Form
await page.getByLabel('Name').fill('John Doe');
await page.getByLabel('Email').fill('john@example.com');
await page.getByLabel('Subject').selectOption('support');
await page.getByLabel('Message').fill('Hello, I need help with...');
await page.getByLabel('Accept Terms').check();
// Submit
await page.getByRole('button', { name: 'Send Message' }).click();
// ตรวจสอบผลลัพธ์
await expect(page.getByText('Message sent successfully')).toBeVisible();
});
test('should validate required fields', async ({ page }) => {
await page.goto('/contact');
await page.getByRole('button', { name: 'Send Message' }).click();
await expect(page.getByText('Name is required')).toBeVisible();
await expect(page.getByText('Email is required')).toBeVisible();
});
ทดสอบ Navigation
test('should navigate through main menu', async ({ page }) => {
await page.goto('/');
// คลิก Navigation
await page.getByRole('navigation').getByRole('link', { name: 'Products' }).click();
await expect(page).toHaveURL('/products');
// ทดสอบ Breadcrumb
await expect(page.getByRole('navigation', { name: 'Breadcrumb' }))
.toContainText('Products');
// ทดสอบ Back button
await page.goBack();
await expect(page).toHaveURL('/');
});
ทดสอบ Authentication
// บันทึก Authentication State เพื่อใช้ซ้ำ
// auth.setup.ts
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('admin@test.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Sign In' }).click();
await page.waitForURL('/dashboard');
await page.context().storageState({ path: authFile });
});
// playwright.config.ts — ใช้ Auth state
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
]
API Mocking และ Network Interception
Playwright สามารถ Intercept Network Request ได้ ทำให้ทดสอบ Frontend แยกจาก Backend ได้สมบูรณ์ ช่วยให้ Test เร็วขึ้นและเสถียรขึ้นเพราะไม่ต้องพึ่ง API จริง
test('should display products from API', async ({ page }) => {
// Mock API Response
await page.route('**/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Product A', price: 100 },
{ id: 2, name: 'Product B', price: 200 },
]),
});
});
await page.goto('/products');
await expect(page.getByText('Product A')).toBeVisible();
await expect(page.getByText('Product B')).toBeVisible();
});
test('should handle API error gracefully', async ({ page }) => {
await page.route('**/api/products', async (route) => {
await route.fulfill({ status: 500 });
});
await page.goto('/products');
await expect(page.getByText('Something went wrong')).toBeVisible();
});
test('should modify API response', async ({ page }) => {
await page.route('**/api/user', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.name = 'Modified Name';
await route.fulfill({ response, json });
});
await page.goto('/profile');
await expect(page.getByText('Modified Name')).toBeVisible();
});
Visual Comparison Testing
Playwright รองรับ Visual Regression Testing ที่เปรียบเทียบ Screenshot กับ Baseline เพื่อตรวจจับการเปลี่ยนแปลงทาง Visual ที่ไม่ตั้งใจ เช่น Layout เลื่อน สีเปลี่ยน หรือ Element หายไป
test('homepage visual test', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});
test('component visual test', async ({ page }) => {
await page.goto('/components');
// Screenshot เฉพาะ Component
const card = page.locator('.product-card').first();
await expect(card).toHaveScreenshot('product-card.png');
});
test('visual test with options', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage-full.png', {
fullPage: true,
maxDiffPixelRatio: 0.05, // ยอมรับความแตกต่างไม่เกิน 5%
mask: [page.locator('.dynamic-content')], // ซ่อน Element ที่เปลี่ยนบ่อย
});
});
// อัปเดต Baseline Screenshots
// npx playwright test --update-snapshots
Trace Viewer และ Debugging
Trace Viewer เป็นเครื่องมือที่ทรงพลังที่สุดของ Playwright สำหรับการ Debug ช่วยให้เห็นทุกขั้นตอนของ Test ราวกับดู Video แต่ดีกว่าเพราะสามารถย้อนดูทีละ Step ได้พร้อม DOM Snapshot, Network Log และ Console Log
# เปิด Trace Viewer
npx playwright test --trace on
npx playwright show-trace trace.zip
# ใน Config — เปิด Trace เมื่อ Retry
use: {
trace: 'on-first-retry', // บันทึก Trace เมื่อ Test fail แล้ว retry
}
# Debug Mode
npx playwright test --debug # เปิด Inspector
PWDEBUG=1 npx playwright test # Debug ทุก Test
# UI Mode — Interactive Testing
npx playwright test --ui
การใช้ test.step สำหรับ Trace ที่อ่านง่าย
test('complete checkout flow', async ({ page }) => {
await test.step('Navigate to shop', async () => {
await page.goto('/shop');
});
await test.step('Add item to cart', async () => {
await page.getByRole('button', { name: 'Add to Cart' }).first().click();
await expect(page.getByTestId('cart-count')).toHaveText('1');
});
await test.step('Complete checkout', async () => {
await page.getByRole('link', { name: 'Cart' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
await expect(page.getByText('Order confirmed')).toBeVisible();
});
});
Playwright กับ CI/CD (GitHub Actions)
Playwright ทำงานร่วมกับ CI/CD ได้ง่ายมาก มี Docker Image สำเร็จรูปที่ติดตั้ง Browser ทุกตัวไว้แล้ว การตั้งค่า GitHub Actions ทำได้ดังนี้:
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${ '{' } !cancelled() { '}' }
with:
name: playwright-report
path: playwright-report/
retention-days: 30
retries: 2 ใน CI เพื่อจัดการกับ Flaky tests และเปิด trace: 'on-first-retry' เพื่อบันทึก Trace เฉพาะ Test ที่ Fail ช่วยลดขนาดไฟล์แต่ยังดีบักได้
Parallel Testing
Playwright รัน Test แบบ Parallel โดยค่าเริ่มต้น แต่ละ Worker Process จะรัน Test File แยกกัน ทำให้ใช้ประโยชน์จาก CPU หลาย Core ได้เต็มที่
// playwright.config.ts
export default defineConfig({
fullyParallel: true, // รัน Test ทุกตัวแบบ Parallel
workers: 4, // จำนวน Worker (default = 50% of CPU cores)
});
// ควบคุม Parallel ในระดับ Test
test.describe.configure({ mode: 'serial' }); // รันตามลำดับ
test.describe.configure({ mode: 'parallel' }); // รันพร้อมกัน
// Test ที่ต้องรันตามลำดับ
test.describe('CRUD operations', () => {
test.describe.configure({ mode: 'serial' });
test('create item', async ({ page }) => { /* ... */ });
test('read item', async ({ page }) => { /* ... */ });
test('update item', async ({ page }) => { /* ... */ });
test('delete item', async ({ page }) => { /* ... */ });
});
Mobile Emulation
Playwright สามารถจำลองอุปกรณ์มือถือได้อย่างสมจริง รวมถึง Touch Events, Viewport Size, Device Scale Factor และ User-Agent ทำให้ทดสอบ Responsive Design ได้โดยไม่ต้องมีอุปกรณ์จริง
// ใน Config — ตั้งค่า Mobile Projects
projects: [
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
{ name: 'Tablet', use: { ...devices['iPad Pro 11'] } },
],
// ใน Test — จำลองเฉพาะ Test
test('mobile menu should work', async ({ browser }) => {
const context = await browser.newContext({
...devices['iPhone 12'],
locale: 'th-TH',
geolocation: { latitude: 13.7563, longitude: 100.5018 },
permissions: ['geolocation'],
});
const page = await context.newPage();
await page.goto('/');
// เปิด Hamburger menu
await page.getByRole('button', { name: 'Menu' }).click();
await expect(page.getByRole('navigation')).toBeVisible();
});
Playwright Codegen — สร้าง Test อัตโนมัติ
Playwright Codegen เป็นเครื่องมือที่ช่วยสร้าง Test Code โดยอัตโนมัติจากการใช้งาน Browser จริง เหมาะสำหรับผู้เริ่มต้นหรือการสร้าง Test เบื้องต้นอย่างรวดเร็ว แม้ Code ที่ได้อาจต้องปรับแต่งเพิ่มเติม แต่ช่วยประหยัดเวลาได้มาก
# เปิด Codegen
npx playwright codegen https://example.com
# Codegen กับ Device Emulation
npx playwright codegen --device="iPhone 12" https://example.com
# Codegen กับ Viewport ที่กำหนด
npx playwright codegen --viewport-size=800,600 https://example.com
# Codegen กับ Auth state
npx playwright codegen --load-storage=auth.json https://example.com
เมื่อเปิด Codegen Browser จะเปิดขึ้นมาพร้อมหน้าต่าง Inspector ทุกการคลิก พิมพ์ หรือ Navigate จะถูกบันทึกเป็น Code อัตโนมัติ สามารถ Copy Code ไปใช้ในโปรเจกต์ได้เลย
Testing Accessibility (a11y)
Playwright สามารถใช้ทดสอบ Accessibility ร่วมกับ @axe-core/playwright ได้ เพื่อตรวจสอบว่า Web Application เข้าถึงได้สำหรับผู้ใช้ทุกคน รวมถึงผู้ใช้ Screen Reader
// ติดตั้ง
// npm install -D @axe-core/playwright
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('should not have accessibility violations', async ({ page }) => {
await page.goto('/');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
test('navigation should be accessible', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.include('nav')
.analyze();
expect(results.violations).toEqual([]);
});
// ใช้ getByRole เพื่อทดสอบ ARIA
test('dialog should be accessible', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Open Dialog' }).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible();
await expect(dialog).toHaveAttribute('aria-labelledby');
// Tab navigation ภายใน Dialog
await page.keyboard.press('Tab');
await expect(page.getByRole('button', { name: 'Close' })).toBeFocused();
});
Test Reporter
Playwright มี Reporter หลายแบบที่ช่วยแสดงผลลัพธ์ Test ในรูปแบบต่างๆ ตามความต้องการ ตั้งแต่แสดงบน Terminal จนถึงสร้าง HTML Report สวยงาม
// playwright.config.ts
export default defineConfig({
// Reporter เดียว
reporter: 'html',
// หลาย Reporter
reporter: [
['list'], // แสดงบน Terminal
['html', { open: 'never' }], // HTML Report
['json', { outputFile: 'results.json' }], // JSON
['junit', { outputFile: 'results.xml' }], // JUnit (สำหรับ CI)
],
});
// เปิด HTML Report
// npx playwright show-report
Custom Reporter
// custom-reporter.ts
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter';
class MyReporter implements Reporter {
onTestBegin(test: TestCase) {
console.log(`Starting: ${test.title}`);
}
onTestEnd(test: TestCase, result: TestResult) {
console.log(`Finished: ${test.title} - ${result.status}`);
}
onEnd() {
console.log('All tests completed!');
}
}
Best Practices สำหรับ Playwright Testing
1. ใช้ User-facing Locators
เลือก getByRole, getByText, getByLabel ก่อน CSS Selector เสมอ เพราะทนทานต่อการเปลี่ยนแปลง HTML มากกว่า ผู้ใช้จริงไม่ได้มองเห็น CSS class แต่มองเห็น Text และ Role ของ Element
2. ใช้ Page Object Model
จัดโครงสร้าง Test ด้วย POM เพื่อลดการ Duplicate Code ถ้า UI เปลี่ยน แก้ไขแค่ที่เดียวใน Page Object ไม่ต้องแก้ทุก Test File
3. ทำ Test ให้ Independent
แต่ละ Test ต้องทำงานได้โดยลำพัง ไม่ขึ้นกับลำดับการรันหรือผลลัพธ์ของ Test อื่น ใช้ beforeEach เพื่อ Setup State ที่ต้องการ
4. หลีกเลี่ยง Hard-coded Waits
// อย่าทำแบบนี้
await page.waitForTimeout(5000);
// ทำแบบนี้แทน — รอ Element ที่ต้องการ
await expect(page.getByText('Loaded')).toBeVisible();
await page.waitForResponse('**/api/data');
5. จัดการ Test Data ให้ดี
สร้าง Test Data ก่อนรัน Test และทำความสะอาดหลังรัน ใช้ API เพื่อสร้าง Data แทนการคลิกผ่าน UI เพราะเร็วกว่าและเสถียรกว่ามาก
6. ใช้ Fixtures สำหรับ Setup ที่ซ้ำกัน
// fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/login.page';
type MyFixtures = {
loginPage: LoginPage;
};
export const test = base.extend({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await use(loginPage);
},
});
// ใช้ใน Test
test('login test', async ({ loginPage }) => {
await loginPage.login('user@test.com', 'pass');
});
7. ใช้ Tags เพื่อจัดกลุ่ม Test
test('fast test @smoke', async ({ page }) => { /* ... */ });
test('slow test @regression', async ({ page }) => { /* ... */ });
// รันเฉพาะกลุ่ม
// npx playwright test --grep @smoke
// npx playwright test --grep-invert @regression
สรุป
Playwright เป็นเครื่องมือ End-to-End Testing ที่ทรงพลังและทันสมัยที่สุดในปี 2026 ด้วย Cross-Browser Support ที่แท้จริง ระบบ Auto-waiting ที่ชาญฉลาด และ Trace Viewer ที่ช่วย Debug ได้อย่างละเอียด ทำให้การเขียน Test เป็นเรื่องที่สนุกและมีประสิทธิภาพมากขึ้น
เริ่มต้นใช้ Playwright วันนี้ด้วย npm init playwright@latest ลองใช้ Codegen สร้าง Test แรก แล้วค่อยๆ เรียนรู้ Locators, Assertions และ Page Object Model จากนั้นเพิ่ม API Mocking, Visual Testing และ CI/CD Pipeline เข้าไป แล้วคุณจะพบว่า Playwright เปลี่ยนวิธีการทดสอบ Web Application ไปอย่างสิ้นเชิง ทำให้ทีมมั่นใจในคุณภาพของ Code ทุกครั้งที่ Deploy
สำหรับข้อมูลเพิ่มเติม สามารถศึกษาได้ที่ Playwright Official Documentation และลองใช้ Playwright Codegen เพื่อเริ่มต้นอย่างรวดเร็ว
