Transcript
00:00 First, let's create the test user credentials by declaring this variable called user info and calling generate user info to get the value. This function generate user info is another helper and it will generate random username, the full name, and an email for our user. So once we have this, all we need to do is also create a password. So let's create a password and assign it a value of Super secret to be, well, Super secret in our tests. So these are the user credentials, but the user record in the database does not exist just yet.
00:30 To make it exist we're gonna use our Prisma client. Since our application uses Prisma, we will do prisma. User. Create and provide this data object with our generated user info and a password. In this case, we're gonna hash the password because that's one of the best practices how you store passwords in a database.
00:48 So once we do this step, now this user variable, this user object is actually a pointer to the user record in the database. And all that's left for us to do is to return this user object and also the password because the password will not be returned on the user record. Now if we leave this utility function as is and use it in tests, it's gonna create this user in the database and that would be it. But following the best practices of testing, we also need to clean up after ourselves. Something somewhere along the lines has to remove that user from the database so we don't pollute it every time we run the test.
01:20 Normally, you would achieve this by deleting the test resources or maybe dropping the entire database in test hooks, like after each or after all. But we can do something better today. Let's go to this object here, this one that we return, and make it a disposable object. Disposable objects are part of standard JavaScript API, and what they allow you to do is collocate the logic that you have, for example creating a user, and the cleanup side effect, basically the dispose callback, in our case deleting that user. To make the object disposable, we will declare a special key here with the value symbol as sync dispose.
01:54 This will let JavaScript know that this object has to run this logic here when it's being disposed, in other words, garbage collected. In the context of our test, this callback will be called when the test is done running. So what we gonna do here is delete the user from the database. I will use this call from here which will call await Prisma user deleteMany and provide the proper query targeting the user by ID, user. Id, which points to our test user that we just created.
02:25 Of course, I'll make this callback as async so we're able to use the await syntax. So by doing this, we're able to co locate our utility logic and the cleanup for that logic which I think is pretty neat. We don't have to spread it across our test setup anymore. Now that we have this create user utility, let's put it to use in our tests. I'll head to this authentication basic.
02:45 Test. Ts and complete this first scenario. It says authenticate using an email and password. So we're talking about a successful authentication flow. So let's start by creating a test user.
02:55 Over here, I will create a variable called user and assign it the result of calling out createuser utility. Notice that here I'm not using const, olet, or war, I'm using the special keyword that's called using. With this special keyword, I'm letting JavaScript know that the user object here is a disposable object. This way JavaScript will know to dispose of this object in a special way, in our case by deleting the test user from the database. Now that we have the test user, let's navigate to the login page using our navigate fixture and providing it the path slash login.
03:27 So once we're on this page, what we want the test to do is to interact with it the same way the user would. And that means filling in the login form. So to do that, we will use the page fixture to find the input elements. There are multiple ways to find input elements. You can find them by role, the text box role, but what I prefer doing is finding them by the label text.
03:47 This means that we're also making an implicit assertion that our input has an associated label in the UI and this helps the user to understand what is this input stands for. So here we're gonna use await page get by label and enter the label. In that case, it's gonna be username And once we done this, Playwright will return us a locator as you already know. This locator allows us to do multiple things. In this case, we'll do dot fill and fill in the value to that input.
04:14 So here we need to put the username. It's not gonna be any fixed value. Instead, we can refer to a user that user created and get its name property. You can notice I'm using this exclamation point here to let TypeScript know that this user and its name will be defined. This is to make sure that we comply with the types because when we retrieve a user from the database, we don't know if they would exist.
04:36 But in the test case, we know that for sure. So once we've done this, we need to also enter the password. We'll do it in a similar fashion. We will call page get by label, in this case password and enter our password so it's also stored in our user object. And now all that's left is to click the login button.
04:54 So let's locate that button on the page by its role, button, and its accessible name that says log in. And now call dot click which will click on that button. So our login form is now filled in with the test user credentials. The assertion here would be to make sure that the user, or in our case, the test, sees certain UI elements that correspond to the authenticated state. In other words, elements that mean that you have successfully logged in.
05:26 So what we're gonna do here is to find a link element on the page with the username. So I will do page, again by role, link, and accessible name, it'll be the same as user dot name. And the assertion here would be to be visible. Now let's run this test. In the terminal, I will run npm run test end to end.
05:48 I can see this test failing which is great. That means that I made a mistake. If I follow this test error, Playwright says that it cannot find this element link with the accessible name username. So one of the reasons this might happen is the authentication didn't succeed. If I take a look at my test, here's the error.
06:06 I'm actually providing username which is full name instead of user dot username. So once I do this correction and run the tests again, now it will pass. And following this example, let's complete the second test case for a failed authentication flow. So here, go to the second test closure. We'll do absolutely similar steps.
06:26 We'll go to the login page so await navigate slash login. We will try filling in the form. Let me just copy this over here. So let's fill the form. Here, we won't have any test user because we want the authentication to fail.
06:41 So I can provide anything here. Cody and my password. Then once I click on this button and submit this form, I want to make sure that our app communicates to the user that authentication has failed. One of the ways that communication can happen is through some error messages on the screen. So I head to the assertion here and write that I expect an element by role alert to have another element inside which I will target by text, by text, and this will be the error message.
07:12 So in our case, it should be invalid username or password. And the matcher I'm using is to be visible, to make sure that this error is visible on the screen. And now let's run the tests again to confirm that they are passing. So npm run test end to end. And both of our authentication scenarios pass.
07:31 One thing I would like to point out is our test user over here. You might have heard a rule that you shouldn't rely on test users, and that's a great rule, but it speaks about how you act in your tests. In other words, you shouldn't perform actions and have expectations that the real user wouldn't do or have, like finding elements by their internal attributes or making sure that some functions were called. Your users don't know about any of that. But in our case, when I say the test user, I'm referring literally to this user object that we use for testing purposes.
08:02 For all our systems care, this is an actual user that exists in the actual database. And what's important here is that how we act in the tests, how we interact with our app. In our case, we're following the actual user journey step by step, performing actions that our users would perform, and this is what makes a great end to end test.
