CupCode.cc
Section 04 · Module 06 · Lesson 01 · ~15–20 นาที

API Testing ด้วย Playwright

เวลาเรียน: 15–20 นาที ต้องรู้มาก่อน: Module 05 (Hooks & Fixtures) รูปแบบ: อ่าน + Code Lab + แบบฝึก

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

  • อธิบายว่า Playwright ทำ API testing ได้โดยไม่ต้องเปิด browser
  • ใช้ request context สำหรับ 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 ที่ครอบคลุม:

  1. GET list — ตรวจว่า response เป็น array
  2. POST — สร้างสินค้าใหม่แล้วตรวจ response
  3. GET by ID — ดึงสินค้าที่เพิ่งสร้างมาตรวจ
  4. PUT — อัปเดต price แล้วตรวจว่าเปลี่ยนจริง
  5. 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 — ใช้ request fixture
  • รองรับทุก HTTP method: GET, POST, PUT, DELETE, PATCH
  • API test เร็วกว่า UI test 10-100 เท่า — ควรใช้ทดสอบ business logic
  • ใช้ baseURL และ extraHTTPHeaders ใน config เพื่อลด code ซ้ำ
  • ตรวจทั้ง status code และ response body เสมอ