Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Locators — ID, Name, CSS, XPath best practices

Mở đầu — vì sao bài này quan trọng

Nếu bạn đã từng viết một test tự động cho web, bạn sẽ nhận ra một sự thật phũ phàng: 80% thời gian bạn debug test không phải vì logic sai, mà vì Selenium không tìm thấy phần tử (element) trên trang. Cái nút "Thanh toán" hôm qua chạy ngon lành, hôm nay lại báo NoSuchElementException. Cái ô tìm kiếm bạn locate bằng một đường XPath dài ngoằng bỗng dưng đứt sau khi dev đổi giao diện. Đây là nỗi đau kinh điển của mọi automation engineer.

Locator — cách bạn "chỉ mặt đặt tên" một phần tử HTML để Selenium tương tác với nó — chính là nền móng của toàn bộ automation UI. Một bộ test được xây trên những locator tệ giống như xây nhà trên nền đất sét: nhìn thì ổn, nhưng chỉ cần trời mưa (dev deploy phiên bản mới) là sụp. Ngược lại, khi bạn thành thạo việc chọn locator đúng — ổn định, nhanh, dễ đọc — thì bộ test của bạn sẽ "sống" qua hàng chục lần refactor giao diện mà không cần đụng tay.

Trong bài này, chúng ta sẽ đi sâu vào bốn chiến lược locator cốt lõi trong Selenium WebDriver: ID, Name, CSS Selector và XPath. Mục tiêu không phải là học thuộc cú pháp, mà là hiểu khi nào dùng cái nào và tại sao — thứ tư duy sẽ theo bạn suốt sự nghiệp SDET.

Khái niệm cốt lõi

Locator là một "địa chỉ" trỏ tới phần tử HTML mà bạn muốn thao tác — click, nhập text, đọc giá trị. Selenium cung cấp lớp By với nhiều chiến lược khác nhau. Hãy đi qua từng cái theo đúng thứ tự ưu tiên bạn nên nhớ.

1. ID — nhanh nhất, ưu tiên số một

ID trong HTML được thiết kế để duy nhất trên toàn trang. Đó là lý do nó là locator lý tưởng: trình duyệt tối ưu việc tra cứu theo ID nên nó nhanh nhất, và vì duy nhất nên gần như không bao giờ nhầm phần tử.

driver.findElement(By.id("username"));

Tương ứng HTML:

<input id="username" type="text">

Quy tắc vàng: nếu phần tử có ID ổn định, luôn dùng ID trước tiên. Cảnh báo duy nhất — nhiều framework hiện đại (React, Angular) sinh ID động kiểu input-a1b2c3 thay đổi mỗi lần build. Loại ID này thì vô dụng, phải né.

2. Name — lựa chọn thứ hai, hay dùng cho form

Thuộc tính name phổ biến trong các form (đặc biệt form được submit theo kiểu truyền thống). Nó không bắt buộc duy nhất, nhưng trong nhiều trang vẫn đủ ổn định.

driver.findElement(By.name("email"));
<input name="email" type="email">

Khi một phần tử không có ID nhưng có name mang ý nghĩa nghiệp vụ rõ ràng, name là ứng cử viên tốt tiếp theo.

3. CSS Selector — con dao đa năng, cân bằng nhất

Đây là locator tôi khuyên bạn đầu tư học kỹ nhất. CSS Selector nhanh (trình duyệt vốn dùng CSS engine để render), cú pháp gọn, và linh hoạt đủ để xử lý gần như mọi tình huống thực tế.

driver.findElement(By.cssSelector("#username"));          // theo id
driver.findElement(By.cssSelector(".btn-primary"));        // theo class
driver.findElement(By.cssSelector("input[name='email']")); // theo attribute
driver.findElement(By.cssSelector("form.login input[type='password']")); // kết hợp

Một vài mẫu CSS bạn nên nằm lòng:

  • #id — chọn theo ID.
  • .class — chọn theo class.
  • tag[attr='value'] — chọn theo thuộc tính.
  • parent > child — con trực tiếp.
  • ancestor descendant — con cháu bất kỳ cấp.
  • [data-testid='submit-btn'] — chọn theo attribute tùy biến, cực kỳ ổn định (sẽ nói kỹ ở phần mẹo).

4. XPath — mạnh nhất nhưng cũng "nguy hiểm" nhất

