Learn CSS Selectors - with examples

As some will already know, I don’t use XPath selectors. Why? The web doesn’t need XPath and rarely (if ever) uses it. The web, in all its richness, use HTML, JavaScript and CSS.

What is CSS? → Cascading Style Sheet

Briefly, CSS is a way to bring style into an HTML document (i.e. a web page). If you want your site banner to be blue instead of white, you do that with CSS. For example, if your site banner is an HTML <div> element with a class “my-banner”…

<div class="my-banner"> My amazing website! </div>

        HTML div element using class attribute

then your CSS style sheet may contain this:

.my-banner {
 background-color:blue;
}

        CSS rule defining the class my-banner

Notice in the CSS code, the classname .my-banner starts with a dot:

image
        CSS class rules start with a dot

that is how CSS identifies a class. HTML, on the other hand, identifies classes with the class= attribute (you therefore don’t need a dot).

Let’s now look at IDs. Let’s say you want to give your banner an id=

<div id="my-website-banner" class="my-banner"> My amazing website! </div>

        HTML div element using class and id attributes

Where CSS uses a dot to identify a class, it uses a # to represent id

#my-website-banner {
  color: white;
  font-size: 30px;
}

        CSS rule defining a rule for the id my-website-banner

Now your banner will have a blue background because of its class, and white text and a 30px sized font because of its id.

But Russ, this is about writing HTML and CSS - what about testing?

You’re right. This was meant to be a quick intro but you need this knowledge to understand what CSS selectors do for testing. One last thing before we move on…

If you wanted to give every div in your web page a red border 2 pixels wide, here is the CSS:

div {
  border: 2px solid red;
}

        CSS rule that will target every div on the page

The point is, it doesn’t matter if you have 5 divs or 500, that code will give ALL divs a red border 2px wide - unless there are more CSS rules that override that rule. For example, if we specify that your #my-website-banner is to have a 5px green border instead of a 2px red one, it will override the more general div rule:

#my-website-banner {
  border: 5px solid green;
  color: white;
  font-size: 30px;
}

        CSS rule defining a rule for the id my-website-banner

How does the #my-website-banner rule override the div rule? That’s down to something called specificity - the id rule is more specific than the general div rule. To learn more about that topic, I suggest you search the web for CSS specificity.


Okay. Enough with the theory. Let’s see how we can use CSS selectors for testing.

For this part, you’ll need your browser open on the Katalon forum “latest” page:

http://forum.katalon.com/

Which browser? Any browser is fine, but I’m using Firefox - I say this because we’re going to use DevTools and I prefer the tools offered by Firefox.

Finding the Search button


        Katalon forum search button

If you use DevTools to inspect the search button, you should see something like this:


      Firefox DevTools showing the details around the search buttonKatalon forum search button

Notice the DevTools inspector landed on the svg image inside the <a> element with an id="search-button". The <a> element is what we want because <a> tags handle clicks. And, luckily for us, IDs are always unique because it’s a universal law of HTML :slight_smile:

As we learned earlier, IDs are more specific than pretty much everything else. This makes our CSS selector super easy and strong (no flaky and lengthy path required)! :sunglasses:

#search-button

Now you can create a Test Object (in the Object Repository) and add that CSS selector - you don’t need anything else! - and use it in a Test Case.

Want to prove the CSS selector works before you create the Test Object?

For that we need to show the console in Devtools. In Firefox, you toggle the console to appear below the inspector window by hitting the Esc key.

To test a selector in the DevTools console we need a web DOM interface (that’s a function, to you and me) called document.querySelector()

If we type this into the console…

document.querySelector("#search-button")

then DevTools should report back with the found element’s details (if we spell the id wrong, we get null).


        Testing a CSS selector in the DevTools inspector and console

So, at this point, you know your selector is completely valid. When you move it into a Test Object in Katalon, the only thing that can stop it working is “you” - you forgot to wait for the page to load, you forgot to check it’s visible, that kind of thing… the selector works fine.

Now let’s take a look at something a little more awkward - something without an id. Note, IDs may still be involved but the element we want to target will NOT have an id.

Let’s say you are working for Katalon. Let’s say @ThanhTo or @duyluong ask you to check the forum home page has a link “FORUM RULES” that should go to a url (href) for post number 9691. And (yes, there’s more) they want you to verify it’s the second link in the same line of text.

Okay. That’s the job. Let’s get to it.

We’re going to inspect “FORUM RULES” and see what we get:


        The FORUM RULES link on the Katalon forum page


        The FORUM RULES link in the DevTools inspector

So a first stab might be to create a selector for the highlighted <a> element and ensure the href attribute contains `9691. That’s okay, but a little flaky. There might be another reference to the same post on the same page. Disaster. We may find the wrong link or nothing (because there’s more than one).

What if we could find the <a> that is a child element of something unique above? If you look back at the inspector window, you’ll notice there is a <div id="ember9" ...> which is an ancestor of the <a> element we want. That’s perfect for us.

Here is a selector that finds <a> elements that are children of `id=“ember9”…

#ember9 a

        CSS selector to find <a> elements beneath the element with id ember9

The problem now is, that selector finds ALL <a> elements that are children of ember9. We want the second one (because the guys said they want you to verify it’s the second link). So, let’s fix the selector using the CSS nth-of-type operator.

#ember9 a:nth-of-type(2)

       CSS selector to find the second <a> element beneath the element with id ember9

