Testing Web Apps with Cypress

Basic Practice Solution

Steve Kinney

Steve Kinney

Testing Web Apps with Cypress

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

The "Basic Practice Solution" 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 live codes the solution to the Basic Practice exercise.


Transcript from the "Basic Practice Solution" Lesson

>> So we've got a bunch of empty tests and I guess if I go over to this by via the rule that we talked about earlier they will all pass because they will not fail. That is a good enough reason for them to pass. That said they don't spiritually pass, they only technically pass.

So let's actually go ahead and play around with some of this. I alluded to this before which is out of the box right now this add item button is disabled. But as soon as you put something in there it becomes enabled and adds itself to the page, right?

Again, the nice part about Cypress pausing at the end of a given test run is you then get the page in that state and you can kinda go visit it and figure out what you wanna do next. So if we tried to just click on that button by ourselves, right?

So let's go over or grab it. So stay test add item. You see there's only one of them on the page which is super helpful. And we'll go ahead and we'll just click on it. Right and you can see it's trying to click on it. It's gonna try for about four seconds to click on it, and then it gives up and then runs through the rest of the tests.

So you can force it if you really wanted to, because again it is using JavaScript to do this. But we don't want to because we wanna test to make sure that it is in fact not working. So that's cool, but we know that if we type something in there we should be able to make it pass, right?

So let's go ahead and grab that element again. That's the new item input. Just copy that over. And we can paste it in. And in here we're gonna type recite good attitude. Good attitude. One of the things I could do in this test I do a lot of my unit tests as well is like values like this that I want to like type and then also verify those screen.

That is a good argument for something I should just store in a variable because I don't want my ID the beginning of the day, I will spell all the words right both times. Towards the end of my workday, I will not spell one of those words, right. And I will think that my code is broken because I spelled out a tude with like three T's, right or something along those lines where my code works just fine.

It's just my brain that doesn't. So we could say like this case item. And the reason I point this out is to kind of point out that this will totally work. And I can verify that it's on the page, so I could say, like, even item, and that will go through and I can see that it passes in this case as well.

I can store values in variables. It's one of the other subtle ways. that the site I get is a little bit different than just a jQuery selector. Because again, all this code is a synchronous, it just looks synchronous, right? But effectively all of those Cy dot whatever methods are effectively kind of going on to a stack and be run one after the other.

Which means what I can't do is that old move where we do a jQuery selector and then we stored in a variable with like dollar sign whatever I was looking for, right? Because in those cases you will not have access to that value because these are really just getting put on a stack somewhere else and executed yeah as it works the queue as it runs through your tests.

We will talk about how to get around that in like literally the next section after this. But for now just know that you can store regular primitive values in variables and they will have closure scope And that will work, but the actual like Cypress chains, you kinda need to think about the chain all the way through.

The other thing that I will kinda we'll see in a little bit as well is that, if I do need the underlying raw value of like, for instance, this jQuery selector, or whatever, or for the other things that we use, if it's the path name, and I literally want the string.

You can do a dot then where you'll, I don't know what I would get after typing I think I get the element this case. Where you'll actually get the underlying value that's in that chain. It looks like a promise, it feels like a promise. It's technically not a promise, right?

Again, you'll notice that we're not writing a wait before every single one of these sentences. As if we you know you might see in like puppeteer or something along those lines because Cypress is taking care of all that for us. And so that is once you get that mental model in place very convenient it can be saving you a lot of extra typing and a lot of headache, but there is that kind of just mental framework that you need to have there to understand that anything that's in a chain that Cyprus is calling, it needs to run through that chain.

You can get to the underlying value with it then, but it's not technically a promise, it's why you don't need to use a weight. Yeah.
>> When you say chain are you brings to the seems like the Cy.methods.
>> Yep.
>> That scope.
>> Yeah, get the input, and we chain on, dot type.

You could do additional things, you could be, okay, go ahead and get all of the checkboxes, and then go get me the first one or the last now you'll with that chain begin to scope it down.
>> And this execute something like a promise dot all.
>> With the shiny methods where it it just grabs them and-

