NỘI DUNG BÀI HỌC

✅ Hiểu được mối liên kết giữa các tầng trong POM (BasePage → Page Object → Test Layer).
✅ Nắm vững cấu trúc dự án hoàn chỉnh và ý nghĩa từng thư mục trong automation framework.
✅ Phân biệt và áp dụng được các mô-đun mở rộng trong POM: Component, Utils, Data, Config.
✅ Biết cách xây dựng dòng chảy nghiệp vụ (Business Flow) giữa các Page Object.
✅ Định hình được tư duy thiết kế framework chuyên nghiệp có khả năng mở rộng và tái sử dụng.



I. ÔN TẬP NGẮN: TRIẾT LÝ CỐT LÕI CỦA POM

1. POM là gì?

Page Object Model là mô hình giúp tách biệt:

  • WHAT – Nghiệp vụ cần kiểm thử.

  • HOW – Cách thực hiện thao tác trên UI.

2. Mục tiêu chính

  • Tập trung logic kỹ thuật vào BasePage.

  • Mô tả nghiệp vụ rõ ràng trong Page Object.

  • Giúp test case ngắn gọn, dễ đọc, dễ mở rộng.


II. TỔNG QUAN VỀ CẤU TRÚC POM NÂNG CAO

Một framework chuyên nghiệp thường mở rộng từ kiến trúc POM cơ bản sang mô hình đa tầng sau:

project_root/
│
├── core/                                ← FOUNDATION LAYER (Kỹ thuật nền)
│   ├── base_page.py                     → Base class cho tất cả Page Object
│   └── base_component.py                → Base class cho Component (UI lặp lại)
│
├── pages/                               ← PAGE OBJECT LAYER (Nghiệp vụ)
│   ├── login_page.py                    → LoginPage: chứa locators + hàm login()
│   ├── inventory_page.py                → InventoryPage: add_to_cart(), verify_item()
│   ├── cart_page.py                     → CartPage: remove_item(), proceed_to_checkout()
│   └── checkout_page.py                 → CheckoutPage: fill_info(), complete_order()
│
├── components/                          ← COMPONENT LAYER (UI tái sử dụng)
│   ├── header_component.py              → Header: logout(), go_to_cart()
│   ├── sidebar_component.py             → Sidebar: open_menu(), navigate_to()
│   └── modal_component.py               → Modal: confirm(), cancel()
│
├── resources/                           ← DATA & CONFIG LAYER (Nguồn dữ liệu)
│   ├── config.json                      → URL, môi trường, credentials
│   ├── credentials.json                 → Dữ liệu login test
│   └── products.json                    → Dữ liệu sản phẩm cho test
│
├── utils/                               ← HELPER LAYER (Hỗ trợ phi UI)
│   ├── db_helper.py                     → Kết nối và truy vấn Database
│   ├── api_client.py                    → Gọi API hỗ trợ test data
│   ├── file_reader.py                   → Đọc file JSON/CSV/Excel
│   └── logger.py                        → Ghi log chi tiết hành động test
│
├── tests/                               ← TEST SCENARIO LAYER (WHAT)
│   ├── test_login_pom.py                → Test đăng nhập
│   ├── test_add_to_cart.py              → Test thêm sản phẩm vào giỏ
│   ├── test_checkout_flow.py            → Test checkout toàn bộ luồng
│   └── test_error_handling.py           → Test lỗi hiển thị UI
│
├── conftest.py                          ← THE GLUE (CONNECTOR)
│                                         → Tạo và bơm fixture (Page Object, Config, DB)
│
├── reports/                             ← OUTPUT LAYER (Kết quả kiểm thử)
│   ├── allure-results/                  → Báo cáo Allure
│   └── pytest-html/                     → Báo cáo HTML chi tiết
│
└── screenshots/                         ← EVIDENCE LAYER (Ảnh minh chứng)
    ├── error_login_fail.png
    └── cart_page_validation.png

 

III. BASEPAGE NÂNG CAO – CHUẨN HÓA & MỞ RỘNG HÀNH ĐỘNG

1. Định nghĩa

BasePagelớp nền tảng duy nhất mà tất cả các Page Object kế thừa.
Trong thực tế, lớp này thường được mở rộng thêm:

  • Wait động (Explicit Wait) để xử lý element chưa sẵn sàng.

  • Logging để ghi lại hành động test.

  • Screenshot on Fail để debug lỗi UI.

  • Hàm tiện ích (helper) như scroll, press, get_text, …


2. Ví dụ – core/base_page.py

from playwright.sync_api import Page, expect, TimeoutError

