October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
CSS selectors

How to Find Elements by CSS Selectors in Playwright

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use page.locator('selector') to find an element with CSS in Playwright; adding css= is optional. For example, await page.locator('button').click() locates a button and clicks it. Playwright resolves locators when an action runs and retries while waiting, so the locator can work with the current page after a re-render. The main challenge is choosing a selector that identifies the intended element—and only that element.

Find an element with a CSS selector

In Playwright, call page.locator() with a CSS selector. Playwright auto-detects CSS when you omit the prefix; use css= when you want to make the selector type explicit, for example in code that also uses XPath. Playwright’s locator documentation describes the syntax and locator behavior.

await page.locator('css=button').click();
await page.locator('button').click();
await page.locator('xpath=//button').click();

A locator describes how to find an element; it is not a fixed reference captured when the line runs. Playwright resolves it when an action runs, and locators are central to its auto-waiting and retry behavior. This helps when a page updates or re-renders before the action can complete.

Write common CSS selectors

Pass familiar CSS syntax to locator(). The examples below use a page that contains the corresponding elements.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// By tag
await page.locator('button').click();

// By class
await page.locator('.submit-button').click();

// By ID
await page.locator('#login').fill('[email protected]');

// By attribute
await page.locator('input[name="email"]').fill('[email protected]');

// By a descendant's relationship to its ancestors
await page.locator('form#login input[type="password"]').fill('secret');

// By direct parent-child relationship
await page.locator('nav > a').first().click();

Tag, class, and ID

A tag selector such as button matches elements by HTML tag. A class selector starts with a dot, as in .submit-button; an ID selector starts with #, as in #login. Class names and page structure often reflect implementation or styling choices, so they may change when the interface is redesigned.

Attributes and relationships

Attribute selectors can target a deliberate property: input[name="email"] matches an input whose name attribute is email. A space between selectors expresses a descendant relationship, so form#login input[type="password"] finds a password input nested somewhere inside the form. The child combinator > requires a direct parent-child relationship: nav > a matches links that are direct children of a navigation element.

Use Playwright’s CSS extensions when they improve precision

Playwright extends CSS with selectors that can express visibility, text, containment, alternatives, or a match’s position. The official Other locators guide documents these extensions. Keep the final locator understandable and ensure it identifies the intended target.

// Visible buttons only
await page.locator('button:visible').click();

// An article containing the text
await page.locator('article:has-text("Playwright")').click();

// A section containing a button, then that button
await page.locator('section:has(button)').locator('button').click();

// A button matching either class
await page.locator('button:is(.primary, .confirm)').click();

// The third matching button
await page.locator(':nth-match(button, 3)').click();

Visibility and text

:visible narrows a match to visible elements. :has-text("Playwright") narrows a match based on contained text. These can help express intent, but a broad text condition may still match multiple elements; check the complete locator rather than assuming the extension makes it unique.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Containment and alternatives

:has() lets a selector require a matching descendant, as in section:has(button). :is() groups alternative selectors, as in button:is(.primary, .confirm). They are useful for narrowing a selector, but nested selector chains can become difficult to maintain if they encode every detail of the current DOM.

Position

:nth-match(button, 3) selects the third matching element using Playwright’s extension. Positional selection is appropriate when order is part of the page’s intended contract; it is fragile when the order can change.

Open shadow DOM

Playwright CSS selectors pierce open shadow DOM, according to its locator guide. This does not mean a selector can access every encapsulated or closed component: the documented behavior is for open shadow roots.

Choose CSS or a user-facing locator

CSS is useful when the structure or an agreed attribute is the contract you want to test. For interactive controls, Playwright recommends considering user-facing locators such as getByRole(), getByText(), getByLabel(), getByPlaceholder(), getByAltText(), getByTitle(), and getByTestId(). They can make tests more resilient when classes, nesting, or visual layout change.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// Locate by the role and accessible name
await page.getByRole('button', { name: 'Sign in' }).click();

// Use CSS when this test ID is an intentional contract
await page.locator('[data-testid="sign-in"]').click();
  • Meaning: A role or label describes how a user encounters a control; CSS describes its structure or attributes.
  • Resilience: Semantic locators can survive styling and layout changes that break class-based selectors or long descendant chains.
  • Uniqueness: Whichever locator you choose, a single-target action should identify the intended element uniquely.
  • Contract ownership: A data-testid or another explicitly agreed attribute can provide a stable team-owned test hook.

