Testing Web Apps with Cypress Tasks & Seeding User Data
Transcript from the "Tasks & Seeding User Data" Lesson
>> Cypress has these things called tasks. And what tasks allow us to do is, effectively define certain things that are gonna go on, that are gonna happen in node js, right? So things that are happening for setting up our server, anything we need to configure outside the browser itself, is contained inside of a task.
[00:00:25] And then within our tasks, we tell Cypress, l go do the task, you see the database for me go to the task like whatever. It's not just about seeding databases that is the, version that I had to deal with, right? Most commonly, but like there's probably something in your application that needs to happen.
[00:00:42] Some other servers I might need to get spun up, or something along those lines that is not necessarily in the browser. And so before in that plugins directory that there was kinda on and config, and we could do stuff in there. So one of the events that you can listen for, is any time side that task is called.
[00:01:06] And that will be called with some test name, which is gonna match up here, and then possibly I don't have a use case. So in possibly some set of arguments or whatever, right? That you would need to do. This could be like what? Environment with what kind of things that you might pass towards, whatever node call that you were doing.
[00:01:26] You can pass those in as well. Now, I should talk about a kind of edge case right now. My prediction is that this is not a permanent edge case, but it is a current edge case. Which is for probably most apps that have been around for a little bit, Cypress probably good to go.
[00:01:49] If you're using TypeScript, you're probably good to go. The interesting edge case is, if you are using ESM modules in a node project. So, if you have that type colon module that you wanna use import, instead of require. Like right now, it gets very angry about that it will restart a plugins.js, a plugins.ts or plugins.coffee.
[00:02:20] That's the life you wanna live. That is an option as well, but like the, normally the way it works with the ECMAScript module settings and node. Which is if you opt in to ECMAScript modules, like import whatever from file, then if you wanna use the old require our module exports, you have to call it CJS.
[00:02:38] And if you opt to the CJS, you have to use MJS for the modules, right? Right now, if you're using ESM modules in node, it doesn't know how to deal with that because it wants to use modules.export. That's the way it's all wired up because, it's using I think Webpack under the hood for all of this.
[00:02:57] And then like, the conflict between what node expects, and what Cypress expects gets very angry. So a few options, one is keep your project using common js out of the box, you don't have a problem. The other option is, if you type Script, you can call the TS file everything will work.
[00:03:17] The other option like is you can just keep this file as TypeScript, and that'll work as well. My sense is there's an open issue, my sense is this will be a thing that is solved. So when we work on this application, because Falcon uses ESM modules out of the box, and so a lot of other things.
[00:03:36] We're gonna use TypeScript, the app is also in TypeScript. Anyways, it was there to do, but in an older app, we're just using common js will work. My hope is that in a short enough period of time that this bug, will have been resolved. But at this moment, if you see me in a TypeScript file, that is why that's, there's no other like big reason around that.
[00:03:55] My sense is that it will resolve itself. So, if we look in here, we can register, there's basically one task event that we're listening for. So, anytime someone calls side a task, right? We have a bunch of keys on an object. They're gonna give me some string, reset, seed, cases that we're gonna use and then, it will do something in this case.
[00:04:18] And so we can kinda look, I have it in here already in the code base. So let's go actually take a look at how, it is in practice here, and so just for the SQLite database, I'm using this library called Prisma. Which is just an abstraction over SQL, we can define a schema, it gives you migrations basically meant that I didn't have to write a bunch of SQL myself.
[00:04:39] Which was the end goal because, it's not a course in SQL, it's a course in Cypress, so I didn't do any of that. We'll look at those files in a second, but basically I'm importing the two files. And I am saying that I wanna have a task called reset, which is gonna reset the database.
[00:04:54] I wanna have a task called seed, which is going to seed the database. Now in my task, I can say, hey, wipe the database clean, right? Or wipe the database clean, and put a bunch of stuff in the database get me to a known stable state. For all these things, as I kinda mentioned in the preamble, if you're willing to mock and stub all of your network requests, you don't have to do this, right?
[00:05:22] But if you are trying to do l, effectively, like a true end to end test where it's hitting a database. And I'm using SQLite, mostly cuz I didn't wanna make, y'all fight with Docker for this course or something along those lines. But, you could theoretically spin up for a adoption like Docker environment, I think the Docker file is in this repo.
[00:05:41] If you wanna do that instead, there is a version of this that works to Postgres just as easily SQLite. But basically, this is calling the things that we are important, they happen to have the same name. Now, it does need to return a value. We'll go look at one of these files in a second, but basically it's using Prisma's Syntax for either deleting everything in the database.
[00:06:03] Or deleting everything to database and adding some stuff. But you'll notice that I'm calling them, they are returning a promise, right? Your tasks do not have to return a promise, but if you want something to like resolve before you move to the next thing. Which is important for me seeding the database, right?
[00:06:20] If it returns a promise Cypress await move on, for that promise to resolve and then move through everything. These files themselves are kind of like outside the, core part of this workshop. I'll just show them to you, so they're not complete black boxes. So for seeding it, I am deleting all the posts, I'm deleting all the users in one transaction, so it goes through.
[00:06:46] And then, I am putting in a first example.com with a password, and some posts attached to it. And I'm creating a second user, right? Nothing particularly interesting here, and if you're calling it directly from the command line, run that function. Otherwise you can import it, as one that gives you a little bit better error stuff, right?
[00:07:05] That's again, that's just the syntax, I'm using to see the database. The details for the app you work in, are obviously gonna be a little bit different. But whatever you need to do, to get that kind of environment set up the way that's supposed to be set up.
[00:07:18] You can basically write those files, they happen to be in this Prisma folder for me. And then the more important part is in your Cypress directory, in that index file, you can register them all on the task event. As I mentioned before, there's basically three ish things that this does.
[00:07:40] You have task events, you have all sorts of before we run a task. After we run them all if you needed, like transpile stuff, because you were l writing everything in closure script. And you needed to like go transpile it, before you run it. This is where you would write all that functionality, will also look like Cypress has like a page with a bunch of plugins, right?
[00:07:58] For all sorts of different things that you might want to do, if you want to write cucumber tests. And have them like translated into site like whatever, right? That's all this all the functionality that question we got earlier in the workshop, happens kinda in this file in the things that you pull in.
[00:08:15] For us, we're just gonna register a bunch of tasks in here, right now. And so we have those in place, so if I go back over to my test in this case, let's say I go integration. And let's do this with the task, I want to replace this.
[00:08:35] What I wanna do is, put the database in a known state seeded with the users, I believe should exist, right? And with those users in place, then what I wanna do is, go ahead and log one in or create them, right? And so that's why I have two, there's one that I'm gonna do, and one that you can do.
[00:08:58] So for instance, if I want to sign up, well then I probably want to just reset the database. I don't care if there's any users in there because, I'm gonna go fill in some fields I really don't want even risk colliding with one in the database. Just empty out the database, we'll go create a user, so on and so forth and then you can do sign in.
[00:09:16] Or set or any reset, we'll run seed we'll change this to one of the known users that we can see in that seed file. And we'll kind just see both of them, so sign up this is like once we have that plugins file in place. It's super simple, and what we'll do is before each.
[00:09:45] All that we need to do, is do side task in this case, we're gonna do reset, right? So now knowing that it's an empty database, I could theoretically just do firstname.lastname@example.org every time. And I know that it will work, the database will be completely emptied out each time and then, we'll go ahead and we'll make a user.
[00:10:12] Now, I'm not doing weird stuff with data now, it still feels a little bit tedious. But we have that as well, let's just go verify that all that works. I didn't make a boo somewhere along the way, so we'll go with task spec. Right? You can see that they are signed in this case, so they signed up and then they signed in with that.
[00:10:39] I can go ahead and rerun this test. And the database will still be in that known good state. It's a little frozen again. So I'll probably kill it in a second and restart it, but we are clearing out the database and we are resetting it every time.