Selectors are at the heart of any UI automation, and in Playwright they are the main way you tell tests which elements to interact with. Poorly designed selectors lead to brittle tests that break whenever the UI changes slightly. Strong selector practices keep your suite stable and easier to debug.
Selector Fundamentals in Playwright
Playwright supports multiple selector engines, including CSS, text, role-based selectors, and more. The recommended approach is to prefer semantic and resilient selectors over brittle ones like long CSS chains or XPaths tied to layout. This improves readability and reduces coupling to implementation details.
// selector-fundamentals.spec.ts
import { test, expect } from '@playwright/test';
test('login form uses robust selectors', async ({ page }) => {
await page.goto('https://demo.myshop.com/login');
// Prefer role + name over raw CSS ids when possible.
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByRole('textbox', { name: 'Password' }).fill('SuperSecret123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Welcome back')).toBeVisible();
});
nth(0) or “the third button” unless there is a stable ordering guarantee.Good selector design starts from the HTML: if the application exposes useful labels, ARIA roles, and stable attributes, your tests can be simpler. Collaborating with developers to add test-friendly attributes can pay off quickly.
Common Mistakes
Mistake 1 โ Copying long, layout-driven CSS from the browser devtools
This is very brittle.
โ Wrong: Using selectors like div:nth-child(3) > span > button that break on small layout changes.
โ Correct: Use semantic selectors such as roles, labels, or custom data attributes.
Mistake 2 โ Ignoring accessibility information in selectors
This wastes a useful signal.
โ Wrong: Selecting elements only by class names when there is a clear label or role.
โ
Correct: Prefer getByRole, getByLabel, and getByText where they make intent obvious.