Create Hybrid Test Automation Framework – Interface Contracts

Datetime:2016-08-23 01:15:49          Topic: Automated Testing           Share

Guys, I am so excited to announce the creation of new series of blog posts- Design & Architecture . The main idea behind this is that I will show more abstract, visionary improvements that you might bring to your test automation projects that do not depend on the used test automation framework such as WebDriver or Testing Frame work . The first articles from Design & Architecture Series are going to be dedicated to the creation of a  Hybrid Test Automation Framework . Through this type of test automation framework, you can quickly execute your tests through different test automation frameworks without changing a single line of code, using only configuration switches.

As you may guess, the creation of a Hybrid Test Automation Framework is not an easy job. A lot of code should be written so I cannot explain everything in a single post. Because of that, I am going to separate logically in the best possible way the content. In this first article, I am going to explain to you how to create the core interface contracts that your test pages and tests will use so that they do not depend on a concrete implementation and at the same time follow the best practices and SOLID principles .

Quick Navigation

Hybrid Test Automation Framework- Interfaces

Primary IDriver Interface

IElementFinder Interface

IJavaScriptInvoker Interface

INavigationService Interface

IDialogService Interface

Hybrid Test Automation Framework in Tests

Hybrid Test Automation Framework Base Page

Non-Hybrid Page Object

Hybrid Test Automation Framework Test Example

Non-Hybrid Test Automation Framework Test Example

Hybrid Test Automation Framework- Interfaces

Primary IDriver Interface

This is the main interface that you will use in your code. As you can found out from the lines below it does not contain any methods, it only inherits a couple of other important contacts. The main idea behind this is to follow the Interface Segregation SOLID Principle . If you need to find elements, you will use the IElementFinder interface if you require updating cookies you will use the I Cookie Service and so forth. The principle states that no client should be forced to depend on methods that it does not use so we split the big interface in several smaller logically separated parts.

public interface IDriver :
IElementFinder ,
INavigationService ,
ICookieService ,
IDialogService ,
IJavaScriptInvoker ,
IBrowser
{
}

view raw IDriver.cs hosted by GitHub

IElementFinder Interface

public interface IElementFinder
{
TElement Find<TElement>(By by) where TElement : class , IElement;
IEnumerable<TElement> FindAll<TElement>(By by) where TElement : class , IElement;
bool IsElementPresent ( Core.By by );
}

view raw IElementFinder.cs hosted by GitHub

The IElementFinder contract holds methods for locating elements on the pages. Also, it contains a logic for checking if an element is present.

The methods return IElement interface which represents a base HTML page element.

public interface IElement : IElementFinder
{
string GetAttribute ( string name );
void WaitForExists ();
void WaitForNotExists ();
void Click ();
void MouseClick ();
bool IsVisible { get ; }
int Width { get ; }
string CssClass { get ; }
string Content { get ; }
}

view raw IElement.cs hosted by GitHub

To support searching of elements inside other container items such as DIVs, IElement inherits from the  IElementFinder interface. All different controls will inherit from the IElement contract. You will find more information about this in the next articles from the series.

Similar to the WebDriver implementation we have an abstract static class By for setting the elements' localization strategy.

public class By
{
public By ( SearchType type , string value ) :
this (type, value, null )
{
}
public SearchType Type { get ; private set ; }
public string Value { get ; private set ; }
public static By Id ( string id )
{
return new By(SearchType.Id, id);
}
public static By InnerTextContains ( string innerText )
{
return new By(SearchType.InnerTextContains, innerText);
}
public static By Xpath ( string xpath )
{
return new By(SearchType.XPath, xpath);
}
public static By Id ( string id , IElement parentElement )
{
return new By(SearchType.Id, id, parentElement);
}
public static By CssClass ( string cssClass )
{
return new By(SearchType.CssClass, cssClass);
}
public static By Name ( string name )
{
return new By(SearchType.Name, name);
}
}

view raw By.cs hosted by GitHub