>> Like, spiritually yeah,
>> Yes. Okay.
>> Yeah, technically no.
>> [LAUGH]
>> Right. Yeah, cool. So then we want to say not only will it put it on the page, but we want to make sure that by default one goes on the unpacked items, right? If I'm adding it on there as well so in this case we kind of can take pieces of this and we'll talk about how to take common things that are happening in your code and make abstractions around them.

Like for instance signing up a user we're login and user is something that maybe you wanna like manually do once, but you might need to do it for every test that's starting fresh, right? There's a way to go about doing that. We're just not totally there yet, but we will get there as well.

So before I clicked on that button, which will submit the form. The other thing that I can also do is I can just go get the form element in this case, there's one of them on the page. Might have a slider as well, but I go ahead, I can just submit it.

And then what I wanna do is see that the unpacked items contains the word that I typed into the text field, right? So it's a little, before we're just like, does it appear on the page Now I'm like, did it appear inside of a particular parent element? All right, so we're getting a little bit more granular here.

So here we'll say this one I happen to know off the top my head is items unpacked. So go find that unpacked items list kind of just to see this one over here. And we want to see that when they add a new item and submit that form that appears there not just anywhere on the page, but where it's supposed to go.

You know, we can make additional assertions about that as well. But you can see I can, I can chain that contains just like I was alluding to earlier with that item. And then it runs through the remainder of the test that don't really do anything. All right cool and it should be the last item on the list.

And this is where the question before about the chaining stuff goes along. We're getting increasingly granular about this so this. So I am going to do a little bit of copy paste action here. And take what I have at this point and we wanna make sure that it is the last one.

So go ahead, get me. This data test and find me the list items in there, right? So this is the same way jQuery works where I can start to at any CSS selector effectively works in this case. So go to the overall list of unpacked items, get me all of the list items.

Now we have multiple ones and this point, I'm looking in that scope, right? And go get me the last one. Let me verify that chain is in there. And it passes, you can see it there for a flash if I skipped or paused the other ones. One of the things you'll notice is I have a Visual Studio Code extension.

There's like three or four of them, actually, if you want to see the most recommended ones, they're on the Cypress, Doc's web page, as well. So you can see, but they're the same thing works in a lot of other unit testing frameworks. Again, it's just smoke and shy, where you can do it or describe that skip.

Or it.only and if you put it .only on an int or describe, it will only run that test or only run that block of tests. If you put a skip, it will skip those tests, right? So if you're working on one particular test that's being problematic, and you don't want to run all the other ones, you could theoretically, I mean, you should use an only or a skip.

The only reason that I didn't do that in this case is because we have so many empty tests like having you manually delete skip on every single test felt rude. So I didn't do it, but in your own day to day as you're writing one test at a time, you might definitely find yourself doing that.

All right, cool. So now we want to see with this filter field, right? It's a different describe block and describe blocks, they're useful for the before each and stuff along those lines. But the major purpose for our case is they sort and organize my tests for me, right?

That's it. In this particular case, I could have a different before each and here, you can see this is nested inside of my major one as I alluded to before. So they're all still visit the page but then after that I'm using describe blocks predominantly for sorting. All right, so we want to type something into the filter field and then make sure that things that match that are on the page.

So again, what I would do in this case is I would go, I would find the filter field where we have data test filter items. Go ahead, copy that over. And now in here we'll type, .There Tooth, one other things I'm well aware is that, yeah, I accidentally imported something because I have auto import set up.

So I just deleted that real quick so I'll type in the word tooth. And then if I just look at the dummy data on the page, I can see that there is toothbrush and toothpaste, they go hand in hand, packing one without the other, it's just not a good thing.

All right, so I want to verify that both of them are on the page and then we'll probably test the opposite of that. But let's go and this is what we talked about before. I don't need to make an assertion. I think that should be there because the act of them being there is good enough.

So here we'll say that side contains toothbrush could I scope it to just the unpacking of the packed items I guess, but the filter works on both. So you could scope it down a little bit as well. Sometimes it's, yeah, you can get super technical about how you want but if anything moves on the page, your tests gonna break while the functionality didn't, right?

