در حال بارگذاری ...

Need JavaScript Testing0:46

So a lot of that got implemented here, which is great. It gives you a really nice API. So you can say, for example, this visit some page and expect to see hello world. It's really nice, it's fluent, it works great. However, one problem with that is it doesn't include JavaScript support. So instead, we're faking the request, we're getting the response, we're inspecting it, but no browser or JavaScript engine is ever involved in that process. Now, I'm hoping to find a nice clean way where we can still use the exact same API, but swap out the underlying logic to use Selenium rather than Symfony. Now, luckily, a lot of the work has already been done for us. So there is a PHPUnit Selenium extension we could use. And if I open up this tab, I think I have it here. Yeah. So this will take us a lot of the way, but the API still isn't exactly what we want. So I'm thinking, why don't we wrap it up. And

Installing Selenium Extension1:32

use. And if I open up this tab, I think I have it here. Yeah. So this will take us a lot of the way, but the API still isn't exactly what we want. So I'm thinking, why don't we wrap it up. And here's the cool thing. What you may find in Laravel is that so many of the components are taking other components, and then just wrapping them up into something a lot more elegant. So let's try this out. I'm going to make a project selenium-research. All right, cd in there. And then I will composer require this project. Now, while that's installing, let's take a look. So we extend this class within a setup method, I specify the browser and the root URL. And then for each test, you can visit a page and then make assertions. So again, I don't necessarily love this API, I want it to feel much more like we have right here, where we can say this see, or this page is or this visit. I think that's a lot more

assertions. So again, I don't necessarily love this API, I want it to feel much more like we have right here, where we can say this C, or this C page is or this visit. I think that's a lot more clean personally. So let me grab this. So we're going to go into my test directory. And I'm just going to call this SeleniumTest. And I'm going to paste that in. Okay, makes sense. So let's update that. And we're going to visit example.com. No, we're not, we're going to visit, I'm using Laravel Valet, which is really cool. So that means I immediately have a domain right here. And in fact, let me just show you. Yeah, there we go. Laravel Valet. So cool. Definitely check that out. Basically create a new Laravel project in a given directory, and you immediately have a .dev domain you can visit. And it even works for things like WordPress and countless CMS tools and stuff like that. Anyways, back on track. So let's try it. phpunit test/Selenium.

Starting Selenium Server3:15

dot dev domain you can visit. And it even works for things like WordPress and countless CMS tools and stuff like that. Anyways, back on track. So let's try it. phpunit test/Selenium. And yeah, it's gonna fail. Unfortunately, they don't give you much feedback, do they? We can probably fix that. But anyways, it's failing because we're using Selenium, but I don't have the Selenium executable or jar file available. So we have to boot that up. So let's look for it. Let's just search for Selenium. We're going to download it. And here we go. 2.5. Okay, so I'm going to move that directly to our project folder. And now we have to boot that up. We do that with the simple command they give you java -jar and then the path to the file. Now another thing I'm thinking is maybe we can isolate that behind the test. So maybe we could use Symfony's Process component where that will asynchronously boot that up for you so that you never have to remember to.

I'm thinking is maybe we can isolate that behind the test. So maybe we could use a Symfony's process component where that will asynchronously boot that up for you so that you never have to remember to do it. I'm not sure how that would work yet. But anyways, that's going to boot up the Selenium server. So now if I open up a new tab and run phpunit again, we should see a browser open. And it does. Okay, so in this case it failed as we would expect. Let's go back. So I expected the title tag to equal this, but instead it's just set to Laravel. I'll show you. Title, Laravel. So let's make this Laravel 5. We run it. It's going to boot up the browser. It's going to fail. Now we're going to update this and they should be equal and they should pass. All right, cool. So at least we're up and running. We're able to load a browser. We're able to inspect some things. At this point I just want to extend the API to make it that much cleaner to use.

Adapting Fluent API5:07

All right, cool. So at least we're up and running. We're able to load a browser. We're able to inspect some things. At this point I just want to extend the API to make it that much cleaner to use. So for example, rather than calling a url method, I like visit. I think that makes a lot of sense. So we could add a protected method here called visit that will accept the path. And then I can just say this->url path. So this is what I mean. We're almost adding an adapter, so to speak. We are taking an existing API and we are adapting it for our own needs and our own API that we want to use. So let's try it. I'm going to say this->visit, but now in this case the path we used is the exact same as the base path. So I can just leave that like so. And now notice how I want to continue chaining. Well, to do that we always need to return the main object. And that'll be true for any of these methods that we create. Okay, let's try this again. We should still get green and we do.