IJavaScriptInvoker Interface

It holds a logic for JavaScript execution.

public interface IJavaScriptInvoker
{
string InvokeScript ( string script );
}

view raw IJavaScriptInvoker.cs hosted by GitHub

INavigationService Interface

INavigationService interface has several methods regarding navigation such as navigating by a relative URL or absolute URL . Also, it contains logic for waiting for specific UR L .

public interface INavigationService
{
event EventHandler<PageEventArgs> Navigated;
string Url { get ; }
string Title { get ; }
void Navigate ( string relativeUrl , string currentLocation , bool sslEnabled = false );
void NavigateByAbsoluteUrl ( string absoluteUrl , bool useDecodedUrl = true );
void Navigate ( string currentLocation , bool sslEnabled = false );
void WaitForUrl ( string url );
void WaitForPartialUrl ( string url );
}

view raw INavigationService.cs hosted by GitHub

IDialogService Interface

Through it, you can handle different dialogs.

public interface IDialogService
{
void Handle ( System.Action action = null , DialogButton dialogButton = DialogButton.OK);
void HandleLogonDialog ( string userName , string password );
void Upload ( string filePath );
}

view raw IDialogService.cs hosted by GitHub

IBrowser Interface

This is one of the most important interfaces, part of the main IDriver interface. Through the  IBrowser contract, you can execute browser-specific actions such as switching frames, refreshing, clicking back/forward buttons and so on.

public interface IBrowser
{
BrowserSettings BrowserSettings { get ; }
string SourceString { get ; }
void SwitchToFrame ( IFrame newContainer );
IFrame GetFrameByName ( string frameName );
void SwitchToDefault ();
void Quit ();
void WaitForAjax ();
void WaitUntilReady ();
void FullWaitUntilReady ();
void RefreshDomTree ();
void ClickBackButton ();
void ClickForwardButton ();
void LaunchNewBrowser ();
void MaximizeBrowserWindow ();
void ClickRefresh ();
}

view raw IBrowser.cs hosted by GitHub

Hybrid Test Automation Framework in Tests

This is how will look like the base page for all pages of your hybrid test automation framework.

Hybrid Test Automation Framework Base Page

public abstract class BasePage
{
private readonly IElementFinder elementFinder;
private readonly INavigationService navigationService;
public BasePage ( IElementFinder elementFinder , INavigationService navigationService )
{
this .elementFinder = elementFinder;
this .navigationService = navigationService;
}
protected IElementFinder ElementFinder
{
get
{
return this .elementFinder;
}
}
protected INavigationService NavigationService
{
get
{
return this .navigationService;
}
}
}

view raw BasePage.cs hosted by GitHub

As you can see the base page does not require all interfaces of the IDriver contract. Most pages need only a way to find elements and to navigate.

Non-Hybrid Base Page

To see the difference, you can find below the code of the non-hybrid version of the BasePage class .

public abstract class BasePage
{
protected IWebDriver driver;
public BasePage ( IWebDriver driver )
{
this .driver = driver;
}
public abstract string Url { get ; }
public virtual void Open ( string part = " " )
{
this .driver.Navigate().GoToUrl( string .Concat( this .Url, part));
}
}

view raw BasePage.cs hosted by GitHub

As you can see, we pass the whole IWebDriver interface. However, often we do not need all methods that it exposes.

Hybrid Page Object