I agree, nth-of-type is a bit of a mouthful. Just remember when supplied with 1, it means the first, with 2 it means the second, and so on. So our selector is saying…

“find the second <a> element that is a descendant of #ember9”.

So far, so good. Now to ensure the <a> element is pointing to post 9691. To do that we can build a selector that examines the href attribute and tell it to verify any portion of its text. In our case, ensure it mentions 9691…

#ember9 a:nth-of-type(2):[href*=9691]

       CSS selector using attribute selector to find post 9691


       DevTools console showing CSS attribute selector

You will notice this selector is using the attribute selector - [attribute-name=value]. But if you look more closely, you’ll see we’re actually using [attribute-name*=value], in our case [href*=9691].

This is saying “select the <a> element with href that contains 9691”.

Aside: In addition to contains, we could have said ends with using $= or we could have used the whole URL using just = but I was concerned if someone changed the title of the post, the test would break :confused:

For more on attribute selectors:
Attribute selectors - Learn web development | MDN

Lastly, we’ll look at something other than <a> elements, namely, <button> elements. And this time we’ll use this page:

On that page you’ll notice there are a number of replies to my original topic. We’re going to focus on the reply from @ThanhTo. Specifically, we’re going to target his user-id, his user-card and also (using JavaScript) click on the Reply button. (Don’t worry, if any spurious responses are posted, I’ll see them and delete them :man_superhero: :sunglasses:

Okay, a quick look at the page in the DevTools inspector shows a bunch of HTML for @ThanhTo’s post:


           ThanhTo’s post in DevTools inspector

You’ll notice I circled …

  1. the post id
  2. his user-id
  3. his user-card
  4. a single <p> element which happens to be his first paragraph inside his post
  5. and lastly, the Reply button

Here are the CSS selectors to target each of those items in the order I specified above:

The CSS selector for the article post id:

#post_22

JavaScript:

document.querySelector("#post_22")

The CSS selector for his user-id:

article[data-user-id='7647']

JavaScript:

document.querySelector("article[data-user-id='7647']")

The CSS selector for his user-card:

#post_22 .trigger-user-card a[data-user-card='ThanhTo']

JavaScript:

document.querySelector("#post_22 .trigger-user-card a[data-user-card='ThanhTo']")

The CSS selector for his first paragraph:

#post_22 div.regular.contents p

JavaScript:

document.querySelector("#post_22 div.regular.contents p")

Of course, we can use the innerText or textContent properties to retrieve the content of his first paragraph like so:

document.querySelector("#post_22 div.regular.contents p").innerText

image
           DevTools console showing ThanhTo’s first paragraph

Lastly, let’s get that Reply button so we can bombard @ThanhTo with a million useless replies :nerd_face:

The CSS selector for the Reply button

#post_22 .actions button.reply

JavaScript:

document.querySelector("#post_22 .actions button.reply")

And here is a minimal Test Case that will click on that button. It is left to you to create a Test Object for the Reply button (replyBtn) using the CSS selector above AND to perform the usual navigation to reach the forum page:

// imports...

// navigate to Katalon forum page...

// wait until page is loaded...

WebUI.click(replyBtn)

Of course, you don’t need to create a Test Object - you could just do it directly in JavaScript:

// imports...

// navigate to Katalon forum page...

// wait until page is loaded...

String js = 'document.querySelector("#post_22 .actions button.reply").click();'
WebUI.executeJavaScript(js, null)

To complete the exercise and fill out a reply to @ThanhTo, you’ll need to do a little more work. :stuck_out_tongue_winking_eye:

I hope you guys find this useful. For a little more reading, try this topic:

7 Likes

Well at least now if I get spammed I know who to blame ! :rofl:

1 Like

Russ, what would you do for a case when you need to check if an element containing certain text exists?

nth-of-type() is not applicable because the elements’ location is dynamic.

Xpath has a contains method, CSS doesn’t (pseudo selectors do not count).

Hey Mate - good to see you around!

Good question!

Perhaps surprisingly, CSS does not have a parent selector either. It’s been “started” and proposed a few times but has never made it into the spec (yet).

When you say the location is dynamic, it’s not likely it’s completely random (page might break). But locating the element will be down to something you can repeat, test by test. Then I’d use JavaScript to access innerText/textContent property.

If you want to share more code, I’ll be more specific :wink:

No, I don’t have anything specific. It is just that sometimes the simplest solution is to select the element by xpath contains() method. I was just wondering if there is a css selector trick that can do that. :thinking:

I could write a XPath to select HTML elements with IP Address-like string, such as <li>172.23.4.56</li>. See my previous post. But I am not sure if we can write a CSS Selector that does the same match.

@Russ_Thomas

Do you have any idea?

In CSS directly, no.

In CSS+JS, yes:

String js = "return document.querySelector(selector-for-the-LI).innerText;"
String content = WebUI.executeJavaScript(js, null)

// or

String js = "return document.querySelector(selector-for-the-LI).textContent;"
String content = WebUI.executeJavaScript(js, null)

https://www.w3.org/TR/selectors-3/#selectors

As you can see, there’s not much to CSS selectors in the spec - could be fitted onto one page, almost. The only necessary thing missing, I’d say, is “parent” (inverse tree traversal). But even that can be worked around (jQuery as it built in, for example).

Something like this?

1 Like

https://api.jquery.com/parent/

But of course, there’s nothing stopping you recreating that using “standard” DOM APIs and document.querySelector() if you don’t mind doing the work. But if jQuery is around, I use it.