Blog We love to talk!

Feb Thu

How To Structure Your Selenium Tests

  •  2-16-2017
selenium

In this post we will cover a few techniques you can use to make your Selenium tests easier to write and maintain. If you are not already familiar with the Selenium basics you might want to read our introduction guide and the basics about elements first.

Test Example

Below you can find a simple example Selenium test which loads a webpage, clicks a button and validates the text of a specific element. So far nothing new:

IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("http://www.browseemall.com");

IWebElement buyNavigationButton = driver.FindElement(By.Id("buy-navigation"));

buyNavigationButton.Click();

IWebElement pricingProfessional = driver.FindElement(By.Id("pricing-prof"));

Assert.AreEqual("249", pricingProfessional.Text);

driver.Close();

In the process of writing more and more tests you will soon realize that big parts of the test code get repeated over and over again. Especially finding elements on the page is prone to get copied from existing tests.

Page Objects

This is all well and good until there is an ID change and you have to go back and edit every single test case by hand. As a solution to this you can use so called Page Objects which contain all locators for a single page or part of the application. This way you only need to change the new ID in one single code file.

Let us take a look at 2 different Page Objects: HomePage and PricingPage:

public class HomePage
{
    private IWebDriver driver;

    private By buyNavigation = By.Id("buy-navigation");

    public HomePage(IWebDriver driver)
    {
        this.driver = driver;
    }

    public void ClickBuyNavigation()
    {
        driver.FindElement(buyNavigation).Click();
    }
}

public class PricingPage
{
    private IWebDriver driver;

    private By professionalPrice = By.Id("price-prof");

    public PricingPage(IWebDriver driver)
    {
        this.driver = driver;
    }

    public string GetProfessionalPrice()
    {
        return driver.FindElement(professionalPrice).Text;
    }
}

As you can see we have centralized the locators of all necessary elements in a separate class. You can now go ahead and use this class in any tests that need to access these specific pages which makes the tests easier to maintain and much more readable. Let us see how the test example above now looks:

IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl("http://www.browseemall.com");

HomePage homePage = new HomePage(driver);
homePage.ClickBuyNavigation();

PricingPage pricingPage = new PricingPage(driver);
Assert.AreEqual("249", pricingPage.GetProfessionalPrice());

driver.Close();

Now you can understand the test case even if you are not familiar with the actual HTML structure of the pages.

Page Factory

It is possible to further simply these Page Objects by using a functionality provided by Selenium: Page Factories. With a Page Factory Selenium will do all the work of finding elements for us. Modifying the HomePage class as a Page Factory gives us:

public class HomePage
{
    private IWebDriver driver;

    [FindsBy(How=How.Id, Using = "buy-navigation")]
    private IWebElement buyNavigation;

    public HomePage(IWebDriver driver)
    {
        this.driver = driver;

        PageFactory.InitElements(driver, this);
    }

    public void ClickBuyNavigation()
    {
        buyNavigation.Click();
    }
}

The usage of this Page Factory in the actual test does not change but it gives you an even easier way to define elements and their locators.

If necessary you can also have Selenium wait on elements inside a Page Factory by using the RetryingElementLocator as shown here:

public PricingPage(IWebDriver driver)
{
    this.driver = driver;            

    RetryingElementLocator locator = new RetryingElementLocator(driver, TimeSpan.FromSeconds(100));
    PageFactory.InitElements(this, locator);
}

Bots

Another great way to structure your tests is by using a so called Selenium Bot.  These can be used to unify actions in your tests. Let us take a look at a really simple bot:

public class ActionBot
{
    private IWebDriver driver;

    public ActionBot(IWebDriver driver)
    {
        this.driver = driver;
    }

    public void Click(By locator)
    {
        Click(driver.FindElement(locator));
    }

    public void Click(IWebElement element)
    {
        element.Click();
    }

    public string GetText(By locator)
    {
        return driver.FindElement(locator).Text;
    }
}

And the usage of this bot in a test case:

IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("http://www.browseemall.com");

ActionBot bot = new ActionBot(driver);

bot.Click(By.Id("buy-navigation"));

Assert.AreEqual("249", bot.GetText(By.Id("pricing-prof")));

driver.Close();

Combining Page Objects and Bots

Of course it is possible to combine the two above concepts to give you a really great test structure. A test using both could look like this:

IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("http://www.browseemall.com");

ActionBot bot = new ActionBot(driver);

bot.Click(By.Id("buy-navigation"));

Assert.AreEqual("249", bot.GetText(By.Id("pricing-prof")));

driver.Close();

Of course for this to work we also need to update the Page Object:

public class HomePage
{
    private IWebDriver driver;

    private ActionBot bot;

    [FindsBy(How=How.Id, Using = "buy-navigation")]
    private IWebElement buyNavigation;

    public HomePage(IWebDriver driver, ActionBot bot)
    {
        this.driver = driver;

        OpenQA.Selenium.Support.PageObjects.PageFactory.InitElements(driver, this);
    }

    public void ClickBuyNavigation()
    {
        bot.Click(buyNavigation);
    }
}

And this is all it takes to create easy to read and maintainable Selenium test cases. Happy testing!

No Comments

Leave a Reply

Your email address will not be published.