Check out a free preview of the full Testing Web Apps with Cypress course

The "Commands" Lesson is part of the full, Testing Web Apps with Cypress course featured in this preview video. Here's what you'd learn in this lesson:

Steve explains how commands allow for custom methods to be added to the Cypress so common code routines can be reused. Arguments can be passed into commands making them easy to abstract.


Transcript from the "Commands" Lesson

>> We can do this together but it is basically taking some parts of your code out, and going through and just kinda dropping it into this command file. So where does that live? So here's the default one that Cypress generates for you, it's all just commented out. But you can see that the syntax is kinda there for you as well, it's Cypress.commands.add.

And then whatever you wanna do, you'll see there's a bunch of arguments, right? Previous subject is like the chaining, right? So if we look at the syntax here, we can see that by default, the one we had simply takes a function. So we'd have, in this case they're calling it login, we called it sign in, right?

And that's expecting two arguments, an email address and a password, we happen to have a user object cuz that was just the dummy data that we had at the time. This previous subject element means you could chain off a cy.get, right? The previous subject being optional means that it's basically how it enforces your usage of it, so on and so forth, right?

And the ability to overwrite existing commands as well for custom things, right? So you can drop them in, basically it's taking the common places in your code, lifting them, and then drying out your tests, right? And there's some philosophical debate here, right? Like your test should be explicit, and sometimes explicitness comes at the cost of dryness, right?

You don't need clever abstractions in your test code when things aren't working, right? You need to see things. That said, I think we can come to the agreement that, if I have a command called signin, I'm not losing a lot of explicitness for what I'm gaining, right? It's pretty, pretty close.

I would warn you not to get carried away here, but train that sense of, yeah, I'm doing these three steps over and over and over again as well. So let's just look at some commands that we have in place, and kinda see, and just kind of an example, right?

So the signin, signup are literally from that last set of tests that we had, just lifted, and put in a command called signin and signup, effectively the same code, just copy and pasted. Some of the things you could do if you wanted to, this is a little too simple to be totally honest with you.

If you get tired of typing data-test, cuz that was a decision to call a data-test we decided on. Some you can see it data-Cypress, data-cy, could be things that are existing in your code based from previous test runner. But we could add getData where we just pass in the attribute.

A more sophisticated version might take also additional selectors, like we had unpacked items and then an ally, right? This isn't really, you tack a .find onto it. I could rework the implementation here to the point where it works better, but it was hard for you to look at and understand.

I decided I don't wanna do that to you. So but to show you the idea that you can kind of create and put abstractions over other commands and do stuff that you need to do, you can even override it which I wouldn't do. But you can have additional things as well.

This is one, this is very specific to whatever framework that you're using. This is one that I was using for a while as I was developing, which is different frameworks that have a certain amount of SSR, server-side rendering. They have a place where the page loads some HTML, right?

And I think echo chamber, a few of the apps totally work with JavaScript disabled. But I think the secret menu item where it's filtering on the fly, and not submitting a form or anything like that didn't, right? So certain frameworks will, I think next does it a little bit differently.

A spell kit fires a spell kit:start event when the JavaScript is loaded. So if you wanted to say, hey, for this thing, I want to wrap this promise that will resolve when that event fires, you could do that. I left this one in here, mostly cuz there's just a few other things to kind of talk about here.

And again, the chances that you'll need any of these it's pretty low, but if you do, then I helped you. [LAUGH] So we can kind of talk about them for a second which is, we wrap a promise, right? Which gives us a chaining ability, and then the idea that you have the window object that you can access, right?

And we're just putting an event listener on the window, and then we're resolving the promise, and that's still giving us our Cypress changes, so we can do like cy.waitforapp. And then whatever we want to do next, right? That's really it, but the idea that you have the window object available to you, the idea that you can wait for a promise to resolve, and then move forward with your tests, are all different things that you can do.

>> All right, could you talk about or show an example of how you might use this? Would this be like something you could, is there a way in before each to await a promise to resolve, or would you wrap everything in a then block or?
>> Yeah, I think I deleted that code cuz it's just one of things I cut preparing, but effectively, Mocha and just out of the box, if you return a promise out of the before each, it will hold and wait for that promise to resolve.

So honestly, all I did when I was gonna, eventually just turned server-side rendering off cuz that was not the situation that just to deal with this workshop. But basically in the before each, I basically returned cy.waitforapp, right? And so that would wait for the JavaScript to load, and then it would do all the stuff on that page, right?

I eventually decided that was just a little bit too much complexity for us right now, but I left it in here because you can see the window and the stuff like that. But yeah, you could just return it and then before each would kind of wait for the app to start before every test, before it did everything else.

>> Yeah, no problem. It seems that I left it in, [LAUGH] cool. So yeah, we can have all of these kind of in place and begin to refactor. So let's actually grab these, especially the, I'm gonna grab these three that were actually in use. Again, these are effectively just things that we could have in our code base that we could just begin to pull out.

So the act of copy and pasting is most likely the way that they would end up in this file. Now, you can see that Cypress, this is something available, the Cypress object, as opposed to just a lowercase cy, is an object where you can read and change. There's a cypress.config, where you can read the configuration and adjust it like, I need longer timeouts longer than four seconds for this file, and then I need whatever you need to do, you can change it on the fly.

This file is not actually all that special, support.index.js is loaded, and you can see that it's loading commands, right? Which is just adding the commands onto the object. There's not really any secrets around this file, if there are other things that you needed to do, this will get called.

And then whatever other files that you require, stuff along the way can kind of be branched out from here. And if you need to change the support file and the options to a different file, you can do all of that. This is just a file that kind of gets loaded to add additional functionality, and it's pulling in commands.

So we have these commands in place. So now, what we should be able to do, is kind of go into this file, and begin to effectively refactor it in my favorite style of refactoring. Watch. [LAUGH] Right, this now becomes cy.signUp(user), right? Signup. SignUp with the user. SignIn with the user, right?

Still, we're still gonna have the problem where we go through the process of the test run and stuff doing all of this. But now we can basically take all the times that we have these three lines of code, replace it with a simple function that is arguably even more descriptive, right?

And this we'll go ahead and we'll visit the page, and do everything we need to do as well, and so our tests become, they sign up, then they sign in. And maybe your process is like when they sign up they get redirected totally. Mostly that does not happen in this application, so that I could make exercises for you to do, [LAUGH] right?

So then we can make sure that it has the right path name, right? And so we can kind of do each one of these but we can basically start to cut down on that code, so this becomes a lot simpler as well. In this case it's also, All right, so now what was that long file before?

That was like randomizing a username and leaving the database a mess. And doing all these repetitive steps is a lot simpler, it's easy to go like, they reset the database before you signup, and they signup and they signin. And they should be at the page, and then email should be on the page right on, right?

You wanna get to the point where your tests are readable. And so this is one of the ways that help us get there as well, right? And like I said, even when we say like getData, you can make any command that you need for anything you find repetitive in your code base.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now