Targeting using element tags
By element tag, I mean input for a field, a for a link, or h1 for a header. This is called an element selector or type selector, since it uses the element name directly. Being explicit about the tag can improve clarity for teammates, even if it doesn’t change what the selector selects elements for.
These can be placed at the front of your selector, like so a[href="https://ghostinspector.com/docs/"]. It’s rare to use just the element tag as the selector, but it could make sense in some situations. For instance, suppose your page has a single <h1>…</h1> element and you want to assert the contents. If you don’t expect any more <h1> elements to be added, then sure, use h1 as the selector.
You may also have some kind of scenario like in this (contrived) example:
<div class="username">Justin</div>
<span class="username">Justin</span>
In this case, using .username will match both elements, since both have that class. You could use div.username or span.username to pin it down to the one you want to target.
Ok, so beyond those simple scenarios, when is this useful?
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler
In most cases, the usefulness of adding this has less to do with the targeting process and more to do with conveying clarity to anyone looking at your selectors. For example, consider this selector: [name="level"]. Now, if there’s only one element with the name level on the page then this selector is perfectly adequate for targeting purposes. But, if I’m a new engineer looking at that selector, I don’t know whether it’s targeting an <input> field, a <select> box, a <textarea>, or something else entirely… Personally, I like the idea of seeing select[name="level"], not because it necessarily helps with targeting, but because it tells me something useful about the element (and probably the action being performed).
You could make a case that this makes the selector slightly more fragile… Perhaps an <input> field that you’re targeting gets changed to a <textarea> in an application update, so the selector breaks because you’ve used input[name="address"] and now it needs to be updated to textarea[name="address"]. That’s a fair argument… so I’d say to use your own judgment and preferences here. I like to include the tag when I feel like it conveys something useful to people working with the test. In my case, that’s typically <input>, <textarea>, <select>, <button>, <a> and occasionally a few others depending on the situation, like <li>.
Targeting using hierarchy
Hierarchy allows you to match based on nesting. A space indicates a descendant selector, meaning the element is found somewhere inside the parent element.
When you want just the immediate child, you can use a child selector with >.
There’s also the adjacent sibling selector + which matches an element that comes right after another at the same level.
Hierarchy in your CSS selectors should be used with caution because it immediately increases the complexity. With that said, using hierarchy is necessary in many situations — and can actually improve clarity in a few.

Here’s a simple DOM illustrating hierarchy where we’ve got <label> elements that are nested inside of (children of) <div> elements:
<div class="name">
<label>Justin</label>
</div>
<div class="email">
<label>test@email.com</label>
</div>
Let’s assume we want to target the <label> that contains “Justin”. How do we do that? We can’t use just label as our selector because that will match both <label> elements. We need to narrow down the parent element first. That selector would look like this:
I match the parent element using its .name class. I add a space afterwards. This indicates that I’m going to target an element contained within the element matched by the first string. After the space, I match the inner element. In this case, I’m just using the label tag name. This selector essentially says, “Find the <label> element somewhere inside of an element with a .name class.”
I can define as many levels of hierarchy as I’d like in a CSS selector using multiple spaces. In a large DOM, I may end up with something like this:
#supportForm .comments textarea
In that selector I’m looking for a <textarea> element, which is inside of an element with a .comments class, which is then inside of an element with an ID set to #supportForm. Of course, if the <textarea> element had a unique id or name attribute, I could make this much simpler… but that’s not always going to be the case.
In addition to using a space between child elements, I can also use an angle bracket like this: .comments > textarea. When I use the angle bracket, it means that the element is a “direct child” of the parent element. In other words, it’s in precisely the next “level” of nested elements — not just anywhere inside. Let’s illustrate this with another (contrived) DOM:
<div class="comments">
<textarea></textarea>
</div>
<div class="comments">
<div class="something">
<textarea></textarea>
</div>
</div>
If I use the selector .comments textarea, it will match both <textarea> elements. However, if I use .comments > textarea, it will only match only the first, since the second block has a <div> nested in between the two which breaks the “direct child” relationship.
The angle bracket can be useful for precision, but can also make selectors more and more brittle because you’re effectively taking away the flexibility of the selector. Adding an extra <div> layer (as in the example above) is a common occurrence, so you’re more prone to breakage when application updates occur. When hierarchy is a must, I’d avoid using the angle bracket unless you really need to (which shouldn’t be often). Sticking to spacing and broader relationships will help to keep your selectors more durable as your application’s DOM changes.
Targeting using ordering
In some case (most often in lists) it can be useful to target elements based on their position in a sequence. Here’s a simple DOM illustration:
<ul class="fruits">
<li>Apple</li>
<li>Orange</li>
<li>Pear</li>
</ul>
I want to target the “Orange” entry. How do I do that? The <li> elements don’t have any distinguishing attributes… Fortunately, CSS provides an :nth-of-type() feature to target elements by their position in a list. For this example, my selector would look like this:
.fruits li:nth-of-type(2)
I simply plug 2 into the :nth-of-type() function since I’m looking for the second <li> element in the list. Simple, right? Positional patterns like :nth-of-type() are a pseudo class selector. They’re powerful and handy for lists or repeating elements, but they also raise css specificity and can become fragile if the order changes. For example, if “Orange” were moved to third in the list, this selector would no longer target it correctly.
Targeting using text contents
If you look at the DOM example in the section above, wouldn’t it be nice and semantic if we could target that <li> element using its “Orange” text? That would be nice, wouldn’t it?… I wish I could tell you that CSS has a tool for that, but unfortunately, standard CSS does not. You may have seen something like this before:
In principle, I love that selector. It’s clear to humans and it won’t break if the order of the list elements changes — semantic and durable. Unfortunately, the :contains() pseudo-class is something that’s only available in the Sizzle selector engine (popularized by jQuery). It’s not available in your browser by default right now. Very sad, I know… Early versions of Selenium injected the Sizzle engine for you, so you could use this feature in your tests. However, with Selenium v2 and up that is no longer done. At Ghost Inspector, we’ve built this into our system — but it’s only available in some of our browser options, not all.
The best solution I can offer right now is to consider mixing in XPath selectors which support matching by text contents. XPath is a method for element selection that has a different syntax than CSS, but is used in the same way. It deserves its own blog post in the future. In the meantime, the XPath selector for the example above would look like this:
//li[contains(text(), "Orange")]
XPath is supported by both Selenium and by Ghost Inspector, so this is a good option when matching an element by its text contents makes sense — which is fairly often. For more details on using XPath, check out our CSS to XPath Conversion Guide.
And if you’d like to refer back to any of these tips, save our CSS Selector Cheat Sheet infographic below.

