مرور کارگاه تست مرورگر0:07
All right. So at this point we mostly have a functional application. And when I say mostly, you already know what that means, right? In real life, you're gonna spend hundreds of hours building your app. In our case, we just have a small handful of hours to condense the entire process, and, uh, we have to take some shortcuts every now and then. But anyhow, with that in mind,
Browser testing with Pest0:22
and, uh, we have to take some shortcuts every now and then. But anyhow, with that in mind, I know the app is mostly working, but we don't have any feature tests or browser tests that confirm it's working, and that's really important to me. So we're gonna work on that in this video. Now, when it comes to physically opening a browser and confirming it works, I prefer to use pest php with its browser testing plugin.
and confirming it works, I prefer to use pest php with its browser testing plugin. So we can go to documentation, scroll down to browser testing, and yeah, you'll see when we call visit, that actually opens a real browser. And then we can make assertions, we can interact with forms, click pages, and links. You get the idea, okay? So let's go ahead and install it. We already have pest, so all I have
Install plugin and Playwright1:00
You get the idea, okay? So let's go ahead and install it. We already have pest, so all I have to do is pull in the plugin and then also playwright, which pest leverages behind the scenes. Let's open the terminal and paste that in. And, oh, it looks like it fails. So here's my guess. I bet when Jeremy originally worked on this pest, uh, version four was not out yet, so we have a conflict. Let's have a look. Let's open composer.json,
version four was not out yet, so we have a conflict. Let's have a look. Let's open composer.json, and if I scroll down, yeah, he has Pest 3.8. Okay, so here's what I'm gonna do. I'm gonna bump this to at least 4.0 and then we don't need phpunit. I think Pest takes care of that. So once again, let's bring this back and I'm gonna do a composer update. Okay? And then I'll try it one more time,
and I'm gonna do a composer update. Okay? And then I'll try it one more time, and hopefully this works and it looks like it did. Perfect. All right, next, if I switch back to the browser, you'll see that we need to add this directory to our .gitignore. So if a pest browser test fails, it'll create a screenshot. And of course, we don't want that committed to version control. So once again, let's go into .gitignore,
that committed to version control. So once again, let's go into .gitignore, and at the very bottom I will paste that path in. Alright, so now if we open the sidebar test, well, why don't we go ahead and add a new browser directory, and this is where our browser specific tests are going to live. And of course, within there as well, I can add the screenshots folder. Alright, so let's create our first test.
Create timeline browser test2:22
I can add the screenshots folder. Alright, so let's create our first test. Alright, so we're gonna switch to side by side and get started. Now I'm gonna create my first test. And generally the way I like to think of my browser test is as actions things my application can do. So for example, uh, you can view the timeline, you can like a post, and I would represent all of those as their own tests.
you can like a Post, and I would represent all of those as their own tests. So let's get started with how about viewTimeline. Perhaps we could organize these into directories, like this represents our posts. And yeah, within there I could create our first test ViewTimelineTest. All right, so let's clear this out and we'll replace it with a simple test. Test. And
and we'll replace it with a simple test. Test. And how about authenticated users can view their timeline? Alright, so yeah, we might say something like, well, given I'm signed in and I'm following another person who has posts, well, when I visit my timeline, I should see their posts. You know, that's effectively what we're testing here. Alright, let's get to work.
You know, that's effectively what we're testing here. Alright, let's get to work. I'm gonna start by building up a factory for myself. So UserFactory, create, I can say user if it helps, you can even write John, you know, you can be very specific. Well, given John has an account, we could do me whatever you want. We'll start with user. Uh, next we're gonna sign John in. So I can say this acting as user next, and I'm following another person who has posts.
So I can say this acting as User next, and I'm following another person who has Posts. So remember Jeremy has already created a handful of tests that demonstrate how that can be done. So how about this Profile can follow another Profile? All right, so given we have a Profile and another Profile we can use, follow, create, follow, I wish I could just say User follow the other User. We might even add that, but nonetheless, that should be enough, uh,
We might even add that, but nonetheless, that should be enough, uh, to create that relationship. So let's grab that and let's implement this. I'll import Follow, and then the first profile will be User profile, and then we need somebody else. So we can do otherUser again, unless we wanna use actual names, which is kind of fun too. So let's copy that and just tweak this otherUser. And now we can say, let's follow the otherUser's profile.
So let's copy that and just tweak this other User. And now we can say, let's follow the other user's profile. Okay, so now before we even write a test, I just want to confirm that this works. So why don't we say the currentUser gimme their profile. I wanna see who they are following. Um, and let's just dd that. And then I'll give this a run. So I can run this, uh, from the terminal. Of course I can run it directly within my editor.
So I can run this, uh, from the terminal. Of course I can run it directly within my editor. If I have a shortcut for that, I'll show you both from the terminal. I could just say pest test, browser view, timeline, or I'm sorry, post view timeline. Uh, but actually I bet this fails. Yes, it does. Okay, so notice whenever you see something like this Target class [config] does not exist, that means you are trying to work
Fix browser test bootstrapping5:19
whenever you see something like this target class config does not exist, that means you are trying to work with the Laravel framework. You're trying to assume that the framework is booted, basically, but it's not. And here's why it's not. If I close that out, if you go into pest php, you'll see this section right here, extend TestCase and TestCase among other things, just kind of bootstraps the framework,
and test case among other things, just kind of bootstraps the framework, resets everything, clears it all out. Uh, but that only applies to the feature directory. We also want it to apply to the browser directory. And I also want to refresh my database, uh, before each test. Okay, so now I bet if I run this again, let's see what we get. Uh, it fails. So argument one follower must be
if I run this again, let's see what we get. Uh, it fails. So argument one follower must be of type Profile, but null was given. Okay, that's interesting. Why would that be the case? So I expected otherUser. Now is it possible when you scaffold a User, they do not have a Profile? That's possible. I don't know if we have some kind of a hook. I don't see anything. Okay, so maybe as part of creating a User, you also have
I don't see anything. Okay, so maybe as part of creating a User, you also have to create a Profile to go along with it. Let's do this. Let's go into UserFactory. And yeah, I don't see anything for a Profile, but if I go into Profile, I do see User, okay, so that might be why Jeremy is thinking in terms of Profiles first before Users. So if we follow his lead there, so maybe we say here Profile,
So if we follow his lead there, so maybe we say here profile, and then we're gonna go through the Profile model that'll create a profile but also an associated User. And then I'm gonna say other profile, all right? Then we're gonna say acting as the User for our first profile. And then we're gonna tweak this and we're gonna say, all right, this profile is going
And then we're gonna tweak this and we're gonna say, all right, this profile is going to follow the other profile. And then I want to die and dump the profile followings to see what we get there. Alright, this time I'm using a um, VS Code extension that allows me to run the test under my cursor by pressing command + T, but it's effectively the same thing. So if we give it a run
but it's effectively the same thing. So if we give it a run and I scroll up to the top, you'll see ultimately it's still just running past on the current file and then filtering it down to the test, uh, related to my cursor, okay? But sure enough, I get a huge collection here that's a bit much to see. So why don't we just say followings to array, we'll give it another run.
So why don't we just say followings to array, we'll give it another run. And this time, sure enough, I am following one profile. This functionality is working. Okay? So if we wanted to just as a stop gap, I could say, well, I expect profile followings to have count of one. Even though this isn't my primary test, it's a way to confirm my expectations, right? Um, however, while we're here, let's try something.
to confirm my expectations, right? Um, however, while we're here, let's try something. What do we have on profile for following? Um, we have three followers. followings, I might even extract this into a trait, but you know what, I wonder if it would be helpful to have a follow method. This would accept the profile that we are following, and then ultimately it can wrap what Jeremy has here. So I'll paste that in. But now the profile
and then ultimately it can wrap what Jeremy has here. So I'll paste that in. But now the profile that's doing the following is this, it's the current instance and it's gonna follow the profile you referenced here. I think this might just make for a slightly cleaner API. I think, let's see if we come back, excuse me, to our test. Yeah, now I can just say, all right, profile, follow the other profile. And I don't have to think about like what is,
follow the other profile. And I don't have to think about like what is, what is the model, what is the static method? This is how I think about it in my head, and if I give it a run, I bet I still get green and I do. Cool. These are just some things you might want to think about. All right, next, when I visit my timeline, all right, well what's the timeline? It's gonna be this section right here, PostIndex.
well what's the timeline? It's gonna be this section right here, post index. All right. Visit the route post index and immediately I'm just going to debug at this point, which will open the browser so that I can inspect and see what we're working with. Okay, let's give it a run. Alright, so now I do see that we're signed in. I can see our feed, but it's currently empty. And that's of course because well the person
I can see our feed, but it's currently empty. And that's of course because well the person or the profile we are following doesn't have any posts of their own. So let's update that. So perhaps we could say for the other profile, let's add some Posts. I could say maybe three Posts for that other profile. So that'll just create the relationship, it'll, it'll scaffold a Post and set the profileId equal to, uh, this one.
it'll scaffold a Post and set the profileId equal to, uh, this one. And then let's call create. All right, so now if I give this another run, we should see um, three Posts. All right? And sure enough, it looks like we're following somebody named Damien and I do see three of their Posts. So why don't we confirm that What I might do is if I open up DevTools directly within here, if we grab one of these components here, yeah,
that What I might do is if I open up DevTools directly within here, if we grab one of these components here, yeah, what's an easy way to target it? Well, it's just a list item, right? And I just wanna say, well, there's three of the Post bead items, right? So here's what I would do. I would come back and with post view right down here, there's the list item. I would give it a data attribute, something like data
right down here, there's the list item. I would give it a data attribute, something like data-test="post", if that makes sense. PostItem, PostFeedItem, whatever would be appropriate here. How about PostFeedItem? Okay, so now we have a hook of sorts. And this is cool because rather than adding an ID where maybe you're referencing it from CSS.
because rather than adding an ID where maybe you're referencing it from CSS or JavaScript, when I do data tests, it's crystal clear that this value exists for the sole purpose of accessing this element from our tests. Okay? So now if I go back to view timeline, our assertion right here, I should see their posts would be, well, let's say, uh, and there's actually a bunch of ways we could do this. I might start by saying assertCount.
and there's actually a bunch of ways we could do this. I might start by saying assertCount. So this will give us a selector and it asserts that the given element is present a given amount of times. So yeah, if I were to say, uh, first I could do a full selector like this, but when I use Pest browser testing, there's a shorthand, I can use the @ symbol. And whenever I precede a selector with the @ symbol,
I can use the at symbol. And whenever I proceed a selector with the at symbol, it's implied that we're trying to access a data test attribute. So now I could just say postFeedItem. Yeah, and that would effectively be the same as data-test="postFeedItem", but it's a little less verbose to write. Okay? So let's give this a run. Oh, and I'm sorry, we expect it to exist three times.
Okay? So let's give this a run. Oh, and I'm sorry, we expect it to exist three times. Alright, run it and it passes. It's green. So just to confirm that we didn't screw something up. Yeah, imagine that we actually make four Posts. Well now if we run it, it's gonna fail because there's actually four Posts on the page rather than three. And you'll notice as I'm running this, it's taking a little bit of time and that's
And you'll notice as I'm running this, it's taking a little bit of time and that's because sometimes the additional posts could come in asynchronously, uh, through an Ajax request or something. So pest is giving you a little wiggle room, it's holding on and saying, all right, is anything else? Anything else? Anything else? No, sorry, after five or 10 seconds it's gonna fail, uh, because four is not three. Cool. Okay. So at this point I will bring it back.
because four is not three. Cool. Okay. So at this point I will bring it back. We only need two in this case. Uh, this assertion, you can keep it if you want. It's not, uh, vital for the test. Sometimes it's helpful just to, to confirm though, once again that your setup was done properly. Um, so that one's up to you, but this is mostly what we want. So let's read it. Given I have a Profile
but this is mostly what we want. So let's read it. Given I have a Profile and then given there's another Profile that has two Posts. Well, if I sign in as the first Profile and then I follow the other Profile, well at that point when I visit my feed, I should see two Posts from that person I'm following. But why don't we actually extend this a little bit? Why don't we create a Post ourself? So why don't we say, all right,
Why don't we create a Post ourself? So why don't we say, all right, well the person we're following has two Posts, but I myself will have a Post as well. Alright, so now we will have one Post from me, two Posts from the other profile, which means when I visit my feed, I should see a total of three Posts, give it a run. And I imagine that returns green and it does, it works. Okay, so now let's do this.
Add more browser tests13:41
And I imagine that returns green and it does, it works. Okay, so now let's do this. Uh, clearly we're not gonna spend hours writing tests that would be very boring and we don't have room for it. But I do wanna show you just a handful of the tests to get you up and running on your own. So let's do a few more. We can view the timeline. Uh, of course we should confirm if you're a guest then you are disallowed from viewing the timeline and things like that.
are disallowed from viewing the timeline and things like that. Uh, but let's do another one. I'll create, how about within the post directory? Let's add a new file for how about viewPostTest.php? So I'm gonna paste in the previous file just so I can save ourselves some time and now we can see, uh, we can say it. How about it? This time it views a single post. Alright? Once again, I'm not gonna write this out,
How about it? This time it views a single post. Alright? Once again, I'm not gonna write this out, but let's just think of it in our heads. Well, given I'm signed in and given I have a post, well, when I view that post, I should see the post. Right when I visit the page to view the post, I should see the post. Um, good enough. All right, let's do it once again. I'm gonna steal this right here.
Um, good enough. All right, let's do it once again. I'm gonna steal this right here. Add a Post to that profile. We're going to sign in. So acting as the profile User. Now, do we wanna go directly to the Post or do we want to go through the feed page and then to the Post? Uh, you can do either one, maybe one test. You go through the feed and for, uh, the remainder of the test, you just go directly.
You go through the feed and for, uh, the remainder of the test, you just go directly to the post once you've confirmed that that, um, that workflow works, right? So let's begin by saying visit the route for home, or I'm sorry, what is it? post index. And now I know I want to click on the relevant link. So once again, I don't know what that is. I'm gonna go into post view.
So once again, I don't know what that is. I'm gonna go into post view. So let's scroll down and I'm gonna look for wherever we can view the post. And it looks like it's within here. Honestly, my instinct would just be to make the entire list item clickable, make that an anchor tag. And you can still do that while having nested anchor tags. There's just some CSS tricks to allow for that.
And you can still do that while having nested anchor tags. There's just some CSS tricks to allow for that. You basically have an anchor here and then you apply CSS to make the clickable area take up the full card. Uh, but we're not gonna do that here. I'm just gonna stick with what they gave me. Um, so in this case, post show, this is how we get there. So once again, I could give it an id, uh, I could give it a unique name.
So once again, I could give it an id, uh, I could give it a unique name or I could give it a data attribute. And that's what we're gonna do. data-test will be visitPostLink or something like that. All right, let's come back and now I could say visitPostLink and let's debug and see if it worked. Yeah, I will often call debug when I'm working on my tests just to make sure, like, okay, I expect to click.
Yeah, I will often call debug when I'm working on my test just to make sure, like, okay, I expect to click that link, but did I get that correct? Let's debug and confirm, give it a run. And yes, it looks like we are viewing that individual Post and I can switch back. Perfect. So now what do I want to confirm? Uh, maybe I confirm that the URL matches, maybe I confirm that it says what I expect. Uh, whatever you want. Perhaps we could do something like
that it says what I expect. Uh, whatever you want. Perhaps we could do something like this, visit this route and then let's search that we see, um, some information about the Post. So how about I'm going to assert that I see the post content and let's see if that works. Yep, it passes. We're in business, so why don't we tweak this to it? Um, shows a single Post
so why don't we tweak this to it? Um, shows a single Post and yeah, once again we can add, uh, authorization if it would make sense. Uh, if you're a guest, do we have any logic that says you're not authorized to see certain Posts, or do we assume that all Posts are inherently public? These are just decisions you make when you're building, uh, the app yourself. Next, let's do maybe one more.
uh, the app yourself. Next, let's do maybe one more. Let's see if I can fit in maybe a couple more before we call it an episode within Post. How about, uh, like postTest? And then let's do this. Let's go back to one of our previous tests and we will steal some of that just to save ourselves some time and then I can say it likes a Post. Alright, so let's do this.
and then I can say it likes a Post. Alright, so let's do this. Why don't we say, uh, a Profile for us, uh, we have a Profile for somebody else and that somebody else has two Posts. We're gonna log in as myself, we're gonna follow that other Profile, okay, we're going to visit our feed, and then I wanna click on the like button. So let's see how we would do that. Once again, I will call debug, give this a run.
So let's see how we would do that. Once again, I will call debug, give this a run. All right, and yeah, we have two Posts and what I wanna effectively do is click on that like button. Alright, so once again we return to post view. I'm gonna look for like, there it is and let's give it a, uh, data attribute. So we could add this to the form. We could submit the form or just click the button.
So we could add this to the form. We could submit the form or just click the button. Why don't we click the button. So dataTest, like postButton. All right, switch back. Once again, uh, let's just assume we have a single Post and I'm going to say click on the likePostButton. Don't forget, you can also nest these things. So you could say, alright, well within the, uh, postFeedItem, then uh, click the likePostButton.
So you could say, alright, well within the, uh, Post feed item, then uh, click the like post button. That way you can target, and this is helpful if you have multiple posts, but you wanna click one specific like button within one specific post, right? In this case, we can be somewhat generic though. Alright, so visit the feed, click on the like post button, let's debug to see how we're doing, and sure enough, we are liking the post it works. Okay, so now let's finish up.
and sure enough, we are liking the post it works. Okay, so now let's finish up with a test not related to posts. Let's add a a folder for maybe, uh, profiles and maybe with n there we could have a test for, I dunno. How about FollowProfileTest? All right, test it follows another profile. All right. And this is gonna be very similar to, uh, some of our setup within view timelines. So once again, we're gonna save ourselves some keystrokes.
of our setup within view timelines. So once again, we're gonna save ourselves some keystrokes. I will paste a lot of that in. And yeah, in this case I don't even need posts. So I want these tests to be as simple as I can. Let's say I have a profile and then there's another profile. Well, I don't even need to sign in at this point. I can just have that profile, follow the other profile, and then yes, I can just have one expectation at this point. This is like an integration test,
and then yes, I can just have one expectation at this point. This is like an integration test, but it should return green and it does. But yeah, I wanna make sure that it actually works in the browser. And we've all seen this before. There might be an integration test that passes, but when you load the browser is not, is not working the way you would expect, right? Uh, this would be related to like a car too.
is not working the way you would expect, right? Uh, this would be related to like a car too. Maybe your unit test for the Car work, each individual piece is functioning, but then when you turn on the car, nothing happens. So we wanna make sure for a browser test, something happens. Okay? So instead of just saying profile follow, I'm gonna go through the browser, this acting as profile User. I'm gonna visit the other profiles, uh, profile page. So visit, and let's see,
I'm gonna visit the other profiles, uh, profile page. So visit, and let's see, I believe it's something like profiles.show. Yep. All right, profiles.show. Uh, presumably we need to give the other profile and let's just debug at this point. And I'm sorry, let's do visit at the top level. All right, give it a run. Okay, it looks like this is the person we need to follow next. I'm gonna click on the follow button itself.
it looks like this is the person we need to follow next. I'm gonna click on the follow button itself. So let's go to the profile page. And I think we put it in a header, right? Yep. Where's that follow link? All right, yep. So let's reformat and then, yeah, right down here at the bottom I can say this will be dataTest, follow button. All right, now we have our hook back to our test. We're going to visit that page.
All right, now we have our hook back to our test. We're going to visit that page. I'm going to click the follow button. And then at this point, if I still want to run this expectation, that's fine. So we give it a run and it should return green and it does. But maybe also visually, what do we expect? Uh, maybe we expect that button to be highlighted in some form. Maybe we expect an, uh, a flash message to display.
to be highlighted in some form. Maybe we expect an, uh, a flash message to display. So for example, I can't remember where this is. Is it ProfileController follow? Yeah. Uh, it looks like we are redirecting back with this flash message you are now following. So why don't we confirm that? Let's assert that we, uh, you are now following, and then let's grab the other profile's handle. Let's give it a run and it passes. Awesome.
and then let's grab the other profiles handle. Let's give it a run and it passes. Awesome. Okay, so we're almost done. It just occurred to me when we were working on the like Post test, uh, we confirmed it works in the browser and then we forgot to write, uh, an assertion ourselves. So what we might do here is if we go into post view, here is like button, and I just wanna figure out like what does it look like if the post has been liked?
and I just wanna figure out like what does it look like if the Post has been liked? Well, one option would be to like check that a certain class was applied, but don't forget, you could also use, uh, a data attribute or you could confirm the likes count. That might be an option as well. Why don't we take that approach? So if we come back here, so let's say assert cn, and I think we give it a selector here,
So if we come back here, so let's say assert cn, and I think we give it a selector here, and then what we expect it to have, okay? So within the like button, once again, we just give this a data-test attribute, like post_count, all right? And now let's say with n like_post_count, I expect to see one because there has been one like, yep, that passes. And then again, at this point, if you want
like, yep, that passes. And then again, at this point, if you want to confirm on the backend, you can do so, right? And yeah, then at this point, if you wanted to programmatically confirm that the database was updated, that would be fine. So in this case, like you might say, okay, grab that post. And then we're gonna say, find the likes relationship, and this should have accounts. Um, how about this? expect likes,
and this should have accounts. Um, how about this? Expect likes, and it's okay, we can fetch the collection to have counts of one. And does that work? It does. Okay. But anyways, with just a single video, we, at the beginnings of a browser test suite in place, we know that we can like a post, we can view a post, we can view a timeline, we can follow a profile. So now at this point, I wanna show you how
Run tests faster24:20
we can view a timeline, we can follow a profile. So now at this point, I wanna show you how to run these very quickly, and then we're all done for today. Let's go into the terminal. And of course I can say php artisan test --filter Browser, and that will run only my browser specific tests. And it'll take just a little bit of time, as you can see each one a couple seconds. But as you can imagine, over time, once you have
as you can see each one a couple seconds. But as you can imagine, over time, once you have a hundred tests, this could take a couple minutes. So you also have the option of running these in parallel. So we give that a run. And because we are using a database in memory, this should work really well. And in fact, as I say that it actually takes longer. You know what? Let's give that another run. I wonder if there's some setup
You know what? Let's give that another run. I wonder if there's some setup or it could be the case where, uh, actually that was quicker that time. It could be the case where for a handful of tests, the performance difference is negligible. But trust me, if you have a very large test suite, running in parallel is going to provide dramatic improvements in terms of speed. Okay? So let me know what you think in the comments below.
dramatic improvements in terms of speed. Okay? So let me know what you think in the comments below. Otherwise, in the next episode, we might try to deploy this thing.
