← Back to Tutorials

7. Smart Locators

Playwright recommends using accessible locators that mimic how users interact with your app. These are more resilient than CSS/XPath.

GetByRole

await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('link', { name: 'Home' }).click();
await page.getByRole('heading', { name: 'Dashboard' });
await page.getByRole('textbox', { name: 'Email' }).fill('test@test.com');
await page.getByRole('checkbox', { name: 'Agree' }).check();

GetByLabel

// Great for form inputs with <label>
await page.getByLabel('First Name').fill('John');
await page.getByLabel('Email address').fill('john@example.com');
await page.getByLabel('Password').fill('secret');

GetByText

// Match by text content (partial or exact)
await page.getByText('Welcome back').click();
await page.getByText('Logout', { exact: true }).click();

GetByPlaceholder & GetByTestId

await page.getByPlaceholder('Search products...').fill('laptop');
await page.getByTestId('add-to-cart').click();

// data-testid attributes in your HTML:
// <button data-testid="add-to-cart">Add</button>

Calendars / Date Pickers

// Fill a date input directly
await page.getByLabel('Date of birth').fill('1990-01-15');

// For custom calendar widgets
await page.getByRole('button', { name: 'Open calendar' }).click();
await page.getByRole('gridcell', { name: '15' }).click();

Locator Chaining & Filtering

// Chain locators
await page.locator('.form').getByRole('button', { name: 'Submit' }).click();

// Filter
await page.locator('.product').filter({ hasText: 'Laptop' }).getByRole('button').click();
await page.locator('li').filter({ hasNotText: 'Out of stock' });
✏️ Exercise: Refactor a test that uses CSS selectors to use getByRole, getByLabel, and getByTestId instead. Run it and verify it still passes.