Recommended Free Tools
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.
#1 Best Overall
// 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.
Rank #2
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.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →// 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-testidor 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.
Rank #4
// 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
- Start with the user’s view. Try a role, label, text, or another user-facing locator when it states the target clearly.
- Use CSS for a real structural need. Prefer a concise tag, attribute, or stable test-hook selector over a long path through incidental wrappers.
- Add only useful narrowing. Use visibility, text, containment, or an ancestor scope when it makes the intended target clearer.
- Check the match count. Use
count()or an assertion such astoHaveCount(1)when uniqueness matters to the test. - Choose position deliberately. Use
first(),last(), ornth()only if the order is part of what the test is verifying.
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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsA 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.
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.
Quick Recap
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.