So a lot of this is a game of loose enough that things aren't breaking for silly reasons, but strict enough that it's actually testing the thing. And that will be a recurring theme that we talk about today as well. So we'll verify that that is on the page.

You can see that one still passes. We'll run through real quick so you can watch it all. Play through all of it, and then the blank ones that go as well. So then we wanna verify the thing that does not exist. So if I type in tooth, I should not see a hoodie on that list.

All right, I'm already wearing the hoodie, it does not need to be taken care of. So here we'll honestly we can grab this part. Because it's again, a similar piece and I guess that there is a lot of shorthand, this feels tedious to type in the data attribute or to keep doing every test.

And I'd love to just give it a short name, or I'd love to do multiple things in a single command. Those are all good feelings that you should think a lot about and meditate on at some point. So we could say, cy.contains. Hoodie should not exist on the page, right?

It is still in the data structure somewhere but it is not on the page, Cypress is only going to your page as a user would do it and seeing if it is presently on the page. If it is hidden from CSS, it is not on the page because your user wouldn't know.

If it is in your Redux store, but currently not being rendered, it is not on the page, right? For any effective things as far as a user of your page is concerned, it's not there. So we can go ahead and we'll save those as well, we'll check it in a second see so that we don't have to keep watching, we'll see.

If I made a boo boo, we'll find out when we go look at the test. So then we want to go ahead and remove all the items, right, in this case there's a button that we would like to click on that we would go ahead as well. There are other ways to do this too, you could theoretically go ahead and find a specific one.

So one of the questions Jesse asked before is, what if I wanted to iterate over them, right? So here we're just saying, does this page contain it, right? I could scope it to packed or unpacked items. I could do a whole bunch of things along those lines, right?

I have these selected for unpacked but there's also one that's just items which is both lists, it's the parent element of both those two lists. So one of the things that I could do in this case if I wanted to make this test a little bit better and then we'll get to remove, just thinking about that question a little bit, is I could go ahead and I could, So I've got that little helper here.

Right, we'll say items. And in there, go ahead and look at all the list signs we did first and last before, right? So I could do also each which is going to give me the underlying jQuery object. Or I could get each and every one of them and work through it, right?

And so it is not the underlying DOM node, it is the jQuery collection, which a lot of times, if you ever wanted the first one, you have to get the index zero out of it, so on and so forth. So I'm gonna take the old jQuery thing that we did which is put a dollar sign to simulate, to say this is not a string or anything like that, it's a jQuery value.

So tent I could also say the syntax a little different should work for those chains if I want to actually just take a value like I would do it in a unit test, in that case I'll use expect. The same effect in there but should is useful for the change we're gonna parse that string in.

Expect is like, no, I'm basically writing this as almost as if it was a unit test because now this isn't the, when I'm working through it, I'm getting the jQuery object like the JavaScript object, right? And I am going ahead and evaluating and calling methods on that object.

It's why I would still probably just make sure that things were on the page where I thought they were, but this is mostly to demonstrate how you might kinda go down one level deeper in the cases that might necessitate it. So I could expect that items, or item, really, right, cuz I'm iterating through them.

Item, not in caps, not yelling it, item.txt which is the jQuery method in this case, To, let's go with include ('Tooth'), right? Let's verify that that works in this case, right? It goes through, they all pass, right? So you can get a level deeper if you need to.

That's what we're talking about that dot then before, right? If t's one value, dot then will kinda give you the underlying object that you can play around with versus the Cypress chain, which are again, kinda abstracting as you continue working with it. You can return something out of here, kinda like you would with a promise and then continue chaining on it as well.

There's also a method called site.wrap, and what site.wrap will do is it'll take anything that is not a Cypress chainable object and it will make it a Cypress chainable object, right? So if you need to pass something you continue chaining on but it's not a Cypress return value, you can do site.wrap, which takes whatever value it is and wraps in something you can continue a Cypress chain with.

So if we needed to do something here as well, I would possibly, this is expect, it's fine, but I can continue chaining if I had a value that I wanted to kind of pass on. If I wanted to get a particular property and there's some syntax for that as well that we'll see, I can do all those things as well.

