Do Cross Browser Test Automation Like A PRO



This article is the first from a series about how to do cross browser test automation with Selenium WebDriver.

The series has 3 parts.

Part 1 explains how to run test scripts for one browser.

Part 2 goes into running test scripts on multiple local browsers.

Part 3 concludes with running test scripts in a cloud Selenium grid.


TEST CASE TO BE AUTOMATED

I will use the www.londondrugs.com site.

The test case to be automated consists in

1. opening the home page of the site




2. clicking on the SEARCH option of the top menu


3. typing a keyword in the SEARCH textbox


4. clicking the submit button





5. checking that the keyword is included in the results page


6. checking that the results count on the results page is greater than 0










The project uses the following architecture:

The test scripts are created in the test class that belongs to the TESTS layer (of the automation architecture).

The test scripts do not use directly any Selenium WebDriver classes and do not declare a WebDriver object.

They use the page object classes instead.

There are 2 page object classes: Home Page class, Results Page class


They belong to the FRAMEWORK layer of the automation architecture.

Both page object classes implement user actions for each site page by using Selenium WebDriver classes (which correspond to the SELENIUM layer of the automation architecture).

The WebDriver object is being created by a Driver class that belongs to the FRAMEWORK layer as well.

The project uses 2 folders:

TESTS folderTestScripts_OneBrowser class

FRAMEWORK folderSettings class,  HomePage class, ResultsPage class,  Driver class



SETTINGS.JAVA



The purpose of this class is to store all settings needed in the project such as

  • site url
  • keyword search
  • page title
  • regular expression used for extracting the results number

public class Settings {

public final static String siteUrl = "http://www.londondrugs.com";    

public final static String keyword = "iphone";

public final static String homePageTitle = "London Drugs";

public final static String resultsPageTitle = "Sites-LondonDrugs-Site";

public final static String resultsCountRegEx = "(.*)(of )(\\d+)( matches)";

}


DRIVER.JAVA


This class creates the browser driver and stores it into a static class member.

By default, the browser driver is for Firefox.

The driver object is created in a static initialize() method.

The driver object is closed in a static quit() method.

The initialize() and quit() methods are created as static so that
  • there is no need of declaring a WebDriver object in the test class
  • the page object classes do not need to pass the driver object from one to another


import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
package FRAMEWORK;

public class Driver { 

private static WebDriver driver;

public static WebDriver getDriver()
{

return driver;

}

public static void initialize() 
{      

driver = getFirefoxDriver();

} 

public static void quit()
{

getDriver().quit();

}

}


HOME PAGE OBJECT CLASS


The home page class declares a few locator members for the 
  • search menu item element
  • search text box element
  • submit button

It declares members for the driver and explicit wait objects that are initialized in the class constructor.

The class constructor opens the site's home page in the url and checks if the home page title is correct.

The home page class has also a search method that
  • clicks the search menu item
  • types the keyword in the search text box
  • clicks the submit button
The result of the search method is a ResultsPage object.


import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
package FRAMEWORK;

public class HomePage {

String searchMenuItemLocator = "//div[@id='search-cta']";
String searchTextBoxLocator  = "//input[@id='searchinput']";
String searchButtonLocator   = "//button[@name='simplesearch']";  

WebDriver driver; 
WebDriverWait wait;               

public HomePage()
{  

this.driver = Driver.getDriver();
wait = new WebDriverWait(driver, 10);

driver.get(Settings.siteUrl);

wait.until(ExpectedConditions.titleIs(Settings.homePageTitle));

}   

public ResultsPage search()
{

wait.until(ExpectedConditions.elementToBeClickable(By.xpath(searchMenuItemLocator))).
click();

wait.until(ExpectedConditions.elementToBeClickable(By.xpath(searchTextBoxLocator))).
sendKeys(Settings.keyword);

wait.until(ExpectedConditions.elementToBeClickable(By.xpath(searchButtonLocator))).
click();    

return new ResultsPage();  

}     

}


RESULTS PAGE OBJECT CLASS


The results page class declares a few locator members for the
  • search keyword element
  • results count element

It declares members for the driver and explicit wait objects that are initialized in the class constructor.

The class constructor checks if the results page title is correct.

The results page class has the following methods

- isKeywordDisplayed()

checks if the keyword is displayed in the results page

- resultsCount()

returns the number of results;
uses the extractValue method which uses a regular expression for extracting the results count

The results of the search method is a ResultsPage object.

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
package FRAMEWORK;

