Join Kit Eason for an in-depth discussion in this video Web UI testing with Canopy, part of F#: Automated Testing for Developers.
- [Instructor] Let's now go to the opposite end of the stack and look at web user interface testing. For this video, we'll use a package called Canopy and a dummy florist website which I've got here. The transaction we want to test is that we click on Build a Bouquet, select Calla Lilies, pick the second color, pick a quantity, and add to basket, and we want to test that the estimated total is $60. That's not the real cost of two calla lilies, because it's a completely dummy website, but it shows the principal.
To do that, firstly, we're going to need to download a thing called ChromeDriver. You can just search for ChromeDriver online, download the latest release, and I'm using Windows so I'll pick that one. Just copy the single .exe. I'm going to put it in my C:\Program Files (x86). No reason to call it anything other than ChromeDriver. And there's no installation. As long as you know the path where it is, that's enough.
Now I'm going to go to Visual Studio, and I'm going to make a new project. It's going to be whatever the latest version of .NET framework is, and it's going to be a Console Application. I'll leave it in my Visual Studio projects, and I'm going to call it CanopyTesting. Now, I'm going to install paket but in a slightly cheaty way. I'm going to open the folder location. I'm just going to go to another project and that one's got the bootstrapper in.
Copy .paket, put it in CanopyTesting. And then we'll just get rid of paket.exe and paket.targets and we'll just keep bootstrapper. Go back to Visual Studio, make sure we saved everything, open up the Package Manager Console, run the bootstrapper, and paket init. Then add nuget Canopy to the single project we've got.
We load, and there we go. We can get rid of everything in Program.fs, make a module called Main, open some namespaces. Probably want System. We'll definitely want canopy with a lowercase c. Likewise, a thing called runner, and we're also going to want the thing which Canopy is based on, which is Selenium, which is a very well-established automation framework. Canopy is essentially a nice F# friendly wrapper 'round Selenium.
Now I'm going to make myself a little AutoOpen module, which we're going to call Selectors, and this is the place where we'll keep the selection paths for all the things in the website. So, the first thing we clicked on, if you remember back to the transaction we performed, was to click on Build a Bouquet, so I'm going to make a let binding called, with back tick quotes, build a bouquet. You don't have to use back tick naming here, but I find it's just a little more friendly when we come to consume the selectors.
And what's the value for that going to be? Well, go back to the website, make sure it's in its initial state. Its initial state had Arrangements clicked. And then right-click on Build a Bouquet, say Inspect. This is in Chrome. And without changing the selection up here, say Copy selector, and if you go back to Visual Studio, you can put a couple of quotes in there and paste.
And that copies my selector, which, given the website's in its initial state, will be guaranteed to pick the right button. But I'm just going to repeat that for all the things we clicked. So the next one was Calla Lilies. Inspect. Copy selector. And I'll just repeat that for everything we did. And then finally, go back to the website.
The last thing we did was to do an Add to Basket. That popup came up, and we wanted to verify this value here, so I'm going to inspect that, copy its path selector, and that's all the selectors we're going to need. We're going to need to set up Chrome, so I'm going to have a module called Initialize. I'm just going to set a value called chromeDir, which comes from Canopy.
We're using a silent operator. And this will need to be whatever path you installed ChromeDrive into, so in my case, that was C:\Program Files_(x86)\ChromeDriver. And we need to start Chrome. So that would cause Chrome to be run when our testing program runs. Now we need an actual test. And we call it "I can order a bouquet." And this strange triple ampersand operator is Canopy's way of expressing a test.
Before it goes the name of a test. After it needs to go the lambda, which is the body of the test. I'm going to use that same way of linking up to a lambda that we did in expector, just like that. Now in the body of a test, I can use a Canopy keyword called url. It will just take Chrome to a URL. The URL I want is the homepage, which is this, including the improved bit.
And I'm going to leave the body of the test at that just for now so we can demonstrate that the automation works, and the next thing I'll need to do is say run() here, and just so we get a chance for the user to read the test results, I will say printfn "Press [Enter] To exit," and we'll just read a key from the console so it doesn't disappear straightaway.
Finally, I will quit(), which will close down Chrome. Oh, yes, I just need to ignore the results of ReadKey. All right, let's run that. And sure enough, it loads up Chrome in its own independent window that it's controlling, and it navigates to Hansel Petal improved. So now let's go back and fill out the body of the test.
We need to click on Build a Bouquet, and that could not be simpler. We simply type click, and build a bouquet should be in my IntelliSense somewhere. There we are. Click is a keyword provided by Canopy, and that will cause the relevant control with whatever path you provide to be clicked, and remember, we defined the build a bouquet selector here. Click calla lilies and color.
Then having selected the color control, and one way to pick a particular color would have been to press the down arrow key, but we can just say press, and that's provided by Canopy as well, Keys.ArrowDown, and the key's enumeration is provided by the OpenQA.Selenium. So that's why I opened that namespace before. Then we need to to click the quantity. By the way, it's not putting in the double back quotes for things up here that don't need them because they have those spaces in, but just for consistency, I'm going to force those in there.
Then we'll do Backspace to delete the existing quantity value, one way of doing it. NumberPad2, one way of pressing two. Enter. And then we need to add to basket, so that's click add to basket. Let's see if that works. Let me just maximize Chrome for you, and yes, we're in exactly the place we wanted to be, which was with the basket preview open.
So that's absolutely perfect. And it saying that the test have passed in this console window simply because the test had no assertion. Incidentally, in early versions of Visual Studio 2017, you may not see the console window when you run a console project. If that starts happening to you, then just save everything, go out of Visual Studio, and back in again, and you'll be fine. So now we need an assertion in our test. Again, that's almost comically easy.
We want path, selector I should say, which is our basket total. That was the number at the bottom of the basket preview. We want a double equals, because that's the equality assertion in Canopy. We want the expected value, which was $60, as a string. In fact, let's firstly make sure the test can fail by putting an incorrect value in there. So let's make it $60 and a cent. So that is the assertion of our test.
And you can see that if I hover over it, the double equals is being provided by Canopy. Now, you'll get a bit of a pause when a test fails like this, simply because it's giving the website a chance to produce the expected number. It doesn't fail immediately because obviously there's inherently delays in web rendering. Yeah, and there are exceptions. Expected 60.01, got 60. I can turn off break on exception. And we see the failure in the console. Right, let's prove the test can pass by putting in the right value.
And there we are, passing test. So let's just take stock. We installed a thing called Canopy, which is a wrap around Selenium. We navigated through the transaction we wanted to do, and we used Chrome, right-click, inspect, copy selector to get the selectors. We put a little module of those in just for convenience. I could just as well have put this literal value down here, but I just think it's more readable if you keep all the names simple when you're actually consuming those selectors.
Makes it more maintainable as well. If these get renamed in the website, we have to just change those in the selectors project. We'll just name that, let me call that Selectors. Then we initialize starting Chrome using the ChromeDriver that we downloaded. Then I wrote a test with a name and a lambda. Then I used Canopy keywords like click and url and press and the assertion to write a really nice test, and I just had a little bit of boilerplate here just to run the test and give the user a chance to read them.
So there's a lot more to Canopy. We're not going to go through it all now, but if we have a little look, for instance, at the assertions page, just look at all the assertions you can have. You have have one of many equals, so you can look through a list that you're not sure the order of and make sure it contains a certain item or it doesn't contain a certain item. Or you can use case insensitive matching, or you can use regular expression matching. So it's really an enormously powerful framework, based, of course, on the power of Selenium underneath.
If you do need to automate a website, this is a great way to do it. Having said that, we have to accept that these UI-style automation tests take a lot of maintenance, because they're very sensitive to changes in the layout, element naming, and so forth. However, if you can cover the key workflows of your site with automation tests, it is a great confidence booster.
Kit Eason explains how to use xUnit—a .NET unit-testing package—to do some test-driven development, and demonstrates how to improve your test run experience using NCrunch, a test runner. He shows how to use FsCheck to generate test cases, and how to use Expecto to move into the world of tests as first-class values. He also covers the use of Canopy to automate the testing of web user interfaces, and of mocking to tame dependencies.
- Solving issues with early versions of Visual Studio 2017
- Classical unit testing with F#
- Creating a testable project and installing Paket and xUnit
- Test driving an implementation
- Improving your test run experience with NCrunch
- Going beyond test cases with FsCheck and Unquote
- Making tests first class using Expecto
- Using mocking to tame dependencies
- Web UI testing with Canopy