Skip to main content

Selectors & Actions

The core of every E2E test is finding elements on the page and interacting with them. Playwright provides a powerful locator system that prioritizes accessibility and resilience over brittle CSS selectors.

Locator Strategies

Playwright recommends locators that reflect how users and assistive technology see the page. These locators are resilient to implementation changes — they survive refactors that change class names or DOM structure.

getByRole — The Preferred Approach

The getByRole locator finds elements by their ARIA role, which is how screen readers identify elements:

// Find a button with the text "Submit"
page.getByRole('button', { name: 'Submit' });

// Find a text input labeled "Email"
page.getByRole('textbox', { name: 'Email' });

// Find a navigation landmark
page.getByRole('navigation');

// Find a heading
page.getByRole('heading', { name: 'Welcome', level: 1 });

getByText — Match Visible Text

When role-based selection is not specific enough, match by visible text:

// Exact match
page.getByText('Sign up for free', { exact: true });

// Partial match (default)
page.getByText('Sign up');

// Regex match
page.getByText(/sign up/i);

getByTestId — The Escape Hatch

When semantic selectors do not work, use data-testid attributes. Add them to your markup specifically for testing:

<div data-testid="user-profile-card">...</div>
page.getByTestId('user-profile-card');

This is a stable contract between your code and your tests. Unlike class names, test IDs do not change during styling updates.

Other Locators

Playwright offers several more locators for specific scenarios:

// By label text (for form fields)
page.getByLabel('Password');

// By placeholder text
page.getByPlaceholder('Search...');

// By alt text (for images)
page.getByAltText('Company logo');

// CSS selector (use sparingly)
page.locator('.sidebar >> .menu-item');

Performing Actions

Once you have a locator, you can interact with the element. Playwright auto-waits for elements to be visible and actionable before performing any action.

Click

await page.getByRole('button', { name: 'Save' }).click();

// Double click
await page.getByText('Edit title').dblclick();

// Right click
await page.getByText('File').click({ button: 'right' });

Type and Fill

// fill() clears the field first, then types
await page.getByLabel('Email').fill('user@example.com');

// type() simulates key-by-key input (useful for autocomplete)
await page.getByLabel('Search').pressSequentially('playwright', { delay: 50 });

Select Dropdowns

// By value
await page.getByLabel('Country').selectOption('us');

// By visible text
await page.getByLabel('Country').selectOption({ label: 'United States' });

Checkboxes and Radio Buttons

await page.getByLabel('Accept terms').check();
await page.getByLabel('Accept terms').uncheck();

// Verify state
await expect(page.getByLabel('Accept terms')).toBeChecked();

Auto-Waiting

Playwright automatically waits for elements before acting on them. When you call click(), Playwright waits until the element is:

  1. Attached to the DOM
  2. Visible
  3. Stable (not animating)
  4. Enabled (not disabled)
  5. Not obscured by another element

This eliminates the need for manual sleep() or waitFor() calls that plague Selenium tests. If the element does not become actionable within the timeout, the test fails with a clear error message.

Chaining Locators

You can scope locators to a specific section of the page:

const sidebar = page.locator('.sidebar');
await sidebar.getByRole('link', { name: 'Settings' }).click();

This is useful when the same text or role appears multiple times on the page. By narrowing the scope first, you avoid ambiguity.

Key Takeaways

  • Prefer getByRole for resilient, accessible selectors.
  • Use getByTestId as a stable escape hatch when semantic selectors fail.
  • Playwright auto-waits — do not add manual sleeps.
  • Use fill() for form inputs and click() for buttons and links.
  • Chain locators to narrow scope when elements are ambiguous.