Use Ranorex When Automating Windows Applications


Use Ranorex when working on test automation for Windows applications.


Ranorex has many qualities that recommend it for test automation:
  • uses C# as programming language
  • supports many application types (windows, mobile and web)
  • has an amazing customer support.
The company that built it does not have other products so they put all their focus and resources into Ranorex.

Everything that follows comes from my test automation experience with Ranorex.

I am not working for Ranorex , nor do I have any business relationship with them.

I am just a fan of the product.



Why not using QTP (UFT) for test automation of windows applications?



Until recently, my answer to this question would be that 

  • QTP uses VB Script as programming language
  • Ranorex uses C#




However, QTP (UFT) has a new module called Lean FT which allows the test automation engineer to use not only VB Script but also Java and C#.

So the language is not a problem.

Actually, UFT offers more languages that Ranorex for test automation.


If not the language, then what recommends Ranorex over QTP?


If you ever had a serious problem with any of the HP QA tools (QTP, QUALITY CENTER, LOAD RUNNER), you know that it takes a long time until HP provides a solution.

You have to go through many customer support levels that are most of the time not helpful.

Eventually, after many weeks (or months; I waited for a Load Runner solution for 6 months once), if you are lucky, you get to speak with someone who is knowledgeable enough to help out.


For Ranorex, since they are a 1 product company, you get help in days.

The Ranorex customer support is made of developers.

All you have to do is to add your problem to the forum.

Someone will pick it up and propose a solution (if available).

If you found a bug in the product, you will be provided with an approximate date of the patch.







Ranorex key functionalities

Record And Play

Like many other commercial test automation tools, Ranorex has a record and play module.

You can use it for recording user scenarios while interacting with your application.

The result of recording is displayed in a matrix where you can make changes:
  1. change the action type
  2. add new action
  3. change the order of actions
  4. remove actions
  5. add delays
  6. change objects
You can also see the C# code generated during recording.

This code can be changed if needed.






Another result of the recording is the object repository.

This is the place where you can see details of the objects used in the user scripts:

  • object type (button, label, radiobutton, textbox, image, etc)
  • object attributes
  • object locator (Ranorex uses XPATH for locators)
  • position of the object in the application object tree


Code Modules

In my opinion, in general, you should not use record and play for test automation.

Instead, create the test scripts slowly using code modules.

Code modules are just files with the CS extension where you can create your classes.

The classes are built using C# and can be of 2 types:

  • run-time classes:  they are used for creating the test scripts
  • normal classes: they are used for implementing the interaction with the application

When creating the classes that implement the interaction with the application, you can use the page object model.

This model says that the test scripts should not include test automation API commands so it is a good idea to use it for 

  • keeping your test scripts short and easy to understand
  • creating your own test automation framework



Spy


The spy is a component that allows exploring the application structure.

You can select any component of the application using the spy and get lots of details about it:
  • xpath expression that matches the component
  • component attributes
  • component value
  • position of the component in the application component tree







Ranorex Studio

The development and execution of the test scripts is being done from the Ranorex Studio.

Ranorex Studio is just an IDE similar with Visual Studio and Eclipse.

It has all functionalities needed when creating code and debugging it:

  • debug components
  • refactoring components
  • build components

The Ranorex API can be used from Visual Studio as well.







Test Cases And Test Suites

Test scripts can be added to test cases.

When this is done, it is possible to add to a test case a 

  • setup script (for setting up the environment needed for the test script) 
  • teardown script (for clearing up the environment)

The test cases allow creating data driven test scripts.

This is done by defining variables in the test scripts and binding them to data tables.

The result of executing the test scripts is displayed in HTML reports that can include any data available in the test scripts.






Learning Materials

You will find on the www.ranorex.com site lots of useful and very detailed materials such as
  1. user guide
  2. free webinars
  3. screencasts
  4. many other
If you are interested in the problems that Ranorex users have, you can check the product's forum.







