1. Locator priority
Prefer locators in this order: getByRole > getByLabel > getByText > getByTestId > CSS > XPath.
Why this order? Higher-priority locators mirror what assistive technologies see, so they're resilient to design changes and describe what a real user can perceive.
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByLabel('Email').fill('qa@example.com');
await page.getByText('Welcome back').waitFor();2. Why user-facing locators?
User-facing locators survive design changes, CSS refactors, and class renames because they depend on accessible names, labels, and visible text instead of implementation details.
3. CSS selectors
CSS selectors are useful when the page has no strong accessible signal. Keep them short and prefer stable attributes over styling classes.
page.locator('.btn-primary');
page.locator('#username');
page.locator("[data-test='login']");4. XPath
Use XPath only when no other option works. XPath is brittle, harder to read, and easier to break when markup changes.
page.locator("xpath=//button[contains(., 'Login')]");5. Chaining & filtering
Chain locators and filter by text to target one element inside repeated UI, such as rows, cards, or menu items.
const adminRow = page.getByRole('row').filter({ hasText: 'Admin' });
await adminRow.getByRole('button', { name: 'Edit' }).click();6. Hands-on task
Write 5 locators for a sample login page: email field, password field, remember-me checkbox, sign-in button, and forgot-password link.
await page.getByLabel('Email').fill('qa@example.com');
await page.getByLabel('Password').fill('secret');
await page.getByRole('checkbox', { name: 'Remember me' }).check();
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('link', { name: 'Forgot password?' }).click();7. What's next
Next, move into Module 04: Assertions & matchers so your tests verify outcomes with clear, reliable expectations.
Up next in the learning path
Module 04: Assertions & matchers