CSS Selector Strategies

For Automated Browser Testing

Justin Klemm

Who am I?

Founder and CEO of Ghost Inspector -- an automated browser testing service.

Try our service for free at:  https://ghostinspector.com

What will we cover?

  • CSS explanation and basic syntax

 

  • More advanced CSS features and approaches that help to address common testing use cases

 

  • High level thoughts and strategies for creating "good" CSS selectors for your tests

Feel free to ask questions, make comments, challenge statements, etc. at any point during this presentation!

What is a CSS Selector?

  • Formal Answer:  A string that uses attributes, hierarchy, position and other properties in a specific syntax to locate one or more elements in a DOM

 

  • Easier Answer:  A way to identify one or more "things" on a web page.

CSS Selector Uses

Applying style to elements:

<style>
    #sample-btn { color: red; }
</style>

Interacting with elements using JavaScript:

<script>
    document.querySelector("#sample-btn").style.display = "none";
</script>
<button id="sample-btn">Submit</button>

Interacting with elements during automated testing:

var wd = require("wd");
var browser = wd.remote();
...
browser.elementByCssSelector("#sample-btn").click();

What makes a "good" selector?

  • With regard to browser testing, we generally want to target a unique (single) element.

 

  • We want "semantic" selectors that are easy for humans (our team) to understand and troubleshoot.

 

  • Test maintenance is a certainty, but we want "durable" selectors that are most likely to continue working as our website evolves. 

CSS Selector Syntax

Targeting using IDs

<input id="email" value="">
#email
  • The "id" attribute is generally an ideal selector
  • Simple and unique on the page (in theory)

Sample element in the DOM:

CSS Selector:

Targeting using IDs (Caveats)

  • Not every element is going to have an ID
  • Intended to be unique, but could appear more than once...
  • IDs could be used dynamically and change on each page load... (Workaround later on)
<input id="email-123" value="">
<input id="email-456" value="">

Sample page load #1:

Sample page load #2:

Targeting using attributes

<input name="email" value="">
[name="email"]
  • An attribute is essentially any property of an element.
  • The usefulness of different properties can vary.

Sample element in the DOM:

CSS Selector:

Targeting using attributes

<a href="/edit/>Edit Entry</a>
[href="/edit/"]

Finding a link using its URL:

CSS Selector:

<input type="text" name="s2aR3e1Djkns" placeholder="Email Address">
[placeholder="Email Address"]

Use creative/semantic attributes to workaround issues:

CSS Selector:

Targeting using attributes

<input id="email" value="">
#email