public class ResultsPage { 

String searchKeywordLocator = "//h2[@class='product-search']/a"; 
String resultsCountLocator  = "(//div[@class='resultshits'])[1]";

WebDriver driver; 
WebDriverWait wait; 

public ResultsPage()
{ 

this.driver = Driver.getDriver();
wait = new WebDriverWait(driver, 10);

wait.until(ExpectedConditions.titleIs(Settings.resultsPageTitle));

}    

public Boolean isKeywordDisplayed()
{ 

return wait.until(ExpectedConditions.
visibilityOfElementLocated(By.xpath(searchKeywordLocator))).isDisplayed(); 

}    

public int resultsCount()
{     

WebElement resultCountElement = wait.until(ExpectedConditions.
visibilityOfElementLocated(By.xpath(resultsCountLocator)));

return Integer.parseInt(extractValue(resultCountElement.getText(),
Settings.resultsCountRegEx, 3));  

}

public String extractValue(String textValue, String regEx, int position)
{          

String result = "";

Pattern pattern = Pattern.compile(regEx);

Matcher matcher = pattern.matcher(textValue);

if (matcher.find())
result = matcher.group(position); 

return result; 

} 

}


TEST CLASS

It includes the following methods:

- setUp()

It uses the @Before annotation so it is executed automatically before the test script.
It initializes the browser driver.

- tearDown()

It uses the @After annotation so it is executed automatically after the test script.
It closes the browser driver.

- testResultsInfo()

It uses the HomePage and ResultsPage classes for implementing the test script.

import static org.junit.Assert.*;
import java.net.MalformedURLException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import FRAMEWORK.Driver;
import FRAMEWORK.HomePage;
import FRAMEWORK.ResultsPage;

public class TestScripts_OneBrowser {                  

@Before
public void setUp() throws MalformedURLException 
{      

Driver.initialize();   

}

@After
public void tearDown()
{    

Driver.getDriver().quit();   

}

@Test
public void testResultsInfo() throws InterruptedException
{        

ResultsPage results = (new HomePage()).search();    

assertTrue(results.isKeywordDisplayed() == true);            

assertTrue(results.resultsCount() > 0);            

}  

}

The code included in the article works.

If you have any difficulty using it or have any questions, please leave them in the Comments section.

Part 2 will show how to extend this code so that the test script runs in multiple local browsers.

Is it easy to learn Selenium WebDriver if I dont know Java?



Learning Selenium WebDriver and Java is rarely as easy as people says that it is.

It is not easy.

It is not difficult.

It is challenging.

If test automation is easy, all testers would know it.

And this article would not exist.

Before we continue, think about how many testers you know who had success learning Selenium by themselves without knowing Java first.

Do you know many?



What is test automation about?


Test automation is about
  1. Creating Test Scripts using

  2. A Programming Language Like Java for

  3. Automating User Scenarios by

  4. Interacting With The Application through

  5. A Test Automation Library Like Selenium WebDriver

The programming language is the most difficult to learn.

Programming languages come from math and include lots of abstractions that are not always intuitive.




What else is involved in test automation?


Test automation with Selenium WebDriver means more than programming with Java.

Test automation requires also
  1. Unit testing framework

  2. The test automation scripts need to be short and independent.

    This is achieved through unit testing frameworks such as JUNIT or TEST NG.

    Key unit testing concepts for test automation are
    • annotations
    • test runner
    • assertions
    • test suites
    • test fixtures

  3. Locators of site elements

  4. Each test automation script interacts with various site elements by
    • clicking them
    • typing text in them
    • getting element status (is displayed, is selected, is enabled)
    • getting element information (value, attributes values, css info)

    Before interacting with a site element, this element has to be found.

    If the element has an id or name, finding it is very easy.

    Otherwise, an expression called element locator has to be created using languages such as CSS and XPATH.

  5. Organize the code with object oriented programming

  6. It is not sufficient to create test automation scripts that work.

    The test scripts should also be clear, easy to change and maintain.

    This is accomplished through models such as Page Objects which require knowledge of object oriented concepts like classes and inheritance.

  7. Use external frameworks

  8. When working on test automation scripts, it is important to think also about reporting, taking screenshots, running scripts in parallel, logging.

    Many of these functionalities are provided by 3rd party libraries.



How challenging is to learn Selenium?



How you learn Selenium determines how big the challenge is.

Learning by yourself makes it very challenging.

It is free as it does not cost any money.

It will cost you, however, lots of time, frustration and effort.

The chances of not succeeding are high.

If you have time to spare, learning by yourself may be an option.

But it is not the best option if you need results fast.

When learning by yourself, there are multiple challenges that you need to manage.



