NỘI DUNG BÀI HỌC

Làm chủ XPath: Hiểu và vận dụng tất cả các phương pháp xác định locator bằng XPath, từ cơ bản đến các kỹ thuật phức tạp sử dụng "trục" (axes).
Thành thạo Codegen: Sử dụng công cụ Playwright Codegen để tự động tạo mã kịch bản, giúp tăng tốc độ viết test và tìm hiểu locators.
Tư duy "Refactor": Hiểu rằng code do máy tạo ra là điểm khởi đầu, và kỹ năng cải tiến (refactor) nó thành code sạch, bền vững mới là mục tiêu cuối cùng.
Giải quyết bài toán khó: Áp dụng XPath axes để giải quyết các bài toán định vị element phức tạp mà các locator thông thường không thể xử lý.



🧠 I. LÀM CHỦ XPATH (PHẦN NÂNG CAO)

Trong buổi trước, chúng ta đã biết đến XPath như một công cụ dự phòng. Hôm nay, chúng ta sẽ đi sâu vào lý do tại sao nó là công cụ mạnh mẽ và không thể thiếu trong các tình huống phức tạp.

1️⃣ Khái niệm XPath

XPath (XML Path Language) là một ngôn ngữ truy vấn, cho phép bạn viết một "đường dẫn" để điều hướng qua các phần tử (elements) trong một tài liệu HTML/XML.

  • Tưởng tượng trang web là một cây gia phả:

    • Thẻ <html> là cụ tổ.

    • Thẻ <body> là ông.

    • Thẻ <div> là cha.

    • Thẻ <p> là con.

  • XPath chính là cách bạn chỉ đường trong cây gia phả đó, ví dụ: "Hãy tìm đến ông body, vào người con div thứ hai, rồi tìm đứa cháu p có chứa chữ 'Xin chào'".

 

2️⃣ Cú pháp XPath từ Cơ bản đến Nâng cao

a. Đường dẫn Tuyệt đối (Absolute Path) - ⛔ KHÔNG NÊN DÙNG

  • Khái niệm: Bắt đầu từ gốc của tài liệu (/html), chỉ đường từng bước một.

  • Cú pháp: Bắt đầu bằng một dấu /.

  • Ví dụ HTML: <html><body><div><input id="user"></div></body></html>

  • Ví dụ Automation: page.locator("/html/body/div/input")

  • Tại sao không nên dùng? Cực kỳ mong manh! Chỉ cần lập trình viên thêm một thẻ <div> ở giữa, đường dẫn này sẽ sai ngay lập tức.

 

b. Đường dẫn Tương đối (Relative Path) - ✅ LUÔN LUÔN DÙNG

  • Khái niệm: Tìm kiếm element ở bất kỳ đâu trong tài liệu mà không cần quan tâm đến đường dẫn từ gốc.

  • Cú pháp: Bắt đầu bằng hai dấu //.

  • Ví dụ Automation: page.locator("//input[@id='user']")

  • Lợi ích: Bền vững hơn rất nhiều vì nó không phụ thuộc vào cấu trúc cố định của trang.

 

c. Các cách xác định Locator với XPath

Loại Khái niệm Ví dụ HTML Ví dụ Automation
Theo Thẻ & Thuộc tính Cách phổ biến nhất: tìm element theo tên thẻ và một thuộc tính bất kỳ (id, class, name...). <input class="form-control" name="username"> page.locator("//input[@name='username']")
Dùng hàm text() Tìm element dựa trên nội dung văn bản chính xác mà nó hiển thị. <button>Đăng ký ngay</button> page.locator("//button[text()='Đăng ký ngay']")
Dùng hàm contains() Tìm element một cách linh hoạt khi thuộc tính hoặc văn bản có chứa các giá trị động. <div id="user-123-abc">...</div> page.locator("//div[contains(@id, 'user-')]")
Kết hợp and / or Tạo ra locator siêu cụ thể bằng cách kết hợp nhiều điều kiện. <button class="btn" name="submit">Login</button> page.locator("//button[@class='btn' and @name='submit']")


3️⃣ XPath Axes (Trục trong XPath)