class BasePage:
    """Lớp cha mở rộng với Wait, Logging, Screenshot và các hành động tái sử dụng."""

    def __init__(self, page: Page):
        self.page = page

    def _visit(self, url: str):
        """Điều hướng trang web."""
        print(f"[NAVIGATE] {url}")
        self.page.goto(url, wait_until="domcontentloaded")

    def _click(self, locator: str, name: str = ""):
        """Click vào element có chờ sẵn sàng."""
        try:
            element = self.page.locator(locator)
            element.wait_for(state="visible", timeout=5000)
            print(f"[CLICK] {name or locator}")
            element.click()
        except TimeoutError:
            print(f"[ERROR] Element {locator} không hiển thị.")
            self._take_screenshot(f"error_click_{name}.png")
            raise

    def _fill(self, locator: str, text: str, name: str = ""):
        """Điền dữ liệu vào ô input."""
        print(f"[FILL] {name or locator}: {text}")
        self.page.locator(locator).fill(text)

    def _assert_visible(self, locator: str, name: str = ""):
        """Kiểm tra element hiển thị."""
        expect(self.page.locator(locator)).to_be_visible()
        print(f"[ASSERT] {name or locator} hiển thị thành công.")

    def _take_screenshot(self, filename: str):
        """Lưu ảnh chụp màn hình (sử dụng khi test fail)."""
        path = f"screenshots/{filename}"
        self.page.screenshot(path=path)
        print(f"[SCREENSHOT] Lưu tại: {path}")
 

IV. COMPONENT OBJECT – TÁCH NHỮNG PHẦN TỬ UI DÙNG CHUNG

1. Khái niệm

Component Object là phần mở rộng của POM, được sử dụng khi có các phần tử UI xuất hiện trên nhiều trang
(ví dụ: Header, Menu, Sidebar, Popup, Footer, …).

Mỗi component là một class độc lập, giúp tái sử dụng hành vi và locator dùng chung.


2. Lợi ích

  • Giảm trùng lặp code.

  • Dễ bảo trì khi thay đổi layout.

  • Tăng khả năng tổ chức và mở rộng framework.


3. Ví dụ – components/header_component.py

from core.base_page import BasePage

class HeaderComponent(BasePage):
    """Đại diện cho thanh header hiển thị trên mọi trang."""

    LOGO = ".app_logo"
    CART_ICON = ".shopping_cart_link"
    MENU_BUTTON = "#react-burger-menu-btn"
    LOGOUT_LINK = "#logout_sidebar_link"

    def open_menu(self):
        """Mở menu điều hướng."""
        self._click(self.MENU_BUTTON, "Menu Button")

    def logout(self):
        """Đăng xuất khỏi hệ thống."""
        self.open_menu()
        self._click(self.LOGOUT_LINK, "Logout Link")

    def go_to_cart(self):
        """Chuyển sang trang giỏ hàng."""
        self._click(self.CART_ICON, "Cart Icon")
 

V. ÁP DỤNG COMPONENT TRONG PAGE OBJECT

Ví dụ – pages/inventory_page.py

from core.base_page import BasePage
from components.header_component import HeaderComponent

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

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

    def __init__(self, page):
        super().__init__(page)
        self.header = HeaderComponent(page)   # Gắn Component Header vào trang

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

    def verify_item_added(self):
        """Kiểm tra biểu tượng giỏ hàng hiển thị số lượng đúng."""
        self._assert_visible(self.SHOPPING_CART_BADGE, "Cart Badge")

    def logout_via_header(self):
        """Đăng xuất qua Header Component."""
        self.header.logout()

👉 InventoryPage có thể sử dụng trực tiếp self.header.logout() hoặc self.header.go_to_cart()
mà không cần lặp lại logic header trong mỗi trang khác.


VI. DATA-DRIVEN TESTING VỚI POM

1. Khái niệm

Data-Driven Testing (Kiểm thử dựa trên dữ liệu) là kỹ thuật tách riêng dữ liệu đầu vào khỏi logic test.
Trong POM, việc này thường được thực hiện bằng cách đọc dữ liệu từ file JSON, CSV, hoặc Excel.


2. Ví dụ cấu trúc file – data/credentials.json

{
  "valid_user": {
    "username": "standard_user",
    "password": "secret_sauce"
  },
  "locked_user": {
    "username": "locked_out_user",
    "password": "secret_sauce"
  }
}


3. Cách sử dụng trong test

import json
from pages.login_page import LoginPage

def test_login_from_json(page):
    login_page = LoginPage(page)

    with open("data/credentials.json") as f:
        creds = json.load(f)

    valid = creds["valid_user"]
    locked = creds["locked_user"]

    login_page.login(valid["username"], valid["password"])
    login_page.assert_login_successful()

    login_page.login(locked["username"], locked["password"])
    login_page.assert_error_message_visible(
        "Epic sadface: Sorry, this user has been locked out."
    )


Ý nghĩa:

→ Giúp tách phần dữ liệu test (input) ra khỏi logic kiểm thử.
→ Dễ dàng mở rộng khi có thêm các bộ dữ liệu khác mà không phải viết lại test case.


VII. PAGE OBJECT MỞ RỘNG – CHUỖI HÀNH ĐỘNG NGHIỆP VỤ

