API Testing ด้วย Playwright
จบบทนี้แล้วคุณจะ...
- อธิบายว่า Playwright ทำ API testing ได้โดยไม่ต้องเปิด browser
- ใช้
requestcontext สำหรับ GET, POST, PUT, DELETE - ตรวจสอบ status code และ response body
- เปรียบเทียบ API test กับ UI test ว่าควรใช้เมื่อไหร่
1 · ทำไมต้อง API Testing?
UI test ทดสอบจาก "หน้าจอ" — ช้า เปราะ และต้องรอ render
API test ทดสอบจาก "ท่อข้อมูล" โดยตรง — เร็ว เสถียร และครอบคลุม business logic ได้ลึกกว่า
| เปรียบเทียบ | UI Test | API Test |
|---|---|---|
| ความเร็ว | ช้า (ต้องเปิด browser) | เร็วมาก (HTTP request ตรง) |
| ความเสถียร | เปราะ (UI เปลี่ยนบ่อย) | เสถียร (API contract ชัดเจน) |
| ครอบคลุม | ดีสำหรับ user flow | ดีสำหรับ business logic |
| ค่าใช้จ่าย | สูง (resource + เวลา) | ต่ำ |
กฎง่ายๆ: ใช้ API test สำหรับ logic และ data validation, ใช้ UI test สำหรับ user experience
2 · Request Context ใน Playwright
Playwright มี APIRequestContext ที่สร้าง HTTP request ได้โดยไม่ต้องเปิด browser:
import { test, expect } from '@playwright/test';
test('GET /api/users returns user list', async ({ request }) => {
const response = await request.get('https://api.example.com/users');
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('data');
expect(body.data.length).toBeGreaterThan(0);
});
สิ่งที่ต้องสังเกต:
{ request }คือ built-in fixture — ไม่ต้อง import อะไรเพิ่มresponse.json()parse body เป็น object ให้เลย- ใช้
expectตัวเดียวกับ UI test
3 · CRUD Operations ครบทุก Method
POST — สร้างข้อมูลใหม่
test('POST /api/users creates a new user', async ({ request }) => {
const response = await request.post('https://api.example.com/users', {
data: {
name: 'สมชาย ทดสอบ',
email: '[email protected]',
role: 'admin'
}
});
expect(response.status()).toBe(201);
const user = await response.json();
expect(user.name).toBe('สมชาย ทดสอบ');
expect(user.id).toBeDefined();
});
PUT — อัปเดตข้อมูล
test('PUT /api/users/:id updates user', async ({ request }) => {
const response = await request.put('https://api.example.com/users/1', {
data: {
name: 'สมชาย อัปเดตแล้ว'
}
});
expect(response.status()).toBe(200);
const user = await response.json();
expect(user.name).toBe('สมชาย อัปเดตแล้ว');
});
DELETE — ลบข้อมูล
test('DELETE /api/users/:id removes user', async ({ request }) => {
const response = await request.delete('https://api.example.com/users/1');
expect(response.status()).toBe(204);
// ยืนยันว่าลบแล้วจริง
const getResponse = await request.get('https://api.example.com/users/1');
expect(getResponse.status()).toBe(404);
});
4 · Response Body Validation แบบละเอียด
test('user response matches expected schema', async ({ request }) => {
const response = await request.get('https://api.example.com/users/1');
const user = await response.json();
// ตรวจสอบโครงสร้าง
expect(user).toEqual(expect.objectContaining({
id: expect.any(Number),
name: expect.any(String),
email: expect.stringContaining('@'),
createdAt: expect.any(String)
}));
// ตรวจสอบว่า email format ถูกต้อง
expect(user.email).toMatch(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
5 · ตั้งค่า Base URL และ Headers
ใน playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
'Authorization': `Bearer ${process.env.API_TOKEN}`,
'Content-Type': 'application/json'
}
}
});
จากนั้นใน test ไม่ต้องใส่ full URL:
test('GET /users with baseURL', async ({ request }) => {
const response = await request.get('/users'); // ใช้แค่ path
expect(response.ok()).toBeTruthy();
});
แบบฝึกหัด: เขียน API Tests สำหรับ CRUD Endpoints
สถานการณ์: คุณมี REST API สำหรับจัดการ "สินค้า" (Products) ที่มี endpoints ดังนี้:
GET /api/products— รายการสินค้าทั้งหมดGET /api/products/:id— ดูสินค้าเฉพาะPOST /api/products— สร้างสินค้าใหม่ (ต้องส่ง name, price, category)PUT /api/products/:id— แก้ไขสินค้าDELETE /api/products/:id— ลบสินค้า
โจทย์: เขียน test suite ที่ครอบคลุม:
- GET list — ตรวจว่า response เป็น array
- POST — สร้างสินค้าใหม่แล้วตรวจ response
- GET by ID — ดึงสินค้าที่เพิ่งสร้างมาตรวจ
- PUT — อัปเดต price แล้วตรวจว่าเปลี่ยนจริง
- DELETE — ลบแล้วตรวจว่า GET ได้ 404
ดูคำตอบแนะนำ
import { test, expect } from '@playwright/test';
test.describe('Products API CRUD', () => {
let productId: number;
test('GET /api/products returns product list', async ({ request }) => {
const response = await request.get('/api/products');
expect(response.status()).toBe(200);
const products = await response.json();
expect(Array.isArray(products.data)).toBeTruthy();
});
test('POST /api/products creates new product', async ({ request }) => {
const response = await request.post('/api/products', {
data: {
name: 'ข้าวหอมมะลิ 5 กก.',
price: 250,
category: 'อาหาร'
}
});
expect(response.status()).toBe(201);
const product = await response.json();
expect(product.name).toBe('ข้าวหอมมะลิ 5 กก.');
expect(product.price).toBe(250);
productId = product.id;
});
test('GET /api/products/:id returns specific product', async ({ request }) => {
const response = await request.get(`/api/products/${productId}`);
expect(response.status()).toBe(200);
const product = await response.json();
expect(product.id).toBe(productId);
expect(product.name).toBe('ข้าวหอมมะลิ 5 กก.');
});
test('PUT /api/products/:id updates price', async ({ request }) => {
const response = await request.put(`/api/products/${productId}`, {
data: { price: 280 }
});
expect(response.status()).toBe(200);
const product = await response.json();
expect(product.price).toBe(280);
});
test('DELETE /api/products/:id removes product', async ({ request }) => {
const deleteRes = await request.delete(`/api/products/${productId}`);
expect(deleteRes.status()).toBe(204);
const getRes = await request.get(`/api/products/${productId}`);
expect(getRes.status()).toBe(404);
});
});
สิ่งที่ต้องจำ
- Playwright ทำ API testing ได้โดยไม่ต้องเปิด browser — ใช้
requestfixture - รองรับทุก HTTP method: GET, POST, PUT, DELETE, PATCH
- API test เร็วกว่า UI test 10-100 เท่า — ควรใช้ทดสอบ business logic
- ใช้
baseURLและextraHTTPHeadersใน config เพื่อลด code ซ้ำ - ตรวจทั้ง status code และ response body เสมอ