continue chaining. Well, to do that we always need to return the main object. And that'll be true for any of these methods that we create. Okay, let's try this again. We should still get green and we do. Cool. Next, right here, assertEquals Laravel 5 in the title. Well, maybe we could change that to C. So I expect to see Laravel 5 on the page. Now, if I call it like this, it's just going to look through the entire HTML source. Or maybe I can also add a tag name that would filter the search down to only the HTML for a particular tag. So like if I did this, then maybe that should do the exact same thing. Let's try this. So we're going to add a new method C, text, and that will accept a tag and it's going to default to the body. So now, well yeah, I mean I could hard code it and say assertEquals text and then do this title. That should work. Let's try it. Yeah, that works. But of course, we can't hard code the title in there. So instead, maybe we

assert equals text and then do this title. That should work. Let's try it. Yeah, that works. But of course, we can't hard code the title in there. So instead, maybe we could just write it out manually. Like maybe I could say, well, it turns out with Selenium there is a method called byTag. There's actually a bunch of filterings. So you could say byXPath, there's byID, byTag, I think byClass, there's a bunch of those. What I might even do is add some kind of find method that will just dynamically figure out if you're giving us a tag or a class or something like that. Anyways, maybe I could do this, byTag, and then I get its text content. Let's see if that works. And crap, it didn't. Okay. Yep, this is real life. So let's see what happens here if I try to hunt down this. Okay, that's going to return to me a Selenium 2 test case element. Let me take a look at that element. There it is. Okay,

see what happens here if I try to hunt down this. Okay, that's going to return to me a Selenium 2 test case element. Let me take a look at that element. There it is. Okay, what methods do I have here? So for any element, I can get it CSS, I can check to see, I can submit it if it's a form, I can get the contents of it. So I think I think that should work. Let's try this. Okay, and that's returning nothing. What the heck? Let's see. So what I'm wondering is maybe maybe the response doesn't include anything in the head tag. Yeah, maybe when you call by tag, it assumes you're starting with the body. That's all I can imagine. Anyways, we could always add a second method called getTitle. Or because very rarely will you be checking the title, we can just assume you're using something in the body. So like if I scroll down Laravel 5 body, and we expect to see that. So let's go back. Okay, Laravel 5 body.

the title, we can just assume you're using something in the body. So like if I scroll down Laravel five body, and we expect to see that. So let's go back. Okay, Laravel five body. And I expect to see that just somewhere on the page. So I can leave that off. All right, let's see if that works. Yeah, it does. But if we were to put that within how about an h2. And then we say I expect to see that within, I don't know, an h1. This should fail, right? Because it can't find an h1 with that text. Yeah, it fails, unable to locate element. And here's another thing we could do, we could wrap up these exceptions, maybe so that we give you a cleaner information for what went wrong. But anyways, if we bring that back to h2, that should work now. That should work now. Cool. But now, here's another problem. We're still doing assertEquals. So what about this? If we were to say h2, or actually, let's get rid of that entirely.

That should work now. Cool. But now, here's another problem. We're still doing assertEquals. So what about this? If we were to say h2, or actually, let's get rid of that entirely. And, and we'll do paragraph instead or something. Anyways, if I were to duplicate that, now it's going to fail. So if I run that, yeah, notice it failed. Now first, it's because we no longer have an h2. But even if I brought that back to using the body tag as the default, it's still not going to work. Yeah, it's going to fail. So that's because we're using assertEquals rather than maybe assertRegExp or, or something like that. So let's fix that. Now, like I said, we, we might use a regular expression, but I think we can also just say assertContains. Okay, so we run it again. And hopefully we get green, and we do cool. Now, before we continue flushing out the API, I already know that my main test class shouldn't have all this junk, I want it

Creating SeleniumTestCase Base10:42

so we run it again. And hopefully we get green, and we do cool. Now, before we continue flushing out the API, I already know that my main test class shouldn't have all this junk, I want it to be as simple as possible. So just as with non JavaScript testing, you would extend TestCase, and that gives you basically that whole API out of the box, I want to do the same thing here. So let's do this ExampleTest. This is what I would want ultimately. Okay, so take a look, PHPUnit tests ExampleTest, we get green. But in that case, it's really fast. We didn't need any JavaScript testing. So we could just do it in memory. However, if I switch that out for SeleniumTestCase, I want that to switch to using the browser. And right now, you can't even find the file. Okay, let's fix that. Let's rename this guy to SeleniumTestCase. Then I'm going to go to composer.json. And right down here, autoload dev. Let's also include tests

even find the file. Okay, let's fix that. Let's rename this guy to SeleniumTestCase. Then I'm going to go to composer.json. And right down here, autoload-dev. Let's also include tests/SeleniumTestCase.php. Okay, next, I'm going to composer dump-autoload to refresh those files. Okay, so if I run it again, it's still going to fail. Not cool. Sorry, I've been doing a lot of Elixir work. Okay, so I still can't find it. That's probably because I didn't rename this. Okay, composer dump again, phpunit, and that should open the browser. And it does. Great. Okay, so now yeah, we can get this setup stuff. And here, we can get rid of that one. And then we have the rest of our API here. So now your main test class is really simple. And think that's really cool. So when you don't want JavaScript, you do it in memory. When you do need JavaScript, you extend one other class, and then continue using the exact same API. I think that's kind of

