CSS Selectors 101
Follow this guide for a brief overview of CSS selectors in testing, their syntax and use cases.
Last updated
Was this helpful?
Follow this guide for a brief overview of CSS selectors in testing, their syntax and use cases.
Last updated
Was this helpful?
CSS barely needs an introduction. In web development it's used for selecting and styling HTML elements. In this guide we will only be discussing the usage of CSS selectors in the testing realm.
Every element that you interact with in an automated test (whether it’s clicking, typing, asserting, etc.) needs to be uniquely identified so that it can be located during the test run. CSS selectors allow automated testing frameworks to do just that. In addition to targeting an element, they can also be used as assertions to verify an element's state or behavior.
As versatile and robust as they are, CSS selectors are at the same time really easy to learn and read. The four most common selector types in practice are type, .class, #id, and [attribute] selectors.
input
selects all <input> elements
.typography5
selects all elements that have typography5 class
#submit-btn
selects all elements that have id with the value of 'submit-btn'
[type="password"]
selects all elements that have type attribute with the value of 'password'
Type selector can be replaced with a universal * one that matches every element type on the page. It's a wildcard that is too broad and greedy on its own but can be useful when combined with other selectors in certain edge-cases. For example when you don't care about the element type or don't know anything about the element but its position.
It's not uncommon that elements don't have any unique identifiers or attributes, which makes targeting them harder. This is when we need to explore compound and complex selectors. A compound selector combines multiple predicates of a single element, which makes for a more precise and refined selection.
input[type="password"]
selects all <input> elements that have type attribute with the value of 'password'
button#confirm
selects all <button> elements that have id with the value of 'confirm'
.prim-color#submit
selects all elements that have prim-color class and id with the value of 'submit'
.secondary[data-testid="affiliateLink"]
selects all elements that have secondary class and data-testid attribute with the value of 'affiliateLink'
However, narrowing selectors can also lead to tests becoming more brittle towards DOM changes. So it is recommended to prioritize stable and predictable attributes like data-testid
over dynamic, auto-generated or position-based ones.
While compound selectors use multiple attributes for enhanced precision, complex selectors include combinators and are based on the relationships between elements or DOM hierarchy. Such selectors can also be called positional and are generally not recommended as they can be susceptible to breakage when DOM changes get introduced. However there are cases where we still need them. It may have to do with the lack of unique attributes of the targeted element or the layout itself may be the object of verification.
The four main types of combinators are:
descendant combinator A B
- selects descendant B (children, grandchildren, etc.) of parent A
child combinator A > B
- selects direct descendant B (children, NOT grandchildren and further) of parent A
next-sibling combinator A + B
- selects element B that immediately follows its sibling A
sibling combinator A ~ B
- selects element B that follows its sibling A (not necessarily immediately)
Some typical examples:
li a
selects all <a> elements that are descendants of the <li> element
footer > #footer-controls
selects all direct descendants (children) of the <footer> element that have id with the value of 'footer-controls'
li + li[data-testid="ecommerceLink"]
selects all <li> elements that have data-testid attribute with the value of 'ecommerceLink' and which immediately follow another <li> element (sibling)
div ~ button#confirmBtn
selects all <button> elements that have id with the value of 'confirmBtn' and which follow <div> element (sibling)
To summarize, a simple selector targets an element by one of its properties #confirm
A compound selector uses multiple properties of an element for more precision button#confirm
A complex selector puts elements in the context of relationships #checkout-modal > button#confirm
CSS also possesses a powerful toolkit of attribute selector operators for crafting selectors that can match a portion of an attribute. For the majority of cases in testing we will only need these four:
(equals) = - matches the attribute exactly
(contains) *= - matches a part of the attribute, regardless of its position
(starts with) ^= - matches a part of the attribute at the start of the value
(ends with) $= - matches a part of the attribute at the end of the value
Partial match selectors are incredibly useful for targeting elements when you don’t know the exact value but want to match based on patterns — something that can be very handy in numerous automated testing scenarios. For instance, imagine we need to select an <img> element that has just been inserted into DOM and [src] is its only predictable attribute, but it gets an auto-generated suffix attached at the end, e.g., <img src="shop-item-bcuk8j002.jpg">
. Since the auto-generated portion can't be predicted, we can use the (starts with) ^= operator to target this element by the part that is constant.
Here are some more examples:
[alt*="cart-image"]
selects all elements that have substring 'cart-image' in their alt attribute
[for*="socials-link"]
selects all elements that have substring 'socials-link' in their for attribute
button[id$="-mui-14"]
selects all <button> elements that have IDs ending with substring '-mui-14'
a[href^=https://docs"]
selects all <a> elements that have href attribute starting with substring 'https://docs'
Even though class can be used with this notation [class*="btn-primary"]
, simply listing dot-separated classes does the same thing and may be easier on the eyes.