Locator (Định vị phần tử — trái tim của Playwright)
automationplaywright
Locator là đối tượng đại diện cho một hoặc nhiều phần tử trên trang — khái niệm trung tâm của Playwright. Khác với cách lấy element truyền thống (query DOM lấy node về rồi thao tác), Locator chỉ lưu "cách tìm" và resolve phần tử mới mỗi lần thao tác. Nhờ lazy evaluation + auto-wait tích hợp sẵn, Locator gần như xoá sạch flaky test do timing mà manual QA chuyển sang automation hay gặp nhất.
Khái niệm & Bản chất
- Locator = công thức tìm phần tử, không phải chính phần tử: khi bạn viết
page.getByRole('button'), Playwright CHƯA đi tìm nút nào cả. Nó chỉ tạo ra một đối tượng lưu mô tả "tìm button". Mã tìm thật sự chỉ chạy vào lúc bạn gọi hành động (click(),fill()...) hoặc assertion (expect(locator).toBeVisible()). - Lazy evaluation (đánh giá trễ): vì resolve muộn, Locator luôn bắt đúng DOM tại thời điểm thao tác. Trang re-render, element bị replace node mới → locator vẫn tìm được node mới, không bị lỗi
StaleElementReferencenhư Selenium. - Auto-wait (tự động chờ): mỗi action trên locator có sẵn cơ chế chờ — Playwright đợi element visible, stable (không đang animation), enabled, nhận được sự kiện... rồi mới click. Không cần
sleep(), không cầnWebDriverWaitviết tay. - Locator thể hiện nhiều phần tử: một locator có thể match 0, 1 hoặc N elements. Đây là feature — dùng
.count(),first(),nth(i)để làm việc với list; strict mode sẽ bắt lỗi khi 1 action rơi vào locator match nhiều hơn 1 phần tử.
Cách hoạt động & API chính
- Tạo locator từ Page:
page.locator(selector)— nhận CSS mặc định, hoặc engine prefixcss=,xpath=,text=,id=. Ngoài ra còn bộpage.getBy*()(role, text, label, placeholder, testId...) được khuyến khích dùng trước. - Tạo locator lồng nhau:
locator.locator(...)— thu hẹp phạm vi tìm kiếm trong 1 sub-tree (vd: tìm nút Xoá trong đúng dòng của bảng). - Mọi action/assertion đều auto-wait:
click(),fill(),check(),textContent(), vàexpect(locator).toXxx()đều tự retry cho tới timeout (mặc định 30s cho action, 5s choexpect). - API không chờ được tuỳ ý:
locator.all()trả về mảng locator con;locator.count()đếm số match hiện tại (trả số ngay, không retry);locator.evaluate(fn)chạy JS trong browser trên node đã resolve. - Timeout tuỳ chỉnh: mọi action nhận
{ timeout: ms }; cấu hình chung trongplaywright.config.tsquause: { actionTimeout, navigationTimeout }vàexpect: { timeout }.
Ví dụ & Cú pháp thực tế
- Tạo locator và để Playwright tự chờ:
``
ts import { test, expect } from '@playwright/test'; test('locator resolve muộn, chịu được re-render', async ({ page }) => { await page.goto('/cart'); // Chỉ tạo "công thức tìm" — chưa query DOM const checkoutBtn = page.getByRole('button', { name: 'Thanh toán' }); await page.reload(); // DOM thay đổi hoàn toàn // Lúc này locator mới resolve — bắt node mới, không stale await checkoutBtn.click(); });`` - Locator match nhiều phần tử:
``
ts const rows = page.locator('table#orders tbody tr'); await expect(rows).toHaveCount(5); // assert số dòng await rows.nth(2).getByRole('button', { name: 'Sửa' }).click(); // thao tác dòng thứ 3 const allRows = await rows.all(); // mảng Locator, lặp bình thường for (const row of allRows) { console.log(await row.textContent()); }`` - So sánh tư duy cũ vs mới: Selenium lấy
WebElementgiữ tham chiếu cứng đến node → DOM re-render là chếtStaleElementReference. Locator chỉ giữ selector → re-render thoải mái vẫn bấm được. Người mới hay sai: viếtconst text = await page.locator('h1').textContent()rồi tái dùngtextcho assert sau — sai, phải giữ locator vàexpect(locator).toHaveText(...)để Playwright tự retry.
Best practices & Khi nào dùng
- Luôn thao tác/asser qua locator, không resolve sớm: tránh
await locator.elementHandle()trừ khi bắt buộc — mất hết lợi ích auto-wait và re-resolve. - Tạo locator một lần, dùng nhiều lần: khai báo
const submit = page.getByRole('button', { name: 'Gửi' })rồi click, expect... — vì lazy nên không sợ DOM đổi giữa các lần dùng. - Đặt locator làm property trong Page Object: mỗi POM expose locator (
get submitButton()) thay vì raw selector string — test đọc tự nhiên, sửa selector 1 chỗ. - Dùng expect(locator) thay vì lấy text rồi so sánh:
expect(locator).toHaveText('OK')tự retry cho tới khi text đúng hoặc hết timeout — chống flake khi UI cập nhật async. - Khi cần đếm hoặc chờ danh sách render:
await expect(rows).toHaveCount(n)chờ tới khi đủ n dòng; đừngawait page.waitForSelector(...)rồi mới count.
Gotchas, So sánh & Lưu ý
- Locator không phải Promise:
await locatorkhông nghĩa gì — phảiawait locator.click()/expect(locator). Gõ thiếu action là bug rất hay gặp. locator.count()vàlocator.isVisible()trả kết quả tức thì, không chờ — dùng trongifđể branch logic; nếu muốn chờ thì dùngexpect(locator).toHaveCount(n)/toBeVisible().- So sánh với Selenium
findElement: findElement trả về WebElement trỏ cứng vào node → stale khi DOM đổi; Playwright Locator + auto-wait = bản thiết kế lại đúng cho app hiện đại render liên tục (React/Vue/Angular). - Không nên
.click()trên locator match nhiều phần tử — strict mode sẽ throw; muốn click tất cả thìfor (const el of await locator.all()) await el.click(). - Locator chain dài quá = giòn:
page.locator('div').nth(3).locator('span').nth(1)vỡ khi UI đổi layout nhẹ. Ưu tiên selector có ngữ nghĩa (role/testid) ở mỗi mắt xích.