Đây là sức mạnh thực sự của XPath, cho phép bạn di chuyển trong cây DOM theo các mối quan hệ gia đình.


1. parent:: (Tìm Cha trực tiếp)

  • Tình huống: Bạn đã tìm thấy một ô input nhưng muốn kiểm tra hoặc tương tác với thẻ <div> chứa nó, có thể để kiểm tra xem form group có class lỗi hay không.

  • Ví dụ HTML:

    <div class="form-group has-error">
        <label>Username</label>
        <input type="text" id="username-field">
    </div>
  • Giải thích & Phân tích XPath: //input[@id='username-field']/parent::div

    • //input[@id='username-field']: Bắt đầu bằng cách xác định vị trí của "đứa con" là ô input.

    • /parent::div: Từ vị trí đó, di chuyển lên một cấp (parent::) để tìm cha trực tiếp của nó, với điều kiện người cha đó phải là một thẻ div.

  • Ví dụ Automation:

    # Tìm thẻ div cha của ô input
    form_group = page.locator("//input[@id='username-field']/parent::div")
    
    # Kiểm tra xem thẻ div cha có class 'has-error' hay không
    expect(form_group).to_have_class("form-group has-error")

2. ancestor:: (Tìm Tổ tiên)

  • Tình huống: Bạn đã tìm thấy nút "Submit", và bạn muốn xác định xem nút này thuộc về form nào trên trang (ví dụ: form đăng nhập hay form đăng ký).

  • Ví dụ HTML:

    <form id="login-form" action="/login">
        <h2>Login</h2>
        <div class="form-group">
            <input type="text" id="user">
        </div>
        <div class="form-actions">
            <button type="submit">Submit</button>
        </div>
    </form>
  • Giải thích & Phân tích XPath: //button[text()='Submit']/ancestor::form

    • //button[text()='Submit']: Bắt đầu bằng cách tìm đứa "chắt" là nút Submit.

    • /ancestor::form: Từ vị trí đó, di chuyển lên các cấp (ancestor::) cho đến khi tìm thấy một tổ tiên là thẻ <form>. Nó sẽ bỏ qua các thẻ div ở giữa.

  • Ví dụ Automation:

    # Tìm thẻ form là tổ tiên của nút Submit
    login_form = page.locator("//button[text()='Submit']/ancestor::form")
    
    # Kiểm tra xem form đó có đúng là form đăng nhập không
    expect(login_form).to_have_attribute("id", "login-form")
     

3. child:: (Tìm Con trực tiếp)

  • Tình huống: Bạn đã tìm thấy một danh sách (<ul>) và bạn muốn lấy tất cả các mục (<li>) là con trực tiếp của nó.

  • Ví dụ HTML:

    <ul id="main-menu">
        <li><a href="/">Home</a></li>
        <li><a href="/products">Products</a></li>
        <li><a href="/contact">Contact</a></li>
        </ul>
    
    
  • Giải thích & Phân tích XPath: //ul[@id='main-menu']/child::li

    • //ul[@id='main-menu']: Tìm đến "người cha" là thẻ <ul>.

    • /child::li: Từ vị trí đó, tìm tất cả các đứa con trực tiếp (child::) là thẻ <li>.

    • Lưu ý: child:: thường được viết tắt. XPath //ul[@id='main-menu']/li có ý nghĩa tương đương và phổ biến hơn.

  • Ví dụ Automation:

    # Lấy tất cả các mục con <li> của menu
    menu_items = page.locator("//ul[@id='main-menu']/child::li")
    
    # Kiểm tra xem có đúng 3 mục menu hay không
    expect(menu_items).to_have_count(3)
     

