NỘI DUNG BÀI HỌC

✅ Hiểu được vấn đề của code “rác” trong automation test.
✅ Nắm được triết lý tách biệt WHAT (nghiệp vụ)HOW (kỹ thuật) trong POM.
✅ Biết cách xây dựng BasePage để gom logic chung.
✅ Tạo được Page Object riêng cho từng trang web.
✅ Biết kết nối POM với Pytest Fixture để viết test ngắn gọn, dễ bảo trì.
✅ Thực hành tạo một LoginPageInventoryPage hoàn chỉnh.

I. VẤN ĐỀ: TEST KHÔNG CÓ KIẾN TRÚC

1. Định nghĩa

Code rác (Unstructured Test Code) là cách viết test trực tiếp bằng Playwright API trong cùng một file, trộn lẫn giữa thao tác kỹ thuật và logic nghiệp vụ.

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")

 

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. KHÁI NIỆM PAGE OBJECT MODEL (POM)

1. Định nghĩa

Page Object Model (POM)một mô hình thiết kế (Design Pattern) dùng trong kiểm thử tự động, trong đó mỗi trang web hoặc component được biểu diễn bằng một lớp (class) riêng biệt.

Đặc điểm:

  • Mỗi class đại diện cho một trang UI cụ thể.

  • Class chứa:

    • Tập hợp các locator.

    • Các hành động nghiệp vụ (business actions) liên quan đến trang đó.

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.

3. Ví dụ minh họa

Thành phần Vai trò Ví dụ
Page Object Đại diện cho một trang web LoginPage, InventoryPage
BasePage Gom logic kỹ thuật chung _click(), _fill(), _visit()
Business Action Hành động có ý nghĩa nghiệp vụ login(), add_item_to_cart()


III. CẤU TRÚC CHUẨN CỦA FRAMEWORK POM

tests/
│   test_login_pom.py
pages/
│   base_page.py
│   login_page.py
│   inventory_page.py
conftest.py

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 – NỀN TẢNG KỸ THUẬT CHUẨN HÓA HÀNH ĐỘNG

1. Định nghĩa

BasePagelớp cha trừu tượng, chứa các hành động cơ bản nhất của Playwright.
Tất cả các Page Object khác đều kế thừa từ lớp này để sử dụng lại các thao tác chuẩn hóa.

2. Chức nă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.

3. Triển khai – pages/base_page.py

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 – BIỂU DIỄN NGHIỆP VỤ TRÊN GIAO DIỆN

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:

  1. Locators – tất cả các selector (CSS/XPath) được khai báo ở dạng hằng số.

  2. 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):
    """Đại diện cho trang đăng nhập của hệ thống."""

    # Locators (được định nghĩa như hằng số)
    URL = "https://www.saucedemo.com/"
    USERNAME_FIELD = "#user-name"
    PASSWORD_FIELD = "#password"
    LOGIN_BUTTON = "#login-button"
    ERROR_MESSAGE = "[data-test='error']"

    # Business Actions
    def goto(self):
        """Điều hướng tới trang Login."""
        self._visit(self.URL)

    def login(self, username, password):
        """Thực hiện nghiệp vụ đăng nhập."""
        self.goto()
        self._fill(self.USERNAME_FIELD, username, name="Username")
        self._fill(self.PASSWORD_FIELD, password, name="Password")
        self._click(self.LOGIN_BUTTON, name="Login Button")

    def assert_login_successful(self):
        """Xác minh đăng nhập thành công."""
        expect(self.page).to_have_url("https://www.saucedemo.com/inventory.html")

    def assert_error_message_visible(self, expected_text):
        """Kiểm tra thông báo lỗi đăng nhập hiển thị đúng."""
        self._assert_text_visible(self.ERROR_MESSAGE, expected_text)


VI. TEST CASE THEO MÔ HÌNH POM


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

from pages.login_page import LoginPage
from playwright.sync_api import expect

def test_successful_login_standard_user(page):
    login_page = LoginPage(page)
    login_page.login("standard_user", "secret_sauce")
    login_page.assert_login_successful()

def test_login_failure_locked_user(page):
    login_page = LoginPage(page)
    login_page.login("locked_out_user", "secret_sauce")
    login_page.assert_error_message_visible(
        "Epic sadface: Sorry, this user has been locked out."
    )
​

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. MỞ RỘNG: PAGE OBJECT CHO TRANG KHÁC

1. Khái niệm

Khi ứng dụng có nhiều trang, mỗi trang cần có Page Object riêng biệt, kế thừa BasePage.
Ví dụ: InventoryPage, CartPage, CheckoutPage.


2. Ví dụ – pages/inventory_page.py

from .base_page import BasePage

class InventoryPage(BasePage):
    """Đại diện cho trang danh sách sản phẩm (Inventory Page)."""

    ITEM_BACKPACK_ADD_BUTTON = "[data-test='add-to-cart-sauce-labs-backpack']"
    SHOPPING_CART_BADGE = ".shopping_cart_badge"

    def add_backpack_to_cart(self):
        """Thêm sản phẩm 'Sauce Labs Backpack' vào giỏ hàng."""
        self._click(self.ITEM_BACKPACK_ADD_BUTTON, "Add Backpack")

    def assert_cart_badge_count(self, expected_count: str):
        """Kiểm tra số lượng sản phẩm trong giỏ hàng."""
        badge_text = self._get_locator(self.SHOPPING_CART_BADGE).inner_text()
        assert badge_text == expected_count, f"Expected {expected_count}, got {badge_text}"



3. Ví dụ test – tests/test_inventory_pom.py

 
from pages.login_page import LoginPage
from pages.inventory_page import InventoryPage

def test_add_to_cart_after_login(page):
    login_page = LoginPage(page)
    inventory_page = InventoryPage(page)

    login_page.login("standard_user", "secret_sauce")
    login_page.assert_login_successful()

    inventory_page.add_backpack_to_cart()
    inventory_page.assert_cart_badge_count("1")



VIII. 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



IX. BÀI TẬP THỰC HÀNH

Trang web: https://www.saucedemo.com/

1. Mục tiêu

  • Tạo cấu trúc thư mục pages/tests/ theo chuẩn POM.

  • Viết BasePageLoginPage hoàn chỉnh.

  • Tạo thêm InventoryPage mô phỏng hành vi thêm sản phẩm vào giỏ.

  • Viết test case đăng nhập → thêm sản phẩm → xác minh giỏ hàng.

2. Tiêu chí đánh giá

Tiêu chí Mô tả đạt yêu cầu
Cấu trúc 3 lớp Có BasePage, LoginPage, InventoryPage rõ ràng
Locator tập trung Không chứa locator trong test
Test rõ nghiệp vụ Test mô tả logic “login + add to cart” bằng ngôn ngữ tự nhiên
Code dễ đọc Có log, tên hàm rõ ràng, không lặp thao tác Playwright


X. 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”.

Teacher

Teacher

Hà Lan

QA Automation

With over 5 years of experience in web, API, and mobile test automation, built strong expertise in designing and maintaining automation frameworks across various domains and international projects. Committed to mentoring and knowledge sharing, I provide practical guidance and proven techniques to help aspiring testers develop their skills and succeed in the automation field.

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