XPath là ngôn ngữ truy vấn cây XML/HTML. Nó làm được những thứ CSS không làm được: đi ngược lên cha (parent), chọn theo text hiển thị, hoặc điều hướng theo axis phức tạp.

driver.findElement(By.xpath("//input[@id='username']"));
driver.findElement(By.xpath("//button[text()='Đăng nhập']"));
driver.findElement(By.xpath("//label[text()='Email']/following-sibling::input"));
driver.findElement(By.xpath("//td[text()='ĐH-2024-0512']/..")); // đi lên cha

Sức mạnh của XPath cũng là cạm bẫy: người mới hay copy XPath tuyệt đối (absolute XPath) từ DevTools kiểu /html/body/div[2]/div[1]/form/div[3]/input — loại này gãy ngay khi dev thêm một <div> bọc ngoài. Luôn dùng XPath tương đối (bắt đầu bằng //).

Bảng tổng hợp thứ tự ưu tiên

LocatorSelenium APIƯu tiênĐặc điểm
IDBy.id#1Nhanh nhất, duy nhất — dùng khi có
NameBy.name#2Tốt cho form, khá ổn định
CSS SelectorBy.cssSelector#3Nhanh, gọn, linh hoạt — vũ khí chính
XPathBy.xpath#4Mạnh nhất (text, cha, axis) nhưng chậm & dễ gãy
Link Text / PartialBy.linkTextphụChỉ cho thẻ <a>
Class Name / TagBy.classNamephụÍt khi duy nhất, dễ trùng
Nguyên tắc chung: đi từ trên xuống. Chỉ tụt xuống locator kém ổn định hơn khi cái trên không khả dụng.

Tình huống thực tế

Ví dụ 1: Tiki và bài học về ID động của React

Một bạn QA junior ở đội automation của một sàn thương mại điện tử lớn (giả định theo mô hình Tiki) được giao viết test cho luồng "Thêm vào giỏ hàng". Bạn ấy mở DevTools, thấy nút có id="add-cart-8f3a92" và dùng luôn By.id("add-cart-8f3a92"). Test chạy pass, bạn ấy vui vẻ merge.

Sáng hôm sau, toàn bộ 40 test regression đỏ lòm. Nguyên nhân: frontend dùng React với thư viện sinh ID ngẫu nhiên theo mỗi lần build, nên 8f3a92 đã đổi thành 2c7b41. ID trông giống ID thật nhưng thực chất là "rác".

Cách xử lý: đội đã làm việc với dev để thêm thuộc tính data-testid="btn-add-to-cart" — một attribute chuyên dụng cho test, cam kết không đổi. Locator trở thành By.cssSelector("[data-testid='btn-add-to-cart']"). Từ đó luồng này chưa gãy thêm lần nào trong 6 tháng.

Bài học: ID nhìn "đẹp" chưa chắc ổn định. Trước khi tin một ID, hãy reload trang vài lần và xem nó có đổi không. ID động là kẻ thù thầm lặng.

Ví dụ 2: Ngân hàng số VN và bài toán bảng giao dịch không có ID

Đội QA của một ứng dụng ngân hàng số (giả định như Timo hoặc Cake) cần test tính năng: "tìm dòng giao dịch có mã GD-2024-0512 và click nút Chi tiết ở cùng dòng đó". Vấn đề: bảng giao dịch được render động, mỗi <tr> không có ID, và nút "Chi tiết" thì dòng nào cũng giống hệt nhau — class trùng, text trùng.

CSS Selector thuần không giải được bài này vì CSS không thể đi từ ô chứa text GD-2024-0512 sang nút anh em (sibling) một cách trực quan khi cấu trúc phức tạp. Đây chính là lúc XPath tỏa sáng:

driver.findElement(By.xpath(
  "//td[text()='GD-2024-0512']/ancestor::tr//button[text()='Chi tiết']"
));

Đường XPath này nói: "tìm ô có mã giao dịch đó, đi ngược lên hàng <tr> chứa nó, rồi tìm nút Chi tiết bên trong hàng ấy." Chính xác tuyệt đối, không phụ thuộc thứ tự dòng.

Bài học: đừng cực đoan bài trừ XPath. Với các thao tác dựa trên text nghiệp vụquan hệ cha-con phức tạp (rất hay gặp trong bảng dữ liệu, form động), XPath là công cụ duy nhất đủ mạnh. Chìa khóa là dùng XPath tương đối và bám vào dữ liệu ổn định (mã giao dịch), không bám vào vị trí (tr[3]).

Ví dụ 3: Startup fintech và cái giá của absolute XPath

Một startup fintech ở TP.HCM có bộ 120 test E2E. Một dev automation lười, thay vì viết locator cẩn thận, đã right-click → Copy → Copy full XPath trong Chrome cho gần như mọi phần tử. Kết quả là hàng loạt locator kiểu:

/html/body/div[1]/div[2]/main/section[3]/div/div[2]/form/div[4]/button

Ba tuần sau, team frontend redesign trang, thêm một banner khuyến mãi bọc ngoài <main>. Chỉ một <div> mới đó thôi đã làm 97/120 test gãy cùng lúc. Team mất trọn hai ngày sửa locator thủ công thay vì làm việc có giá trị hơn.

Bài học: absolute XPath là món nợ kỹ thuật (technical debt) tồi tệ nhất trong automation. Một locator tốt phải mô tả phần tử là gì (nút submit của form login), chứ không phải phần tử nằm ở đâu trên cây DOM. Nếu bạn thấy dấu /html/body/... trong code review, hãy chặn nó lại ngay.

Hướng dẫn từng bước

Đây là quy trình chọn locator mà tôi áp dụng cho mọi phần tử mới:

Bước 1 — Mở DevTools và soi phần tử. Nhấn F12, dùng công cụ Inspect (Ctrl+Shift+C) click vào phần tử. Đọc kỹ HTML của nó: có ID không? có name không? có data-testid không? có class ý nghĩa không?

Bước 2 — Kiểm tra ID có ổn định không. Nếu có ID, reload trang 2–3 lần và so sánh. Nếu ID giữ nguyên và không có ký tự hash ngẫu nhiên → dùng By.id. Xong.

Bước 3 — Không có ID tốt? Tìm attribute chuyên dụng. Ưu tiên data-testid, data-cy, data-qa. Nếu có, dùng By.cssSelector("[data-testid='...']"). Nếu chưa có, hãy đề nghị dev thêm — đây là khoản đầu tư đáng giá nhất cho automation.

Bước 4 — Dùng CSS Selector cho phần lớn trường hợp còn lại. Kết hợp tag + attribute + class để tạo selector vừa đủ cụ thể mà không quá cứng nhắc. Ví dụ form.checkout button.submit.

Bước 5 — Chỉ dùng XPath khi thật sự cần. Khi phải chọn theo text hiển thị, hoặc đi ngược lên phần tử cha, hoặc điều hướng sang sibling — dùng XPath tương đối.

Bước 6 — Test locator ngay trong Console trước khi viết code. Đây là mẹo tiết kiệm thời gian cực lớn:

// Test CSS selector — nếu ra đúng 1 phần tử là ổn
document.querySelectorAll("[data-testid='btn-add-to-cart']")

// Test XPath $x("//button[text()='Đăng nhập']")

Nếu kết quả trả về đúng một phần tử (không phải 0, không phải nhiều) thì locator của bạn đủ cụ thể. Chỉ khi qua bước này bạn mới đưa vào code Java.

Bước 7 — Đặt locator vào chỗ tập trung. Đừng rải locator khắp nơi trong test. Gom chúng lại (bài Page Object Model sau sẽ nói kỹ) để khi giao diện đổi, bạn chỉ sửa một chỗ.

Lỗi thường gặp & mẹo

Lỗi 1 — Copy absolute XPath từ DevTools. Như ví dụ 3, đây là con đường ngắn nhất tới địa ngục bảo trì. Luôn dùng XPath tương đối (//).

Lỗi 2 — Locator quá cụ thể hoặc quá lỏng lẻo. div.card > div.body > div.row > span.price quá cụ thể, gãy khi cấu trúc đổi nhẹ. Ngược lại By.className("btn") quá lỏng, trang có 50 nút btn thì Selenium lấy nhầm. Hãy tìm điểm cân bằng: đủ cụ thể để duy nhất, đủ lỏng để chịu được thay đổi nhỏ.

Lỗi 3 — Tin vào class dùng cho styling. Các class như mt-4, text-red-500 (kiểu Tailwind) chỉ để trang trí và dev đổi liên tục. Đừng bao giờ locate theo chúng.

Lỗi 4 — Quên rằng By.className không nhận nhiều class. By.className("btn primary") sẽ lỗi. Muốn chọn phần tử có cả hai class, dùng CSS: By.cssSelector(".btn.primary").

Mẹo 1 — Vận động cho data-testid. Món quà lớn nhất bạn có thể xin từ team frontend là các attribute test chuyên dụng. Chúng tách biệt hoàn toàn khỏi styling và cấu trúc, nên gần như bất tử. Một dòng <button data-testid="submit-order"> giúp tiết kiệm hàng trăm giờ bảo trì.

Mẹo 2 — Với text tiếng Việt có dấu, cẩn thận encoding. Khi locate //button[text()='Đăng nhập'], đảm bảo file source lưu UTF-8 để dấu tiếng Việt khớp chính xác. Khoảng trắng thừa cũng làm text() không khớp — cân nhắc normalize-space(): //button[normalize-space()='Đăng nhập'].

Mẹo 3 — Ưu tiên CSS hơn XPath khi cả hai làm được. CSS nhanh hơn trên nhiều trình duyệt và cú pháp dễ đọc hơn. Chỉ "leo thang" lên XPath khi CSS bó tay (chọn theo text, đi lên cha).

Mẹo 4 — Dùng contains() cho giá trị động một phần. Nếu ID kiểu user-profile-8f3a có phần đầu cố định, dùng CSS [id^='user-profile-'] (bắt đầu bằng) hoặc XPath //div[contains(@id,'user-profile-')].

Bài tập thực hành

Hãy mở trang đăng nhập của một trang bất kỳ (ví dụ trang login demo hoặc chính sản phẩm bạn đang test) và làm các bài sau. Với mỗi phần tử, viết ít nhất hai locator khác nhau rồi tự chấm cái nào ổn định hơn.

  • Ô email và ô mật khẩu: Viết locator bằng cả ID (nếu có), Name, CSS và XPath. Kiểm tra từng cái trong Console bằng document.querySelector() / $x() để chắc chắn ra đúng một phần tử.
  • Nút Đăng nhập theo text: Viết XPath chọn nút dựa trên text hiển thị (có dấu tiếng Việt), dùng normalize-space() để tránh lỗi khoảng trắng.
  • Bài toán bảng: Tìm một trang có bảng dữ liệu (danh sách đơn hàng, giao dịch...). Viết một XPath chọn nút hành động ở đúng dòng chứa một giá trị cụ thể — mô phỏng ví dụ ngân hàng ở trên.
  • Săn locator xấu: Copy full XPath của một nút từ DevTools, sau đó tự viết lại thành CSS Selector ngắn gọn, dựa trên attribute. So sánh độ dài và độ bền của hai phiên bản.
  • Đề xuất cải tiến: Chọn 3 phần tử khó locate nhất trên trang, viết một đề xuất ngắn cho team frontend về việc thêm data-testid nào cho từng phần tử.
Làm xong 5 bài này, bạn sẽ có phản xạ chọn locator đúng gần như tự động.

Tóm tắt

  • Locator là nền móng của automation UI; locator tốt quyết định bộ test của bạn "sống" hay "chết" qua mỗi lần dev đổi giao diện.
  • Thứ tự ưu tiên: ID → Name → CSS Selector → XPath. Đi từ trên xuống, chỉ tụt xuống khi cái trên không khả dụng.
  • ID nhanh và duy nhất nhưng coi chừng ID động do React/Angular sinh ra.
  • CSS Selector là vũ khí chính: nhanh, gọn, linh hoạt cho hầu hết tình huống.
  • XPath mạnh nhất khi cần chọn theo text hiển thị hoặc đi ngược lên cha/sibling — nhưng luôn dùng XPath tương đối, tuyệt đối tránh absolute XPath copy từ DevTools.
  • Khoản đầu tư giá trị nhất: thuyết phục team frontend thêm data-testid. Nó tách locator khỏi styling và cấu trúc, giúp test gần như bất tử.
  • Luôn test locator trong Console (querySelectorAll, $x) trước khi đưa vào code — đảm bảo ra đúng một phần tử.
Nắm vững locator rồi, bạn đã sẵn sàng cho bài tiếp theo về Wait Strategies — cách xử lý các phần tử xuất hiện chậm, để test không còn báo lỗi "không tìm thấy phần tử" chỉ vì trang chưa kịp load.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi