Transcript from the "Cypress e2e Tests Walkthrough" Lesson
>> And, so I'm gonna collapse all of the code in the project. We're gonna go into Apps, and then we're gonna go into dashboard-e2e, so your dashboard. So if you have an app, then NS will generate and end to end project right next to it, because they are totally separate applications.
[00:00:19] And then within the source of this, you have four main folders, Fixtures, Integration, Plugins, and Support. And within the fixtures, this is pretty simple, that we have just a JSON file. So in this case, it's widgets, and I just have some mock data. So I think it's really interesting that when you talk about building production applications, how often this concept of mocking out your endpoints and mocking out your data comes up.
[00:00:56] So even in end-to-end test I'm creating a fixture for my test data. And then, within the integration folder, this is where I'm putting my test. So I'm gonna open this up, but I'm gonna talk about something else before we get into this piece. And that is, within our support, I've created two new folders and because this would all be in one folder if you generate it.
[00:01:26] But I separate these out is I have this file called widgets.po.ts, and so what po stands for is a page object. So this is a pattern I believe that Martin Fowler is maybe one of the first people to write about it. But one of the big problems that you have in index testing is that the templates that you're testing change, is that you move a button over here, remove a field over here, etc.
[00:01:56] And, so what you're doing with a page object. So I think of this a lot of times is like a facade. Is that you're obstructing those details of interacting with the application from the rest of your test. And so from here, in terms of the test itself, what I can say is getWidgetsList.
[00:02:18] And so, in the code, I'm certain this is in here somewhere, I'm just gonna get Widgets list and I may not, let's go with get widget, so getWidgets here. So this one, getWidgets, and I'm getting the list, and then I'm getting the list item. Well, when I need to call this over here, then I'm saying, getWidgets.
[00:02:45] So in respect, I don't have to worry about under the hood how I'm interacting with my template. I'm just saying, should list all the widgets, but all of the implementations that low level details is been obstructed out, and it's handled here within the page object. So the page object surfaces an API for you to query the, to say I just want you to do this thing.
[00:03:11] And it's going to do it without having to worry about the underlying details. What this also allows you to do is it allows you to share this functionality across multiple test. And, so what this does is, you're no longer having to write this selector over and over and over.
[00:03:29] You can now re-use it across your test. And so you'll notice here that these are all action kind of like verb thing. So it has this kind of verb-noun format, it's like, get this. So get the delete button, select the widget, clear the form. And remember, I mentioned that it handles some of the sequencing.
[00:03:55] And, so I think of this is in a way, kind of like an opportunity to write macros. So for instance, if I wanted to fill the form out with a new widget. I'm able to stack these commands into a single essentially function that I can call and say complete new widget form, create widget, update widget, etc.
[00:04:17] And, so this allows me to extract out the low level details for this spec, that from here, I'm saying, do this thing. And then, I can write my assertion on it. And, so you'll notice here that also now my tests are becoming very small, very specific in that, Underlying within my test itself, it typically exists in this format.
[00:04:49] It should do so, I'm describing what I wanna do, and then I have a single command. One, or two commands, that is performing the actions in the test itself and then performing an assertion. So here, selected which an item clear the form assertion. So typically, it's one or two commands and then my assertion underneath of that.
[00:05:19] Now, what you'll see here as well, that within this, I have a before block, and I'm doing a couple things. So one thing that I'm doing is I'm doing site.fixture widgets, and I'm taking that JSON, and I'm assigning it to a local property here. The reason why I'm doing that is because when you have the mock data locally, and then within the application you can compare those together.
[00:05:52] And, so this is how you can essentially write your assertions. So what I'll do here is, when I call get widgets because I have two copies of the data is, I should say getWidgets should have a length of the widgets' link. So I'm now comparing the UI against the sample data or the mock data that I've loaded into my application.
[00:06:17] And, so now even if you look here I have in my page object, I predefined some additional state that I'm loading in. And, so this is why when you're running into and test is that you have your application state or your presentation state and then have like a local version of that you can start to compare against.
[00:06:38] And, so this is why I'm calling fixture then, and I'm building that up. And, so this is why in somebody's case that you'll see, So, I wanna delete the updateMockWidget. So, I'm saying go find this in the list and delete it, and then, what I'm doing is I'm saying, this should no longer exist in the list.
[00:07:02] And, so that is what's happening there. Now, one other thing I'd like to point out, is that, if we go to, our code here, this is in the page object. Is that I'm calling cy.create entity, update entity, delete entity. And this is kind of interesting, because this looks just like cy.get.
[00:07:31] And, so cy.get is a built-in command that allows you too as you can imagine, get something from the DOM. And, so we're saying get the form save button and then issue this action on it. So, cy.get, but create entity, update entity, and delete entity, they are totally custom and so for me, this is mind-boggling.
[00:08:00] Is that whereas I think a page object is like a facade. Cypress commands or custom commands is like a shared common service. And, so if I were to do like an item's feature and test this, I would still use cy.updateEntity. Where did this come from? Well, thank you for asking that.
[00:08:23] If you go into your commands folder, and you click here, that you have the ability to add custom commands that are now available to the rest of your application. And, so this is incredibly convenient. And, so what I did in this case for me to mock out the data and mock out the endpoints, because this is all happening automatically is that, I'm saying I wanna create a command called load data, and I just pass in an array of the models that I want to load, which is then calling get entities, and I'm starting the server.
[00:09:09] So you start basically, you're signaling to Cypress like start the server, and then set up this mock route. And, so this is a way for me when I say in this command when I'm saying load data widgets. But if I had like items, so let's say I needed to load more than one is I could just go items like this.
[00:09:31] It's gonna loop over, and it's automatically going to set those routes up, but, this is done in such a way. So remember, talking about abstracting complexity that I can use this over and over and over. Is that I can say, load data, and then I can because it's restful, I know what the URL is gonna look like every single time, it's the base URL plus the model.
[00:10:00] And, so I'm able to not only set up the server, but more so this mock route to grab this end. Not only that is, I'm able to say set up this mock route in return, dynamically return this fixture. And, so I'm passing in this case widgets. And, so it's saying when you get to the base URL for /widgets, return the widgets fixture in its place.
[00:10:29] And I'm able to do the same thing for post, put, and delete. And so very, very quickly I should have a total standalone module almost like how to mark out quickly APIs in Cypress. I was able to do this in I don't know, 10, 15 lines of code.
[00:10:51] Maybe a little more, maybe 20, and what's interesting here is that when you add an entity. So let's say, I have an entity and I want to add it into my list is that, I have an additional method here at entity that I'm saying give me all of the entities, but then I want to return those entities.
[00:11:14] In addition, I want to add my mock entity on top of it. And, so I'm just doing some very simple data manipulation. But this allows me when I run my test to essentially simulate all of the interactions that I have within my normal rest, server communication, back and forth.
[00:11:37] And, so Cypress commands if you think of page objects as facades commands are like your high level services that you can then share across your various spec files. Now, the lesson that I wanna show you is that The one last thing that you have to do and this is very, very easy is that you have to instrument your application to be what I would say cipher's friendly.
[00:12:13] And, so what I do here is, I will go through and as I'm developing this is that I use this data-cy. And, so I've seen data, test ID. There's a couple variations of this, but this is what's recommended by Cypress and I give it a very specific name.
[00:12:35] And, so I'm not even using, like CSS elements, whatever it is that, I'm able to go in. And within the page object, I'm able to essentially grab this, like this widgets list, and it's [SOUND] instantaneous. There's no like maybe this is something else or whatever because I've seen it.
[00:12:59] And because your selectors are brittle, it falls apart. So in the HTML itself, you just go through, and this is the only thing that you have to do, so setting this up is very, very non-invasive. And I would say that engineers, if all they did, was went through and put test IDs on their DOM, that would be a huge step forward.
[00:13:22] And, so you'll see here that I'm also setting up some additional test IDs, but they're dynamic, and so this is another thing. So instead of going and saying I wanna grab the third item in the list and doing this weird like array like zero or one and trying to basically guess on the index.
[00:13:47] What I'm doing is when I loop over this is because I know the ID of the widget, I'm baking this into the selector. And when I'm looking for that, because I'm using essentially that same ID, then I can say go find me this particular element based on the ID, versus instead of like having to count and find it or iterate over it.
[00:14:13] It's just is like laser sharp, like, go and find me the widget that has this ID. And, so if we go back into our page object, you'll see here, getWidget item while I'm passing the widget in. And then, I'm dynamically using a string literal, returning that specific thing.
[00:14:35] And, so you'll never have to guess and count what the number is in the list and say well, if there's four, and I just added one now there's five, and I got to guess. I've seen a lot of that mental assumption in references happening and this is how it praise.
[00:14:51] And, so the first step Is instrument, your templates in a way that you can refer to it by name, in this case by ID, and look it up immediately. And what this allows you to do now is you essentially, you decorate your DOM with their IDs then, What I do in the process for this is I first decorate the DOM, then I go and write my page object.
[00:15:22] And, so I'm saying these are the things that I need to get and then from there within my spec, I describe all the interactions that I want and then wire up. I page ups or commands, assertions, and you're good to go.