Is Ranorex a good test automation tool for beginners?

It is a very good tool to use when starting learning test automation.

All information that a beginner needs is available on the Ranorex site, either in the user guide, product guide, screencasts or on the forum.

The user guide and screencasts are very detailed and clear and created for beginners.

The forum provides lots of examples and solutions to many problems.

The record and play tool gets the beginner started right away by providing information about
  1. actions
  2. repository
  3. objects attributes
  4. xpath locators
  5. C# code created for each recording session



When Did I Use Ranorex?

The first time I used Ranorex was in 2013.

My employer at that time had a windows application that used Sharepoint workflows for document management.

This application was crashing randomly all over the place.

I suspected a memory leak being the cause of the random crashes.

So I tried using QTP first.

This did not go well as QTP crashed every time I installed it.

It was probably not compatible either with the hardware of the computer I was using or with the enterprise Windows version.

The next tool I tried was Ranorex.

I knew about it from various QA blogs and online testing magazines.

I recorded a few scripts, customized them and then ran them continuously for a few hours.

This was sufficient for confirming that the memory leak is indeed present as the memory used by the application went up 5 times in 1 hour.

The company purchased the Ranorex license after the memory leak investigation and used it in a few projects.


The second time I used Ranorex was this year.

This time, I was looking into automating user scenarios for a WPF application.

The test automation code was used in this case for 2 purposes:


  • check periodically for memory leaks
  • automate the smoke build process




In conclusion, if you cannot use Selenium WebDriver for test automation, try Ranorex.

You will not be disappointed.


How To Write Better Test Automation Code With Test Driven Development

Test automation code can be improved greatly with the Test Driven Development (TDD) principles:
  • don't write a line of new code unless you first have a failing automated test 
  • eliminate duplication





How does test driven development work?


The development process is as follows when TDD principles are applied:
  1. write a test 
  2. make it compile 
  3. make it run 
  4. remove duplication 
  5. continue from 1


These tasks are also called Red/Green/Refactor:

RED - write a little test that doesn't work, perhaps it doesn't even compile

GREEN - make the test work quickly, committing whatever sins necessary in the process

REFACTOR - eliminate all the duplication created in just getting the test to work



How can TDD be used for test automation projects?


The easiest way of seeing TDD in action is through a short automation project.

The following project uses page objects for automating a test case.

The project will be created so that
  • page object classes are created


The test case to automate is quite simple:
  1. open the home page of the Vancouver Public Library site
  2. do a keyword search 
  3. on the results page, check that the number of results is greater than 0



Create the test class template


Lets create the test class template first.

The test class has the following components:

  • empty test script: testBookDetailsDisplayed() 
  • setUp() method for creating the driver object and starting the browser 
  • tearDown() method for closing the driver object and closing the browser

import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;


public class VplTests {

WebDriver driver;

@Before 

public void setUp()
{

System.setProperty("webdriver.chrome.driver", "C:\\Selenium\\BrowserDrivers\\chromedriver.exe");

driver = new ChromeDriver();

}

@After
public void tearDown()
{

driver.quit();

}

@Test
public void testBookDetailsDisplayed()
{

}

}


Executing the code will return the PASS status (green).





Practice RED/GREEN/REFACTOR




Open The Home Page Of The Site


The test script does not do anything yet.

Lets add some code to it.

@Test
public void testBookDetailsDisplayed()
{

HomePage homePage = new HomePage(driver);

assertEquals(homePage.getTitle(), "Vancouver Public Library - Home");

}

The new code lines are needed for the following reasons:
  • create an object (homePage) that corresponds to the HomePage class; the driver object is provided to the HomePage class constructor. 
  • create an assertion to check if the title of the homePage is correct


Running the test script will return the fail status (red).

The test script fails because the HomePage class does not exist.

To go from red to green, I will create the HomePage class and add minimal code to it so that the test script works:

public class HomePage{

public HomePage(WebDriver driver)
{

}

public String getTitle()
{

return "Vancouver Public Library - Home";

}

}

Running the test script will return the pass status (green).


Next, lets do the refactoring phase:

public class HomePage{

WebDriver driver;

String siteUrl = "http://www.vpl.ca";

public HomePage(WebDriver driver)
{
this.driver = driver;
driver.get(siteUrl);
}

public String getTitle()
{

return driver.getTitle();

}

}


Running the test script gets us green status again and we see things happening in the browser.

The updated HomePage class has a few differences compared to its previous version:
  • uses a driver member 
  • stores the driver parameter of the constructor in the driver class member 
  • opens the browser 
  • loads the site in the browser in the constructor using driver.get() 
  • returns the actual title of the page using driver.getTitle()



Do A Keyword Search


Next, lets add the code that does the keyword search.

@Test
public void testBookDetailsDisplayed()
{

HomePage homePage = new HomePage(driver);

assertEquals(homePage.getTitle(), "Vancouver Public Library - HomePage");

ResultsPage resultsPage = homePage.search("java");

assertEquals(resultsPage.getTitle(), "Search | Vancouver Public Library | BiblioCommons");

}

Running the test script fails with a red status.


The search method needs to be added to the HomePage class together with a few other things.

For the search to happen, a few actions are needed:
  • the user clicks in the search text box field
  • the user types the keyword in the search text box
  • the user clicks the search button
  • the results page is loaded  

I will add to the HomePage class 2 members for the locators needed for

  • the search text box
  • search button fields


public class HomePage{

WebDriver driver;

String siteUrl = "www.vpl.ca";

String searchTextBoxLocator = "//input[@id='globalQuery']";

String searchButtonLocator = "//input[@class='search_button']";

public HomePage(WebDriver driver)
{

this.driver = driver;

driver.get(siteUrl);

}

public String getTitle()
{
return driver.getTitle();
}

}



The search method will take a parameter for the keyword value:

public class HomePage{

WebDriver driver;

String siteUrl = "www.vpl.ca";

String searchTextBoxLocator = "//input[@id='globalQuery']";

String searchButtonLocator = "//input[@class='search_button']";

public HomePage(WebDriver driver)
{

this.driver = driver;
driver.get(siteUrl);

}

public String getTitle()
{
return driver.getTitle();
}

public void search(String keyword)
{

WebElement searchTextBox = driver.findElement(By.xpath(searchTextBoxLocator));
searchTextBox.sendKeys(keyword);

WebElement searchButton = driver.findElement(By.xpath(searchButtonLocator));
searchButton.click();

}

}


Running the test script will fail one more time (red).

This time, the failure is caused by the fact that the search method does not return a ResultsPage object.

When using page objects, if the result of a method is that a new page is displayed, the method should return a new page object.


Lets make the change that gets us from red to green:

public class HomePage{

WebDriver driver;

String siteUrl = "www.vpl.ca";

String searchTextBoxLocator = "//input[@id='globalQuery']";

String searchButtonLocator = "//input[@class='search_button']";

public HomePage(WebDriver driver)
{

this.driver = driver;
driver.get(siteUrl);

}

public String getTitle()
{
return driver.getTitle();
}

public ResultsPage search(String keyword)
{

WebElement searchTextBox = driver.findElement(By.xpath(searchTextBoxLocator));
searchTextBox.sendKeys(keyword);

WebElement searchButton = driver.findElement(By.xpath(searchButtonLocator));
searchButton.click();

return new ResultsPage(driver);
}



The change consists in adding the return type of the search method and returning a ResultsPage object.

The driver object is passed to the constructor of the ResultsPage object.

The test script still fails when executed.

We need the ResultsPage class as well:

public class ResultsPage
{

WebDriver driver;

public ResultsPage(WebDriver driver)
{
this.driver = driver;
}

public String getTitle()
{
return driver.getTitle();
}

}

}


