This article is a part of series on Cypress basics. You can check out some other articles on my blog where I provide step by step explanations of some Cypress basics + some extra tips on how you can take things one step further. So far, I wrote about:
If you came here via Google search, you are probably wondering why code like this does not work in Cypress:
If you are here just for the solution, scroll down to the section named Possible solutions If you want to understand what is going on, read on.
So why is id
undefined? When you dig into docs, it might get a little confusing. There’s article about how commands in Cypress are asynchronous, then maybe read a little bit about how you should handle variables, you’ll try async/await
, but then find out that does not work either. So what is going on?
Let’s add a couple of console.log()
functions to our test and see how will the test behave. Just by looking at the code, can you guess what will be printed out in browser console?
Maybe you guessed it right. But I guess you are curious why is this the answer:
As I said earlier, the answer is in the docs, but it might be a little confusing. At least for me it was. So here’s another way to think about it.
Cypress commands run in a chain. Each chain link ties to the one before and is also tied to the one after. This way Cypress ensures that you don’t run into race conditions and will automatically wait for the previous command to finish. Let me give you an example.
Again, no command will run until the one before is finished. If any of the commands don’t finish on time, (usually 4 seconds) test fails.
So what happens with the code that is outside the chain? Well, since it’s not part of the chain, there’s nothing that forces it to wait, and gets executed immediately.
Let’s now look at the example with a fresh perspective.
Hopefully, the console.log()
functions make a little more sense now. But what about that id
variable? It seems like being used inside the chain. Or is it?
Actually not. It is passed as an argument, so technically it is not inside the command chain, but passed "from outside". We declared this variable at the beginning of the test. Within our test, we are telling Cypress that we want to execute .visit()
command with whatever '/board/' + id
is.
It starts to make a little more sense when we take a closer look into our "inside chain vs. outside chain" principle. Let’s take a look at the code again:
Now that the problem is clearer, let’s look at how we can pass values around in our test using different methods. There are many solutions to this, so let’s look at at least a few.
The easiest solution is to make sure that anything we include everything in our command chain. To use the new value, we need to call our .visit()
function inside the command chain. That way, the id
will be passed with a new value. Of course, multiple .then()
funcitons can potentially cause a "pyramid of doom", so this solution is best for cases when you want to immediately pass a single variable.
Since Cypress runs it()
blocks one by one, you can split the logic into multiple tests and use a "setup" it()
function for assigning your variables and then execution it()
block to use that variable. However, this approach might be quite limiting, as you need a separate block for every variable change. It’s also not the best test design, as not every it()
function is now a test. This can also create a weird domino effect, where a failure of a test can be caused by a previous test.
A slightly better way to split a test is to use before()
or beforeEach()
hooks. This way you are splitting your test in a more logical way. You have a preparation phase, which is not part of the test, and an execution phase, which is the test itself. Another advantage of this approach is that when a hook fails, you’ll get a clear information about this in the error log.
We can skip creating a variable altogether and use aliases instead. They are not that different from variables, but they live directly in the context of our test. The advantage of this approach is that we don’t need to use the alias right away, but we can use it later in our test.
Aliases are actually part of Mocha - a framework that is bundled within Cypress and is used for executing tests. Whenever you use .as()
command, it will create the alias within Mocha context which can be accessed by using this
keyword as shown in example. It will be a common variable, so you can share variables between tests in the spec. However, this
keyword cannot be used in functions with arrow expression () => {}
, but needs to be used with traditional function expression, function() {}
. See the example
There are a couple of more examples that can help you with storing variables in Cypress, these are just a few of them. I shared some more advanced examples in my older blog on how to handle data from API, you can check it out here.
From time to time I send some useful tips to your inbox and let you know about upcoming events. Sign up if you want to stay in loop.