4. descendant:: (Tìm Con cháu)

  • Tình huống: Bạn đã tìm thấy một div chứa thông tin sản phẩm và muốn tìm một thẻ span hiển thị chữ "Sale" có thể nằm rất sâu bên trong cấu trúc HTML.

  • Ví dụ HTML:

    <div class="product-card" data-product-id="123">
        <img src="image.jpg">
        <div class="product-info">
            <h3>Laptop Pro</h3>
            <p class="price">
                <span class="sale-badge">Sale</span> $999
            </p>
        </div>
    </div>
    
    
  • Giải thích & Phân tích XPath: //div[@data-product-id='123']/descendant::span

    • //div[@data-product-id='123']: Tìm đến "ông tổ" là thẻ div của sản phẩm.

    • /descendant::span: Từ vị trí đó, tìm kiếm ở tất cả các cấp con cháu (descendant::) để tìm một thẻ <span>.

    • Lưu ý: descendant:: thường được viết tắt bằng //. XPath //div[@data-product-id='123']//span có ý nghĩa tương đương và phổ biến hơn.

  • Ví dụ Automation:

    # Tìm thẻ span sale bên trong thẻ div của sản phẩm
    sale_badge = page.locator("//div[@data-product-id='123']/descendant::span[@class='sale-badge']")
    
    # Kiểm tra xem huy hiệu Sale có tồn tại không
    expect(sale_badge).to_be_visible()
     

5. following-sibling:: (Tìm Anh/Em đứng sau)

  • Tình huống: Trong một bảng, bạn đã tìm thấy ô chứa tên một người, và bạn muốn lấy thông tin ở ô "Email" nằm cùng hàng, ở cột bên phải.

  • Ví dụ HTML:

    <tr>
        <td>John Doe</td>
        <td>johndoe@example.com</td>
        <td>Admin</td>
    </tr>
  • Giải thích & Phân tích XPath: //td[text()='John Doe']/following-sibling::td

    • //td[text()='John Doe']: Tìm đến ô <td> chứa tên "John Doe".

    • /following-sibling::td: Từ vị trí đó, tìm tất cả các anh em (sibling) đứng sau (following) nó mà cũng là thẻ <td>. XPath này sẽ trả về 2 element (ô email và ô vai trò).

  • Ví dụ Automation:

    # Lấy ô email nằm sau ô tên "John Doe"
    # Dùng [1] để lấy người anh em đầu tiên tìm thấy
    email_cell = page.locator("//td[text()='John Doe']/following-sibling::td[1]")
    
    # Kiểm tra nội dung email
    expect(email_cell).to_have_text("johndoe@example.com")
     

6. preceding-sibling:: (Tìm Anh/Em đứng trước)

  • Tình huống: Ngược lại với ở trên. Bạn tìm thấy ô chứa email và muốn lấy tên của người đó ở cột bên trái.

  • Ví dụ HTML: (Giống như trên)

    <tr>
        <td>John Doe</td>
        <td>johndoe@example.com</td>
        <td>Admin</td>
    </tr>
    
    
  • Giải thích & Phân tích XPath: //td[text()='johndoe@example.com']/preceding-sibling::td

    • //td[text()='johndoe@example.com']: Tìm đến ô <td> chứa email.

    • /preceding-sibling::td: Từ vị trí đó, tìm tất cả các anh em (sibling) đứng trước (preceding) nó mà cũng là thẻ <td>.

  • Ví dụ Automation:

    # Lấy ô tên nằm trước ô email
    name_cell = page.locator("//td[text()='johndoe@example.com']/preceding-sibling::td")
    
    # Kiểm tra tên
    expect(name_cell).to_have_text("John Doe")

     

🌟 Các trường hợp đặc biệt: Khi nào KHÔNG cần dùng XPath Axes?

Sử dụng XPath Axes rất mạnh mẽ, nhưng đôi khi có những cách đơn giản và dễ đọc hơn. Luôn ưu tiên locator đơn giản nhất!

Trường hợp 1: Tìm input dựa vào label

  • Bài toán: Tìm ô input có label là "Password".

  • Cách nghĩ dùng Axes: //label[text()='Password']/following-sibling::input

  • ✅ Cách tốt hơn (dùng Playwright locator):

    # Dễ đọc, bền vững và là best practice
    page.get_by_label("Password").fill("secret_sauce")

     

Trường hợp 2: Tìm một nút bên trong một form cụ thể

  • Bài toán: Tìm nút "Login" chỉ nằm trong form có id là login-form.

  • Cách nghĩ dùng Axes: //form[@id='login-form']/descendant::button

  • ✅ Cách tốt hơn (dùng CSS Selector):

    # Ngắn gọn và hiệu quả hơn
    page.locator("#login-form button").click()

 

