Introducing Dusk Testing0:00
Now that we've built so many modal features, I'm really keen to refactor the code base. But what I wanna do first, introduce a test suite so that we can guarantee that these features keep working while we refactor. And I could write some specific front-end tests for few or some specific backend tests for Laravel. But what I rather want to do is introduce end-to-end testing with Laravel Dusk. And with Laravel Dusk we can run tests in an actual real
Installing and Running Dusk0:20
with Laravel Dusk. And with Laravel Dusk we can run tests in an actual real browser so we can check in the browser if our features keep working. Let's go to the terminal and let's install Laravel Dusk with Composer. So Laravel Dusk as a dev dependency, let's hit enter and let it install. And then we're gonna run php artisan dusk:install, which is gonna scaffold some things.
And then we're gonna run php artisan dusk:install, which is gonna scaffold some things. Let's go to the editor. And in our tests directory we now have a Browser subdirectory, which has an example test. And this test just visits the homepage and then asserts that it's somewhere in the page finds this Laravel word. Let's go back to the terminal. Let's run php artisan dusk and see if it works.
Let's go back to the terminal. Let's run php artisan dusk and see if it works. So yeah, this example test did pass, um, but we didn't see anything happening in the browser. And by default, Feld dusk runs hatless so you don't see anything, but if you really wanna see what's happening, you can add --browse as an option. So let's run it again. And here you saw real quick a browser.
So let's run it again. And here you saw real quick a browser appearing and disappearing. Now just to show you what's happening, let's add a pause of maybe, uh, one second. Let's go to the terminal and run it again. And here you can see that it actually visited the homepage, so that works. But of course we don't want to test the homepage. We want to test our modal.
Testing Modal Open/Close1:59
But of course we don't want to test the homepage. We want to test our modal. So let's see what we want to test. Let's go back to Firefox. And what I want to test first is if I click on edit that this modal appears that the route has changed and when I click on cancel, that it disappears and that we are back at the index route. So let's start wiring the test. Let's go back to the editor and let's start with the name. I'm gonna replace this with, it can open a modal.
Let's go back to the editor and let's start with the name. I'm gonna replace this with, it can open a model and close it and I've just replaced test with it. But in past, that's just the same. It's another prefix. And I actually prefer this one. So we're not one to visit the homepage, but we want to visit that users.index route. We don't want to pass anymore. And before we forget, we should log in because this route is behind an auth middleware.
And before we forget, we should logIn because this route is behind an auth middleware. So we should call the logIn method and we can pass a User model in here, an Eloquent model or just an id. And I'm gonna pass 1 because this is a demo application and it doesn't really matter which user is logged in. So we're logging in, we are visiting the page. And then we want to wait for a specific element. And I'm gonna pass table because we are visiting this route,
And then we want to wait for a specific element. And I'm gonna pass table because we are visiting this route, but we still want to give you some time to render the actual page. So we want to continue after this table element is feasible. So we wait for the table and then we want to press on the edit edit button. But this table has something like a hundred users, so we want to be very specific, which user we want to edit. So let's grab the first user, um, user.
Adding Dusk Selectors3:35
so we want to be very specific, which User we want to edit. So let's grab the first User, um, User. Whoops, User ordered by name. We are going to grab the first one. And then we need to add a dusk selector to our table. Let's see how that works. Let's go to uh, our index component, our index page and find that added button. Here it is, here's the edit button. And here we can do dusk as an attribute.
Here it is, here's the edit button. And here we can do dusk as an attribute. We could call it editUser. And then we want to add the userId at the end of it. So I'm gonna make this dynamic addedUser with this syntax userId. And let's check in the browser if this works. Gonna inspect the first one and that has this dusk attribute addedUser 29. So that works fine. And then in our test, instead
and that has this dusk attribute added User 29. So that works fine. And then in our test, instead of passing a label, we can use the @ sign and then editUser and pass it that userId. Next up we wanna wait for some text. So waitForText editing user because that's the title of our modal. And then we want to check if the route has changed in the browser. So we're gonna do assertUrlIs,
changed in the browser. So we're gonna do assertUrlIs, and that should be users.edit with this $user as a route parameter. Then we want to close the modal. So we're going to again press a button, Cancel, which should be uppercase. And then we want to wait until this text disappears. So for that we're going to use waitUntilMissingText again, editing User.
So for that we're going to use wait until missing text again, editing User. And then we wanna make sure that we are back at the users index route like this. Let's see if it works. Let's go to the terminal and let's run php artisan dusk browse again. And that seems to work, so that's really great. But there's one little thing that I want to change in here and it's this method, press Cancel that I want to change because this cancel button could be anywhere on the page.
and it's this method, press Cancel that I want to change because this cancel button could be anywhere on the page. It's not specifically scoped to the modal. So if we were to add a cancel button, maybe in the future outside of the modal, this test might break. So what we wanna do is use the within method that accepts a selector. I'm gonna introduce a modal content selector. And by the way, you could pass in any CSS selector in here. So if you have a class, you can do it like this.
And by the way, you could pass in any CSS selector in here. So if you have a class, you can do it like this or maybe an id, but I'm gonna use the desk selector. And the second argument is a new browser instance browser. And now this browser instance is scoped to our model content. So if we call this press method and it's gonna look for this cancel button, it's only gonna search within our model content. But of course we still need to add this
it's only gonna search within our model content. But of course we still need to add this to the actual model, um, component. So let's grab it. Let's go to our model and scroll to the content section. That would be in here. So here we have our title slot and the default slot, and then I'm gonna add it to this diff. So dusk equals modalContent. And you know what, we could also use this for waitForText.
So dusk equals modal content. And you know what, we could also use this for waitForText. So instead of waiting for some specific text that might, uh, change in the future, we could hear waitFor again, modal content. Let's see if this works. Let's go to the terminal. Let's run php artisan dusk again. And that seems to work and maybe we could do the same with waitUntilMissingText. So here we're gonna use wait, uh, untilMissing.
wait until missing text. So here we're gonna use wait, uh, until missing. And instead of using the modal content selector, I'm gonna use a modal wrapper selector and I'll show you why in the Modal component. So let's scroll to the top. And you might remember this afterLeave event. So we're using this uh, to redirect back to the base routes. So we really wanna make sure that this diff is not rendered anymore.
So we really wanna make sure that this diff is not rendered anymore. So we're going to add a dust selector here of modelWrapper. And then in our example test, we don't have to change anything anymore. Oh, this should work as well. Let's see. Yeah, really great. So now we only have this cancel button that's hardcoded with some text, but I guess that's fine. So let's continue with the next test.
Testing User Updates8:41
with some text, but I guess that's fine. So let's continue with the next test. Let's see if we can actually update a User so it can update a User. So I'm just gonna copy most of this stuff. We want to crap a User and wait for, uh, wait for the modal content. Don't really care about the route anymore. So again, we are visiting the index page. We press that, uh, user button, that added user button.
So again, we are visiting the index page. We press that, uh, User button, that added User button. We wait for the model content. And then within the model content we are gonna update the user's name. So browser with a browser, we're gonna type name and then we gonna add some fake name, uh, let's call it newName for now. We're also gonna type a new email address,
let's call it newName for now. We're also gonna type a new email address, which is gonna be newEmail. And then we are gonna press update. Oops, update. So let's set that newName and newEmail here. newName is fakeName and newEmail is fakeSafeEmail. And of course we need to pass that here to the callback. So use newName and newEmail. So we've hit update.
So use newName and newEmail. So we've hit update and then we want to wait for the text User updated successfully because that's our toast message. Did I write that correctly? Yeah, we'll see in a minute. Um, and then we want to refresh the User. So we were gonna grab the User attributes from the database and then we're going expect that the User's name, that's gonna be the newName and the User's email address is gonna be
that's gonna be the new name and the user's email address is gonna be the new email address. Let's close the callback, it's safe and let's try this one so it can update the User. I don't want to test everything within this file, I just want to test this one. It can update the User. I'm gonna grab update, and then in the terminal I'm gonna use dusk update.
I'm gonna grab update, and then in the terminal I'm gonna use dusk update. And this is a little alias that I have in my terminal. So now it's just gonna use, um, that one single test. It can update a User. Let's see if that works. And that seems to work as well. So really great. Let's continue. What more can we test? Let's test, uh, validation so it can show a validation error within the modal. Again, we're going to grab most of this stuff.
Testing Validation Errors11:35
so it can show a validation error within the modal. Again, we're going to grab most of this stuff maybe up until here, uh, but we don't want this new name and new email. Gonna remove that. I'm just gonna type some invalid email. So something like, not a valid email. Then we're gonna press update. And then actually within the model content, we want to wait for a text and that would be something like the email fields should be a valid email address.
for a text and that would be something like the email fields should be a valid email address. I think it's something like this. Let's try that. Uh, let's fix this first. Yeah, so now I'm gonna run php artisan dusk validation. The email field must be a valid email address. I think I've used attributes. And one cool feature of Dusk is that it created a screenshot of the failing test. So here we can look at it again.
that it created a screenshot of the failing test. So here we can look at it again. So we can see the actual validation error. And it's not the field that I missed, but it's must and I used shoot. So the email field must be a valid email address. And then I wanna check if the User was not really updated. So we are gonna expect that the User and we are gonna call freshEmail, uh, not to be what did we pass?
and we are gonna call fresh email, uh, not to be what did we pass? Not valid email. So we just wanna make sure that, that it really didn't update this User. Let's try again, dusk validation and now it works. So now we've tested that we can open and close the modal in the first test. We've tested that.
and close the modal in the first test. We've tested that. We can actually update it and we've tested that. We can show validation errors within the modal. And in the next episode, we're gonna refactor this a bit because as you see where we have a lot of repetition in here. So we can do better.
