NỘI DUNG BÀI HỌC
✅ Nắm vững sự khác biệt giữa các cơ chế "Wait" trong automation truyền thống và "Auto-Waiting" thông minh của Playwright.
✅ Thành thạo kỹ năng phân tích DOM và viết Locators ổn định bằng CSS Selector và XPath.
✅ Ưu tiên sử dụng các locator hiện đại và bền vững của Playwright (get_by_role
, get_by_text
, get_by_label
).
✅ Thực hành viết một kịch bản test hoàn chỉnh, áp dụng cả Wait và Locator để đảm bảo tính ổn định.
✅ So sánh trực tiếp với Selenium để thấy rõ sức mạnh và sự tinh gọn mà Playwright mang lại.
🧠 I. KHÁI NIỆM VỀ WAIT (CHỜ ĐỢI)
Vì sao cần Wait?
Tưởng tượng bạn gọi điện cho một người bạn và ngay lập tức hỏi "Bạn nghe rõ không?" mà không chờ họ nhấc máy. Script automation cũng vậy. Nó thực thi lệnh nhanh hơn rất nhiều so với tốc độ trình duyệt tải trang, render các phần tử JavaScript, hay thực hiện một API call.
➡️ Wait chính là cơ chế giúp script đồng bộ hóa, "chờ" cho ứng dụng web sẵn sàng trước khi thực hiện hành động tiếp theo, tránh các lỗi "Element not found" hay "Element not interactable".
1. Ba loại Wait chính trong Automation truyền thống (như Selenium)
Đây là 3 khái niệm nền tảng mà bạn cần hiểu để thấy được sự đột phá của Playwright.
a. Implicit Wait (Chờ đợi Ngầm định)
-
Khái niệm: Đây là một thiết lập toàn cục, giống như bạn ra lệnh: "Này script, từ giờ trở đi, mỗi khi mày tìm một element nào đó mà không thấy ngay, hãy kiên nhẫn chờ tối đa X giây trước khi báo lỗi".
-
Cách hoạt động: Bạn chỉ cần cài đặt một lần duy nhất ở đầu script.
# Ví dụ trong Selenium driver.implicitly_wait(10) # Thiết lập chờ ngầm định là 10 giây # Từ đây trở đi, mọi lệnh find_element đều sẽ tự động chờ tối đa 10s driver.find_element(By.ID, "username") driver.find_element(By.ID, "password")
-
Tình huống sử dụng: Thường được dùng trong các dự án nhỏ, đơn giản để tiết kiệm việc viết code wait lặp đi lặp lại. Tuy nhiên, đây được coi là một thói quen không tốt vì nó thiếu linh hoạt và có thể che giấu các vấn đề về hiệu năng của trang web.
b. Explicit Wait (Chờ đợi Tường minh)
-
Khái niệm: Đây là một lệnh chờ cụ thể và có điều kiện. Giống như bạn ra lệnh: "Hãy chờ tối đa X giây cho đến khi CỤ THỂ cái nút 'Login' này có thể được click".
-
Cách hoạt động: Bạn phải viết lệnh chờ cho từng tình huống cụ thể mà bạn muốn.
# Ví dụ trong Selenium from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) # Tạo một đối tượng chờ tối đa 10s # Chỉ chờ cho element 'login-button' được phép click login_button = wait.until(EC.element_to_be_clickable((By.ID, "login-button"))) login_button.click()
-
Tình huống sử dụng: Đây là phương pháp chuẩn và được khuyến khích nhất trong Selenium. Nó được dùng khi bạn cần chờ một element xuất hiện, biến mất, có thể click, hoặc có một thuộc tính nào đó thay đổi. Nó chính xác và đáng tin cậy.
c. Fluent Wait
-
Khái niệm: Đây là một phiên bản nâng cao của Explicit Wait, cho phép bạn tùy chỉnh sâu hơn, ví dụ như tần suất kiểm tra (polling) và bỏ qua một số loại lỗi nhất định trong khi chờ.
-
Tình huống sử dụng: Rất hiếm khi cần đến. Chỉ dùng trong các kịch bản cực kỳ phức tạp, ví dụ như một element thỉnh thoảng xuất hiện rồi biến mất (flashing) và bạn muốn script bỏ qua các lỗi "không tìm thấy" tạm thời.
Trong các framework truyền thống như Selenium, việc quản lý Wait là một kỹ năng quan trọng.
2. Wait trong Playwright Python: Cuộc cách mạng "Auto-Waiting" 🎯
Đây là tính năng thay đổi cuộc chơi của Playwright. Bạn không cần phải lo lắng về việc thêm wait một cách thủ công trong hầu hết các trường hợp.
👉 Cơ chế: Mặc định, MỌI lệnh tương tác như .click()
, .fill()
, .check()
... đều được tích hợp sẵn một loạt các bước kiểm tra (Actionability Checks). Playwright sẽ tự động chờ cho đến khi tất cả các điều kiện này được đáp ứng.
-
Các kiểm tra tự động bao gồm:
-
Element đã được gắn vào cây DOM.
-
Element đang hiển thị (visible).
-
Element ổn định (stable - không bị che bởi element khác, không có animation).
-
Element có thể nhận tương tác (enabled).
-
Element có thể chỉnh sửa (editable - đối với
.fill()
).
-
✅ Ví dụ thực tế:
from playwright.sync_api import sync_playwright, expect
def test_auto_wait_in_playwright(page):
# Giả sử trang này có 1 nút "Load Data" và sau 3 giây mới hiện ra bảng dữ liệu
page.goto("https://www.your-dynamic-page.com")
page.get_by_role("button", name="Load Data").click()
# BẠN KHÔNG CẦN VIẾT BẤT KỲ LỆNH WAIT NÀO Ở ĐÂY
# Playwright sẽ tự động chờ cho đến khi element '#data-table' xuất hiện và sẵn sàng
first_row = page.locator("#data-table tbody tr:first-child")
# expect cũng có cơ chế auto-waiting tích hợp!
expect(first_row).to_be_visible(timeout=5000) # Chờ tối đa 5 giây
3. So sánh trực tiếp Wait với Selenium
Tình huống | Code Selenium (Cần Explicit Wait) | Code Playwright (Tự động) |
Click một nút xuất hiện sau 2s | login_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "loginBtn"))) login_button.click() |
page.get_by_role("button", name="Login").click() |
4. Khi nào cần "Wait" tường minh trong Playwright?
Dù auto-waiting rất mạnh mẽ, bạn vẫn cần các lệnh wait tường minh cho các sự kiện không phải là tương tác trực tiếp với element.
🕵️♀️ II. LOCATORS – CÁCH XÁC ĐỊNH PHẦN TỬ TRÊN TRANG
1️⃣ Khái niệm Locators
Locator là một chuỗi truy vấn hoạt động như một "địa chỉ" để Playwright tìm thấy một (hoặc nhiều) phần tử trên trang web. Việc chọn đúng locator quyết định 80% sự ổn định của test case.
2️⃣ Các loại Locator phổ biến (Truyền thống)
a. CSS Selector
-
Mô tả: Cú pháp mạnh mẽ để chọn element dựa trên ID, class, thuộc tính và mối quan hệ cha-con.
Ký hiệu Ý nghĩa Ví dụ HTML Ví dụ Locator (trong Playwright) Diễn giải chi tiết # ID <input id="user-login"> page.locator("#user-login") Dấu # có nghĩa là "Tìm element có ID chính xác là 'user-login'". ID là duy nhất trên một trang, nên đây là cách tìm kiếm nhanh và đáng tin cậy nhất. . Class <button class="btn btn-primary"> page.locator(".btn-primary") Dấu . có nghĩa là "Tìm element có chứa class là 'btn-primary'". Một element có thể có nhiều class, và nhiều element có thể chung một class. [] Thuộc tính (Attribute) <input name="password"> page.locator("[name='password']") Dấu ngoặc vuông [] dùng để tìm theo bất kỳ thuộc tính nào. [name='password'] có nghĩa là "Tìm element có thuộc tính name với giá trị là 'password'". (không có) Tên thẻ (Tag Name) <h1>Welcome</h1> page.locator("h1") Viết thẳng tên thẻ có nghĩa là "Tìm tất cả các element có thẻ là h1". > Con trực tiếp <div> <p>Text</p> </div> page.locator("div > p") Dấu > có nghĩa là "Tìm thẻ p là con trực tiếp (nằm ngay bên trong) của thẻ div". (dấu cách) Con cháu <div> <span> <p>Text</p> </span> </div> page.locator("div p") Dấu cách có nghĩa là "Tìm thẻ p nằm bên trong (bất kể cấp nào) của thẻ div".
-
Ví dụ:
-
Theo ID (duy nhất):
page.locator("#username")
-
Theo Class:
page.locator(".btn-primary")
-
Theo thuộc tính:
page.locator("input[name='password']")
-
Theo quan hệ cha-con:
page.locator("div.form-group > input")
-
b. XPath
Hãy coi XPath như hệ thống định vị GPS, nó có thể đi theo những con đường phức tạp hơn.
-
Khái niệm: XPath cho phép bạn duyệt cây DOM theo cấu trúc của nó. Nó mạnh hơn CSS nhưng cú pháp cũng phức tạp hơn.
-
Ví dụ đơn giản:
-
Tìm theo text chính xác:
🧪 HTML:<button>Đăng nhập</button>
# "Tìm thẻ button có nội dung text chính xác là 'Đăng nhập'" page.locator("//button[text()='Đăng nhập']")
Tìm theo một phần text:
🧪 HTML:
<h3>Chào mừng trở lại, Lan Hà!</h3>
# "Tìm thẻ h3 có nội dung text chứa chuỗi 'Chào mừng'" page.locator("//h3[contains(text(), 'Chào mừng')]")
-
3️⃣ Các Locator hiện đại trong Playwright (Best Practices)
Playwright khuyến khích sử dụng các locator ngữ nghĩa, giúp test case của bạn dễ đọc như văn xuôi và bền vững hơn khi giao diện thay đổi.
Kim tự tháp ưu tiên:
-
Role (Vai trò người dùng)
-
Label / Text (Văn bản nhìn thấy)
-
Test ID (Thuộc tính dành riêng cho test)
-
CSS / XPath (Khi các cách trên thất bại)
Method | Ý nghĩa | Ví dụ | Khi nào dùng tốt nhất? |
get_by_role() |
Xác định phần tử theo vai trò người dùng (nút, link, checkbox...). Ưu tiên số 1. | page.get_by_role("button", name="Login") |
Nút, link, checkbox, heading, radio button... |
get_by_text() |
Tìm theo văn bản hiển thị trên màn hình. | page.get_by_text("Forgot password?") |
Các đoạn text, tiêu đề, thông báo lỗi. |
get_by_label() |
Tìm một ô input dựa vào thẻ <label> của nó. Rất hiệu quả cho form. |
page.get_by_label("Email Address") |
Các ô input, textarea, select trong form. |
get_by_placeholder() |
Tìm một ô input dựa vào văn bản gợi ý (placeholder). | page.get_by_placeholder("Enter your name") |
Các ô input trong form. |
get_by_test_id() |
Tìm theo thuộc tính data-testid . Cần sự phối hợp của team dev. |
page.get_by_test_id("cart-icon-badge") |
Các element khó xác định, cần độ ổn định tuyệt đối. |
4️⃣ Ví dụ tổng hợp
🧪 HTML:
<div class="form-group">
<label for="email-input">Email Address</label>
<input id="email-input" placeholder="Enter your email" data-testid="email-field" />
<button class="btn-primary">Submit</button>
</div>
🧩 Playwright Python (Nhiều cách để chọn cùng 1 element):
def test_all_locators(page):
page.goto("https://example.com")
email_input = page.locator("#email-input") # Cách 1: CSS theo ID
email_input = page.locator("[placeholder='Enter your email']") # Cách 2: CSS theo thuộc tính
# Cách 3 (Tốt hơn): Dùng get_by_label
email_input = page.get_by_label("Email Address")
# Cách 4 (Tốt nhất nếu có): Dùng get_by_test_id
email_input = page.get_by_test_id("email-field")
email_input.fill("lanha.tester@example.com")
# Click nút Submit
page.get_by_role("button", name="Submit").click()
🧩 BÀI TẬP THỰC HÀNH
🧠 Bài 1: Đăng nhập thành công
-
Truy cập trang
https://www.saucedemo.com/
-
Sử dụng
page.get_by_placeholder("Username")
để điềnstandard_user
. -
Sử dụng
page.get_by_placeholder("Password")
để điềnsecret_sauce
. -
Click nút Login bằng
page.get_by_role("button", name="Login")
. -
Dùng
expect
để chờ và xác nhận tiêu đề "Products" xuất hiện:expect(page.get_by_text("Products")).to_be_visible()
. -
In ra số lượng sản phẩm có trên trang (gợi ý: locator là
.inventory_item
).
🧠 Bài 2: Xử lý đăng nhập thất bại
-
Cũng tại trang trên, cố tình nhập sai password.
-
Click nút Login.
-
Sử dụng
page.locator("[data-test='error']")
kết hợp vớiexpect().to_contain_text()
để chờ và xác minh thông báo lỗi "Username and password do not match" xuất hiện.
🔍 TÓM TẮT KIẾN THỨC
Chủ đề | Ghi nhớ cốt lõi | Lời khuyên vàng |
Wait | Playwright có Auto-Waiting mạnh mẽ, bạn không cần thêm wait thủ công cho các hành động tương tác. | Chỉ dùng wait tường minh (wait_for_* , expect ) khi chờ các sự kiện (URL, response, load state). Tránh xa wait_for_timeout . |
Locators | Có 2 nhóm: Truyền thống (CSS, XPath) và Hiện đại (get_by_* ). |
Ưu tiên tuyệt đối cho các locator hiện đại theo thứ tự: Role -> Label/Text -> Test ID. Chỉ dùng CSS/XPath khi thực sự cần thiết. |