Working with selectors
Puppeteer, Playwright and most other UI automation tools reference UI elements through selectors. Becoming proficient in the use of selectors is a hard requirement for writing scripts. An eye for good, solid selectors can make the difference between unstable (or “flaky”) high-maintenace scripts and solid, reliable ones.
This guide aims to get new users started with selectors, and to serve a refresher for more experienced automators. Even though some content will be specific to Puppeteer and Playwright, most principles will apply to most, if not all, UI automation tools.
Different types of selectors
Depending on the tool and the application being tested, different kind of selectors might be available. Puppeteer and Playwright share two main selector types:
In addition, Playwright also supports
- Text selectors, allowing users to select an element based on its text content
- Playwright-specific selectors, which are implemented in Playwright directly (and might be unofficial proposed or pseudo-CSS selectors)
- Chained selectors, in which one selector is run relatively to the previous one(s)
Let’s look at each type of selector a little closer.
Note that Playwright also lets you define your own selector engine.
CSS selectors
Originally used to target HTML elements for applying CSS rules, CSS selectors are the go-to for referencing web UI elements across automation tools. Powerful and flexible, they should enable you to reference most, if not all, elements on the page.
Examples:
#add-to-cart
selects the item with idadd-to-cart
, e.g.<button id="add-to-cart">Buy</button>
.preview
selects the item with classpreview
, e.g.<li data-v-5ad54829="" class="preview">...</li>
.preview .preview-price
selects the item with classpreview-price
within the element with classpreview
[data-test=login-button]
selects the item with attributedata-test
equal tologin-button
, e.g.<button id="btn-login" aria-label="Log in" data-test="login-button">Log in</button>
a.preview-link
selects the item of typea
with classpreview-link
, e.g.<a href="https://example.com/ class="preview-link">Example</a>"
#navbar > .button-cart
selected the item with classbutton-cart
within the item with idnavbar
Note that there might be multiple items corresponding to one selector. Make sure you are referencing the right one in your script.
XPath selectors
XPath was coinceived to reference nodes within an XML document. It can also be used to reference HTML elements, just like CSS. The different ways it can be used to traverse the DOM, as well as its ability to support multiple boolean conditions and reference elements via text content make it a useful backup option for CSS selectors.
Examples:
//button
selects the item of typebutton
, e.g.<button id="btn-signup">Sign up</button>
//*[@id="add-to-cart"]
selects the item of any type with idadd-to-cart
, e.g.<a href ="" id="add-to-cart">Buy</a>
//li/a
selects the item of typea
which is a child of item of typeli
//div[3]
selects the third item of typediv
//button[text()="Submit"]
selects the item of typebutton
with textSubmit
//div[@data-testid="cta" and text()="Configure"]
selects the item of typediv
that has attributedata-testid
equal tocta
and contains textConfigure
Text selectors
Text selectors allow selecting an element via its text content directly. They are Playwright-specific.
Examples:
text=Add
selects the element containing textAdd
,add
or similar (case insensitive)text="Add to cart"
selects the element containing exactly the textAdd to cart
Playwright-specific selectors
Playwright-specific selectors are implemented in Playwright directly, and can fill in the gaps where CSS and XPath selectors might fall short.
Examples:
:nth-match(:text("Details"), 2)
selects the second element containing textDetails
Chained selectors
With Playwright, multiple selectors of different types can be combined to reference elements relative to other elements.
Examples:
css=preview >> text=In stock
selects the item with classpreview
and text contentIn stock
,in stock
or similar (case insensitive)
Finding selectors
There are different ways one can go about finding a selector for one or more UI elements. Let’s take a look at each one.
Looking at the page’s source code
Once you know enough about selectors, looking at a page’s HTML will be enough to start writing your own. For each page you load in your browser, you will be able to see the source code:
Inspecting the page
You can also use your browser’s inspector tool, as found e.g. in the Chrome Dev Tools, to highlight elements on the page and see their attributes.
In the case of the Chrome DevTools, you can also generate different kinds of selector straight from the Elements tab:
Auto-generated selectors can be brittle. Always make sure the selectors you end up deploying in your finished scripts follow best practices.
Recording scripts
If you are looking to generate an entire script and don’t feel like finding selectors for your elements one by one, you can try an automated recording tool, e.g. the open-source Headless Recorder.
A recorder will output a script based on a sequence of page interactions in your browser, complete with auto-generated (in most cases CSS) selectors. You will always want to double check the selectors one by one and potentially tweak them to ensure they follow best practices.
The Playwright Inspector
The Playwright Inspector’s Explore feature can be used to select elements in the browser and generate selectors.
Testing selectors
No matter how a selector is obtained, it is always a good idea to test it out on the target page to make sure it works consistently.
Writing scripts in small increments and running after each new selector is introduced is a good way to quickly spot selectors that either do not work or reference a different element from the one we intended.
We can also test our selectors directly in the browser, before touching our script. In the Chrome DevTools, for example, we are able to test CSS selectors using commands such as
document.querySelector(<selector>)
, or its shorter form$(<selector>)
, which will return the first element matching the specified criteriaquerySelectorAll(<selector>)
, or its shorter form$$(<selector>)
, which will return all the elements matching the specified criteria)
Similarly, we can test XPath selectors using $x(<selector>)
, which will return all the element matching our criteria.
The Playwright-specific selectors can be tested by running the Playwright Inspector, which provisions an accessible playwright
object in the console.
Choosing selectors
The selectors you choose to use in your scripts will help determine how much maintenance work will go into your scripts over the course of their lifetime. Ideally, you want to have robust selectors in place to save yourself time and effort going forward.
The attributes of a good selector are:
- Uniqueness: choose a selector that will identify the target element, and nothing else; IDs are the natural choice, when available.
- Stability: use an attribute that is unlikely to change as the page gets updated lowers the chances that you will need to manually update it.
- Conciseness: prefer short selectors that are easier to read, understand and possibly replace if a script breaks.
Examples of bad selectors
Avoid this kind of selector whenever possible:
.A8SBwf > .RNNXgb > .SDkEP > .a4bIc > .gLFyf
- not concise
- likely not stable: class names are auto-generated, they could change rapidly
.g:nth-child(3) > .rc
- likely not stable: is the third child of
.g
always going to be present? - likely not unique: is it always going to be the right element?
- likely not stable: is the third child of
a[data-v-9a19ef14]
- not stable: attribute is auto-generated and changes between deployments
- likely not unique: is it always going to be the right element?
//div[1]/table[1]/tbody/tr[7]/td/a
- not concise
- likely not stable: reliant on a precise page structure; extremely brittle
text=Continue
- likely not stable: the text might change for multiple reasons (restyling, localisation…)
- likely not unique: is it always going to be the right element?
Examples of (potentially) good selectors
The following might be good selectors:
#elementId
- concise
- unique, as long as the page contains valid HTML
- generally stable
a[data-something=value]
- concise
- unique, as long as
value
is - potentially stable, as long as
value
does not change very often
#overlay.close-button
- concise
- unique, as long as only one element has class
.close-button
- potentially stable, as long as
.close-button
does not change very often
div[@data-testid="cta"]
- concise
- unique, as long as only one element has attribute
data-testid
equal tocta
- potentially stable, as long as
data-testid
is not changed often
Further reading
- W3C CSS spec for CSS selectors.
- CSS selector intro from Mozilla.
- Playwright’s selector documentation
- Using script recorders