JavaScript testing #7. Diving deeper into commands and selectors in Cypress

JavaScript Testing

This entry is part 7 of 8 in the JavaScript testing tutorial

In the previous part of this series, we’ve learned the basics of the Cypress framework. Although the above knowledge is enough to write our first tests, there is much more to explore. In this article, we dive a bit deeper into the mechanisms of Cypress. By doing so, we can avoid some troubles along the way and make our tests run smoother.

Asynchronous nature of Cypress commands

An example of a Cypress command is the   function. So far, we’ve written quite a few selectors using it. We might think that under the hood, it works in the same way as regular JavaScript DOM selectors. That’s not exactly the case here.

We’ve already pointed out that Cypress retries the   selector for us. Either it succeeds, or a timeout is reached, and the test fails. In fact, the   is asynchronous!

We might think that the above operations can run in parallel if they are asynchronous. That’s not the case, though. Let’s inspect this test:

Even though the above functions are asynchronous, they run sequentially, To better visualize this, we can think of executing Cypress commands as putting elements in a queue. The details of how they operate are managed under the hood. The above code results in the following actions:

  1. Opening a website
    • and waiting for the load event to fire
  2. Looking for the input and filling it with value
    • and retrying until it is found in DOM
  3. Looking for the button and clicking it
    • while retrying until it is found and clickable
  4. Traversing the DOM tree looking for the term “JavaScript” in the search title
    • performing the assertion until it succeeds

The waiting and retrying occur before the next step begins. As you can see, Cypress does quite a lot under the hood to keep our code simple and clean.

Because commands in Cypress are asynchronous and promised-based, their return value has the   function, among others. We can use it to interact with the result of the promise.

The  is a global function that acts in a similar way to the assertions that we defined as strings passed to the   function. Using it, we could manipulate the element in a more complicated way.

The  function also accepts a callback function in a very similar way to the   function.

It differs from the   function because Cypress reruns the callback of the   function until no assertions throw inside it.

The   function is an alias of 

More on selectors

So far, the only selector function that we’ve used is  . The Cypress framework offers more:


The   function allows us to find a descendant of a particular DOM element. We need to chain it after another command that looks for DOM elements, for example,  .


Works similar to  , but accepts a callback function.

An important note is that in the example above,   and   traverse the DOM tree only within the   element.


With the   function, we can get the children of a DOM element. If we don’t provide it with a selector, it gives us all children.


It allows us to search through the ancestors of an element in a DOM tree, optionally filtered by a selector.


The   function makes possible looking for an element containing a particular text value.


Using the   function, we can get an element in an array that has a particular index.

Good practices regarding selectors

A considerable problem when writing End-to-End tests is the issue of dealing with UI that is subject to change. When using Cypress, we write selectors all the time. To prevent them from breaking, we need to put some thought into it. If we rely too much on classes and tag names, things might get complicated when we update our interface. To prevent this from happening, we should write selectors that work even if there are some changes to the UI.

Fortunately, HTML5 was designed to be easily extended. The   attribute gives us an option to store additional information on regular HTML elements. Any attribute that starts with   is a data attribute. We can make use of the above by adding the   attribute.

Other popular naming conventions are   and 

By correctly inserting the above attributes, we can make sure that our selectors will not suffer when we make some changes to the UI.


In this article, we’ve dived more in-depth into the mechanisms of the Cypress framework. With a more extensive knowledge of how Cypress commands work, we can understand them better and make our code less prone to mistakes. Today we’ve also learned more about selectors and how we can use additional functions such as   and  .

Series Navigation<< JavaScript testing #6. Introduction to End-to-End testing with CypressJavaScript testing #8. Integrating Cypress with Cucumber and Gherkin >>
Notify of
1 Comment
Newest Most Voted
Inline Feedbacks
View all comments
1 year ago

When would you advise against e2e?