If you've been using Cypress, you might be familiar with commands chaining. That means, for example, that the context of our first command is passed on to the second command.
The other interesting thing here is that this command chain works well with Cypress retry logic.
For example, if I would put number 2 here and I would save my test, you can see that the test actually takes a couple of seconds to fail.
What happens here is that when the assertion in our
should command is not fulfilled, Cypress will retry not only our assertion, but also our
Let me demonstrate what I mean.
When I retry my test and add a second task, my test is going to pass.
So, although we were waiting for the
should assertion to pass, when the state of my application changed and I added the second task, the
get command would retry too.
And that is great, because even when my test goes faster than the changes in my application would happen, my test script is prepared for that.
However, the important thing to remember is that only the previous command is retried. Let me show you what I mean.
I'm going to select the second task by using
eq and bypassing number 1. This is going to select the second element, “bread”, and I'm also going to change my assertion. Let's type
What I'm doing here is that I'm adding the assertion for the first task.
So, I'll run my test.
And during the test, I'm going to reorder these, but still my test will not pass.
That is because in my chain, when I'm doing my
should assertion, if the assertion doesn't pass only the previous command is going to be retried, but not the first one.
Let's look into more detail into what is happening here.
So, in the browser, I will open my console and I will first click on my
Now, in my console, you can see details of my command. You can see the name of my command, what it has yielded, what was the selector, and how many elements we have found.
The important part here is this “Yielded” part. This contains the array of elements that my
get command is going to pass on to the next command.
So, when I look into the details of
eq command, you can see that our
eq command was actually applied to the elements that our previous command has yielded.
eq command is working with these 2 elements.
And since there is nothing special that our
eq command does (it just filters the second element), it's going to yield our second task. That is the one with the text “bread”.
It is going to pass this element into our assertion, which is expected to see the “milk” text inside our task. But it actually shows “bread”. Since this assertion doesn't pass, it's going to retry the
eq command again.
But as you can see, it's still applying to the same 2 tasks that were found at the moment when our
get command finished.
Even though we are selecting the element number 1 in our
eq command is actually numbering from number 0, so that would be 0, 1, 2, 3, 4, 5. We are still referencing the same element. Cypress will keep the reference to that element even though as our DOM changes. So even when I reordered my elements, it would still keep referencing the same element that I selected using
It's really important to pay attention to what each command is yielding.
This is super helpful while debugging our tests. Not only is understanding yielding logic important for debugging, but it is also useful for writing your tests.
This relationship between commands is often referred to as a “parent-child relationship”.
So, we have parent commands, child commands, and we also get something that is called dual commands.
You can probably guess from looking at our test, which one of these are going to be parent commands and which are child commands.
The typical example of a parent command would be our
get command is usually chained off our Cypress object.
A typical example of a child command would be the
should command, because, of course, if we want to make an assertion on something, we first need some context, and we will get that from some other command.
A really nice example of a dual command is
contains. Although this command sounds like an assertion, it's actually a selecting command.
So, if I type
cy .get('[data-cy=list]') .eq(1) .contains('milk')
And I will delete these previous tests now and save my test.
You can see that it is selecting our text inside our first list.
Now, if I added another list to our application, so let's call that “done”.
I will add the same task inside (“milk”), and I will rerun my test.
When we hover over this, we can see that we are referencing still the same element.
That is because our
contains command will look inside the whole DOM. And it's going to return the first element that has this text.
If the goal is to select the second element, what I would do is that I would first select the proper list. So, I would use
.get('[data-cy=list]'). Then I would use the
eq to select the second element.
And then I would use 'contains' to select the element with the text “milk”.
When I now hover over my command, you can see that the second time we are referencing the “milk” item inside the second list, and the first time stays the same.
So, we are selecting the first element.
If we look into the detail of our
contains command, we can see that the first one behaves like a parent command.
We see the content for which we want to look, and we see what the command has yielded.
When I look into the second command, you can see that we have this “Applied to” attribute, which tells us that our
contains command was actually working with the context of the element that we have passed to it.
And that will be our second list.
To sum this up, if a command in the command chain is not passing, it is actually going to retry the command before that, but it is not going to retry the whole command chain.
The second thing to remember is that each of our commands in our command chain is actually passing something on to the next command. To see what that is, you can click on the command and looking into the “Yielded” attribute.
The relationship between the commands in the command chain is referred to as a parent-child relationship.
A parent command is usually at the beginning of a command chain and a child command usually works with what the parent command has yielded.
A dual command can actually behave as a parent or a child command, depending on the context of where that command is used.