NỘI DUNG BÀI HỌC
🧠 I. KHÁI NIỆM XPATH
👀 1️⃣ XPath là gì?
Ở Bài 2, chúng ta đã học get_by_* (tìm theo góc nhìn người dùng) và CSS Selector (tìm siêu tốc). Vậy XPath (XML Path Language) là gì? Hãy tưởng tượng nó là một chiếc Hệ thống định vị GPS cực kỳ phức tạp có thể bấu víu vào bất kỳ manh mối nhỏ nào để chỉ đường cho Robot Playwright.
💡 Sự thật phũ phàng: XPath chạy chậm hơn CSS một chút xíu và nhìn code khá "lằng nhằng". Bạn chỉ nên dùng XPath khi CSS và các hàm get_by_* đều bó tay!
Vậy tại sao phải học XPath? Vì nó có 2 siêu năng lực mà CSS không thể làm được:
-
Tìm theo chữ ẩn sâu (Text): Tìm một thẻ chứa chữ kết hợp với nhiều điều kiện siêu phức tạp.
-
Định vị theo Gia phả (Độc quyền): Khả năng đi từ phần tử này, chiếu sang phần tử khác (Từ Con nhìn lên Cha, từ Anh tìm ra Em).
🎯 II. XPATH TUYỆT ĐỐI VS XPATH TƯƠNG ĐỐI
Nếu coi XPath là bản đồ chỉ đường, chúng ta có 2 cách chỉ:
1️⃣ ❌ XPath Tuyệt đối (Absolute XPath) - TUYỆT ĐỐI TRÁNH XA!
-
Đặc điểm: Bắt đầu bằng một dấu gạch chéo
/. Nó chỉ đường từ gốc rễ trên cùng của trang web đi xuống từng bậc một. -
Ví dụ:
/html/body/div[2]/div[1]/form/input[3] -
💡 Bình dân học vụ: Giống như chỉ đường: "Từ cổng thành, đi thẳng 2 ngã tư, rẽ trái, vào hẻm 1, gõ cửa nhà số 3". Trông thì có vẻ chính xác, nhưng nếu ngày mai ủy ban phường đập hẻm xây đường mới (Dev thay đổi giao diện), Robot sẽ lạc đường và kịch bản sẽ Fail đỏ lòm!
2️⃣ ✅ XPath Tương đối (Relative XPath) - CHÂN ÁI LÀ ĐÂY!
-
Đặc điểm: Bắt đầu bằng hai dấu gạch chéo
//. Bỏ qua gốc rễ, "nhảy dù" thẳng xuống nơi có phần tử cần tìm. -
Ví dụ:
//input[@id='email'] -
💡 Bình dân học vụ: Giống như gọi taxi: "Chở tôi đến thẳng cái nhà có số id là 'email'". Kệ cho Dev có bứng cái nhà đó đi đâu, Robot vẫn tìm được!
🏛️ III. "BẢNG CỬU CHƯƠNG" CÚ PHÁP XPATH CƠ BẢN
Để chuyển đổi từ CSS sang XPath, bạn chỉ cần nhớ công thức cốt lõi này: 👉 //TênThẻ[@ThuộcTính='Giá Trị']
| Loại tìm kiếm | Mã HTML thực tế | Tìm bằng XPath Tương đối |
| Theo ID | <input id="user"> |
//input[@id='user'] |
| Theo Class | <button class="btn"> |
//button[@class='btn'] |
| Bất kỳ thuộc tính nào | <input name="pwd"> |
//input[@name='pwd'] |
| Chứa một phần (Contains) | <div class="btn-login-123"> |
//div[contains(@class, 'btn-login')] (Bỏ qua dãy số 123 lằng nhằng) |
| Theo chữ hiển thị (Text) | <a href="#">Quên mật khẩu</a> |
//a[text()='Quên mật khẩu'] |
🧭 IV. ĐỈNH CAO XPATH: ĐỊNH VỊ THEO "GIA PHẢ" (XPATH AXES)
Đây chính là linh hồn của XPath. Hãy dùng nó khi: Phần tử bạn muốn thao tác KHÔNG CÓ id/class rõ ràng, nhưng nó lại nằm cạnh một phần tử rất dễ tìm. Ta sẽ tìm phần tử dễ trước, rồi từ đó làm "bàn đạp" chiếu sang phần tử khó!
Để hiểu rõ cách XPath lội ngược, lội xuôi, chúng ta hãy coi cấu trúc HTML như một Cây Gia Phả. Hãy nhìn vào ví dụ sau, giả sử chúng ta lấy thẻ <span class="name">Nguyễn Văn A</span> làm BẢN THÂN (Bàn đạp) để đi tìm những người họ hàng xung quanh:
🧪 Cây Gia Phả HTML:
<div class="user-card"> <!-- 👴 TỔ TIÊN (Ông Nội): Chứa tất cả mọi người -->
<h3>Hồ sơ</h3> <!-- 🧔 BÁC: Khác cha, nằm trên -->
<div class="details"> <!-- 👨 CHA RUỘT: Ôm trực tiếp các con vào lòng -->
<label>Tên:</label> <!-- 👦 ANH RUỘT (Sinh trước) -->
<span class="name">Nguyễn Văn A</span> <!-- 🎯 BẢN THÂN (Làm bàn đạp) -->
<button class="btn-edit">Sửa</button> <!-- 👶 EM RUỘT (Sinh sau) -->
</div>
<div class="footer"> <!-- 👨 CHÚ: Khác cha, nằm dưới -->
<button class="btn-delete">Xóa</button> <!-- 🏃 NGƯỜI NỐI GÓT (Following) -->
</div>
</div>
Chúng ta chia các từ khóa (Axes) thành 4 nhóm hướng đi. Cú pháp chung luôn là: 👉 //Bàn_Đạp/từ_khóa::Tên_Thẻ
🕵️♂️ NHÓM 1: NHÌN SANG NGANG (TÌM ANH EM RUỘT)
Điều kiện để làm anh em ruột: Phải nằm chung trong lòng một người Cha (thẻ bao bọc).
🕵️♂️ NHÓM 2: LỘI NGƯỢC DÒNG (TÌM BỀ TRÊN)
Độc chiêu của XPath, thứ mà CSS Selector không bao giờ làm được!
| Từ khóa XPath | Ý nghĩa (Ngôn ngữ Bình Dân) | Ví dụ / Cách Robot di chuyển |
parent:: |
Tìm CHA RUỘT: Lùi lên đúng 1 cấp, tóm lấy thẻ ôm trực tiếp Bản thân. (Có thể viết tắt bằng |
Đứng từ "Nguyễn Văn A", tìm lên cái thẻ div
Hoặc viết tắt: |
ancestor:: |
Tìm TỔ TIÊN: Lùi lên nhiều cấp (Cha, Ông Nội, Cố...). Tìm lớp vỏ bao bọc ngoài cùng. |
Đứng từ "Nguyễn Văn A", tóm lấy toàn bộ thẻ bự
(Chiến thuật: Cực kỳ hữu dụng để tóm gọn cả 1 Table/Grid). |
🕵️♂️ NHÓM 3: ĐI XUYÊN MÀN ĐÊM (KHÔNG CẦN CÙNG CHA)
Dùng khi phần tử muốn tìm nằm ở một nhánh khác, nhưng chắc chắn xuất hiện trước/sau Bản thân.
🕵️♂️ NHÓM 4: NHÌN XUỐNG DƯỚI (TÌM CON CHÁU)
Bạn có thể viết tắt bằng dấu / hoặc // cho gọn.
| Từ khóa XPath | Ý nghĩa (Ngôn ngữ Bình Dân) | Tương đương với cách viết tắt |
child:: |
Tìm Con ruột: Nằm ngay dưới bụng 1 cấp. |
Viết dài: Viết tắt: |
descendant:: |
Tìm Con Cháu: Nằm bên trong lòng, bất kể mấy cấp. |
Viết dài: Viết tắt: |
🧾 V. VÍ DỤ THỰC CHIẾN: ĐƯA "GIA PHẢ" VÀO PLAYWRIGHT
Hãy xem xét bài toán Quản lý nhân sự: Có rất nhiều nút Xóa giống hệt nhau, làm sao để bấm đúng nút Xóa của "Nguyễn Văn A"?
🧪 Cấu trúc HTML (Giả lập):
<div class="user-row">
<span class="name">Nguyễn Văn A</span>
<span class="role">Tester</span>
<button class="btn-delete">Xóa</button>
</div>
<div class="user-row">
<span class="name">Trần Văn B</span>
<span class="role">Dev</span>
<button class="btn-delete">Xóa</button>
</div>
🧩 Kịch bản Playwright Python giải quyết bài toán:
from playwright.sync_api import Page
def test_xpath_gia_pha(page: Page):
page.goto("https://admin-example.com")
# -------------------------------------------------------------
# 1. TUYỆT CHIÊU "EM RUỘT" (following-sibling)
# BÀI TOÁN: Xóa Nguyễn Văn A
# CHIẾN THUẬT: Lấy chữ "Nguyễn Văn A" làm Bàn Đạp, nhìn sang
# thằng "Em ruột" mang thẻ button bên dưới để click!
# -------------------------------------------------------------
nut_xoa_A = page.locator("//span[text()='Nguyễn Văn A']/following-sibling::button")
nut_xoa_A.click()
# -------------------------------------------------------------
# 2. TUYỆT CHIÊU "CHA RUỘT" (parent:: hoặc /..)
# BÀI TOÁN: Lấy toàn bộ thông tin của Trần Văn B
# CHIẾN THUẬT: Đứng từ "Trần Văn B", lùi ra ngoài (tìm Cha ruột)
# để tóm trọn cái thẻ <div class="user-row"> chứa cả dòng.
# -------------------------------------------------------------
# Dùng cách viết tắt (/..) cho nhanh gọn:
dong_cua_B = page.locator("//span[text()='Trần Văn B']/..")
print("Thông tin dòng: ", dong_cua_B.text_content())
# -------------------------------------------------------------
# 3. TUYỆT CHIÊU KẾT HỢP CONTAINS
# BÀI TOÁN: Tìm nút xóa có class chứa chữ "delete" (Phòng trường hợp class bị đổi)
# -------------------------------------------------------------
nut_xoa_B = page.locator("//span[text()='Trần Văn B']/following-sibling::button[contains(@class, 'delete')]")
nut_xoa_B.click()
print("✅ Đã thi triển thành công bí kíp Gia phả XPath!")
(Lưu ý của Playwright: Nếu chuỗi của bạn bắt đầu bằng // hoặc .., Playwright sẽ tự động hiểu đó là XPath mà không cần bạn phải khai báo dài dòng).