Prefer a short selector tied to an intentional contract over a chain that mirrors every wrapper in the current markup. Use a semantic locator when it better expresses the user-visible purpose.

Handle multiple matches and strictness

Single-target actions such as click() are strict: if the locator matches several elements, Playwright throws a strictness violation rather than guessing which one you meant. Multi-element operations such as count() can work with a locator that matches several elements.

const buttons = page.locator('button');
await expect(buttons).toHaveCount(3);
await buttons.nth(1).click();

The example assumes that the test has imported Playwright’s expect and that three buttons are expected. nth(1) chooses the second match because indexes are zero-based. Use a position only when that position is the intended contract; after page changes, the same position can refer to a different button.

Narrow the locator before selecting by position

When possible, add context that distinguishes the intended element instead of using first(), last(), or nth() to silence ambiguity.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// Scope the submit button to the checkout form
await page.locator('form#checkout button[type="submit"]').click();

// Find the action button inside the list item for Mary
await page.locator('li')
  .filter({ hasText: 'Mary' })
  .getByRole('button', { name: 'Say hello' })
  .click();

Use first(), last(), or nth() when order itself matters and is maintained deliberately—not merely because several elements happened to match in today’s markup.

Build a maintainable selector step by step

  1. Start with the user’s view. Try a role, label, text, or another user-facing locator when it states the target clearly.
  2. Use CSS for a real structural need. Prefer a concise tag, attribute, or stable test-hook selector over a long path through incidental wrappers.
  3. Add only useful narrowing. Use visibility, text, containment, or an ancestor scope when it makes the intended target clearer.
  4. Check the match count. Use count() or an assertion such as toHaveCount(1) when uniqueness matters to the test.
  5. Choose position deliberately. Use first(), last(), or nth() only if the order is part of what the test is verifying.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot CSS locator failures

A click fails with a strictness violation

Cause: The locator matched more than one element, while click() requires one target. Fix: Scope the selector to a form, row, or other meaningful container; add a stable attribute; or use a user-facing locator that distinguishes the control. Check the match count before choosing a positional match.

The locator finds no element

Cause: The selector may not match the rendered markup, may depend on a class that changed, or may be too narrowly scoped. Fix: Check the tag, spelling, attributes, relationship combinators, and container. If visibility is required, confirm that the target is actually visible rather than removing :visible without understanding the page state.

The test acts on the wrong matching element

Cause: A broad selector or positional choice identifies a different match than intended. Fix: Add a meaningful scope or attribute, or switch to a role or label locator with a distinguishing name. If order is the actual requirement, make that explicit in the test.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A selector breaks after a redesign

Cause: The test relied on styling classes, nesting, or another incidental detail that changed. Fix: Prefer a user-facing locator or coordinate with the application team on a stable test attribute. Avoid encoding every current wrapper in a descendant chain.

A selector does not reach into a component

Cause: The relevant content may not be in an open shadow root; the documented CSS piercing behavior applies to open shadow DOM. Fix: Check how the component exposes its content and whether the target is accessible through a supported locator. Do not assume that CSS access to open roots implies access to closed roots.

Or skip the browser setup

If your goal is a screenshot rather than an interaction test, ScreenshotNeo can capture a page through one GET request. It is a screenshot API and MCP server for developers; it does not replace Playwright for locating and interacting with page elements in a test.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request options and response details. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before a shot; each removal step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Responses identify page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. Sign up free for 1,000 screenshots a month, no card required.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I omit the css= prefix in Playwright?

Yes. Playwright auto-detects CSS for a selector passed to page.locator(); use css= when an explicit selector type improves clarity.

Does Playwright CSS work with open shadow DOM?

Yes. Playwright’s CSS selectors pierce open shadow DOM; that documented behavior does not establish access to closed shadow roots.

When should I use nth() instead of narrowing a locator?

Use nth() when the element’s position is deliberately part of the test contract. Otherwise, narrow by context or a distinguishing attribute so the locator describes the intended element.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.