NỘI DUNG BÀI HỌC
🧠 I. KHÁI NIỆM CỐT LÕI VỀ XPATH
1️⃣ XPath là gì?
Ở bài học trước, chúng ta đã làm quen với get_by_* (tìm theo góc nhìn người dùng) và CSS Selector (tìm siêu tốc dựa vào "ngoại hình"). Vậy còn XPath (XML Path Language) là gì?
-
Định nghĩa chuẩn: XPath (XML Path Language) là một ngôn ngữ truy vấn được sinh ra để tìm kiếm và định vị chính xác bất kỳ thành phần (element) nào trên một trang web (hoặc file XML).
-
Nếu trang web của bạn là một thành phố khổng lồ vô cùng phức tạp, thì XPath chính là tọa độ GPS. Nó giúp bạn chỉ thẳng tay vào đúng vị trí của một cái nút bấm, một dòng chữ hay một tấm ảnh mà không bao giờ sợ nhầm lẫn.
💡 Sự thật phũ phàng: XPath chạy chậm hơn CSS một chút xíu (khoảng vài mili-giây, mắt thường khó nhận ra) và nhìn code khá "lằng nhằng". Vậy tại sao tester lại "tôn thờ" XPath? Vì nó có 2 siêu năng lực độc quyền:
Tìm theo nội dung chữ (Text): Tìm một thẻ chứa đoạn chữ dài ngoằng hoặc chỉ chứa một từ khóa ẩn sâu bên trong.
Lội ngược dòng (Gia phả): Khả năng đi từ phần tử Con nhìn ngược lên phần tử Cha, hoặc từ phần tử Anh tìm ra phần tử Em. CSS Selector hoàn toàn "bó tay" ở khoản này!
2️⃣ Cách XPath nhìn nhận trang web
XPath không quan tâm trang web của bạn có màu sắc đẹp hay xấu. Nó nhìn toàn bộ mã nguồn HTML dưới dạng một Cây gia phả (DOM Tree Structure):
-
Gốc (Root): Là thẻ
<html>bọc ngoài cùng. -
Nhánh (Nodes): Các mục lớn như
<body>,<div>,<table>. -
Lá (Leaves): Các chi tiết nhỏ nhất không chứa ai bên trong nó nữa (ví dụ: một từ đơn lẻ, một cái icon).
Để tìm đến một "cái lá", bạn phải đi theo một con đường (Path) từ gốc rẽ qua các nhánh. Đó là lý do nó có tên là X-Path.
🎯 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 để viết tọa độ:
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 một cách máy móc 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] -
Tại sao lại "phế"? Giống như chỉ đường: "Từ cổng thành, đi thẳng qua 2 ngã tư, rẽ trái, vào hẻm 1, gõ cửa nhà số 3". Trông thì có vẻ rất chính xác, nhưng nếu ngày mai Dev thêm một cái banner quảng cáo (tức là xây thêm một ngã tư mới), Robot tự động hóa của bạn sẽ lạc đường ngay lập tức! Kịch bản test 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ễ, nó đóng vai trò như một chiếc trực thăng "nhảy dù" thẳng xuống nơi có phần tử cần tìm. -
Ví dụ:
//input[@id='email'] -
Tại sao nên dùng? Giống như việc bạn gọi taxi và nói: "Chở tôi đến thẳng cái nhà có ID là 'email'". Kệ cho Dev có bứng cái nhà đó đi đâu hay thêm bớt giao diện ra sao, Robot vẫn luôn đánh hơi và tìm được đúng mục tiêu.
🏛️ III. "BẢNG CỬU CHƯƠNG" CÚ PHÁP XPATH CƠ BẢN VÀ NÂNG CAO
Để viết XPath mượt mà, bạn chỉ cần nhớ một công thức cốt lõi duy nhất:
👉 //TênThẻ[@ThuộcTính='Giá Trị']
Dưới đây là tổng hợp mọi loại XPath tương đối từ cơ bản đến nâng cao bạn sẽ dùng khi làm nghề:
| Loại tìm kiếm | HTML Thực Tế | XPath Tương Đối | Diễn giải |
| Theo ID | <input id="user"> |
//input[@id='user'] |
Dấu @ đại diện cho thuộc tính. Khuyên dùng nhất vì ID thường là duy nhất. |
| Theo Class | <button class="btn"> |
//button[@class='btn'] |
Phải khớp chính xác toàn bộ chuỗi class. |
| Thuộc tính bất kỳ | <input name="pwd"> |
//input[@name='pwd'] |
Có thể dùng với type, placeholder, data-testid... |
Thẻ bất kỳ (*) |
<a class="link"> |
//*[@class='link'] |
Tìm bất kỳ thẻ nào mang class là 'link' (Bỏ qua Tên Thẻ). |
| Khớp một phần (Contains) | <div class="btn-123"> |
//div[contains(@class, 'btn')] |
Cực kỳ quan trọng! Dùng khi Dev sinh ra các ID/Class có gắn số ngẫu nhiên thay đổi liên tục. |
| Bắt đầu bằng (Starts-with) | <input id="user_01"> |
//input[starts-with(@id, 'user_')] |
Tìm những thẻ có ID bắt đầu bằng chữ "user_". |
| Chữ hiển thị (Text tuyệt đối) | <a>Quên mật khẩu</a> |
//a[text()='Quên mật khẩu'] |
Phải khớp 100% từng chữ cái và không có khoảng trắng thừa. |
| Chữ hiển thị (Text chứa) | <a>Xin chào Lan Hà</a> |
//a[contains(text(), 'Lan Hà')] |
Khuyên dùng thay vì text tuyệt đối để tránh lỗi do khoảng trắng hoặc dấu xuống dòng ẩn. |
| Kết hợp 2 điều kiện (AND) | <input id="1" type="text"> |
//input[@id='1' and @type='text'] |
Ép phần tử phải thỏa mãn đồng thời cả 2 điều kiện. |
| Một trong hai điều kiện (OR) | <input type="text"> |
//input[@type='text' or @type='email'] |
Thỏa mãn 1 trong 2 là được lấy. |
🧭 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).