Testing Form Submission12:33

really cool. So when you don't want JavaScript, you do it in memory. When you do need JavaScript, you extend one other class, and then continue using the exact same API. I think that's kind of cool. Okay, why don't we why don't we do a little bit more together. Let's figure out how we could test a form. So something like this, how about it fills out a form. Okay, so we're going to visit maybe some page called form. And then I'm going to say type something into an input. So type foobar, or Hello World, into an input with a title of message. Okay, so that's just going to look for an input with a name of message. All right. So type that, all right. So type that, and then I want to press a submit button. And then I expect to see just a dummy message that says the form was submitted. Okay, so we're going to try to get this to work together. Right now, it's going to fail, right?

And then I expect to see just a dummy message that says the form was submitted. Okay, so we're going to try to get this to work together. Right now, it's going to fail, right? Yeah, you cannot call a command with multiple method arguments. I think that's because there already is a type method included with the Selenium extension we installed. So we're actually going to override that to use our own functionality. So let's do that now. Visit, and we'll say type value into, and this is going to be the name of the element. So how do how do I do this? Let's go back to Chrome. Now in this case, I happen to know how to do it. But I just want to show you how I found it. Because obviously, this stuff you got to research. And unfortunately, there's not great documentation for the Selenium extension for PHPUnit. You're basically redirected to a test class, where you can kind of hunt down what it is you need to do. And that's

not great documentation for the Selenium extension for PHPUnit. You're basically redirected to a test class, where you can kind of hunt down what it is you need to do. And that's fine. So let's look for submit. Let's see, test forms can be submitted. Okay, this is what we want. So you visit a URL, you find an element by its name attributes, and then you can set its value. So you type into that input using this value method. Then it looks like they hunt down the form using a CSS selector, and then they call submit. But we could also just hunt down the button itself and then call click on it. Okay, so let's try this out. If we want to type into it, we find the element and then we call a value method. Okay, so we could say this by name, and then set the value of the input. And then once again, we return this so we can continue chaining. Okay, still gonna fail. But hopefully, we've changed the error message. Yeah. Okay, unable to

and then set the value of the input. And then once again, we return this so we can continue chaining. Okay, still gonna fail. But hopefully, we've changed the error message. Yeah. Okay, unable to locate elements. So here's the problem. Right now, we're calling a /form URL. But right now, I don't have that. So once again, yeah, notice how the the error messages aren't that great. In real life, I would want to intercept these and give you a lot more information. So it almost gives you your next step, it would say something like, hey, this route doesn't exist yet to go create it, you know, something like that. Anyways, I can work on that in a bit. For now, let's visit form. And that's just going to load a few. And I'm just going to call it form because we're testing. Okay, resources/views/form. And then here, we're going to have a form that posts to something like formEndpoints.

because we're testing. Okay, resources, views form. And then here, we're going to have a form that posts to something like form endpoints. Anyways, so we're going to have an input. And the name is message. Okay, we run it again. And we're on to the next error. So no press method is in existence. Of course not. So let's do this. press. And that's going to give us the text of the button. But we could also modify that to be the ID of the button or the name of the button or, or even a generic tag name. For now, though, I'm going to start with text. So it seems like what we would need to do here is find the tag the tag with the given text, or really find the button with the given text. Now I know that there isn't anything like this by text or by inner HTML or anything like that. So what I'm thinking we'll have to do is use some kind of XPath, which I'm not overly familiar with. So let's see,

isn't anything like this by text or by inner HTML or anything like that. So what I'm thinking we'll have to do is use some kind of XPath, which I'm not overly familiar with. So let's see, I'm going to see what he has here. Okay, so if you use XPath, you can hunt down an element, and then you can provide the attribute equals a value. Yeah, I'm not too well versed in that area. But I think that'll give me what we need. So let's see, we're going to say this by XPath. And then we're going to look for to start just a button. But we might want to say like a button or an input. I don't know if I can use like a regular expression there or not. Anyways, we'll start with a button where the attribute equals the given value. Now, I have used Selenium in the past, I think there's there's something like contains. Let's hunt it down. Is there anything called contains in here? assertContains? No, let's look for Selenium contains text.