We are back to green :)




Check That Results Number Is Greater Than 0

The last part of the script is about checking that the number of results is greater than 0.

I will add the assertion in the test script first:

@Test
public void testBookDetailsDisplayed()
{

HomePage homePage = new HomePage(driver);

assertEquals(homePage.getTitle(), "Vancouver Public Library - HomePage");

ResultsPage resultsPage = homePage.search("java");

assertEquals(resultsPage.getTitle(), "Search | Vancouver Public Library | BiblioCommons");

assertTrue(resultsPage.getResultsCount() > 0);

}


Running this test scripts fails with red status.



Lets go to green fast:

public class ResultsPage
{

WebDriver driver;

public ResultsPage(WebDriver driver)

{
this.driver = driver;
}

public String getTitle()

{
return driver.getTitle();
}

public int getResultsCount()
{
return 10;
}

}


The test script passes now with green status.

Since the getResultsCount() method returns always 10, it needs refactoring:

public class ResultsPage
{

WebDriver driver;

String resultLinkLocator = "//a[@testid='bib_link']";

public ResultsPage(WebDriver driver)
{
this.driver = driver;
}

public String getTitle()
{
return driver.getTitle();
}

public int getResultsCount()
{

List resultsList = driver.findElements(By.xpath(resultLinkLocator));
return resultsList.size();
}

}


The test script and the page object classes are final.

Executing the test script gives us the final green status.


How To Generate a Selenium WebDriver Html Report

Use the JUNIT TestWatcher class for creating Selenium HTML reports that are
  • custom and
  • cumulative



The TestWatcher JUNIT class allows overriding the failed() and succeeded() JUNIT methods that are called automatically when tests fail or pass.

Custom code can be added to the overriding methods for storing the tests names and their pass/fail status in HTML files.





Test results are displayed visually by default

Lets start with a simple test class with 4 tests:

import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class TestClass {

@Test public void testScript1() {
 assertTrue(1 < 2);
}

@Test public void testScript2() {
assertTrue(1 > 2);
}

@Test public void testScript3() {
assertTrue(1 < 2);
}

@Test public void testScript4() {
assertTrue(1 > 2);
}

}






When the test class is executed, we will get the following results:
  • testScript1 and testScript3: Pass
  • testScript2 and testScript4: Fail
There is no option of exporting the execution results to a file.




Use the TestWatcher JUNIT class for getting the pass/fail status for each test


When a test passes or fails during execution phase, the failed() and succeeded() JUNIT methods are being invoked automatically.

The TestWatcher JUNIT class allows overriding the following methods:

  • protected  void  failed(Throwable e, Description description) 

          failed() method is invoked when a test fails

  • protected  void  finished(Description description) 

          finished() method is invoked when a test method finishes (whether passing or failing)

  • protected  void  skipped(AssumptionViolatedException e, Description description) 

          skipped() method is invoked when a test is skipped due to a failed assumption.

  • protected  void  starting(Description description) 

          starting() method is invoked when a test is about to start

  • protected  void  succeeded(Description description) 

          succeeded() method is invoked when a test succeeds




Let's see how we can modify the previous test class to take advantage of the TestWatcher class behavior:

  • create a class (WatchManClassConsole) that implements a JUNIT rule through the TestWatcher() class
  • the succeeded() method is overridden so that the names of passed tests are displayed at the console with the successful status
  • the failed() method is overridden so that the names of the failed tests are displayed at the console with the failed status
  • the test class needs to extend the WatchManClassConsole class to take advantage of the overridden succeeded() and failed() methods


import static org.junit.Assert.assertTrue;
import org.junit.Test;

public class TestClass2 extends WatchManClassConsole {

@Test public void testScript1() {
assertTrue(1 < 2); >
}

@Test public void testScript2() {
assertTrue(1 > 2);
}

@Test public void testScript3() {
assertTrue(1 < 2);
}

@Test public void testScript4() {
assertTrue(1 > 2);
}
}


