NỘI DUNG BÀI HỌC

🔑 Phần 1: "Tấm Hộ Chiếu" JWT & Hệ Thống Bảo Mật AuthN/AuthZ

🛣️ Phần 2: Điều Hướng Dữ Liệu Với Path Params & Query Params

📖 Phần 3: Chiến Thuật Xử Lý Phân Trang (Pagination)

📦 Phần 4: Multipart/Form-Data - "Container" Chứa Dữ Liệu Phức Tạp

 



🔐 Phần 1: AUTHENTICATION, AUTHORIZATION VÀ CƠ CHẾ JWT


PHÂN BIỆT AUTHENTICATION VS. AUTHORIZATION

Hai thuật ngữ này hay đi cùng nhau và viết tắt cũng na ná nhau (AuthNAuthZ), nên rất dễ nhầm. Hãy dùng ví dụ "Đi Máy Bay" để giải thích.

1. Authentication (Xác thực) - "Bạn là ai?" 🆔

  • Định nghĩa: Là quá trình xác minh danh tính của người dùng.

  • Ví dụ: Khi bạn đến sân bay, bạn đưa Hộ chiếu/CCCD cho nhân viên an ninh. Nhân viên nhìn ảnh, nhìn mặt bạn và xác nhận: "Ok, đúng là ông Nguyễn Văn A".

  • Trong phần mềm: Là lúc bạn nhập Username + Password. Hệ thống kiểm tra trong Database xem có khớp không.

  • Kết quả: Nếu đúng -> Hệ thống cấp cho bạn một cái "Thẻ bài" (Session ID hoặc Token).

2. Authorization (Phân quyền) - "Bạn được làm gì?" 👮

  • Định nghĩa: Là quá trình kiểm tra xem người dùng (đã được xác thực) có quyền truy cập vào tài nguyên cụ thể nào đó không.

  • Ví dụ: Sau khi qua cửa an ninh (Authentication), bạn cầm vé máy bay đi ra cửa lên tàu bay.

    • Vé hạng Thương gia (Admin): Được vào phòng chờ VIP, được đi lối ưu tiên.

    • Vé hạng Phổ thông (User): Chỉ được ngồi ghế thường, không được vào phòng VIP.

  • Trong phần mềm: Bạn click vào trang /admin/dashboard. Hệ thống kiểm tra: "Ông A này có role là Admin không? Có thì cho vào, không thì chặn lại".

  • Lỗi đặc trưng:

    • Sai Authentication -> Trả về 401 Unauthorized (Chưa đăng nhập).

    • Sai Authorization -> Trả về 403 Forbidden (Đã đăng nhập nhưng không đủ tuổi/không đủ quyền).


 TẠI SAO LẠI CẦN JWT (JSON WEB TOKEN)?

Trước khi có JWT, thế giới dùng Session-based Authentication.

Vấn đề của cách cũ (Session)

  1. Client: Login xong, Server tạo ra 1 SessionID, lưu vào bộ nhớ (RAM/Database) của Server.

  2. Server: Gửi SessionID về cho Client (dưới dạng Cookie).

  3. Vấn đề:

    • Tốn RAM: Nếu có 1 triệu user online, Server phải lưu 1 triệu cái session trong RAM.

    • Khó mở rộng (Scaling): Nếu bạn có 3 con Server (A, B, C). User login vào Server A (lưu session ở A). Request sau User bị điều hướng sang Server B -> Server B không thấy session đâu -> Bắt login lại. (Rất phiền).

Giải pháp JWT (Stateless - Không lưu trạng thái)

JWT ra đời với tư duy: "Server không cần nhớ gì cả. Hãy đưa toàn bộ thông tin cho Client tự cầm. Server chỉ cần ký tên đóng dấu là được."

 CẤU TRÚC GIẢI PHẪU CỦA JWT

Một chuỗi JWT nhìn như một đống ký tự lằng nhằng, nhưng thực ra nó gồm 3 phần, ngăn cách nhau bởi dấu chấm .:

AAAAAA.BBBBBB.CCCCCC