in the past, I think there's there's something like contains. Let's hunt it down. Is there anything called contains in here? assertContains? No, let's look for Selenium containsText. Yeah, I think I saw it. Okay, so findElementByXPath. Yeah. So where the element you find contains the given text of my this is exactly what we want. Okay, so I can say containsText. And then we could compare that against the text that you pass in. Yeah, I think that will do the trick. Let's, let's use double quotes here. Like so. So is that right? We're looking for a button that contains the given text equal to what we pass into the method. Yeah, I hope that should do the trick. So let's go back to our example test. We figured out how to type something in, we've learned how to press a button, and then we expect it to fail here. So let's run it. It opens the browser, fills it out, submits the form, but now unable to locate the element with

we've learned how to press a button, and then we expect it to fail here. So let's run it. It opens the browser, fills it out, submits the form, but now unable to locate the element with the given text. Okay, so that makes sense, right? We don't have a button, button type equals submit. And now we do have the button. So we run it again. There we go, called a member function C on null. Okay, so that means we did not make this fluent. And further, we know it's going to fail because it's posting to this endpoint, but I don't even have that. So let's do that now. Route::post. And let's see, what did we expect? Yeah, you know what, I'm just going to grab all of that and return it here. Now I'm thinking this should still fail because I'm not sending through a token. Failed asserting that submit contains the form was submitted. Ah, you know what, we didn't click the button. So yeah, we tracked it down, but then we didn't call click.

a token. Failed asserting that submit contains the form was submitted. Ah, you know what, we didn't click the button. So yeah, we tracked it down, but then we didn't call click. Yeah, so with this example, we find the button and then we click it, or the one we saw in the documentation was something like by CSS selector, and then submit that form. So we are still accomplishing the same thing. Okay, let's try it again. Yeah, did you see that token mismatch exception? And actually on that note, notice how we're trying to read the browser, but it closes so quickly. What might be nice is to add like a hold method where you can say sit tight for three seconds. Okay, let's add that. So right, maybe down here at the bottom, hold. And I'm just going to do a simple sleep here. sleep for these seconds. And that should keep the browser open. So we run it.

So right, maybe down here at the bottom, hold. And I'm just going to do a simple sleep here. sleep for these seconds. And that should keep the browser open. So we run it. There we go. Two, three, and then it should close. Okay, so that's our next step. Now we're getting a TokenMismatchException because that's enabled by default. And in fact, I'll show you. If we go to the Kernel, let's see. verifyCsrfToken. So this is part of the web middleware group, which is wrapped around every route within here. So if you take a look at it, verifyCsrfToken. We have to go up here. And let's see how we handle it. Yeah, tokens match. There we go. So we're getting the token from the request. And we're comparing that against the token from the form. And if they do not match up, an exception gets thrown. Okay, so we can just use a simple csrf field. And that will spit out the necessary HTML that we want.

the token from the form. And if they do not match up, an exception gets thrown. Okay, so we can just use a simple csrf field. And that will spit out the necessary HTML that we want. So I think this should give a screen impact. Yeah, it does. Great. So let's come back. We can get rid of the hold method. And this looks good. Now maybe we also want to say seePageIs form submitted. Let's add that one. So we know it's not going to work. Let's not even bother testing it. Let's go ahead and create the method seePageIs and then you give me a path. Now you know how earlier let's go back up. Yeah, right here. So you can set the URL. Or if you just call it with no arguments, that's going to send through or return to you the current URL. So like for example, let's just dd this URL and see what we get. Call to a member function seePageIs on no man, I keep forgetting to do this. Okay, one more time. Yeah, there we go. So maybe,

let's just die and dump this URL and see what we get. Call to a member function. See page is on no man, I keep forgetting to do this. Okay, one more time. Yeah, there we go. So maybe, maybe we could do this. Maybe at the top, we could have baseURL. And then for each new project, you would set this. So in my case, I would set that equal to this. And then when I perform a test like this, we could say, this assertContains really assertEquals. So I would say this baseURL plus the path you give me, and then compare that against whatever the current URL happens to be. Okay, so if you say right here, see pages slash formSubmitted, I'm just going to append that to I'm just going to append that to this URL right here. And that should give us what we need. And then in fact, I can update this guy to the baseURL. Okay, so let's see if that works. We run it again. And it failed. Looks like I just said it wrong. So formSubmitted,

And then in fact, I can update this guy to the base URL. Okay, so let's see if that works. We run it again. And it failed. Looks like I just said it wrong. So form submitted, we set that to formEndpoint. There we go. Okay, so I think you get the basic idea of what I'm going for here. So we're taking an existing API, the Selenium two extension for PHPUnit, and we're adapting it for use with the API that I want. And this is what you would get with Laravel 5.2. Now in this case, notice, we're not dependent upon JavaScript. So I would just replace that with TestCase so that your tests are as quick as possible. It's almost instant. It's really cool. However, yeah, we're adding the Selenium TestCase for the situations where you're making some kind of AJAX request so that you can load up a browser, execute the JavaScript, you can wait for an AJAX request to finish, we could we could flush out the API to allow for

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