The IDs that we already covered are just an attribute (as are classes, which we'll cover later).

Sample element in the DOM:

This selector:

[id="email"]

is simply shorthand for this:

Targeting using attributes

<input type="radio" name="x" value="1">
<input type="radio" name="x" value="2">

<input type="radio" name="y" value="1">
<input type="radio" name="y" value="2">
[name="x"][value="2"]

Attributes can be combined to match more precisely.

Sample DOM:

CSS Selector:

You can combine more than just attributes. IDs, classes, etc (which we'll cover) can also be combined:

input[name="something"].some-class.another-class

Targeting using attributes

<input name="sample-email-address" value="">
[name^="sample"]

Wait, there's more! We can swap out the "=" operator to do other types of matching.

Sample element in the DOM:

Match in the beginning of "name":

[name*="email"]

Match in the middle of "name":

[name$="address"]

Match at the end of "name":

Targeting using attributes

<a href="/user/12345/edit/">Edit User</a>
[href^="/user/"][href$="/edit/"]

When are these other operators useful?

Sample link with dynamic value in the URL:

CSS Selector:

<a href="https://staging.company.com/dashboard/?session=3284792">Dashboard</a>
[href*="/dashboard/"]

Sample link with variable domain & session in the URL:

CSS Selector:

Targeting using attributes

<input id="email-123" value="">
[id^="email"]

Going back to the "dynamic" ID issue... Possible workaround:

Sample elements in the DOM:

CSS Selector:

<input id="email-456" value="">
<input id="email-789" value="">

Targeting using element tag

<input name="email" value="">
input[name="email"]

Sample element in the DOM:

CSS Selector:

  • The element tag can be added in front of the selector.
  • Rarely useful in actually narrowing down the target.
  • But, it can be useful for clarification purposes.

Targeting using element tag

select[name="level"]
[name="level"]

Consider this selector:

Are we targeting an <input> field, <select>, <textarea> or something else? That may impact our actions.

 

It's useful to know:

Recommendation: Include the element tag when it adds clarity. I like to use it for <input>, <textarea>, <select>, <button>, <a>, maybe <li> or others...

Targeting using class names

<div class="foo bar">...</div>
.foo
  • The "class" attribute contains a space-delimited list of class names
  • Typically for styling but can be useful for targeting

Sample element in the DOM:

CSS Selector:

.foo.bar
[class*="foo"]

Targeting using class names

<textarea class="form-control">Bootstrap</textarea>
<div class="intro-section">...</div>

Be picky when using classes in your selectors.

They're generally designed for groups of elements, not individual elements.

Avoid style-oriented & "framework" classes:

Prefer classes with specific/semantic value:

<button class="btn-msg-submit">Send Message</button
<div class="ng-scope">AngularJS stuff</div>

Targeting using hierarchy

<div class="name">
    <span class="label">Justin</span>
</div>
<div class="email">
    <span class="label">test@test.com</span>
</div>
.label

Often necessary... Immediately increases complexity, but sometimes actually improves clarity.

Sample DOM:

Simple selector will match both <span> elements:

.name .label

Use parent to match <span> containing "Justin" only:

Targeting using ordering

<ul class="fruits">
    <li>Apple</li>
    <li>Orange</li>
    <li>Pear</li>
</ul>
.fruits li:nth-of-type(2)

In some case (most often in lists) it can be useful to target elements based on their position in a sequence.

Sample DOM:

CSS Selector:

Targeting using text contents

<ul class="fruits">
    <li>Apple</li>
    <li>Orange</li>
    <li>Pear</li>
</ul>
li:contains("Orange")

Wouldn't it be nice if we could target using text contents? Sadly not possible with standard CSS right now :(

Sample DOM:

Non-standard CSS Selector (jQuery/Sizzle only):

//li[contains(text(), "Orange")]

XPath Alternative:

How do I know when to use each method?

Combination of experience with CSS and knowledge of your application. If in doubt, my logic looks something like this:

  1. Is there an "id" attribute?
  2. Is there a "name" attribute?
  3. Are there other unique-ish attributes I can use?
  4. Can I use attribute operators to match a portion?
  5. Are there usable class names that make sense?
  6. Start looking at hierarchy for ancestor elements. Apply steps 1-5 again. Recursion!

Should an application be modified for easier targeting?

Tricky question... decision should be made as a team.

  • Adding "id" or "data-testing" attributes solely for testing can be useful, but understand the tradeoffs:
    • Moving some maintenance outside of tests
    • Increasing size of markup... Do they make it to production? Slowing down load time
<button data-testing="btn-submit">Submit</button>

Example:

button[data-testing="btn-submit"]

Tools for generating selectors

  • Recording tools can be useful -- but think of their output as boilerplate. Always review.
  • Browsers have options for generating selectors:

Building selectors yourself

Get acquainted with your browser's developers tools:

document.querySelector(...);
document.querySelectorAll(...);

First matching element:

All matching elements:

Takeaways

  • Make sure you're targeting single elements.
  • Make selectors as semantic (human) as you can.
  • Use application knowledge to "future proof" selectors.
  • Think beyond just IDs and classes. Leverage everything that CSS & the DOM offers you.
  • Algorithmically generated selectors are "boilerplate".
  • Get comfortable with your browser's dev console.

The End

You can find a more in-depth blog post on the topic of "CSS Selector Strategies for Automated Browser Testing" on the Ghost Inspector Blog:

 

https://ghostinspector.com/blog/css-selector-strategies-automated-browser-testing/

 

Twitter:  @justinklemm

CSS Selector Strategies

By Justin Klemm

CSS Selector Strategies

  • 1,818