There is a huge amount of Selenium and Java study materials available for beginners.

Blogs, books, online courses.

Some of them are good and adequate for a beginner, others are not.

The challenge is selecting the good ones.

Also, after a while, your level of knowledge changes from beginner to intermediate.

For testers with intermediate level for Selenium and Java, there are less study materials available.

The same challenge remains but it becomes more evident.

How will you find study materials that take you forward instead of being just confusing?




Test automation requires multiple skills as explained above.

The order that these skills are learned is important.

In other words, what will you learn first?

The programming language?

XPATH?

Browser concepts?

Unit testing?

Page object model?

CSS?

The same question applies to learning each individual skill.

Where will you start, what will be the next step, and the step after, etc?




Without a clear learning map and a good compass to navigate it, learning Selenium may be very challenging.




Each skill required for test automation is vast.

Learning it fully takes a very long time and is usually not necessary for working on test automation.

The challenge is to know how much of each skill is needed for starting test automation in the shortest time possible.




One thing happens for sure when learning test automation.

You run into issues and problems about a specific element or about how different elements work together.

And you dont know how to solve the issue and have no idea how to find information or help.

Without a support system that helps you in difficult situations, your learning may be stuck or stopped.



Everyone has expectations before starting learning.

Incorrect expectations are that learning will be easy and fast and that there is a limited number of things to learn.

There are always more things to learn than you expected.

And no, learning will not be easy and will not be fast.

The opposite is true.

How you define your goals increases or decreases the chance of failing.

A goal like "I will become a test automation expert in 3 months" is unrealistic.

A much better goal is "In 3 months, I will be able to create simple test automation scripts for the www.abc.com site".

Starting small and learning continuously new skills increase the chances to success.

Learning Selenium takes time.

It will not happen in a week or a month.

Each individual skill needs to be learned separately.

Then, you should learn how to use them together.

In time, without focus and perseverance, boredom or frustration can happen which may bring your learning to a halt.




I want to be clear.

It is not impossible to learn Selenium and Java by yourself.

It can be done.

But there are better and easier ways of doing it than by yourself.

Is there a less challenging way of learning Selenium?


Learning Selenium does not have to be this challenging.

Being trained by someone else is easier, less challenging and gets started in a relative short time.

It is not free but it saves lots of time and lots of effort.

This is because the trainer

  • has the resources that you need

  • has past experience training people like you

  • has the answers for your questions

  • knows the easiest and shortest way to take to your goal

  • has the map of how your learning should happen

  • he is there for you when you need help

  • he keeps you focused



The decision about how to learn is yours.

What will you do?

Make Your Automation Scripts Dynamic With Parameters

JUNIT provides the ability of having test scripts with parameters.

When unit tests do not have parameters, executing them will provide the same result, over and over.

Using unit tests with parameters makes the unit tests dynamic and with better coverage.


                                                                                                                             


How is this done in JUNIT?

Lets start with a simple JUNIT test class that has only one unit test:

public class ScriptsWithParameters {

private int variable1 = 1;

private int variable2 = 2;

@Test
public void test() {

System.out.println(variable1 + " - " + variable2);


}

}

The class has 2 members (variable1, variable2) that have default values.

Executing the unit test displays

1 - 2

What we want is to be able to have the unit test executed repeteadly.

Every time it is executed, the variables values should change.

To do this, we need 2 things:


  1. create the list of values
  2. a way of passing the values to the class members (variable1, variable2)


Create the list of parameter values



The list of values is added to the test class as follows:

@RunWith(Parameterized.class)
public class ScriptsWithParameters {

@Parameters
public static Collection data() {
    return Arrays.asList(new Object[][] 
   {     
           { 0, 0 }, 
           { 1, 1 }, 
           { 2, 1 }, 
           { 3, 2 }, 
           { 4, 3 }, 
           { 5, 5 }, 
           { 6, 8 }  
    });
}

private int variable1;

private int variable2;

@Test
public void test() {

   System.out.println(variable1 + " - " + variable2);

}

}

The list is in reality a list of lists of values.

Each element of the list is a list of values.

Every time a unit test runs, a value from the list is selected.

The @Parameters annotation is required.



Pass the values to the parameter class members


This happens through the test class constructor:

@RunWith(Parameterized.class)
public class ScriptsWithParameters {

@Parameters
public static Collection data() {
       return Arrays.asList(new Object[][] 
           { 0, 0 }, 
           { 1, 1 },  
           { 2, 1 }, 
           { 3, 2 }, 
           { 4, 3 }, 
           { 5, 5 }, 
           { 6, 8 }  
       });
}