1. Header (AAAAAA)

Chứa thông tin về thuật toán mã hóa.

{
  "alg": "HS256", // Thuật toán ký
  "typ": "JWT"
}

2. Payload ( BBBBBB) - QUAN TRỌNG NHẤT

Chứa dữ liệu (Claims) mà Server muốn gửi gắm. Đây là nơi chứa thông tin User.

{
  "sub": "1234567890",  // User ID
  "name": "Anh Tester", // Tên
  "role": "admin",      // Quyền (Dùng để Authorization)
  "iat": 1516239022,    // Thời điểm tạo (Issued At)
  "exp": 1516242622     // Thời điểm hết hạn (Expiry)
}

⚠️ Lưu ý  Payload này chỉ được Mã hóa Base64 (dạng encode ai cũng dịch ngược được), KHÔNG ĐƯỢC MẬT MÃ HÓA.

👉 Tuyệt đối không để Password hay thông tin thẻ tín dụng vào đây, vì ai nhặt được Token cũng có thể đọc được nội dung này (dùng trang jwt.io để soi).

3. Signature ( CCCCCC) - Chữ ký bảo mật

Đây là phần đảm bảo tính toàn vẹn. Server sẽ tạo ra chữ ký bằng công thức:

Sign = Hash(Header + Payload + SecretKey)

  • SecretKey: Là cái chìa khóa bí mật chỉ nằm trên Server. Không ai biết.

  • Cơ chế bảo vệ: Nếu Hacker cố tình sửa Payload (ví dụ sửa role: "user" thành role: "admin"), thì khi Server tính toán lại Hash, nó sẽ ra một chuỗi Signature khác hẳn với cái Signature đang có -> Server biết ngay là Token giả mạo -> Từ chối (Reject).


LUỒNG HOẠT ĐỘNG (THE FLOW) 

Đây là luồng đi mà Tester cần nắm rõ để viết test case.

  1. Login: Client gửi User/Pass lên Server.

  2. Verify & Sign: Server check đúng Pass -> Lấy thông tin User -> Tạo JWT -> Ký tên bằng SecretKey.

  3. Response: Server trả JWT về cho Client.

  4. Store: Client nhận JWT, lưu vào LocalStorage (phổ biến) hoặc Cookie.

  5. Request: Client muốn lấy dữ liệu, gửi Request kèm theo Header:

    Authorization: Bearer <token_jwt_cua_toi>

  6. Verify: Server nhận request -> Lấy Token ra -> Dùng SecretKey để verify chữ ký.

    • Nếu chữ ký đúng và exp chưa hết hạn -> Cho phép truy cập (lấy role trong payload để phân quyền).

    • Nếu chữ ký sai hoặc hết hạn -> Trả về 401.


KIẾN THỨC MỞ RỘNG 

1. Tại sao token hết hạn (Expired)?

Trong payload có trường exp. Đây là cơ chế bảo mật. Nếu hacker trộm được token, hắn chỉ dùng được trong 1 khoảng thời gian ngắn (vd: 15 phút). Hết giờ token tự hủy.

-> Test case: Login -> Chờ quá thời gian token -> Gọi API -> Expect 401.

2. Refresh Token là gì?

Vì Access Token (cái JWT ở trên) thường sống rất ngắn (15-30p). Chẳng lẽ cứ 30p bắt user login lại?

-> Server sẽ cấp thêm 1 cái Refresh Token (sống lâu hơn, vd: 7 ngày). Khi Access Token hết hạn, Client âm thầm dùng Refresh Token để xin cấp Access Token mới mà User không hề hay biết.

3. Lưu JWT ở đâu an toàn nhất?

  • LocalStorage: Dễ làm (JS đọc ghi thoải mái). Dễ bị dính lỗi XSS (Hacker chạy script lấy trộm token). -> Dùng cho các khóa học cơ bản/Project nhỏ.

HttpOnly Cookie: Khó làm hơn. JS không đọc được (chống XSS). An toàn hơn. -> Dùng cho Production/Ngân hàng