import org.junit.Rule; 

import org.junit.rules.TestRule; 
import org.junit.rules.TestWatcher; 
import org.junit.runner.Description; 
import org.junit.runners.model.Statement; 

public class WatchManClassConsole {

@Rule public TestRule watchman = new TestWatcher() { 

@Override public Statement apply(Statement base, Description description) { 
return super.apply(base, description); 


@Override protected void succeeded(Description description) { System.out.println(description.getDisplayName() + " " + "success!"); 


Override protected void failed(Throwable e, Description description) { System.out.println(description.getDisplayName() + " " + e.getClass().getSimpleName()); 
}
}; 
}

The result of executing the test class is below:



We have now both visual junit test results and console results.

This is a step in the right direction.

We can create next the cumulative, customized HTML report.




Build the custom, cumulative HTML report for Selenium test results



The last thing to do is writing the code for generating the HTML file that stores the pass/fail results for the test scripts.

The class that uses the TestWatcher overriding capabilities is called now WatchManClassHtml:

  • static File and BufferWriter members are used for writing information to the existing test result file
  • the setUp() junit method is used for setting up the environment; this includes
    • opening the existing test result file
    • writing the <HTML> and <BODY> html nodes to the file
    • writing the test execution date and time to the file
  • the tearDown() junit method is used for cleaning the environment:
    • writing the </HTML> and </BODY> html nodes to the file
    • closing the BufferWriter object
    • opening the HTML file
  • succeeded() method writes to the html file the names of the tests that passed
  • failed() method writes to the html file the names of the tests that failed


Each time the tests are executed, the tests results are appended to the test results file.

The test class is the same as before.

The only change added to it is to inherit from the WatchManClassHtml class:

import static org.junit.Assert.assertTrue;
import org.junit.Test; public class TestClass2 extends WatchManClassHtml {

@Test public void testScript1() {
assertTrue(1 < 2);
}

@Test public void testScript2() {
assertTrue(1 > 2);
}

@Test public void testScript3() {
assertTrue(1 < 2);
}

@Test public void testScript4() {
assertTrue(1 > 2);
}
}


import static org.junit.Assert.*;
import java.awt.Desktop;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class WatchManClassHtml {

static File f;
static BufferedWriter bw;

@BeforeClass
public static void setUp() throws IOException {

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
Date date = new Date();
f = new File("c:\\temp\\test_results.htm");
bw = new BufferedWriter(new FileWriter(f, true));
bw.write("<html><body>");
bw.write("<h1>Test Case Status - " + dateFormat.format(date) + "</h1>");

}

@AfterClass
public static void tearDown() throws IOException
{

bw.write("</body></html>");    
bw.close();
Desktop.getDesktop().browse(f.toURI());

}

@Rule public TestRule watchman = new TestWatcher() {

@Override public Statement apply(Statement base, Description description) {
return super.apply(base, description);
}

@Override protected void succeeded(Description description) {
try {
bw.write(description.getDisplayName() + " " + "success!");
bw.write("<br/>");
}
catch (Exception e1) {
System.out.println(e1.getMessage());
}
}

@Override protected void failed(Throwable e, Description description) {
try {
bw.write(description.getDisplayName() + " " + e.getClass().getSimpleName());
bw.write("<br/>");
}
catch (Exception e2) {
System.out.println(e2.getMessage());
}
}
};
}




The results of executing the test scripts are below:


Feel free to try the code by yourself and leave any questions about it in the COMMENTS section.

Thanks.



NEXT




How To Create The Selenium Driver Object In The Test Automation Framework

Good test automation practices say that no WebDriver API should be used in test scripts.

The browser driver object should also not be created and closed in the test class but in the automation framework.

Having the browser driver object created in the setUp() method of each test class is redundant and error prone.

Dont create and close the driver object in the test class

Lets start with a simple test case for the Vancouver Public Library site:
  1. Open the home page of the site
  2. Execute a keyword search
  3. On the results page, click the title of the first result
  4. On the details page, check that the book title is correct (displayed and including more than 1 character)
The code that implements the test case is below

import static org.junit.Assert.*; 
import org.junit.After; 
import org.junit.Before; 
import org.junit.Test; 
import org.openqa.selenium.WebDriver; 
import org.openqa.selenium.chrome.ChromeDriver; 

public class TestCreateDriverInBaseClass { 

String keyword = "java"; 
WebDriver driver; 

@Before public void setUp() 
{ driver = new ChromeDriver();} 

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

@Test public void testFirstResult() throws Exception { 

HomePage home = new HomePage(driver); 

ResultsPage results = home.search(keyword); 

DetailsPage details = results.selectResult(1); 

assertTrue(details.correctBookTitle() == true); 



The test class is very straightforward:

  • uses 2 fields, one for the search keyword, the second for the driver object 
  • setUp() method for creating the driver object and opening the browser 
  • tearDown() method for closing the driver object and closing the browser 
  • one test script, testFirstResult(), that implements the test case 


The typical test automation architecture uses the following layers:





In this architecture, each layer communicates only with the next layer so
  • test scripts communicate only with the framework classes; test scripts do not communicate directly with the WebDriver API
  • framework classes communicate with the WebDriver API; the framework classes are created using the page object model
  • WebDriver API communicates with the browser

Our test script looks pretty good from the test automation architecture point of view:
  • it uses the HomePage class for creating the home object
  • the search() method of the HomePage class implements the keyword search; since the result of a keyword search is opening the results page, the search method returns an object of the ResultsPage class (it returns a page object)
  • the selectResult() method of the ResultsPage class selects a result; since the outcome of clicking a result is going to the details page, the selectResult() method returns an object of the DetailsPage class  
  • the correctBookTitle() method of the DetailsPage class is used for checking if the book title is displayed and it has more than 0 characters
  • the assertion checks if the book title is correct
The test script does not include any WebDriver API.

The HomePage, ResultsPage and DetailsPage classes do not include WebDriver API either.

This is because all page object classes inherit from the Base Class.

All basic interactions with the site are implemented in the Base Class:
  1. open a page
  2. get a page title
  3. find an element using explicit wait
  4. get the value of an element
  5. check if an element is displayed 
The WebDriver APIs exist only in the base class.



See the complete code below:

package CreateDriver; 

import org.junit.Rule; 
import org.openqa.selenium.By; 
import org.openqa.selenium.WebDriver; 
import org.openqa.selenium.WebElement; 
import org.openqa.selenium.chrome.ChromeDriver; 
import org.openqa.selenium.support.ui.ExpectedConditions; 
import org.openqa.selenium.support.ui.WebDriverWait; 

public class BasePage 


WebDriverWait wait; 
WebDriver driver; 

public BasePage(WebDriver driver) 

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


public WebElement find(String locator) 

return wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(locator))); 


public void clickElement(String locator) 
{ find(locator).click();} 

public String getPageTitle() 
{ return driver.getTitle();} 

public void open(String url) 
{ driver.get(url);} 

public void typeText(String locator, String keyword) 

find(locator).sendKeys(keyword); 


public String getValue(String locator) 
{ return find(locator).getText();} 

public Boolean isDisplayed(String locator) 
{ return find(locator).isDisplayed(); } 



public class HomePage extends BasePage 


String searchFieldLocator = "//input[@id='globalQuery']"; 
String searchButtonLocator = "//input[@class='search_button']"; 

String siteUrl = "http://www.vpl.ca"; 

public HomePage(WebDriver driver) throws Exception 


super(driver); 

open(siteUrl); 

if (getPageTitle().equalsIgnoreCase("Vancouver Public Library - Home") == false) 
 throw new Exception("this is not the home page"); 



public ResultsPage search(String keyword) throws Exception 


typeText(searchFieldLocator, keyword); 
clickElement(searchButtonLocator); 

return new ResultsPage(driver); 





public class ResultsPage extends BasePage


String resultLinkLocator = "(//a[@testid='bib_link'])"; 

public ResultsPage(WebDriver driver) throws Exception 


super(driver); 

if (getPageTitle().equalsIgnoreCase("Search | Vancouver Public Library | BiblioCommons") == false) 
 throw new Exception("this is not the results page"); 



public DetailsPage selectResult(int i) throws Exception 


resultLinkLocator = resultLinkLocator + "[" + i + "]"; 

clickElement(resultLinkLocator); 

return new DetailsPage(driver); 





public class DetailsPage extends BasePage


String bookTitleElementLocator = "//h1[@id='item_bib_title']"; 
String bookAuthorElementLocator = "//a[@testid='author_search']"; 

public DetailsPage(WebDriver driver) throws Exception 


super(driver); 

if (getPageTitle().indexOf("Vancouver Public Library | BiblioCommons") < 0) 
 throw new Exception("this is not the details page"); 



public Boolean correctBookTitle() 


return getValue(bookTitleElementLocator).length() > 0 && isDisplayed(bookTitleElementLocator); 





The test class looks good with one exception:
 creating and closing the driver object

We should create and close the driver object outside of the test class as well.




Create/close the driver in the page object classes

One option is to move the code that creates/closes the driver object from the test class to the page object classes.

This solves the problem of having the test scripts 100% free of WebDriver API but creates another issue.

Each page object class will have code for creating and closing the driver.

Having duplicated code for managing the driver is not a good idea.



Create/close the driver in base class constructor

Since the page object classes are using the base class for all site interactions, how about we move the driver code to the base class as well?

The first place where driver object code can go is in the the base class constructor.

The driver object is declared first as a member of the base class:

WebDriverWait wait; 

WebDriver driver; 

public BasePage() 


driver = new ChromeDriver(); 

wait = new WebDriverWait(driver, 10); 



This does not work unfortunately because the driver will be instantiated for each page object.

See what happens in our code:

  • a driver object is created for the HomePage object; the browser is opened and the site is loaded in it
  • when the ResultsPage object is created, another driver object is created; another browser instance is loaded but with no site in it; the site is still loaded on the first browser instance

What other options do we have?



Create a static driver member of the base class and instantiate it in a static block


First, we need the ability of creating the driver object once only and re-use it for all page objects.

The driver object should also be closed once at the end of the script.

The "create once only" reminds us of static class members:

protected static WebDriverWait wait;
protected static WebDriver driver;

If the driver is, however, still created in the base class constructor, things will not be very different. 

But if the driver could be created before the base class constructor, then we are onto something.

So static blocks enter the scene.

The code from a static block is executed once only for the base class.

What is even better is that the static block code executes before the constructor:

package CreateDriver; 

import org.junit.Rule; 
import org.openqa.selenium.By; 
import org.openqa.selenium.WebDriver; 
import org.openqa.selenium.WebElement; 
import org.openqa.selenium.chrome.ChromeDriver; 
import org.openqa.selenium.support.ui.ExpectedConditions; 
import org.openqa.selenium.support.ui.WebDriverWait; 

public class BasePage 


protected static WebDriverWait wait; 

protected static WebDriver driver; 

static 


driver = new ChromeDriver(); 
wait = new WebDriverWait(driver, 10); 



public static void closeBrowser() 
{ driver.quit(); }


public WebElement find(String locator) 


return wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(locator))); 



