Demystifying CSS Selectors in Cypress

Posted on Dec 4, 2023

#intro

#cypress

#testautomation

#css-selectors

Table of Contents

Introduction

If you’re diving into automated testing with Cypress, understanding CSS selectors is crucial. CSS selectors play a pivotal role in locating and interacting with elements on a web page, making them an essential skill for writing reliable Cypress tests. In this blog post, we’ll explore why CSS selectors are needed, how to find them, and how to use them effectively in your Cypress tests.

Why do we need CSS Selectors in Cypress?

Unlike humans, test automation tools are not intuitive and need to be told exactly which element to look for on a page to interact with. This is where selectors come into play. By default, Cypress uses CSS selectors. There are other ways to locate elements (like xpath), but we recommend sticking with CSS selectors. Here are some reasons why CSS selectors are essential in Cypress:

  • Precise Element Targeting: CSS selectors enable you to pinpoint specific elements on a web page, such as buttons, input fields, or links, ensuring that your tests interact with the right elements.
  • Dynamic Content: Web pages often have dynamic content that can change between test runs. CSS selectors help you adapt to these changes by providing a flexible way to locate elements based on their attributes and relationships.
  • Reusable Tests: Well-structured CSS selectors can be reused across multiple tests, promoting code efficiency and maintainability.

How do the CSS selectors work?

CSS selectors serve as the indispensable tools for precisely identifying and interacting with elements on a web page. These selectors function as patterns and criteria that instruct the automation framework on which elements to target during test execution. Instead of relying on human intuition, test automation tools rely on CSS selectors to navigate the Document Object Model (DOM) of the web page, effectively pinpointing the elements that match the specified criteria. Once a match is found, the automation tool can perform actions like clicking, typing, or asserting values on the selected element. This precise targeting capability is essential for writing reliable automated tests, ensuring that the correct elements are interacted with regardless of changes in the web application’s structure or content.

Example of DOM:

<html>
  <body>
    <div class="header">
      <h1>My First Heading</h1>
    </div>
    <div class="paragraph">
      <p>My first paragraph.</p>
    </div>
    <p>Outside of div paragraph</p>
  </body>
</html>

This DOM has 7 elements: html, body, div, h1, another div, p and another p

Where to find the elements?

Before you can do any actions or checks on the page with Cypress, you need to find the appropriate selector for the element you want to interact with. There are are a few ways of finding the selectors:

  • Browser Developer Tools: Most modern web browsers offer developer tools that allow you to inspect elements on a page. Right-click on an element and select “Inspect” to open the developer tools. From there, you can identify the element’s attributes and structure to create a CSS selector.
  • Cypress Selector Playground: Cypress provides a Selector Playground that lets you interactively test and generate CSS selectors. You can access it by clicking the “Selector Playground” button in the Cypress test runner, located next to the URL of the app under test. Checking elements in selector playground

We do recommend finding your own selectors though, because automatic selector finder might not use the best readable one or the shortest. A quick comparison:
Selector playground found :nth-child(3) > .container > .row > #utilities > p, which finds the one element, but it’s more prone to errors due to using a long list of parents. If the DOM changes (a div or any other element is added between), element will not be found. Manually you could find that .banner + div #utilities p works better in this case. You can try finding a better one yourself in the Cypress Kitchen Sink app (https://example.cypress.io).

Rules for selectors in test automation

CSS selectors should be:

  • Unique
    Should only find one element, unless done otherwise on purpose. Check the selector in browser dev tools or Cypress test runner to make sure only one was found. Checking elements in dev tools
  • Stable
    • Resilient to changes in the application code
    • Classes or ID’s might be dynamic, so it might not be a good idea to use those
    • Use dedicated data-* attributes if possible
  • As short as possible
    While still being unique and stable

How to select elements with Cypress?

In most cases to select an element with Cypress, you use cy.get() command. Let’s look into different ways to select elements:

  • Select by Element Type:
    To select an <a> (anchor) element on a page: cy.get('a')
    To select an <input> element: cy.get('input')
  • Select by Class Name:
    To select an element with a specific class, use . in front of the class name, e.g., <button class="primary-button">: cy.get('.primary-button')
  • Select by ID:
    To select an element with a unique ID, use # in front of the class name, e.g., <div id="header">: cy.get('#header')
  • Select by Attribute:
    To select an element with a specific attribute value, add [] around the attribute name and value e.g., <input data-testid="username">: cy.get('[data-testid="username"]')
  • Select by Attribute Contains:
    To select an element with attributes containing a specific value, e.g., <button class="btn primary-btn">: cy.get('[class*="primary"]')
  • Select by Element Hierarchy:
    To select a child element within a parent element, e.g., <ul class="menu"><li>Item 1</li></ul>: cy.get('.menu li')
  • Select by Pseudo-Classes:
    To select the first element of a certain type, e.g., the first <li>in an unordered list: cy.get('ul li:first-child')
    To select the second element of a certain type, e.g., the first <li>in an unordered list: cy.get('ul li:nth-child(2)')

Combining selectors

It’s always possible and often needed to include multiple attributes of the same element or parent elements for your selectors, to be able to select a unique element on the page. Here are a few examples:

  • cy.get('input[type="text"]')
    This selector combines an element type selector (input) with an attribute selector ([type="text"]) to target an <input> element with the attribute type set to "text". If that’s the only input with the type “text” on the page, this selector might be enough.
  • cy.get('ul.menu > li:nth-child(2)')
    This selector combines a parent selector (ul.menu) with a direct child selector (li) to select the second <li> element that are direct children of a <ul> element with the class menu. The parent might be needed when there are multiple li elements on the page
  • cy.get('div.paragraph p')
    This selector combines a div element with class paragraph (see the example of DOM above) with a child element p that doesn’t need to be a direct child. The p element outside the div will not be selected.

Finding selectors with certain text

Instead of using cy.get(), you can use cy.contains() for locating and interacting with elements based on their text content. It’s important to remember that cy.contains() is not an assertion (.should('contain', 'text') is) and it should be used only when you want the test to fail upon text changes. It allows you to find elements that contain specific text within their structure. While you can use cy.contains() with a simple string to target elements with exact text matches, it becomes even more versatile when combined with CSS selectors. By providing a selector as the first argument, you can narrow down your search to elements that both match the selector and contain the desired text. This combination enables precise targeting of elements in complex web applications. For instance, if you want to click a button with the text “Submit” within a specific form, you can use cy.contains('#sample-form button', 'Submit') to ensure you’re interacting with the correct button within that form, even if there are multiple “Submit” buttons on the page. This approach enhances the reliability and flexibility of your Cypress tests when dealing with dynamic content and complex DOM structures. We don’t recomment using cy.contains() without the selector, because when the text changes, the error message will tell you the element is not there, but the actual thing is the text has changed and the element is still there.

Conclusion

In conclusion, understanding CSS selectors is paramount for successful test automation with Cypress. This blog post has explored the significance of CSS selectors in Cypress, why they are essential, how to find them, and how to use them effectively. We’ve delved into various selector types and discussed best practices for creating reliable and maintainable selectors.

As you embark on your journey with Cypress and test automation, remember that practice and experimentation are key to mastering CSS selectors. By honing your selector skills, you’ll be better equipped to write robust and efficient Cypress tests that can adapt to changing web application structures and content.

Happy testing, and may your Cypress tests be precise and resilient in the face of dynamic web pages! If you have any further questions or need clarification on any topic covered in this post, feel free to reach out for assistance.