Advanced CSS Selectors for Automated Testing
Once you’re comfortable with the basics, it’s worth exploring more advanced CSS selectors that can make your automated tests more flexible and powerful. These selectors allow you to target elements more precisely and handle complex scenarios.
Pseudo-class selectors
Pseudo-classes help you select elements based on state or position. Here are a few common ones that work well in test automation:
-
:first-child– Selects the first child of a parent. -
:last-child– Selects the last child of a parent. -
:nth-child(n)– Selects the nth child of a parent. For example,li:nth-child(3)targets the third list item. -
:not(selector)– Excludes elements that match a given selector.
Example:
This will target list items that do not have the active class.
Attribute selectors
Attribute selectors allow you to find elements by their attributes or parts of those attributes. These are especially useful in test automation when elements have data attributes.
-
[data-test="login-button"]– Targets an element with an exact attribute match. -
[href^="https"]– Selects elements where the attribute begins with a specific value. -
[href$=".pdf"]– Selects elements where the attribute ends with a specific value. -
[href*="download"]– Selects elements where the attribute contains a specific substring.
Common Mistakes When Writing CSS Selectors
When selectors break, automated tests often become unreliable. Here are a few mistakes that can make your selectors brittle and harder to maintain.
Overly specific selectors
Relying on long, detailed selectors tied to the DOM hierarchy (for example div.container > ul > li > a) makes your test fragile. If the page structure changes, your selector will likely break. Keep your selectors short and descriptive.
Depending on generated class names
Frameworks sometimes create dynamic class names like button-abc123. These are unstable and can change without warning. Instead, request that your development team add consistent id or data-test attributes to critical elements.
Selecting based on style or order only
Targeting elements based on their visual order (such as the “third button on the page”) leads to unreliable tests. It’s better to use semantic attributes or unique IDs whenever possible.
CSS Selector Cheat Sheet FAQS
How do I know when to use the different methods?
Welp, that’s a tough one. The most obvious answer is “experience”. As you work with HTML, CSS and the DOM more often (and in different situations), you’ll start to get a handle on what might work for different scenarios. But experience takes time, so let me offer some other advice.
I’ve designed this post to (sort of) walk through selector options from most ideal (simple and durable) to least. There are exceptions to this logic, but I usually take this approach when building a selector:
- Is there an
idattribute? Use that. - Is there a
nameattribute? Use that. - Is there some other unique attribute that I can use, like
placeholder? Use that. - Can I use attribute operators to match a portion and uniquely identify this element, like a link’s
href? Use that. - At this point I’ll start looking at class names to see if anything looks usable.
- If there’s nothing specific enough, I’ll take what I have so far (which may be a useful attribute or class that just isn’t specific enough or may be just the element tag) and I’ll start stepping out and looking at the hierarchy outside of the element. The same logic applies when targeting parent elements, so this logic is recursive. Just keep in mind that you don’t need to use the “direct” parent. You could jump back a few levels to a parent element that has an
idattribute or makes more sense semantically.
Another hugely helpful asset in deciding how to build these CSS selectors for your tests is your own knowledge of the application that you’re testing. What do you know about the way the DOM is designed AND what do you know about the way it may change in the future? Are portions of the UI more active than others? What’s likely to change? Are class names changed frequently? How does the engineering team think about the DOM? Ask them! Try to get a handle on how they go about changing the DOM and factor that into your own considerations.
What’s the difference between simple, group, and complex selectors?
A simple selector is the most basic way to target a page element, like an id selector (#login), a class selector (.btn-submit), or a type selector (input). These are easy to read and usually the most durable.
A group selector (sometimes called a grouping selector) lets you apply the same rule to multiple selectors at once. For example, h1, h2, h3 would target all headings. This is useful for styling, but can also help in automated tests when you want to check several related elements together.
A complex selector combines multiple conditions, like hierarchy or attributes, to pinpoint a specific html element. For example, #signupForm input[name="email"] matches the email input only inside the signup form. Complex selectors are powerful, but they increase css specificity and can be more fragile if the page layout changes.
When building automated tests, the general rule is to start with simple selectors, use grouping only when it makes sense, and reserve complex selectors for situations where nothing else works.
Should I modify my application to make targeting easier in my tests?
This is a tricky question that I get asked quite a bit. Believe it or not, you can always match an element on the page uniquely with a CSS selector. However, that selector may end up being super ugly and brittle with no better alternatives available.
If you find yourself in a situation where there’s no “good” selector available to locate an element, is it acceptable to have the engineering team add an id or some other attribute that you can use? This can be really handy. Obviously a unique id makes for an easy selector. I’ve also heard folks suggest the use of [data-testing="id-here"] or [qa="id-here"] type attributes specifically for targeting during testing.
I have mixed feelings about this approach. It can make your life much easier, but also comes with trade-offs… If you decide to go this route, make sure everyone understands those tradeoffs. Here are the two downsides that I see:
- By adding attributes to your application’s markup which exist for the sole purpose of testing, in some ways, you’re adding another level of “maintenance” outside of your test cases themselves.
- Adding attributes to your application’s markup increases its size. Unless you’re doing something really slick, this extra code is probably making it to production which means that you’re technically slowing down your live application.
What tools can I use to help me generate CSS selectors?
If you’re a Ghost Inspector user, we’ve got a test recorder for your browser that will generate CSS selectors for you as you record a test. However, those selectors are generated algorithmically and are meant to be reviewed afterwards. The recorder doesn’t know anything about your application or what’s dynamic, so they’re often going to be a bit more rigid than necessary. In some dynamic cases, they simply won’t work without modification. Chrome too has a built-in option for generating a CSS selector by clicking on the element in using its developer tools:

But again, without any human knowledge, these selectors can sometimes be a bit ugly. There’s really no substitute for your own skills and knowledge here. The one tool that’s probably most useful is simply your browser’s developer console, which allows you to query the DOM for your selectors using JavaScript, like this:
document.querySelectorAll('.my-selector')

Get familiar with that line of code! It’s incredibly useful for figuring out exactly what your CSS selector will match on the page, both when building selectors and when troubleshooting them. You can also use document.querySelector() to match a single element, but I recommend using document.querySelectorAll() during your development process to help ensure that you’re only matching the element you expect, and not others (which will happen often).
Tips for troubleshooting
A couple of quick tips if you’re running into problems with a CSS selector:
- Have you used
document.querySelectorAll()to ensure that the selector is matching exactly (and only) what you intend on the page? - Are you targeting an attribute that’s dynamic and changing on each page load? For instance, like the dynamic IDs that we covered above.
- Is the element that you’re targeting inside of a
frameoriframe? Each frame has a completely separate DOM, so that complicates things. Unfortunately, that’s outside of the scope of this post, but if you’re using Ghost Inspector, see our documentation on frames. - Could your problem actually be timing related? Are you searching for an element before the element has been created? This is common in JavaScript-heavy applications. Ghost Inspector automatically waits for elements to appear in the DOM before proceeding with each step which remedies this, but if you’re writing your own Selenium tests, you may need to add a “Wait” command.