public void clickElement(String locator) 
{
find(locator).click();
}


public String getPageTitle() 
{
return driver.getTitle();



public void open(String url) 

driver.get(url);



public void typeText(String locator, String keyword) 
{ find(locator).sendKeys(keyword); } 


public String getValue(String locator) 
{ return find(locator).getText();} 


public Boolean isDisplayed(String locator) 
{ return find(locator).isDisplayed(); } 



The test class looks a bit different with the new changes:

import CreateDriver.*; 
import static org.junit.Assert.*; 
import org.junit.After; 
import org.junit.Before; 
import org.junit.Test; 

public class TestCreateDriverInBaseClass 


String keyword = "java"; 

@Test public void testFirstResult() throws Exception 


HomePage home = new HomePage(); 

ResultsPage results = home.search(keyword); 

DetailsPage details = results.selectResult(1); 

assertTrue(details.correctBookTitle() == true); 



@Before public void setUp() { } 

@After public void tearDown() 
{ BasePage.closeBrowser(); } 






Because the driver object is static and is initialized in the static block, it is created once for the Base class (for the HomePage object).

The ResultsPage and DetailsPage objects will use the same static object without initializing it again.

The constructors of the HomePage, ResultsPage and DetailsPages do not need the WebDriver parameter.

In the test class, there is no WebDriver member any longer.

The setUp() method is empty.

The tearDown() method uses a static method of the BasePage class that just closes the static driver.

How To Do Mouse Over In Selenium WebDriver

Use the Actions class of the Selenium WebDriver API for emulating complex user gestures such as mouse over.

Some sites use listboxes and dropdown lists that expand when the user mouses over them. 

Finding the listbox and clicking it may not work correctly in such cases.

The user must mouse over the listbox first and then click it.


Problem

Let's take for example the results page of the BestBuy.ca site.

The page includes a dropdown list for changing the sort order of the results.



The dropdown list expands when mousing over it so the user can select one of the sort orders.

The user will go through the following steps to change the sort order:
  1. mouse over the dropdown list
  2. mouse over a sort option and click it
How do you emulate these actions in a webdriver script?



Solution

The first step can be done using the moveToElement() method of the Actions class:

Actions builder = new Actions(driver);
builder.moveToElement(sortList).perform();


What about the other step?

The Actions class allows creating a chain of actions and executing it.

A chain of actions example looks as follows:

Actions builder = new Actions(driver);
builder.moveToElement(element).click().perform();


First, an action builder object is created.

All user actions (mouse over the element, click the element) are added to the action builder object. After adding the actions to the action builder object, this object will execute the actions from the chain.

Lets see how the webdriver mouseover example code looks like:

import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class Class1 {

  WebDriver driver;
  WebDriverWait wait;

  String url = "http://www.bestbuy.ca/Search/SearchResults.aspx" +
               "?path=ca77b9b4beca91fe414314b86bb581f8en20&query=ipad";

  String sortListLocator = 
          "(//span[@class='sprite-base icon-filter-dropdown'])[1]";

  String highToLowSortLocator = "(//li[@class='sorting-subitem']" +
                                "/a[contains(text(), 'High to Low')])[1]";

  @Before
  public void setUp() { 

    System.setProperty("webdriver.chrome.driver", 
                       "C:\\Selenium\\BrowserDrivers\\chromedriver.exe");

    driver = new ChromeDriver();
    wait = new WebDriverWait(driver, 10);
    driver.manage().window().maximize();

  }

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

  @Test
  public void testMouseOver() throws InterruptedException {

    driver.get(url);

    assertTrue(wait.until(ExpectedConditions.
               titleContains("Best Buy"))); 
  
    Thread.sleep(1000);

    WebElement sortList;
    sortList = wait.until(ExpectedConditions.
               elementToBeClickable(By.xpath(sortListLocator)));

    Actions builder = new Actions(driver);
    builder.moveToElement(sortList).perform();

    WebElement highToLowSort;
    highToLowSort= wait.until(ExpectedConditions.
                   elementToBeClickable(
                   By.xpath(highToLowSortLocator)));

    builder.moveToElement(highToLowSort).click().perform();  
      
    assertTrue(wait.until(ExpectedConditions.
               urlContains("sortBy=price&sortDir=desc")));

  }

}