🛤️Phần 2: PATH PARAM vs. QUERY PARAM


Khái niệm & Sự khác biệt

🅰️ Path Parameter (Tham số đường dẫn)

  • Vị trí: Nằm trực tiếp trong đường dẫn URL (Sau dấu /).

  • Vai trò:Định danh (Identifier). Nó giúp xác định chính xác một tài nguyên cụ thể.

  • Tính chất: Bắt buộc (Required). Nếu thiếu nó, đường dẫn sai và thường trả về 404 Not Found.

  • Ví dụ:

    • URL: https://shopee.vn/product/12345

    • 12345 là Path Param. Nó đại diện cho Mã sản phẩm.

    • 👉 Nếu đổi 12345 thành 99999, nó ra một sản phẩm hoàn toàn khác.

🅱️ Query Parameter (Tham số truy vấn)

  • Vị trí: Nằm sau dấu chấm hỏi ? và nối với nhau bằng dấu &.

  • Vai trò:Bộ lọc / Sắp xếp (Filter / Sort). Nó không thay đổi tài nguyên, mà chỉ thay đổi cách hiển thị tài nguyên đó.

  • Tính chất: Thường là tùy chọn (Optional). Nếu bỏ đi, API vẫn trả về dữ liệu (thường là trả về danh sách mặc định).

  • Ví dụ:

    • URL: https://shopee.vn/search?keyword=ao-thun&color=red

    • keyword=ao-thuncolor=red là Query Params.

    • 👉 Chúng giúp lọc ra những cái áo thun màu đỏ.

So sánh trực quan (Bảng ghi nhớ)

Đặc điểm Path Param Query Param
Dấu hiệu Nằm sau dấu gạch chéo / Nằm sau dấu ?, dạng key=value
Mục đích Xác định (Cái này là cái nào?) Lọc/Sắp xếp (Lấy cái màu gì? Trang mấy?)
Bắt buộc? Có (Thường là vậy) Không (Thường là Optional)
Thay đổi Đổi Param -> Ra đối tượng khác Đổi Param -> Ra danh sách khác (cùng loại)
Ví dụ /users/100 (User số 100) /users?role=admin (Lọc các user là admin)


Cách viết Code trong Playwright (Best Practice) 🛠️

 

❌ Cách viết xấu (Cộng chuỗi thủ công)

Hay làm thế này, rất dễ sai sót và nhìn rối mắt:

const id = 1;
const role = 'admin';
// Dễ thiếu dấu ? hoặc &
await request.get('/users/' + id + '?role=' + role + '&active=true');

✅ Cách viết chuẩn Senior

1. Xử lý Path Param (Dùng Template String)