So you can drop down a level deeper if you need to. But I would also question whether or not you need to sometimes, right? I did not get anything done really with this test, I couldn't do otherwise, other than demonstrating that. It's one of those things, do you always need it, no.

When you need it, do you definitely need it? Sure, all right, and so mostly putting a mental note in your head that it exists, and then we can kind of think about it like that. So there is a button to remove all the items in the page. So this is, again, where I might do a similar selector thing.

But I don't have to iterate through all of them. I'm gonna be like, hey, if I remove all the items from the page, and I go to get all the list items, there shouldn't be any, right? So let's go ahead, let's grab that as well. And we'll say Remove All.

Take that and we will click it, In this case. And then what we basically wanna say is that those items, again, it's just CSS selectors, right? It's just that getting a data attribute or any HTML attribute in square brackets is long and gnarly. But go through and there shouldn't be any.

All right, and we'll go through, it's running through all my tests in this case. At this point, there are none on the page as soon as we click that button, right? One of these extensions lets me just click something to add .only, you can choose to install that or not.

That is, I forget which is exact one of this, I think it's the Open Cypress one, you can see the ones that pop up here as well. Cool, remove individual items on the page, all right, you can get a sense of how this is gonna work, right? If you look, I can remove an individual one, I will click on it.

In this case, it should no longer appear for that given one. This one's gonna be kind of interesting because I wanna make sure that it's the one that I chose not to have on the page, right? So here we're gonna have to say like, go get me a given one, and then click on it and make sure that that one is not there.

Cool, so let's actually play around with that one for a second. So this is one of those things where we need to basically store it. But we can't store those selectors in variables like we could in jQuery cuz of the whole asynchronous thing that we talked about earlier, right?

So we're going to have to be, click this one, but then I wanna make sure that one is not on the page. Which one? Normally, we would store it in a variable, right? Can't store in a variable we're just talking about, so we need to figure out some of the various components and concepts that we're talking about is trying to see it in practice.

So here we will say, we'll go ahead and grab a given one or let's do all of them, right? So I'll do gda, this is my shortcut. And I'm gonna say go through all the items, get each list item. And then what I wanna do is iterate through them.

And that will give me just the, I guess, dollar sign alive, cuz it's the jQuery object. If I'm gonna follow rules that are not forced upon me, that's what I should do. So we'll go through, and that's now a jQuery object, which is a JavaScript object, and I wanna chain it, which is the method that we can use to, how do I ask a question by giving you the answer in it?

Take something that's not a Cypress chain and turn it back into a Cypress chain.
>> Wrap?
>> Wrap, all right? So here we can see site.wrap for the first time in action. Where, cool, as we iterate through each one, go through and find it. And we wanna make sure that it is not on the page.

All right, so now I've got just that one in this case. And so what I might choose to do here is I have it. I wanna find its particular ('[data-test="remove"]'), right? So now we have that given li, we wanna find its removed button, and we want to click on it.

Well, we wanna make sure it's there, and we also wanna make sure that it's not there anymore. So click. At this point, we could theoretically, Let's try this out. I remove it and then make sure that one is not on the page. Let's find out together if that works.

And it does, right? So now we can do separate things with it. We have it in that closure. We can do each one of those one at a time. So basically, it's going through every single one. It's going ahead finding its remove button, it's clicking it. And it's verifying that that one is not on the page as well.

We will learn about aliases imminently. And when we do, there are ways to say, hey, this thing, I do need to store it with a way to reference it later. It just won't be in a variable, right? So that there are other and a lot of times, slightly easier ways, depending for doing this for common things on your page, right?

Like if you had a complicated form or a login screen, you'd just wanna find these things constantly. There are ways to do that as well. All right, for some of these, I think we've gotten the major point here. We can verify, in this case, that clicking on it should move it from one list to the other, right?

I think we've got the core concept here, right? And so we'll leave some of these as a further kind of exercise. There is the completed examples if you wanna kinda check different ways to do it. For some of them, there's, here's one way to do it, we can get increasingly, we can try a few different ways in that as well, but for the sake of kind of, I think we've gotten this kind of core piece.

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