Trường hợp 3: Tìm một ô cụ thể trong một hàng của bảng

  • Bài toán: Tìm email của "John Doe" trong bảng.

  • Cách nghĩ dùng Axes: Dùng following-sibling như ví dụ trên.

  • ✅ Cách khác (kết hợp locator): Đôi khi việc tìm cả hàng rồi tìm con sẽ dễ đọc hơn.

    # Tìm hàng chứa "John Doe" trước
    row = page.locator("tr", has_text="John Doe")
    
    # Sau đó tìm ô email bên trong hàng đó
    email_cell = row.locator("td:nth-child(2)") # Tìm <td> thứ 2
    
    expect(email_cell).to_have_text("johndoe@example.com")



⚡️ II. TĂNG TỐC VỚI PLAYWRIGHT CODEGEN

1️⃣ Codegen là gì?

Codegen (Code Generator) là một công cụ của Playwright, nó mở một trình duyệt, theo dõi mọi hành động của bạn (click, gõ phím, cuộn chuột) và tự động dịch các hành động đó thành mã nguồn Playwright Python.

Lưu ý quan trọng: Codegen là một trợ lý, không phải là một lập trình viên chuyên nghiệp. Nó giúp bạn:

  • Tạo nhanh script nháp.

  • Học và khám phá locator cho các element phức tạp.

  • Gỡ lỗi bằng cách xem Playwright đề xuất locator nào.

 

2️⃣ Khởi chạy và Sử dụng

  1. Mở terminal (Command Prompt, PowerShell, hoặc terminal trong VS Code).

  2. Gõ lệnh:

    playwright codegen https://www.saucedemo.com/
  3. Hai cửa sổ sẽ xuất hiện:

    • Cửa sổ trình duyệt: Nơi bạn thực hiện các thao tác như một người dùng bình thường.

    • Cửa sổ Playwright Inspector: Nơi mã nguồn được tự động sinh ra.

 

3️⃣ Thực hành với Codegen

  1. Ghi kịch bản (Record):

    • Trên trang saucedemo.com, hãy nhập "standard_user" vào ô username, "secret_sauce" vào ô password, và click nút Login.

    • Quan sát cửa sổ Inspector, bạn sẽ thấy các dòng code Python tương ứng được tạo ra.

  2. Sao chép và "Refactor":

    • Copy đoạn code được tạo ra vào một file test_login.py.

    • Phân tích code: Bạn sẽ thấy Codegen có thể tạo ra một locator khá phức tạp, ví dụ: page.locator("[data-test='login-button']").

    • Cải tiến (Refactor): Hãy tự mình viết lại locator đó theo cách tốt hơn đã học: page.get_by_role("button", name="Login").

    • Mục tiêu: Luôn luôn cải tiến code do máy tạo ra để nó dễ đọc và bền vững hơn.

 

🧩 BÀI TẬP THỰC HÀNH

🧠 Bài 1: Thử thách với XPath Axes

  1. Truy cập trang https://the-internet.herokuapp.com/tables.

  2. Yêu cầu: Viết một locator XPath duy nhất để tìm số tiền nợ (Due) của người có họ là "Bach" trong bảng Table 1.

  3. Gợi ý:

    • Bắt đầu bằng cách tìm ô chứa chữ "Bach": //table[@id='table1']//td[text()='Bach']

    • Từ đó, dùng following-sibling::td để di chuyển sang ô chứa số tiền nợ.

🧠 Bài 2: Codegen và Refactor

  1. Truy cập trang https://the-internet.herokuapp.com/challenging_dom.

  2. Dùng Codegen để ghi lại hành động: click vào nút màu đỏ (có chữ "qux").

  3. Sao chép code đã tạo.

  4. Phân tích: Nút này có ID và text thay đổi mỗi lần tải lại trang. Locator do Codegen tạo ra có thể sẽ không chạy được ở lần tiếp theo.

  5. Refactor: Viết một locator ổn định hơn bằng cách dựa vào class của nó (ví dụ: page.locator("a.button.alert")). Thêm một expect để xác nhận bạn đã click đúng.

    playwright codegen https://www.saucedemo.com/

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