Selenium Testing Framework Pt 2: Base Classes

Posted by Jason Smiley in Selenium Resources

This is part 2 in a 3-part guest blog post series by Jason Smiley, a QA Engineer at Gerson Lehrman Group. In my last blog post, I showed what testing as a whole would look like, regardless of what we actually need to test. Just to recap, we have tests that check and validate results, actions that are a set of steps to take before having a value we can check, and pages that can be checked or interacted with. For the sake of this article, let's assume we will be testing several different websites with different code bases that are partially related. For example, we will have an admin site that can add, edit, and remove content from a different public content site. Assuming we have two different test projects, or one for each site, the first thing we need to do is create a base test, action, and page class that can be used by either test project to reduce the amount of code that needs to be written. These can also reduce any maintenance that needs to be done on updating common functionality. Later, we can have another set of base classes that extend the shared base classes but are more specific to the project under test. Since there will be shared code, it is a good idea to create a separate project that can be referenced in your test projects. This allows you to change base code sets fairly easily, which can be useful if changing to a different testing framework.

Selenium Base Test Class

First, we'll talk about a base test class. All tests need a browser, so first we must write a function that will create a browser. The tests will need to connect to the DB to allow for data checking, and it will also need utility functions so you can look at popups or switch between active frames. Let's create SeleniumTestBase.cs class, which has these functions. In C#, it will look something like this:

public SeleniumTestBase
protected IWebDriver myDriver;

public SeleniumTestBase (IWebDriver webDriver) { myDriver = webDriver; }

public DataTable GetData(string connString, string SQL) { //Some code which will query a DB for you }

public IWebDriver StartBrowser(string browserType) { //Some code which will launch different types of browsers }

public void SwitchToWindow(string handle) { //Some code which will switch windows for you }

public void SwitchToFrame(string handle) { //Some code which will switch frames for you }

public void LaunchUrl(string URL) { //Some code which will navigate directly to the specified URL. }

public void Refresh() { //Some code to refresh the page you are currently on } }

Now, it's important to note here that many of these functions don’t seem to be that hard to implement. In that case, why create a base class to encapsulate standard functionality already inherent to the Selenium Framework? There are two reasons for this. One is that you will have less typing to do, which is always nice. The other is that, should you decide to change testing frameworks (from say Selenium 1 to Selenium 2), you will only need to update one class that handles all of the basic interactions. The ways of doing things may change from one framework release to the next. By encapsulating your code, you ensure that your code will always work the same as maintenance is performed since all tests will still call the same functions.

Selenium Action Base Class

Now we'll move on to actions. Actions will execute the steps that will change the state of the website being tested (such as being signed in). Actions may also need to revert changes done by tests to a database for data cleanup, navigate to different pages, refresh the page, or interact with different frames and windows from the current active environment. Although the interactions between an action class and a page will be test specific, we can start writing the ways an action will interact with a browser or DB. A SeleniumActionBase.cs class will probably look something like this:

public SeleniumActionBase
protected IWebDriver myDriver;

public SeleniumActionBase(IWebDriver webDriver) { myDriver = webDriver; }

public DataTable ExecuteSQL(string connString, string SQL = “”, string SPName = “”, string[] Parameters = new string[] {}) { //Some code which will execute a query or stored proceedure for you }

public void SwitchToWindow(string handle) { //Some code which will switch windows for you }

public void SwitchToFrame(string handle) { //Some code which will switch frames for you }

public void LaunchUrl(string URL) { //Some code which will navigate directly to the specified URL. }

public void Refresh() { //Some code to refresh the page you are currently on } }

There are a couple of things I'd like to clarify about the above example when it comes to updating the database during test scripts:

  • If you are going to manually execute SQL to clean up your work after a test, you should be able to do so by using custom SQL or by calling stored procedures directly (hence the optional parameters). In my experience, it is always better to use a stored procedure rather than using your own SQL to update a DB to ensure that you aren’t creating a data issue.
  • You might not be able to actually update a DB directly using SQL due to internal security protocols. Or maybe you won’t even be comfortable doing this as your test code could break the DB. However, executing SQL to clean up your code is going to be much faster and more dependable than doing it through the UI. I'm not saying you should or should not clean up your tests this way, but if you are going to execute SQL to clean up your code, I'd advocate doing it this way.

Selenium Page Class

The last piece of the puzzle are the actual pages themselves. Pages are essentially just a grouping of page elements you wish to check and interact with. To code this the most efficient way, you should break the idea of a page into two separate classes. One class, which I will refer to as a page class, is specific to the test project in the sense that it relates to the actual pages you want to test. From a coding perspective, these are the locator strings. Regardless of what framework you are using, you need to be able to find your elements. Typically, this is done by Id, XPath, or CSS, and generally won’t change unless the page you're testing changes. Since some locator strings could be based on the browser start index, and browser start indexes can either be 1 or 0, the page class will need to know which browser is being used. Based on this, a SeleniumPageBase.cs class would look something like this:

public SeleniumPageBase
protected IWebDriver myWebDriver;

public SeleniumPageBase(IWebDriver webDriver) { myWebDriver = webDriver; }

protected int browserIndex { get { return /Code to get Browser index/; } } }

The second page class will handle the actual interactions between the test or action and the page. This is usually done by Selenium itself, however, a Selenium object isn’t always easy to use. A lot of times, you have to wait for the DOM to update the element you want to check, click, or interact with, especially if the page you are testing is using AJAX or Javascript. If an element fails, you might want to add additional error messaging to say what caused the interaction to fail. By using a level of encapsulation, you can better control the use of your Selenium or testing framework code. Since this class is supposed to handle interactions of specific elements on a page, I will call this class a SeleniumPageElement. It is important to note that a page element class will need to mimic what a IWebElement –– or whatever framework you choose –– can do, as our actions and tests will be interacting with this class. By taking the testing framework’s place, we make it easier to swap out testing frameworks for new ones with little changes in test code. Here is an example of what a SeleniumPageElement.cs class would look like:

public SeleneniumPageElement
public By myBy;
public IWebDriver myDriver;
public int? myIndex;
public SeleneniumPageElement myParent;

public SeleneniumPageElement(By locator, IWebDriver webDriver, SeleneniumPageElement parent = null, int? index = null) { myBy = locator; myDriver = webDriver; myIndex = index; myParent = parent; }

public IWebElement GetNow(string errorMessage = “”) { try { if(myParent != null) { if(myIndex != null) { return myParent.GetNow().FindElements(myBy)[MyIndex]; } return myParent.GetNow().FindElement(myBy); }

if(myIndex != null) { return myDriver.FindElements(myBy)[MyIndex]; } return myDriver.FindElement(myBy); } catch(Exception e) { if(errorMessage.Equals(string.Empty) == false) { throw new Exception(errorMessage, e); } throw e; } } public IWebElement WaitingGet(int seconds = 5, string errorMessage = “”) { //Waiting function using above method. }

public void Click(int seconds = 5, string errorMessage = “”) { WaitingGet(seconds, errorMessage).Click(); }

In the last post of this series, I will go over how to connect what I covered in posts 1 and 2 and put it all together in your project. Footnote: You will also need to write other waiting functions as well as interaction functions * to handle your framework calls. The above functions will handle the structure of the page element class to allow your other functions to handle specific interactions such as clicking or other types of waiting such as WaitUntilVisible(int seconds = 5, string errorMessage = “”) or WaitUntilNotVisible(int seconds = 5, string errorMessage = “”).

Discuss: Selenium Testing Framework Pt 2: Base Classes

Free Trial

Get access to a free 14-day trial version, or contact Sales for more information.