Dùng dấu backtick (`) và ${} để điền biến vào đường dẫn.

const userId = 123;
// Gọn gàng, dễ đọc
const response = await request.get(`/users/${userId}`);

2. Xử lý Query Param (Dùng Option params)

Playwright hỗ trợ tự động ghép chuỗi Query. Bạn chỉ cần ném Object vào, nó tự lo dấu ?&.

test('Get Users với bộ lọc', async ({ request }) => {
  const response = await request.get('/users', {
    // 👇 Playwright sẽ tự biến cái này thành: /users?role=admin&page=1&sort=desc
    params: {
      role: 'admin',
      page: 1,
      sort: 'desc'
    }
  });

  expect(response.ok()).toBeTruthy();
});

👉 Lợi ích của việc dùng params:

  • Không bao giờ lo thiếu dấu ? hay &.

  • Tự động mã hóa (URL Encode): Ví dụ bạn tìm tên "Nguyen Van A", Playwright sẽ tự đổi thành Nguyen%20Van%20A cho hợp lệ. Nếu cộng chuỗi tay bạn phải tự làm việc này rất mệt.


📖 Phần 3: CHIẾN THUẬT XỬ LÝ PHÂN TRANG (PAGINATION)


 Bản chất: Tại sao phải phân trang?

Hãy tưởng tượng bạn vào thư viện tìm sách.

  • Không phân trang: Thủ thư bê ra 10.000 cuốn sách đổ ụp trước mặt bạn. Bạn bị đè bẹp (Crash App/Timeout).

  • Có phân trang: Thủ thư đưa bạn 10 cuốn/lần. Bạn xem xong, bảo "Cho tôi xem tiếp 10 cuốn nữa" (Next Page).

👉 Trong API: Phân trang dùng 2 tham số chính (Query Param) mà chúng ta vừa học:

  1. page: Trang số mấy? (Ví dụ: 1, 2, 3...)

  2. limit (hoặc size, per_page): Lấy bao nhiêu dòng mỗi trang?


Chiến lược Test Phân Trang (Test Strategy)

Khi test API phân trang, bạn cần kiểm tra 3 kịch bản chính:

✅ Kịch bản 1: Kiểm tra Số lượng (Limit)

  • Input: page=1, limit=5.

  • Expect: API phải trả về đúng 5 item. Không được thừa, không được thiếu (trừ khi trang cuối).

✅ Kịch bản 2: Kiểm tra Logic Chuyển trang (Next Page)

  • Input: Gọi Page 1 lấy 5 item đầu. Sau đó gọi Page 2.

  • Expect: Dữ liệu ở Page 2 phải KHÁC Page 1. (Tránh lỗi gọi trang 2 mà vẫn trả về dữ liệu trang 1).

✅ Kịch bản 3: (Nâng cao) Quét toàn bộ dữ liệu (Scan All)

  • Input: Muốn tìm xem "Sản phẩm A" nằm ở trang nào trong 100 trang?

  • Action: Phải dùng Vòng lặp (Loop) để gọi API liên tục cho đến khi tìm thấy hoặc hết trang.


Lưu ý 👨‍🏫

  1. Offset vs Page:

    • Đa số API dùng page (Trang 1, 2, 3).

    • Một số API cũ dùng offset (Vị trí bắt đầu).

      • Trang 1: offset=0, limit=10

      • Trang 2: offset=10, limit=10

    • 👉 Cần đọc tài liệu API để biết nó dùng kiểu nào.

  2. Trang cuối cùng:

    • Làm sao biết hết trang?

    • Cách 1 (Dễ): Mảng trả về rỗng [].

    • Cách 2 (Chuẩn): API trả về tổng số trang (vd: total_pages: 5). Code sẽ lặp for (i=1; i<=5).

  3. Default Value:

    • "Nếu không truyền limit, API sẽ trả về bao nhiêu?"

"Thường là 10 hoặc 20 tùy Server cài đặt mặc định."


📦 Phần 4: TẠI SAO CẦN MULTIPART/FORM-DATA?

Hãy tưởng tượng bạn cần gửi một kiện hàng từ Hà Nội vào Sài Gòn.

  • JSON (application/json): Giống như gửi một bức thư viết tay. Mọi thứ phải là Ký tự (Text).

  • Form Data (multipart/form-data): Giống như gửi một Container hàng hóa. Bên trong Container có thể chứa thư (Text) và cả những cỗ máy khổng lồ (File Binary).

Dưới đây là 3 lý do cốt lõi khiến JSON "đầu hàng" và phải nhường sân cho Form Data:

Vấn đề về Dữ liệu Nhị phân (Binary Data) 💾

JSON chỉ hiểu chữ (Text)

File ảnh, video, PDF thực chất là một chuỗi các bit 0 và 1 (Binary).

JSON là định dạng văn bản ({ "key": "value" }). Nếu bạn cố nhét trực tiếp mã nhị phân của tấm ảnh vào JSON, cấu trúc JSON sẽ bị vỡ ngay lập tức vì các ký tự lạ.


Giải pháp tạm thời của JSON: Base64

Để gửi ảnh qua JSON, người ta phải dùng kỹ thuật Base64 (Biến file ảnh thành một chuỗi ký tự dài ngoằng).

  • Hậu quả: Kỹ thuật này làm tăng dung lượng file lên khoảng 33%.

    • Ví dụ: Bạn upload file video 100MB.

    • Nếu dùng JSON (Base64): Gói tin sẽ phình to thành ~133MB.

    • 👉 Tốn băng thông, Server phải tốn CPU để giải mã ngược lại.

Giải pháp của Form Data

Nó gửi nguyên đai nguyên kiện (Raw Binary).

  • File 100MB gửi đi đúng 100MB.

  • Không cần mã hóa/giải mã.

  • 👉 Tối ưu tốc độ và tài nguyên hệ thống.


Vấn đề về "Trộn lẫn" dữ liệu (Mixed Content) 🥗

Trong thực tế, 1 Request thường không chỉ có mỗi file, mà còn kèm theo thông tin khác.

Ví dụ: Form đăng ký làm thẻ nhân viên.

  1. Họ tên: "Nguyễn Văn A" (Text)

  2. Ảnh chân dung: avatar.png (File)

  3. File CV: cv.pdf (File)

Nếu dùng JSON, bạn rất khó để gom đống lộn xộn này vào một chỗ một cách hiệu quả.

Form Data giải quyết bằng cách chia nhỏ gói tin (Multipart):

Nó chia request thành nhiều phần nhỏ (Parts), ngăn cách nhau bởi một cái Vách ngăn (Boundary).

Cấu trúc thực tế của một gói tin Form Data trông như thế này:

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW  <-- Vách ngăn 1
Content-Disposition: form-data; name="username"

Nguyen Van A  <-- Dữ liệu Text
------WebKitFormBoundary7MA4YWxkTrZu0gW  <-- Vách ngăn 2
Content-Disposition: form-data; name="avatar"; filename="anh.png"
Content-Type: image/png

(Dữ liệu nhị phân của tấm ảnh nằm ở đây...)
------WebKitFormBoundary7MA4YWxkTrZu0gW-- <-- Vách ngăn kết thúc

👉 Server đọc đến đâu, xử lý đến đó. Nó biết chính xác đâu là tên, đâu là ảnh nhờ các vách ngăn này.


Khả năng Streaming (Xử lý dòng chảy) 🌊

Đây là lý do quan trọng nhất với các hệ thống lớn.

  • Với JSON: Server thường phải đợi nhận TOÀN BỘ cục JSON khổng lồ (đã Base64) vào RAM, sau đó mới parse (phân tích) để lấy dữ liệu ra. -> Dễ tràn RAM (Out of Memory).

  • Với Form Data: Server có thể xử lý theo dạng Dòng chảy (Stream).

    • Dữ liệu file chảy vào đến đâu, Server ghi xuống ổ cứng đến đó.

    • Không cần load cả file 1GB vào RAM.


📊 Bảng so sánh tổng kết

Đặc điểm JSON (application/json) Multipart Form Data
Dữ liệu hỗ trợ Text, Number, Boolean, Array, Object. Binary (File) + Text.
Gửi file Phải mã hóa Base64 (Tăng 33% dung lượng). Gửi Raw (Nguyên bản, nhanh).
Cấu trúc Cây (Tree structure). Các phần nối tiếp nhau (Parts).
Hiệu năng Tốt cho dữ liệu nhỏ, cấu trúc phức tạp. Tốt nhất cho việc upload file lớn.
Server xử lý Phải parse toàn bộ JSON. Có thể Stream (đọc/ghi) từng phần.

🎯 Túm lại

"Tại sao cần Form Data?"

  1. Để không phải mã hóa file sang text (tránh làm file phình to).

  2. Để gửi được nhiều loại dữ liệu (Text + Ảnh + Video) trong cùng 1 lần nhấn nút.

  3. Để Server xử lý nhẹ nhàng hơn, không bị tốn RAM giải mã.

Chính vì thế, trong Playwright hay bất kỳ công cụ nào, hễ đụng đến Upload File là 99% chúng ta phải chuyển sang chế độ Multipart/Form-Data.

Teacher

Teacher

Nguyên Hoàng

Automation Engineer

With 7+ years of hands-on experience across multiple languages and frameworks. I'm here to share knowledge, helping you turn complex processes into simple and effective solutions.

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học