Framework – Your First Framework Part 2
In part one, we built our Driver factory class that will allow us to create an instance of a browser driver, depending on the parameter we passed in. This was an important first step, as without this we would have to manually create our browser instance in our tests themselves. By doing it this way, we are able to keep this logic hidden away in our framework and then call our Initialise method with a parameter in either the test or a test setup class. It’s far cleaner and more efficient.
With part two, we’re going to look at our DriverSetup and our DriverDispose classes. This isn’t quite going to be another single class article but we’re not going to see anything too exciting yet, and I promise it’s the last time but we really need to make sure we understand these two classes. These are the two classes that we absolutely must have, after that, everything else we add is just a bonus and improving functionality.
In the following articles, we will look at these extra classes and learn how we can create a framework that will allow us to create brilliantly simple tests.
using OpenQA.Selenium; namespace AutomationFramework.Engine { public static class DriverBase { private static IWebDriver WebDriver { get; set; } internal static IWebDriver Instance { get { if(WebDriver == null) { throw new WebDriverException("Webdriver has not been initialised"); } return WebDriver; } set { WebDriver = value; } } } }
This is a simple class. Not really much to it but it’s going to be used virtually every where throughout our framework and tests so it’s important we understand it.
Our first line is creating a IWebDriver property that we are going to be using for another property, Instance which is also a IWebDriver. We could just use a single property with simple get and set methods, but because we want to do some exception handling, we’ll do it this way. We’re going to be doing a lot of exception handling throughout the framework. You might be wondering what the internal keyword is. Internal allows us to declare a property or method and use it through the assembly that it’s declared in. This is great for when you want to limit access to the rest of the solution but not be as restricted as you are with private, where it can only be used in the class it’s declared in.
When tests start failing, you want to make sure the cause of failure is as clear as possible, minimising the need to go through and debug every single line of code, which can be an extremely tedious and time consuming process. By adding proper exception handling, you’re able to pass clear messages describing exactly what the error is and what was happening, so when you look at the stack trace, you can straight away see exactly where the test has failed.
The reason we have made this class static is so that we are able to reference it and the internal property throughout the entire Engine assembly without having to create an object instance. The use of static in this context allows for much cleaner code and given how many times we are going to be referencing our Instance property, it would be a real pain to have to create an object in any class we wanted to use it in.
using OpenQA.Selenium; namespace AutomationFramework.Engine { public static partial class Driver { public static void Dispose() { DeleteCookies(); Quit(); DisposeExplicit(); } public static void Close() { try { DriverBase.Instance.Close(); } catch(WebDriverException) { throw new WebDriverException("Failed to close the current window"); } } public static void DeleteCookies() { try { DriverBase.Instance.Manage().Cookies.DeleteAllCookies(); } catch(WebDriverException) { throw new WebDriverException("Failed to clean up cookies"); } } public static void Quit() { try { DriverBase.Instance.Quit(); } catch (WebDriverException exception) { throw new WebDriverException($"Failed to quit the current instance of the Webdriver. Message: {exception.Message}"); } } public static void DisposeExplicit() { try { DriverBase.Instance.Dispose(); } catch (WebDriverException exception) { throw new WebDriverException($"Failed to dispose of the current instance of the Webdriver. Message: {exception.Message}"); } } } }
Just as important as having good setup and initialise methods, is having good dispose methods. We need to make sure that when we are done with our driver instance, that we clean up manage our resource usage, which if not done properly, can quickly mount up over the course of a whole test suite.
You’ve probably noticed the class is a partial class. This is because our Driver class will be split across many different partial classes dealing with many things from element interaction to script invoking. We will cover these in later articles.
These dispose methods are purely wrapper methods for the existing WebDriver methods, they are pretty self explanatory in what they are doing. You might be asking “what’s the point then?”, and it’s a valid question. Again, it’s all about exception handling. You can see in method we are putting everything in a try catch statement, and then sending clear messages with the cause of failure and the exception messages.
It also allows us to easily access these methods via Intellisense when using our Instance WebDriver object. And that is one of the main goals of our framework of course, to make writing code for our framework, as well as our tests, as simple as possible. A lot of methods might seem like overkill that we cover in later articles, but when you see how they are used, it will all make sense, and you gain an appreciation for those wrapper methods.
So we have covered our basic classes, that are the must haves and the spine of our framework. I know part one and part two were lacking in any exciting code but it really was important that we covered these in detail, as we will be using them so much in the rest of our code that you might not have understood what was going on without that basic understanding. In the next article, we’re going to look at the rest of our partial classes for Driver, which will cover window handling, element interaction and script execution.