public partial class BingMainPage : BasePage
{
public BingMainPage (
IElementFinder elementFinder ,
INavigationService navigationService )
: base (elementFinder, navigationService)
{
}
public void Navigate ()
{
this .NavigationService.NavigateByAbsoluteUrl( @"http://www.bing.com/" );
}
public void Search ( string textToType )
{
// It is going to be implemented in the next article.
////this.SearchBox.Clear();
////this.SearchBox.SendKeys(textToType);
this .GoButton.Click();
}
public int GetResultsCount ()
{
int resultsCount = default ( int );
resultsCount = int .Parse( this .ResultsCountDiv.Content);
return resultsCount;
}

view raw BingMainPage.cs hosted by GitHub

Similar to the base page, here we pass only the abstract hybrid test automation framework's contracts. Also, we need to implement the Navigate method manually.

Non-Hybrid Page Object

public partial class BingMainPage : BasePage
{
public BingMainPage ( IWebDriver driver ) : base (driver)
{
}
public override string Url
{
get
{
return @"http://www.bing.com/" ;
}
}
public void Search ( string textToType )
{
this .SearchBox.Clear();
this .SearchBox.SendKeys(textToType);
this .GoButton.Click();
}
public int GetResultsCount ()
{
int resultsCount = default ( int );
resultsCount = int .Parse( this .ResultsCountDiv.Text);
return resultsCount;
}
}

view raw BingMainPage.cs hosted by GitHub

The only difference compared to the hybrid version is that the BingMainPage is coupled with the concrete IWebDriver implementation.

Hybrid Page Map

public partial class BingMainPage
{
public IElement SearchBox
{
get
{
return this .ElementFinder.Find<IElement>(By.Id( " sb_form_q " ));
}
}
public IElement GoButton
{
get
{
return this .ElementFinder.Find<IElement>(By.Id( " sb_form_go " ));
}
}
public IElement ResultsCountDiv
{
get
{
return this .ElementFinder.Find<IElement>(By.Id( " b_tween " ));
}
}
}

view raw BingMainPage.cs hosted by GitHub

Here I used the improved version of the Page Object Pattern - the element map is implemented as a partial class of the primary page object class. We use the ElementFinder property that comes from the BasePage class to locate the different elements. As you have probably noticed, the different properties return the I El ement interface so that the map is not coupled with the concrete implementation of the controls.

Non-Hybrid Page Map

public partial class BingMainPage : BasePage
{
public IWebElement SearchBox
{
get
{
return this .driver.FindElement(By.Id( " sb_form_q " ));
}
}
public IWebElement GoButton
{
get
{
return this .driver.FindElement(By.Id( " sb_form_go " ));
}
}
public IWebElement ResultsCountDiv
{
get
{
return this .driver.FindElement(By.Id( " b_tween " ));
}
}
}

view raw BingMainPage.cs hosted by GitHub

Similar to the page object class the non-hybrid element map is coupled with the WebDriver's concrete implementation.

Hybrid Test Automation Framework Test Example

[TestClass]
public class BingTests
{
private BingMainPage bingMainPage;
private IDriver driver;
[TestInitialize]
public void SetupTest ()
{
this .driver = new SeleniumDriver();
this .bingMainPage = new BingMainPage( this .driver, this .driver);
}
[TestCleanup]
public void TeardownTest ()
{
this .driver.Quit();
}
[TestMethod]
public void SearchForAutomateThePlanet ()
{
this .bingMainPage.Navigate();
this .bingMainPage.Search( " Automate The Planet " );
this .bingMainPage.AssertResultsCountIsAsExpected( 264 );
}
}

view raw BingTests.cs hosted by GitHub

As you can see from both examples, the code is almost identical with the only difference that our test automation framework can be switched in the first version if you assign another concrete implementation to the IDriver interface variable.

Non-Hybrid Test Automation Framework Test Example

[TestClass]
public class BingTests
{
private BingMainPage bingMainPage;
private IWebDriver driver;
[TestInitialize]
public void SetupTest ()
{
this .driver = new FirefoxDriver();
this .bingMainPage = new BingMainPage( this .driver);
}
[TestCleanup]
public void TeardownTest ()
{
this .driver.Quit();
}
[TestMethod]
public void SearchForAutomateThePlanet ()
{
this .bingMainPage.Open();
this .bingMainPage.Search( " Automate The Planet " );
this .bingMainPage.AssertResultsCountIsAsExpected( 264 );
}
}

view raw BingTests.cs hosted by GitHub





About List