private int variable1;

private int variable2;

public ScriptsWithParameters(int p1, int p2) {

variable1 = p1;

variable2 = p2;

}

@Test
public void test() {

      System.out.println(variable1 + " - " + variable2);

}

}


How does it work?
  • Before the unit test runs, the test class is loaded.
  • When the test class is loaded, a list of values is selected: {0, 0}
  • The values are provided as parameters to the constructor of the class.
                      public ScriptsWithParameters(int p1 = 0, int p2 = 0)
                      {
                            variable1 = p1;
                            variable2 = p2;
                       }
  • In the constructor, the constructor parameters are assigned to the class members.

Executing the test class will execute the unit test multiple times, once for each set of values:

0 - 0
1 - 1
2 - 1
3 - 2
4 - 3
5 - 5
6 - 8


The JUNIT execution status shows one result line for each list of values:









Can unit tests with parameters be done differently?


Unit tests with parameters can be created without using the test class constructor:

@RunWith(Parameterized.class)
public class ScriptsWithParameters3 {

@Parameters
public static Collection data() 
{
     return Arrays.asList(new Object[][] 
           {     
               { 0, 0 }, 
               { 1, 1 }, 
               { 2, 1 }, 
               { 3, 2 }, 
               { 4, 3 }, 
               { 5, 5 }, 
               { 6, 8 }  
    });
}

@Parameter 
public int variable1;

@Parameter(value = 1)
public int variable2;

@Test
public void test() {

       System.out.println(variable1 + " - " + variable2);

}

}

In this case, the class members are annotated with @Parameter.

Before the unit test runs, the test class is loaded.

When the test class is loaded, a list of values is selected:

{0, 0}

The first value is assigned to variable1.

The second value is assigned to variable2.


How do use unit tests with parameters for test automation?


The test case to be automated is this:

1. open the www.vpl.ca site

2. do a keyword search using java as the keyword

3. validate that the number of results is greater than 0

4. on the results page, change the sort order from the default sort order to Author

5. verify that the new sort order is correct

When creating the test automation script, the keyword and sort order should be parameterized.

The following values can be used for the 2 parameters:

search keyword: java, vancouver, cats, dogs

sort order: Author, Title, Rating, Relevance

The test automation script without parameters is below:

public class tests {   

public WebDriver driver;       

//use this class member for the search keyword value
private String keywordValue = "java";

//use this class member for the new sort order
private String sortOrderValue = "Author";         

@Before
public void setUp() {
System.setProperty("webdriver.chrome.driver",            
"C:\\Users\\asiminiuc\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe"); 

driver = new ChromeDriver();                  
}


@After
public void tearDown() {  
driver.quit();    
}                     

@Test
public void tests1() throws InterruptedException {             
HomePage homePage = new HomePage(driver);

ResultsPage resultsPage = homePage.search(keywordValue);    
assertTrue(resultsPage.getItemsNumber() > 0);            

resultsPage = resultsPage.changeSortOrder(sortOrderValue);      
assertEquals(resultsPage.getSortOrderValue(), sortOrderValue);     
}

The test script with parameters is very similar

@RunWith(value = Parameterized.class)
public class testsPARAMETERS {   

public WebDriver driver;       

private String keywordValue, sortOrderValue;         

public testsPARAMETERS(String keyword, String sortOrder) {
  keywordValue = keyword;
  sortOrderValue = sortOrder;
}      

@Parameters
public static Collection data() throws IOException {
Object[][] data = {
                      {"java,Author"}, 
                      {"vancouver,Title"}, 
                      {"cats,Rating"}, 
                      {"dogs,Relevance"}
                  }; 

return Arrays.asList(data);       
}

@Before
public void setUp() {    

System.setProperty("webdriver.chrome.driver", 
"C:\\Users\\asiminiuc\\AppData\\Local\\Google\\Chrome\\Application\\chromedriver.exe"); 

driver = new ChromeDriver();                       
}

@After
public void tearDown() {
  driver.quit();
}                        


@Test
public void tests1() throws InterruptedException {                           

HomePage homePage = new HomePage(driver);

ResultsPage resultsPage = homePage.search(keywordValue);       
assertTrue(resultsPage.getItemsNumber() >= 0);              

resultsPage = resultsPage.changeSortOrder(sortOrderValue);      
assertEquals(resultsPage.getSortOrderValue(), sortOrderValue);                       

}

}




READ NEXT

1. Why is unit testing essential for test automation?

2. WebDriver test automation is like driving a taxi

3. How to learn test automation from zero