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:

  1. CSS selectors
  2. XPath selectors

In addition, Playwright also supports

  1. Text selectors, allowing users to select an element based on its text content
  2. Playwright-specific selectors, which are implemented in Playwright directly (and might be unofficial proposed or pseudo-CSS selectors)
  3. 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:

  1. #add-to-cart selects the item with id add-to-cart, e.g. <button id="add-to-cart">Buy</button>
  2. .preview selects the item with class preview, e.g. <li data-v-5ad54829="" class="preview">...</li>
  3. .preview .preview-price selects the item with class preview-price within the element with class preview
  4. [data-test=login-button] selects the item with attribute data-test equal to login-button, e.g. <button id="btn-login" aria-label="Log in" data-test="login-button">Log in</button>
  5. a.preview-link selects the item of type a with class preview-link, e.g. <a href="https://example.com/ class="preview-link">Example</a>"
  6. #navbar > .button-cart selected the item with class button-cart within the item with id navbar

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:

  1. //button selects the item of type button, e.g. <button id="btn-signup">Sign up</button>
  2. //*[@id="add-to-cart"] selects the item of any type with id add-to-cart, e.g. <a href ="" id="add-to-cart">Buy</a>
  3. //li/a selects the item of type a which is a child of item of type li
  4. //div[3] selects the third item of type div
  5. //button[text()="Submit"] selects the item of type button with text Submit
  6. //div[@data-testid="cta" and text()="Configure"] selects the item of type div that has attribute data-testid equal to cta and contains text Configure

Text selectors

Text selectors allow selecting an element via its text content directly. They are Playwright-specific.

Examples:

  1. text=Add selects the element containing text Add, add or similar (case insensitive)
  2. text="Add to cart" selects the element containing exactly the text Add 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:

  1. :nth-match(:text("Details"), 2) selects the second element containing text Details

Chained selectors

With Playwright, multiple selectors of different types can be combined to reference elements relative to other elements.

Examples:

  1. css=preview >> text=In stock selects the item with class preview and text content In 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:

selectors from page source

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.

getting selectors via the browser inspector

In the case of the Chrome DevTools, you can also generate different kinds of selector straight from the Elements tab:

generate selectors in devtools

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.

record selectors with 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.

playwright inspector explore feature

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

  1. document.querySelector(<selector>), or its shorter form $(<selector>), which will return the first element matching the specified criteria
  2. querySelectorAll(<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.

playwright inspector object in 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:

  1. .A8SBwf > .RNNXgb > .SDkEP > .a4bIc > .gLFyf
    • not concise
    • likely not stable: class names are auto-generated, they could change rapidly
  2. .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?
  3. 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?
  4. //div[1]/table[1]/tbody/tr[7]/td/a
    • not concise
    • likely not stable: reliant on a precise page structure; extremely brittle
  5. 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:

  1. #elementId
    • concise
    • unique, as long as the page contains valid HTML
    • generally stable
  2. a[data-something=value]
    • concise
    • unique, as long as value is
    • potentially stable, as long as value does not change very often
  3. #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
  4. div[@data-testid="cta"]
    • concise
    • unique, as long as only one element has attribute data-testid equal to cta
    • potentially stable, as long as data-testid is not changed often

Further reading

  1. W3C CSS spec for CSS selectors.
  2. CSS selector intro from Mozilla.
  3. Playwright’s selector documentation
  4. Using script recorders

Last updated on April 26, 2024. You can contribute to this documentation by editing this page on Github