NỘI DUNG BÀI HỌC
✅ Triết lý POM: Nắm vững sự tách biệt giữa WHAT (nghiệp vụ) và HOW (kỹ thuật).
✅ Xây dựng BasePage: Gom nhóm các hành động chung (click, fill) để dùng lại.
✅ Thiết kế Page Object: Tạo các lớp đại diện cho từng trang web cụ thể.
✅ Tối ưu hóa: Kết nối POM với Test Case để viết code ngắn gọn, dễ bảo trì.
✅ Thực hành: Xây dựng cấu trúc hoàn chỉnh cho
LoginPage và InventoryPage. I. VẤN ĐỀ: KHI CODE LÀ MỘT "MỚ HỖN ĐỘN"
Hãy tưởng tượng bạn viết một kịch bản "Đăng nhập" bằng cách ghi tất cả các bước vào một file duy nhất:
Ví dụ:
def test_login(page):
page.goto("https://www.saucedemo.com/")
page.locator("#user-name").fill("standard_user")
page.locator("#password").fill("secret_sauce")
page.locator("#login-button").click()
expect(page).to_have_url("https://www.saucedemo.com/inventory.html")
Tại sao cách này lại tệ?
-
"Sửa một chỗ, hỏng mười chỗ": Nếu lập trình viên đổi ID của ô nhập từ
#user-namethành#username, bạn sẽ phải đi tìm và sửa tay ở hàng chục file test khác nhau. -
"Đọc không hiểu gì": Một người mới nhìn vào sẽ thấy một đống các dòng lệnh kỹ thuật (click, fill) mà không hiểu được "ý đồ nghiệp vụ" (Đăng nhập, thêm hàng vào giỏ) là gì.
-
"Làm đi làm lại": Mỗi lần muốn test Đăng nhập, bạn lại phải chép lại y hệt 4 dòng trên.
2. Hệ quả
| Vấn đề | Mô tả | Tác động |
|---|---|---|
| Locator trùng lặp | Dùng cùng một selector ở nhiều nơi | Khi UI thay đổi phải sửa hàng loạt test |
| Lẫn lộn nghiệp vụ và kỹ thuật | Code test chứa cả hành động kỹ thuật (click, fill) và ý định nghiệp vụ (đăng nhập) |
Người đọc khó hiểu test đang kiểm tra điều gì |
| Mất tính tái sử dụng | Phải viết lại thao tác tương tự cho nhiều test | Tốn thời gian, dễ lỗi |
3. Nguyên tắc DRY (Don’t Repeat Yourself)
Định nghĩa: Không lặp lại cùng một đoạn mã hoặc logic ở nhiều nơi.
Mục tiêu: Khi một hành vi (như login) thay đổi, chỉ cần chỉnh ở một vị trí duy nhất.
Ý nghĩa trong automation: Đảm bảo test framework dễ bảo trì, nhất quán và mở rộng lâu dài.
🏛️ II. GIẢI PHÁP: PAGE OBJECT MODEL (POM) LÀ GÌ?
1. Khái niệm
POM là một "phong cách" viết code thông minh. Thay vì viết lung tung, chúng ta chia mỗi trang web thành một đối tượng (Object) riêng biệt.
-
Mỗi trang web (Trang Login, Trang Sản Phẩm, Trang Giỏ Hàng) sẽ là một Class (một "tệp hồ sơ" riêng).
-
Trong đó, chúng ta chia làm 2 phần:
-
Dữ liệu (Locators): Danh sách các nút bấm, ô nhập liệu (nếu ID thay đổi, ta chỉ cần sửa đúng 1 chỗ).
-
Hành động (Business Actions): Các việc bạn làm trên trang (Ví dụ: Thay vì click 3 lần, bạn chỉ cần gọi 1 hàm
login()).
-
2. Mục tiêu
-
Tách biệt WHAT và HOW:
-
WHAT: Ý định nghiệp vụ (“đăng nhập thành công”)
-
HOW: Cách thao tác kỹ thuật (“nhập username”, “click nút login”)
-
-
Giảm lặp lại code: Các hành động UI chỉ được định nghĩa một lần.
-
Tăng khả năng bảo trì: Khi locator thay đổi, chỉ cần cập nhật trong Page Object tương ứng.
-
Dễ đọc và dễ mở rộng: Test thể hiện ý định nghiệp vụ thay vì thao tác kỹ thuật.
III. CẤU TRÚC CHUẨN CỦA FRAMEWORK POM
Để dự án dễ quản lý, chúng ta chia theo 3 tầng (3 lớp):
-
Tầng BasePage (Tầng móng): Gom tất cả các thao tác "nhàm chán" (click, điền text, mở web) vào một chỗ để dùng chung.
-
Tầng Page Objects (Tầng nội thất): Mỗi file đại diện cho 1 trang web. Chứa các "kịch bản" riêng của trang đó (login, add_to_cart).
-
Tầng Tests (Tầng người dùng): Nơi bạn viết test case. Code ở đây cực kỳ ngắn gọn, chỉ gọi tên hành động thay vì viết lệnh kỹ thuật.
| Lớp | Nhiệm vụ chính | Ví dụ |
|---|---|---|
| BasePage | Chứa các thao tác Playwright cấp thấp, có thể tái sử dụng | _click(), _fill(), _visit() |
| Page Object | Mô tả nghiệp vụ của một trang cụ thể, kế thừa BasePage | LoginPage.login(), InventoryPage.add_to_cart() |
| Test Case | Gọi các hành động nghiệp vụ để kiểm thử chức năng | test_login_successful() |
IV. BASEPAGE – GOM NHÓM NHỮNG CÁI CƠ BẢN
Thay vì gọi lệnh Playwright trực tiếp, ta tạo một "ông chủ chung" là BasePage để quản lý các thao tác nền tảng:
-
Gom nhóm các hàm Playwright thường dùng (
goto,click,fill,assert) -
Thêm logic phụ trợ: logging, xử lý lỗi, timeout, hoặc wait tùy chỉnh.
-
Đảm bảo các Page Object tuân thủ cùng một chuẩn hành động.
from playwright.sync_api import Page, expect, Locator, TimeoutError
class BasePage:
"""Lớp cha chứa các hành động Playwright cơ bản, kế thừa cho mọi Page Object."""
def __init__(self, page: Page):
self.page = page
def _visit(self, url: str):
"""Điều hướng tới URL được chỉ định."""
print(f"[BasePage] Truy cập: {url}")
self.page.goto(url, wait_until="domcontentloaded")
def _get_locator(self, locator: str) -> Locator:
"""Trả về đối tượng Locator từ chuỗi selector."""
return self.page.locator(locator)
def _click(self, locator: str, name: str = ""):
"""Thực hiện click với xử lý lỗi và ghi log."""
try:
print(f"[Click] {name or locator}")
self._get_locator(locator).click()
except TimeoutError:
print(f"[Lỗi] Không thể click vào {locator}")
raise
def _fill(self, locator: str, text: str, name: str = ""):
"""Điền dữ liệu vào ô input."""
print(f"[Fill] '{text}' vào {name or locator}")
self._get_locator(locator).fill(text)
def _assert_text_visible(self, locator: str, text: str):
"""Kiểm tra văn bản mong đợi hiển thị trên giao diện."""
print(f"[Assert] Kiểm tra '{text}' hiển thị")
expect(self._get_locator(locator)).to_contain_text(text)
V. PAGE OBJECT – MÔ TẢ TRANG ĐĂNG NHẬP
1. Định nghĩa
Page Object là một lớp (class) mô tả toàn bộ hành vi nghiệp vụ của một trang web cụ thể.
Mỗi Page Object gồm hai phần chính:
-
Locators – tất cả các selector (CSS/XPath) được khai báo ở dạng hằng số.
-
Business Actions – các hành động nghiệp vụ mô phỏng tương tác người dùng.
2. Mục tiêu
-
Gom nhóm tất cả các thao tác liên quan đến một trang duy nhất.
-
Giúp test chỉ gọi đến các hành động nghiệp vụ thay vì mã kỹ thuật chi tiết.
-
Khi UI thay đổi, chỉ cập nhật trong Page Object tương ứng.
3. Ví dụ – pages/login_page.py
from .base_page import BasePage
from playwright.sync_api import expect
class LoginPage(BasePage):
# Phần 1: Gom các cái nút ở đây
URL = "https://www.saucedemo.com/"
USER_INPUT = "#user-name"
PASS_INPUT = "#password"
LOGIN_BTN = "#login-button"
# Phần 2: Mô tả hành động
def login(self, user, password):
self.page.goto(self.URL)
self._fill(self.USER_INPUT, user)
self._fill(self.PASS_INPUT, password)
self._click(self.LOGIN_BTN)
1. Cấu trúc Test
Test case chỉ nên mô tả luồng nghiệp vụ thay vì thao tác kỹ thuật.
Không được chứa Playwright API trực tiếp như locator, click, fill.
2. Ví dụ – tests/test_login_pom.py
3. Đặc điểm
| Tiêu chí | Đặc điểm của test sạch |
|---|---|
| Ngắn gọn | Không chứa locator hoặc lệnh Playwright thô |
| Dễ đọc | Mô tả rõ ý định kiểm thử bằng ngôn ngữ nghiệp vụ |
| Dễ bảo trì | Khi UI thay đổi, test không cần chỉnh sửa |
| Dễ mở rộng | Có thể thêm trang hoặc nghiệp vụ khác mà không ảnh hưởng test cũ |
VII. TỔNG KẾT KHÁI NIỆM TRONG POM
| Khái niệm | Định nghĩa | Ý nghĩa trong automation |
|---|---|---|
|
POM (Page Object Model) |
Mô hình tổ chức test trong đó mỗi trang được biểu diễn bằng một class |
Giúp tách biệt nghiệp vụ và kỹ thuật, dễ bảo trì |
|
BasePage |
Lớp cha chứa hành động cơ bản của Playwright |
Chuẩn hóa thao tác, tránh lặp lại mã |
|
Page Object |
Class đại diện cho một trang cụ thể |
Gom locator và hành động nghiệp vụ tại một nơi |
|
Business Action |
Hành động có ý nghĩa nghiệp vụ (vd: login, add_to_cart) |
Là “ngôn ngữ” của test case, giúp test dễ đọc |
|
Locator |
Selector trỏ tới element trong UI |
Quản lý tập trung, dễ thay đổi khi UI đổi |
|
Test sạch (Clean Test) |
Test chỉ mô tả nghiệp vụ, không chứa chi tiết kỹ thuật |
Dễ đọc, dễ hiểu, dễ mở rộng |
VIII. KẾT LUẬN
Page Object Model là nền tảng cốt lõi để xây dựng framework kiểm thử tự động bền vững.
Nó giúp tách biệt giữa logic kỹ thuật và nghiệp vụ, giảm thiểu trùng lặp mã, và tạo ra test dễ đọc, dễ mở rộng.
Việc nắm vững POM giúp học viên bước đầu hình thành tư duy kiến trúc framework, thay vì chỉ “ghi script chạy được”.