Trong framework thực tế, test flow thường liên quan đến nhiều trang liên tiếp.
Nhờ POM, ta có thể kết nối các Page Object để tạo thành chuỗi hành động nghiệp vụ.

Ví dụ – Quy trình “Login → Add to Cart → Checkout”

from pages.login_page import LoginPage
from pages.inventory_page import InventoryPage
from pages.cart_page import CartPage
from pages.checkout_page import CheckoutPage

def test_checkout_complete(page):
    # Login
    login_page = LoginPage(page)
    login_page.login("standard_user", "secret_sauce")
    login_page.assert_login_successful()

    # Add to Cart
    inventory_page = InventoryPage(page)
    inventory_page.add_backpack_to_cart()
    inventory_page.verify_item_added()

    # Go to Cart
    inventory_page.header.go_to_cart()

    # Checkout
    cart_page = CartPage(page)
    cart_page.start_checkout()
    checkout_page = CheckoutPage(page)
    checkout_page.fill_customer_info("Lan", "Ha", "70000")
    checkout_page.finish_checkout()
    checkout_page.assert_checkout_success()

 

Ưu điểm của chuỗi này:

  • Mỗi trang có trách nhiệm riêng.

  • Không cần lặp lại logic login hay add to cart ở nhiều test khác.

  • Test mô tả hành trình người dùng thực tế bằng ngôn ngữ nghiệp vụ.


Sơ đồ 1 chiều

tests/ (WHAT)
   ↓  (gọi nghiệp vụ)
pages/ (Page Objects – HOW nghiệp vụ theo trang)
   ↓  (tái sử dụng)
components/ (UI dùng chung theo thành phần)
   ↓  (kế thừa/ủy quyền)
core/ (Base classes: BasePage, BaseComponent – HOW kỹ thuật)
   ↓  (thao tác thật)
Playwright API (page, locator, expect)

resources/ (config, testdata) → được đọc bởi tests/ hoặc pages/ (khi cần)
utils/ (db, api, file, logger) → được gọi bởi tests/ hoặc pages/ (khi cần)
conftest.py (fixtures) → khởi tạo “đối tượng + dữ liệu” và bơm vào tests/
reports/, screenshots/ → nhận log, ảnh, kết quả assert khi chạy


VIII. TỔNG KẾT KIẾN THỨC POM NÂNG CAO

Khái niệm Định nghĩa Ứng dụng trong automation
BasePage nâng cao Lớp cha chuẩn hóa hành động với Wait, Logging, Screenshot Giúp test ổn định, dễ debug
Component Object Mô hình tách phần tử UI dùng chung (header, sidebar, popup) Tăng tái sử dụng, giảm lặp code
Data-Driven Testing Cách tách dữ liệu test ra khỏi logic test Dễ mở rộng, dễ quản lý test data
Business Flow Chuỗi hành động kết hợp nhiều Page Object Mô phỏng luồng người dùng thực tế
Clean Test Case Test chỉ thể hiện luồng nghiệp vụ, không chứa kỹ thuật Dễ đọc, dễ bảo trì, rõ ràng với non-dev


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

Trang web: https://hrm.anhtester.com/erp/login

Yêu cầu:

📦 PlaywrightProject/
│
├── Core/                                  # Thư mục chứa lớp nền (BasePage) dùng chung
│   ├── base_page.py                       # Định nghĩa các hàm cơ bản: open_url, click, fill, screenshot
│   └── __init__.py                        # Giúp Python nhận diện đây là package
│
├── components/                            # Thư mục chứa các thành phần UI tái sử dụng (Header, Footer)
│   ├── header_component.py                # Xử lý menu header: click từng mục, chụp ảnh, logout
│   └── __init__.py
│
├── pages/                                 # Thư mục chứa các Page Object đại diện cho từng trang
│   ├── login_page.py                      # Trang đăng nhập: đọc dữ liệu từ JSON, login, chụp ảnh, logout
│   └── __init__.py
│
├── data/                                  # Lưu trữ dữ liệu test (JSON, CSV, Excel)
│   ├── credentials.json                   # Chứa thông tin đăng nhập test
│   └── __init__.py
│
├── results/                               # Lưu kết quả chạy test (report HTML)
│   ├── report.html                        # Báo cáo tự động sinh sau khi chạy pytest
│   └── __init__.py
│
├── screenshots/                           # Thư mục lưu toàn bộ ảnh chụp màn hình khi test
│   └── (ảnh tự sinh khi chạy test)
│
├── tests/                                 # Chứa file test case thực tế (Pytest)
│   ├── test_login_flow.py                 # Kịch bản: mở → đăng nhập → chụp ảnh → logout
    └── __init__.py

​

Đánh giá hoàn thành:

Tiêu chí Mức độ đạt yêu cầu
Có Component Header riêng
Dữ liệu test tách riêng file JSON
Flow liên kết nhiều Page Object
Không chứa locator trong test
Test chạy ổn định, dễ đọ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