Mô hình Page Object (PO) là một mẫu thiết kế phổ biến trong kiểm thử tự động giao diện người dùng (UI), đặc biệt hiệu quả với các công cụ như Selenium. Mục tiêu chính của PO là tạo ra một lớp (class) đại diện cho mỗi trang (hoặc một phần quan trọng của trang) trong ứng dụng web. Bằng cách này, mọi tương tác với các phần tử trên trang và các hành động liên quan đến trang đó đều được đóng gói trong lớp tương ứng, giúp mã kiểm thử trở nên dễ đọc, dễ bảo trì và tái sử dụng cao.
Ưu điểm của Mô hình Page Object:
- Giảm sự lặp lại mã: Các phần tử và hành động trên trang được định nghĩa một lần duy nhất trong lớp Page Object, tránh việc lặp lại mã định vị phần tử trong các test case.
- Nâng cao khả năng đọc: Mã kiểm thử trở nên rõ ràng hơn vì các hành động được gọi thông qua các phương thức có ý nghĩa (ví dụ:
enter_username(),click_login_button()), thay vì chuỗi các lệnh định vị phần tử và thao tác trực tiếp. - Dễ bảo trì: Khi giao diện người dùng thay đổi (ví dụ: một ID phần tử thay đổi), bạn chỉ cần cập nhật định nghĩa phần tử trong lớp Page Object tương ứng, thay vì phải tìm và sửa đổi ở nhiều test case khác nhau.
- Tái sử dụng cao: Các Page Object có thể được sử dụng trong nhiều kịch bản kiểm thử khác nhau mà không cần viết lại mã, tăng cường hiệu quả phát triển kiểm thử.
Cấu trúc thư mục phổ biến cho dự án Page Object:
pages/: Chứa các lớp Page Object, mỗi lớp đại diện cho một trang hoặc một thành phần cụ thể trên trang.test_cases/: Chứa các kịch bản kiểm thử, sử dụng các Page Object để tương tác với ứng dụng.reports/: Nơi lưu trữ các báo cáo kiểm thử được tạo ra.utils/hoặchelpers/: Chứa các hàm tiện ích, cấu hình chung, trình điều khiển trình duyệt, v.v.
Ví dụ thực tế: Áp dụng Page Object Model để kiểm thử chức năng đăng nhập trang web mail.163.com
Chúng ta sẽ xây dựng các lớp theo mô hình PO để kiểm thử việc đăng nhập. Ví dụ này tập trung vào việc định vị phần tử, xử lý khung (iframe) và thực hiện các hành động cơ bản trong kiểm thử web.
1. Lớp BasePage: Lớp cơ sở cho các Page Object
Lớp này chứa các phương thức chung mà tất cả các Page Object khác có thể kế thừa và sử dụng, chẳng hạn như định vị phần tử một cách linh hoạt, chuyển đổi giữa các khung (iframe), hoặc khởi động trình duyệt.
- Sử dụng tham số biến đổi
*locatorgiúp phương thức định vị phần tử linh hoạt hơn, cho phép truyền bất kỳ cặp định vị nào (ví dụ:(By.ID, "someId")hoặc(By.XPATH, "//some/xpath")). - Sử dụng
WebDriverWaitkết hợp vớiexpected_conditionsđể đảm bảo phần tử đã xuất hiện trước khi tương tác, tăng độ tin cậy của kiểm thử. - Xử lý chuyển đổi iframe bằng cách tìm phần tử iframe trước, sau đó chuyển trình điều khiển Selenium sang frame đó.
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
class BasePage:
def __init__(self, driver):
"""
Khởi tạo BasePage với một instance WebDriver.
"""
self.driver = driver
self.wait = WebDriverWait(driver, 10) # Khởi tạo WebDriverWait với thời gian chờ 10 giây
def find_element(self, *locator):
"""
Định vị và trả về một phần tử trên trang.
Chờ cho đến khi phần tử hiện diện trong DOM.
"""
return self.wait.until(EC.presence_of_element_located(locator))
def switch_to_frame(self, *locator):
"""
Chuyển đổi context của WebDriver sang một iframe cụ thể.
Đầu tiên tìm iframe bằng locator, sau đó chuyển driver tới iframe đó.
"""
frame_element = self.wait.until(EC.presence_of_element_located(locator))
self.driver.switch_to.frame(frame_element)
def launch_browser_and_navigate(self, url):
"""
Mở trình duyệt, điều hướng đến URL được cung cấp và tối đa hóa cửa sổ.
"""
print(f"Mở trình duyệt và điều hướng đến: {url}")
self.driver.get(url)
self.driver.maximize_window()
sleep(2) # Tạm dừng để đảm bảo cửa sổ đã tối đa hóa và ổn định
2. Lớp LoginPage: Đại diện cho trang đăng nhập
Lớp này định nghĩa các locator cho các phần tử cụ thể trên trang đăng nhập và cung cấp các phương thức để thực hiện các hành động trên trang đó (nhập tên người dùng, mật khẩu, nhấn nút đăng nhập, v.v.).
from pages.BasePage import BasePage
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support import expected_conditions as EC
class LoginPage(BasePage):
# Định nghĩa các locator dưới dạng hằng số để dễ quản lý
FRAME_LOGIN = (By.XPATH, "//div[@id='loginDiv']/iframe")
INPUT_USERNAME = (By.NAME, "email")
INPUT_PASSWORD = (By.NAME, "password")
BUTTON_LOGIN = (By.ID, "dologin")
# Locator giả định cho thông báo lỗi sau khi đăng nhập thất bại
# Cần điều chỉnh theo cấu trúc HTML thực tế của trang web
ERROR_MESSAGE_LOCATOR = (By.XPATH, "//div[@class='ferrorhead' or contains(@class, 'error-message')]")
def switch_to_login_frame(self):
"""
Chuyển đổi context của WebDriver đến iframe chứa form đăng nhập.
"""
self.switch_to_frame(*self.FRAME_LOGIN)
print("Đã chuyển đổi sang iframe đăng nhập.")
def enter_username(self, username):
"""
Nhập tên người dùng vào trường input tương ứng.
"""
self.find_element(*self.INPUT_USERNAME).send_keys(username)
print(f"Đã nhập tên người dùng: {username}")
def enter_password(self, password):
"""
Nhập mật khẩu vào trường input tương ứng.
"""
self.find_element(*self.INPUT_PASSWORD).send_keys(password)
print(f"Đã nhập mật khẩu (không hiển thị chi tiết vì lý do bảo mật).")
def click_login_button(self):
"""
Nhấn vào nút đăng nhập.
"""
self.find_element(*self.BUTTON_LOGIN).click()
print("Đã nhấn nút đăng nhập.")
def perform_login(self, username, password):
"""
Thực hiện toàn bộ quá trình đăng nhập bao gồm:
chuyển đổi iframe, nhập thông tin và nhấn nút đăng nhập.
"""
print("Bắt đầu thực hiện thao tác đăng nhập.")
self.switch_to_login_frame()
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
sleep(3) # Chờ một chút để kết quả đăng nhập hiển thị hoặc thông báo lỗi xuất hiện
def get_error_message(self):
"""
Lấy văn bản thông báo lỗi nếu có trên trang.
Trả về None nếu không tìm thấy thông báo lỗi.
"""
try:
error_element = self.wait.until(EC.visibility_of_element_located(self.ERROR_MESSAGE_LOCATOR))
return error_element.text
except Exception:
return None # Không tìm thấy thông báo lỗi trong thời gian chờ
3. Lớp LoginTest: Kịch bản kiểm thử đăng nhập
Lớp này kế thừa từ unittest.TestCase để tạo các kịch bản kiểm thử. Nó sử dụng lớp LoginPage để tương tác với trang web, đảm bảo tách biệt logic kiểm thử và logic tương tác UI.
- Phương thức
setUpđược gọi trước mỗi kịch bản kiểm thử để khởi tạo trình điều khiển và mở trang đăng nhập. - Phương thức
tearDownđược gọi sau mỗi kịch bản để đóng trình duyệt, dọn dẹp tài nguyên. - Sử dụng các phương thức của
LoginPageđể thực hiện các bước đăng nhập vàself.assertEqualhoặcself.assertTrueđể xác nhận kết quả.
from pages.LoginPage import LoginPage
from selenium import webdriver
import unittest
from time import sleep
class LoginTest(unittest.TestCase):
def setUp(self):
"""
Thiết lập môi trường kiểm thử trước khi mỗi test case được chạy.
Khởi tạo WebDriver và đối tượng LoginPage.
"""
self.driver = webdriver.Chrome()
self.base_url = "http://mail.163.com/"
self.login_page = LoginPage(self.driver)
self.login_page.launch_browser_and_navigate(self.base_url)
sleep(2) # Đảm bảo trang đã tải xong trước khi bắt đầu test
def test_invalid_login_credentials(self):
"""
Kiểm thử trường hợp đăng nhập với thông tin không hợp lệ.
Mong đợi một thông báo lỗi xuất hiện.
"""
print("--- Bắt đầu kịch bản kiểm thử: Đăng nhập với thông tin không hợp lệ ---")
invalid_username = "user_non_existent@163.com"
invalid_password = "wrongpass123"
self.login_page.perform_login(invalid_username, invalid_password)
# Sau khi đăng nhập thất bại, kiểm tra xem thông báo lỗi có hiển thị và nội dung có đúng không.
# Lưu ý: Cần xác định chính xác văn bản lỗi thực tế trên trang web.
error_message = self.login_page.get_error_message()
print(f"Thông báo lỗi nhận được: '{error_message}'")
self.assertIsNotNone(error_message, "Thông báo lỗi phải hiển thị khi đăng nhập sai.")
self.assertIn("Tài khoản hoặc mật khẩu không đúng", error_message or "",
"Nội dung thông báo lỗi không chính xác với kỳ vọng.")
print("Kịch bản kiểm thử đăng nhập không hợp lệ hoàn tất.")
# Bạn có thể thêm các test case khác như:
# def test_valid_login(self):
# """
# Kiểm thử trường hợp đăng nhập thành công.
# """
# print("--- Bắt đầu kịch bản kiểm thử: Đăng nhập thành công ---")
# self.login_page.perform_login("valid_username@163.com", "valid_password")
# # Ví dụ kiểm tra URL chuyển hướng hoặc sự hiện diện của phần tử sau khi đăng nhập thành công
# # self.assertIn("welcome", self.driver.current_url)
# print("Kịch bản kiểm thử đăng nhập thành công hoàn tất.")
def tearDown(self):
"""
Dọn dẹp môi trường kiểm thử sau khi mỗi test case được chạy, đóng trình duyệt.
"""
self.driver.quit()
print("Đã đóng trình duyệt.")