- Tác giả
- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
Tìm hiểu Playwright Locator - Hướng dẫn chi tiết và Best Practices
Giới thiệu
Trong bài trước, chúng ta đã học cách sử dụng Playwright Code Generator để tự động sinh mã test. Tuy nhiên, để trở thành một test automation engineer giỏi, bạn cần hiểu sâu về Locators - cách xác định và tương tác với các elements trên trang web.
Playwright Locator là một trong những tính năng mạnh mẽ nhất của Playwright, cho phép chúng ta tìm và tương tác với các phần tử trên trang web một cách hiệu quả và đáng tin cậy. Locator là trung tâm của cơ chế auto-waiting và retry-ability trong Playwright, giúp cho việc viết test case trở nên dễ dàng và đáng tin cậy hơn.
Locator là gì?
Locator là một đối tượng trong Playwright được sử dụng để tìm và tương tác với các phần tử trên trang web. Nó cung cấp một cách tiếp cận mạnh mẽ và linh hoạt để xác định vị trí các phần tử, giúp cho việc viết test case trở nên dễ dàng và đáng tin cậy hơn.
Locator trong Playwright
Khác với các công cụ automation khác, Playwright Locators có những đặc điểm độc đáo:
- Auto-waiting: Tự động chờ element xuất hiện trước khi thực hiện action
- Retry-ability: Tự động retry khi element chưa sẵn sàng
- Strict mode: Đảm bảo chỉ tìm thấy duy nhất một element
- Lazy evaluation: Chỉ tìm kiếm element khi thực sự cần thiết
Các loại Locator phổ biến
1. Role-based Locators (Khuyến nghị sử dụng)
Role-based locators dựa trên accessibility roles, giúp test gần gũi hơn với cách người dùng thực sự tương tác:
<!-- HTML Example -->
<button type="submit">Submit</button>
<input type="text" aria-label="What needs to be done?" />
<a href="/login">Sign in</a>
<h1>Todo List</h1>
// Tìm phần tử bằng role
// Tìm button theo role
await page.getByRole('button', { name: 'Submit' }).click();
// Tìm link theo role và name
await page.getByRole('link', { name: 'Sign in' }).click();
// Tìm textbox theo role
await page.getByRole('textbox', { name: 'What needs to be done?' }).fill('Learn Locators');
// Tìm heading theo role
await expect(page.getByRole('heading', { name: 'Todo List' })).toBeVisible();
// Tìm checkbox theo role
await page.getByRole('checkbox').first().check();
Các role phổ biến:
button
,link
,textbox
,checkbox
,radio
heading
,list
,listitem
,table
,cell
dialog
,alert
,menu
,menuitem
2. Text-based Locators
Tìm kiếm elements dựa trên text content:
<!-- HTML Example -->
<button>Submit</button>
<span>Click me</span>
<div>Welcome to our website</div>
// Tìm phần tử bằng text content
// Tìm theo text chính xác
await page.getByText('Submit').click();
// Tìm theo text một phần (case-insensitive)
await page.getByText('click me', { exact: false }).click();
// Tìm theo regex pattern
await page.getByText(/^Click \d+$/).click();
await page.getByText('Welcome to our website').isVisible();
3. Label-based Locators
Tìm kiếm form elements dựa trên labels:
<!-- HTML Example -->
<label for="username">Username</label>
<input id="username" type="text" />
<label for="password">Password</label>
<input id="password" type="password" />
// Tìm phần tử bằng label
await page.getByLabel('Username').fill('testuser');
await page.getByLabel('Password').fill('password123');
4. Placeholder-based Locators
Tìm kiếm input elements dựa trên placeholder text:
<!-- HTML Example -->
<input type="text" placeholder="What needs to be done?" />
<input type="search" placeholder="Search todos..." />
// Tìm theo placeholder
await page.getByPlaceholder('What needs to be done?').fill('New task');
await page.getByPlaceholder('Search todos...').fill('important');
4. Test ID Locators (Khuyến nghị cho testing/automation)
Sử dụng data-testid
attributes - phương pháp tốt nhất cho test automation:
<!-- HTML Example -->
<button data-testid="submit-button">Submit</button>
<input data-testid="username-input" type="text" />
<div data-testid="error-message">Invalid credentials</div>
// Tìm phần tử bằng data-testid
await page.getByTestId('submit-button').click();
await page.getByTestId('username-input').fill('testuser');
await page.getByTestId('error-message').isVisible();
6. CSS Selectors
Sử dụng CSS selectors truyền thống khi cần thiết:
// Class selector
await page.locator('.todo-input').fill('CSS Selector Test');
// ID selector
await page.locator('#add-button').click();
// Attribute selector
await page.locator('[data-automation="todo-item"]').first().click();
// Pseudo-selectors
await page.locator('li:first-child').click();
await page.locator('button:has-text("Delete")').click();
7. XPath Selectors
Sử dụng XPath khi cần logic phức tạp:
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// XPath với text content
await page.locator('//button[text()="Add"]').click();
// XPath với contains
await page.locator('//div[contains(@class, "todo-item")]').first().click();
// XPath với multiple conditions
await page.locator('//input[@type="text" and @placeholder="What needs to be done?"]').fill('XPath Test');
Locator Chaining và Filtering
Playwright cho phép kết hợp và lọc locators để tạo ra selections chính xác hơn:
1. Locator Chaining
test('Locator chaining example', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Chain locators
await page
.locator('.todo-list')
.locator('.todo-item')
.first()
.locator('button')
.click();
// Equivalent shorter syntax
await page.locator('.todo-list .todo-item:first-child button').click();
});
2. Filtering Locators
Filtering Locators cho phép chúng ta tìm kiếm các phần tử dựa trên các điều kiện cụ thể:
test('Filtering locators example', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Filter by text content
const todoWithText = page.locator('.todo-item').filter({ hasText: 'Learn Playwright' });
await todoWithText.getByRole('checkbox').check();
// Filter by another locator
const todoWithButton = page.locator('.todo-item').filter({
has: page.getByRole('button', { name: 'Edit' })
});
await todoWithButton.click();
// Filter by not having text
const todoWithoutCompletedText = page.locator('.todo-item').filter({
hasNotText: 'Completed'
});
await expect(todoWithoutCompletedText).toHaveCount(3);
});
Best Practices khi sử dụng Locator
1. Ưu tiên cao nhất sử dụng Test ID
- Thêm
data-testid
vào các phần tử cần test - Giúp test case ổn định và dễ bảo trì
- Không bị ảnh hưởng bởi thay đổi UI
<!-- Trong HTML -->
<button data-testid="submit-button">Submit</button>
// Trong test
await page.getByTestId('submit-button').click();
2. Ưu tiên sử dụng Role và Label
- Sử dụng
getByRole()
vàgetByLabel()
giúp test case dễ đọc và bảo trì hơn - Ít bị ảnh hưởng bởi thay đổi UI
- Tuân thủ các tiêu chuẩn accessibility
// 👍 Tốt
await page.getByRole('button', { name: 'Submit' }).click();
// 👎 Không nên
await page.locator('button.buttonIcon.episode-actions-later').click();
3. Kết hợp và lọc Locator
<!-- HTML Example -->
<ul>
<li>Product 1</li>
<li>Product 2 <button>Add to cart</button></li>
<li>Product 3</li>
</ul>
// Kết hợp nhiều điều kiện
const button = page.getByRole('button')
.and(page.getByTitle('Subscribe'));
// Lọc theo text
const product = page.getByRole('listitem')
.filter({ hasText: 'Product 2' });
// Xử lý nhiều phần tử
for (const li of await page.getByRole('listitem').all()) {
await li.click();
}
// Kết hợp lọc và tìm kiếm
await page
.getByRole('listitem')
.filter({ hasText: 'Product 2' })
.getByRole('button', { name: 'Add to cart' })
.click();
4. Tránh sử dụng CSS Selector và XPath
- CSS Selector và XPath dễ bị break khi UI thay đổi
- Khó đọc và bảo trì
- Không phản ánh trải nghiệm người dùng
// 👎 Không nên
await page.locator('div.container > button.submit-btn').click();
await page.locator('//button[contains(@class, "submit")]').click();
// 👍 Nên dùng
await page.getByRole('button', { name: 'Submit' }).click();
Sử dụng Playwright Inspector để tạo Locator
Playwright cung cấp công cụ codegen để tự động tạo locator tốt nhất cho bạn. Công cụ này sẽ:
- Tự động tìm locator tốt nhất dựa trên role, text và test id
- Cải thiện locator nếu tìm thấy nhiều phần tử phù hợp
- Giúp bạn không phải lo lắng về việc test bị fail do locator
Cách sử dụng codegen
npx playwright codegen https://dummy-demo-njndex.web.app/
Sử dụng VS Code Extension
- Cài đặt Playwright extension cho VS Code
- Sử dụng tính năng "Pick Locator" để tìm locator
- Debug test trực tiếp trong VS Code
Xử lý các trường hợp đặc biệt
1. Chờ phần tử xuất hiện
// Chờ phần tử xuất hiện trước khi tương tác
await page.getByRole('button').waitFor();
2. Xử lý nhiều phần tử
// Lấy tất cả các phần tử
const buttons = await page.getByRole('button').all();
for (const button of buttons) {
await button.click();
}
3. Lọc phần tử theo điều kiện
// Lọc phần tử theo text
const visibleButton = await page.getByRole('button')
.filter({ hasText: 'Submit' });
// Lọc phần tử theo locator khác
const button = page.getByRole('button')
.and(page.getByTitle('Subscribe'));
Locator Assertions - Kiểm tra trạng thái phần tử
await expect(page.getByRole('heading', { name: 'Todo List' })).toBeVisible();
await expect(page.getByTestId('error-message')).toHaveText('Tài khoản không hợp lệ');
Bạn có thể dùng locator kết hợp với expect() để kiểm tra visibility, text, state, v.v.
Tái sử dụng Locator với biến
const todoInput = page.getByPlaceholder('Add a new task');
await todoInput.fill('Học Playwright');
await todoInput.press('Enter');
Đặt locator vào biến giúp:
- Dễ đọc
- Dễ tái sử dụng
- Dễ maintain nếu UI thay đổi
Kết luận
Playwright Locator là một công cụ mạnh mẽ giúp chúng ta viết các test case hiệu quả và đáng tin cậy. Bằng cách tuân thủ các best practices như:
- Ưu tiên sử dụng Role và Label
- Sử dụng Test ID cho các phần tử phức tạp
- Tránh sử dụng CSS Selector và XPath
- Kết hợp và lọc Locator một cách hiệu quả
- Sử dụng Playwright Inspector để tạo locator
Chúng ta có thể tạo ra các test case dễ bảo trì và ít bị ảnh hưởng bởi thay đổi UI.